Skip to content
Draft
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
169 changes: 159 additions & 10 deletions .github/workflows/dotnet-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ on:
push:
branches:
- main
tags:
- '**'
pull_request:
branches:
- main
# Publishing a GitHub Release builds production NuGet packages versioned from the release tag
# and attaches them to the release. Publishing a release also pushes its tag, so this workflow
# does not also trigger on tag pushes; that would build every release twice.
release:
types:
- published

permissions:
contents: read
Expand Down Expand Up @@ -98,6 +102,12 @@ jobs:
name: Build + Test Library
runs-on: windows-latest
timeout-minutes: 60
# Release runs sign the library DLLs with Azure Artifact Signing. Azure only trusts GitHub's OIDC token
# from the `release` environment, so pull requests and pushes to main build and pack unsigned DLLs.
environment: ${{ github.event_name == 'release' && 'release' || '' }}
permissions:
contents: read
id-token: write

steps:
- name: Checkout
Expand All @@ -109,8 +119,20 @@ jobs:
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
if ($env:GITHUB_EVENT_NAME -eq 'pull_request') {
if ($env:GITHUB_EVENT_NAME -eq 'release') {
# Release tags are the package version, optionally prefixed with `v` (e.g. `v10.1.0` or `10.1.0-pre1`).
$nugetPackageVersion = $env:RELEASE_TAG -replace '^v', ''
if ($nugetPackageVersion -notmatch '^(\d+\.\d+\.\d+)(-[0-9A-Za-z.-]+)?$') {
throw "Release tag '$($env:RELEASE_TAG)' is not a valid NuGet version. Use a tag such as v10.1.0 or v10.1.0-pre1."
}

# AssemblyVersion and AssemblyFileVersion come from the NuGetVersion MSBuild property, which must not contain a prerelease suffix.
"ReleaseNuGetVersion=$($Matches[1])" >> $env:GITHUB_ENV
Write-Host "GitHub Release Tag = $($env:RELEASE_TAG)"
}
elseif ($env:GITHUB_EVENT_NAME -eq 'pull_request') {
$commitId = $env:PR_HEAD_SHA.Substring(0, 7)
$nugetPackageVersion = "$($env:CurrentSemanticVersionBase)-build-$($env:PR_NUMBER).$($env:GITHUB_RUN_NUMBER)+$commitId"
Write-Host "GitHub PR = $($env:PR_NUMBER), Commit = $commitId"
Expand Down Expand Up @@ -176,11 +198,115 @@ jobs:
name: code-coverage-report
path: ${{ runner.temp }}/CodeCoverage

- name: Pack AsyncAwaitBestPractices NuGet
run: dotnet pack $env:PathToAsyncAwaitBestPracticesCsproj -c Release "-p:PackageVersion=$env:NugetPackageVersion"
# Building, signing, and packing are separate steps so the DLLs are signed after they are built and
# `dotnet pack --no-build` packages those signed DLLs instead of rebuilding them.
- name: Build AsyncAwaitBestPractices + AsyncAwaitBestPractices.MVVM
run: |
$buildArguments = @('-c', 'Release', "-p:PackageVersion=$env:NugetPackageVersion")
if ($env:ReleaseNuGetVersion) {
$buildArguments += "-p:NuGetVersion=$env:ReleaseNuGetVersion"
}

dotnet build $env:PathToAsyncAwaitBestPracticesCsproj @buildArguments
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet build $env:PathToAsyncAwaitBestPracticesMVVMCsproj @buildArguments
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Check Azure Artifact Signing Configuration
if: ${{ github.event_name == 'release' }}
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARTIFACT_SIGNING_ENDPOINT: ${{ vars.ARTIFACT_SIGNING_ENDPOINT }}
ARTIFACT_SIGNING_ACCOUNT_NAME: ${{ vars.ARTIFACT_SIGNING_ACCOUNT_NAME }}
ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ vars.ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
run: |
$missing = @('AZURE_CLIENT_ID', 'AZURE_TENANT_ID', 'AZURE_SUBSCRIPTION_ID', 'ARTIFACT_SIGNING_ENDPOINT', 'ARTIFACT_SIGNING_ACCOUNT_NAME', 'ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME') |
Where-Object { [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($_)) }
if ($missing) {
throw "Add these secrets/variables to the 'release' environment (Settings > Environments > release): $($missing -join ', ')"
Comment on lines +225 to +228
}

- name: Find Library DLLs to Sign
id: files-to-sign
if: ${{ github.event_name == 'release' }}
run: |
$files = @(
Get-ChildItem -Path 'src/AsyncAwaitBestPractices/bin/Release/*/AsyncAwaitBestPractices.dll'
Get-ChildItem -Path 'src/AsyncAwaitBestPractices.MVVM/bin/Release/*/AsyncAwaitBestPractices.MVVM.dll'
)
if ($files.Count -eq 0) {
throw "No library DLLs were found to sign."
}

- name: Pack AsyncAwaitBestPractices.MVVM NuGet
run: dotnet pack $env:PathToAsyncAwaitBestPracticesMVVMCsproj -c Release "-p:PackageVersion=$env:NugetPackageVersion"
$files.FullName | ForEach-Object { Write-Host $_ }
"files<<EOF" >> $env:GITHUB_OUTPUT
$files.FullName >> $env:GITHUB_OUTPUT
"EOF" >> $env:GITHUB_OUTPUT

- name: Sign In to Azure
if: ${{ github.event_name == 'release' }}
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Sign Library DLLs with Azure Artifact Signing
if: ${{ github.event_name == 'release' }}
uses: azure/artifact-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0
with:
endpoint: ${{ vars.ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ vars.ARTIFACT_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ vars.ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
files: ${{ steps.files-to-sign.outputs.files }}
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256

- name: Pack AsyncAwaitBestPractices + AsyncAwaitBestPractices.MVVM NuGets
run: |
$packArguments = @('-c', 'Release', '--no-build', "-p:PackageVersion=$env:NugetPackageVersion")
if ($env:ReleaseNuGetVersion) {
$packArguments += "-p:NuGetVersion=$env:ReleaseNuGetVersion"
}

dotnet pack $env:PathToAsyncAwaitBestPracticesCsproj @packArguments
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet pack $env:PathToAsyncAwaitBestPracticesMVVMCsproj @packArguments
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Verify Authenticode Signatures in NuGet Packages
if: ${{ github.event_name == 'release' }}
run: |
Add-Type -AssemblyName System.IO.Compression.FileSystem
$packages = @(Get-ChildItem -Path ./src -Recurse -Filter *.nupkg | Where-Object { $_.FullName -match '[\\/]bin[\\/]Release[\\/]' })
if ($packages.Count -eq 0) {
throw "No NuGet packages were produced."
}

foreach ($package in $packages) {
$extractPath = Join-Path $env:RUNNER_TEMP ([System.IO.Path]::GetRandomFileName())
[System.IO.Compression.ZipFile]::ExtractToDirectory($package.FullName, $extractPath)
$assemblies = @(Get-ChildItem -Path (Join-Path $extractPath 'lib') -Recurse -Filter *.dll -ErrorAction SilentlyContinue)
if ($assemblies.Count -eq 0) {
throw "No library DLLs were found in $($package.Name)."
}

foreach ($assembly in $assemblies) {
$signature = Get-AuthenticodeSignature -FilePath $assembly.FullName
Write-Host "$($package.Name) $($assembly.FullName.Substring($extractPath.Length)): $($signature.Status) ($($signature.SignerCertificate.Subject))"
if ($signature.Status -ne 'Valid') {
throw "Authenticode signature verification failed for $($assembly.FullName): $($signature.StatusMessage)"
}
if ($null -eq $signature.TimeStamperCertificate) {
throw "The Authenticode signature for $($assembly.FullName) is not timestamped."
}
}

Remove-Item -Path $extractPath -Recurse -Force
}

- name: Check AsyncAwaitBestPractices Dependencies Vulnerabilities
if: ${{ !cancelled() }}
Expand All @@ -204,15 +330,38 @@ jobs:
exit 0

- name: Copy NuGet Packages to Staging Directory
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
run: |
$stagingDirectory = New-Item -ItemType Directory -Force -Path (Join-Path $env:RUNNER_TEMP 'nuget')
Get-ChildItem -Path ./src -Recurse | Where-Object { $_.Extension -match 'nupkg' } | Copy-Item -Destination $stagingDirectory.FullName

- name: Publish NuGet Packages
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: nuget
path: ${{ runner.temp }}/nuget
if-no-files-found: error
if-no-files-found: error

attach_release_assets:
name: Attach NuGet Packages to GitHub Release
if: ${{ github.event_name == 'release' }}
needs:
- build_sample
- build_test_library
runs-on: ubuntu-latest
timeout-minutes: 10
# Only this job can write to the repository, and it runs no build tools or third-party actions besides artifact download.
permissions:
contents: write

steps:
- name: Download NuGet Packages
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: nuget
path: ${{ runner.temp }}/nuget

- name: Upload NuGet Packages to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: gh release upload "$RELEASE_TAG" "$RUNNER_TEMP"/nuget/*.nupkg "$RUNNER_TEMP"/nuget/*.snupkg --repo "$GITHUB_REPOSITORY" --clobber
Loading