Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
# The workflows pin every action to a commit SHA with its version in a comment; Dependabot bumps both.
# All action updates come in one PR, since each PR's Build run needs approval on the pr-build environment.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns: ['*']
commit-message:
prefix: ci
153 changes: 113 additions & 40 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,49 +1,122 @@
name: Build Controls & Gallery

# This workflow builds the iNKORE.UI.WPF.Modern.Controls and iNKORE.UI.WPF.Modern.Gallery projects and uploads the artifacts for use in other workflows or releases.
name: Build

# Builds the NuGet package and the Gallery and keeps them as run artifacts; nothing is published (see release.yml).
# Pull requests wait for a maintainer to approve the pr-build environment first; v* tags build right away.
on:
push:
branches: [ ]
pull_request:
branches: [ ]
push:
tags: ['v*']
workflow_dispatch:

permissions:
contents: read

# A new push to a PR cancels the older run that is still waiting for approval.
concurrency:
group: build-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
approve:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
environment: pr-build # the required reviewers on this environment are the approval gate
steps:
- run: echo "Approved"

build:
needs: approve
if: ${{ !cancelled() && (needs.approve.result == 'success' || needs.approve.result == 'skipped') }}
runs-on: windows-latest
outputs:
version: ${{ steps.version.outputs.version }}
package: ${{ steps.files.outputs.package }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0

- name: Choose version
id: version
shell: pwsh
run: |
if ($env:GITHUB_REF_TYPE -eq 'tag' -and $env:GITHUB_REF_NAME -like 'v*') { $arg = "-p:Version=$($env:GITHUB_REF_NAME.Substring(1))" }
else { $arg = "-p:VersionSuffix=ci.$env:GITHUB_RUN_NUMBER" }
"VERSION_ARG=$arg" >> $env:GITHUB_ENV
# The version the package should come out with; test-package checks it against this.
$version = msbuild source/iNKORE.UI.WPF.Modern.Controls/iNKORE.UI.WPF.Modern.Controls.csproj -getProperty:Version $arg
"version=$version" >> $env:GITHUB_OUTPUT

- name: Build solution
shell: pwsh
run: >-
msbuild iNKORE.UI.WPF.Modern.sln -restore
-p:Configuration=Release "-p:Platform=Any CPU" -p:ContinuousIntegrationBuild=true $env:VERSION_ARG

- name: Pack
shell: pwsh
run: >-
msbuild source/iNKORE.UI.WPF.Modern.Controls/iNKORE.UI.WPF.Modern.Controls.csproj -t:Pack -p:NoBuild=true
-p:Configuration=Release -p:ContinuousIntegrationBuild=true $env:VERSION_ARG

# name@version keeps the boundary between name and version clear. The nupkg keeps NuGet's id.version.nupkg
# name on purpose: local folder feeds find packages by that file name, so renaming it breaks restore (NU1102).
- name: Zip Gallery
id: files
shell: pwsh
run: |
$package = Get-Item .nuget/iNKORE.UI.WPF.Modern.*.nupkg
$gallery = "iNKORE.UI.WPF.Modern.Gallery@$($package.BaseName.Substring('iNKORE.UI.WPF.Modern.'.Length)).zip"
New-Item -ItemType Directory -Force artifacts | Out-Null
Compress-Archive -Path source/iNKORE.UI.WPF.Modern.Gallery/bin/Release/net6.0-windows10.0.18362.0/* `
-DestinationPath "artifacts/$gallery"
"package=$($package.Name)" >> $env:GITHUB_OUTPUT
"gallery=$gallery" >> $env:GITHUB_OUTPUT

# archive: false uploads each file as is, so the artifact is the file itself, named after it,
# instead of a zip wrapped around it. Download them with download-artifact@v8 by that name.
- name: Upload package
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
path: .nuget/${{ steps.files.outputs.package }}
archive: false
include-hidden-files: true # .nuget/ is a dot folder, which upload-artifact skips by default
if-no-files-found: error

- name: Upload Gallery
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
path: artifacts/${{ steps.files.outputs.gallery }}
archive: false
if-no-files-found: error

# Checks the package's layout and versions, then builds small WPF apps against it and runs them (tests/Package).
# A failure here fails the run, and release.yml only releases a tag whose Build run succeeded.
test-package:
needs: build
# Without this the job is skipped on tag builds (approve is skipped there), and the run would still succeed.
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: windows-latest

env:
VERSION: ${{ needs.build.outputs.version }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
6.0.x
7.0.x

- name: Restore NuGet packages
run: |
nuget restore iNKORE.UI.WPF.Modern.sln

- name: Build solution
run: |
msbuild iNKORE.UI.WPF.Modern.sln /p:Configuration=Release /p:Platform="Any CPU"

- name: Upload Controls artifact (.NET 6.0)
uses: actions/upload-artifact@v4
with:
name: iNKORE.UI.WPF.Modern.Controls
path: source\iNKORE.UI.WPF.Modern.Controls\bin\Release\net6.0-windows10.0.18362.0

- name: Upload Gallery artifact
uses: actions/upload-artifact@v4
with:
name: iNKORE.UI.WPF.Modern.Gallery
path: source\iNKORE.UI.WPF.Modern.Gallery\bin\Release\net6.0-windows10.0.18362.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0

- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 8.0.x # the net8.0 consumer runs on it

- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ needs.build.outputs.package }}
path: nupkg

- name: Check package structure
shell: pwsh
run: tests/Package/Test-PackageStructure.ps1 -Package (Get-Item nupkg/*.nupkg).FullName -Version $env:VERSION

- name: Build and run consumers
shell: pwsh
run: tests/Package/Test-Consumers.ps1 -Package (Get-Item nupkg/*.nupkg).FullName -Version $env:VERSION
82 changes: 82 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Release

# Publishing a GitHub release for a v* tag (written by hand on the Releases page) starts this.
# After approval on the nuget environment, the tag's Build outputs are pushed to NuGet.org and attached to the release.
# To retry, run it by hand on the tag (Use workflow from -> Tags); both steps are safe to repeat.
on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: release-${{ github.ref_name }}

jobs:
publish:
if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
runs-on: ubuntu-latest
environment: nuget # the required reviewers on this environment approve every release
permissions:
actions: read # download the tag build's artifacts
contents: write # attach files to the release
id-token: write # NuGet trusted publishing
env:
TAG: ${{ github.ref_name }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
steps:
# Build uploads the nupkg and the Gallery zip unwrapped (archive: false): each artifact is named after its file.
- name: Find the tag's build
id: build
run: |
run_id=$(gh run list --workflow build.yml --branch "$TAG" --event push --status success --limit 1 --json databaseId --jq '.[0].databaseId')
if [ -z "$run_id" ]; then
echo "::error::No successful Build run for $TAG yet. Wait for it to finish (or re-run it), then re-run this workflow."
exit 1
fi
artifacts=$(gh api "repos/$GH_REPO/actions/runs/$run_id/artifacts" --jq '.artifacts[] | select(.expired | not) | .name')
package=$(grep -E '\.nupkg$' <<< "$artifacts" || true)
gallery=$(grep -E '^iNKORE\.UI\.WPF\.Modern\.Gallery@.*\.zip$' <<< "$artifacts" || true)
if [ -z "$package" ] || [ -z "$gallery" ]; then
echo "::error::Build run $run_id for $TAG has no package or Gallery artifact (expired?). Re-run that build, then this workflow."
exit 1
fi
echo "run-id=$run_id" >> "$GITHUB_OUTPUT"
echo "package=$package" >> "$GITHUB_OUTPUT"
echo "gallery=$gallery" >> "$GITHUB_OUTPUT"

- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ steps.build.outputs.package }}
run-id: ${{ steps.build.outputs.run-id }}
github-token: ${{ github.token }}
path: dist

- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ steps.build.outputs.gallery }}
run-id: ${{ steps.build.outputs.run-id }}
github-token: ${{ github.token }}
path: dist

# dotnet nuget push needs an SDK; install one rather than rely on what the runner image happens to ship.
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 10.0.x

- name: NuGet login
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
id: login
with:
user: ${{ secrets.NUGET_USER }}

- name: Push to NuGet.org
run: >-
dotnet nuget push "dist/*.nupkg" --api-key "${{ steps.login.outputs.NUGET_API_KEY }}"
--source https://api.nuget.org/v3/index.json --skip-duplicate

- name: Attach files to the release
run: gh release upload "$TAG" dist/* --clobber
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ PublishScripts/

# Nuget personal access tokens and Credentials
nuget.config
!tests/Package/Consumers/nuget.config

# Microsoft Azure Build Output
csx/
Expand Down
19 changes: 14 additions & 5 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
<Project>
<PropertyGroup>
<Product>iNKORE.UI.WPF.Modern</Product>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>
<PropertyGroup>
<Product>iNKORE.UI.WPF.Modern</Product>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<PropertyGroup>
<!-- Default for local builds. CI builds append -p:VersionSuffix=ci.<run>; releases pass -p:Version=<tag>. -->
<VersionPrefix>0.10.2</VersionPrefix>

<!-- The NuGet package is defined by source/iNKORE.UI.WPF.Modern.nuspec and packed from
iNKORE.UI.WPF.Modern.Controls only; packing any other project would produce a clashing package. -->
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>
Loading
Loading