diff --git a/.github/actions/publish-artifacts/action.yaml b/.github/actions/publish-artifacts/action.yaml new file mode 100644 index 0000000..94b7426 --- /dev/null +++ b/.github/actions/publish-artifacts/action.yaml @@ -0,0 +1,67 @@ +name: Publish artifacts +description: Publish artifacts + +runs: + using: composite + steps: + - name: ๐Ÿ“ฅ Collect artifacts + run: azure-pipelines/artifacts/_stage_all.ps1 + shell: pwsh + if: always() + +# TODO: replace this hard-coded list with a loop that utilizes the NPM package at +# https://github.com/actions/toolkit/tree/main/packages/artifact (or similar) to push the artifacts. + + - name: ๐Ÿ“ข Upload project.assets.json files + if: always() + uses: actions/upload-artifact@v4 + with: + name: projectAssetsJson-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/projectAssetsJson + continue-on-error: true + - name: ๐Ÿ“ข Upload variables + uses: actions/upload-artifact@v4 + with: + name: variables-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/Variables + continue-on-error: true + - name: ๐Ÿ“ข Upload build_logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: build_logs-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/build_logs + continue-on-error: true + - name: ๐Ÿ“ข Upload test_logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: test_logs-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/test_logs + continue-on-error: true + - name: ๐Ÿ“ข Upload testResults + if: always() + uses: actions/upload-artifact@v4 + with: + name: testResults-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/testResults + continue-on-error: true + - name: ๐Ÿ“ข Upload coverageResults + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverageResults-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/coverageResults + continue-on-error: true + - name: ๐Ÿ“ข Upload symbols + uses: actions/upload-artifact@v4 + with: + name: symbols-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/symbols + continue-on-error: true + - name: ๐Ÿ“ข Upload deployables + uses: actions/upload-artifact@v4 + with: + name: deployables-${{ runner.os }} + path: ${{ runner.temp }}/_artifacts/deployables + if: always() diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5334bac --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,69 @@ +name: ๐Ÿญ Build + +on: + push: + branches: + - main + - validate/* + pull_request: + workflow_dispatch: + +env: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + BUILDCONFIGURATION: Release + # codecov_token: 4dc9e7e2-6b01-4932-a180-847b52b43d35 # Get a new one from https://codecov.io/ + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages/ + +jobs: + build: + name: ๐Ÿญ Build + + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-22.04 + - macos-14 + - windows-2022 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # avoid shallow clone so nbgv can do its work. + - name: โš™ Install prerequisites + run: | + ./init.ps1 -UpgradePrerequisites + dotnet --info + + # Print mono version if it is present. + if (Get-Command mono -ErrorAction SilentlyContinue) { + mono --version + } + shell: pwsh + - name: โš™๏ธ Set pipeline variables based on source + run: azure-pipelines/variables/_pipelines.ps1 + shell: pwsh + - name: ๐Ÿ›  build + run: dotnet build -t:build,pack --no-restore -c ${{ env.BUILDCONFIGURATION }} -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904 /bl:"${{ runner.temp }}/_artifacts/build_logs/build.binlog" + - name: ๐Ÿงช test + run: azure-pipelines/dotnet-test-cloud.ps1 -Configuration ${{ env.BUILDCONFIGURATION }} -Agent ${{ runner.os }} + shell: pwsh + - name: ๐Ÿ’…๐Ÿป Verify formatted code + run: dotnet format --verify-no-changes --no-restore + shell: pwsh + if: runner.os == 'Linux' + - name: ๐Ÿ“š Verify docfx build + run: dotnet docfx docfx/docfx.json --warningsAsErrors --disableGitFeatures + if: runner.os == 'Linux' + - name: โš™ Update pipeline variables based on build outputs + run: azure-pipelines/variables/_pipelines.ps1 + shell: pwsh + - name: ๐Ÿ“ข Publish artifacts + uses: ./.github/actions/publish-artifacts + - name: ๐Ÿ“ข Publish code coverage results to codecov.io + run: ./azure-pipelines/publish-CodeCov.ps1 -CodeCovToken "${{ env.codecov_token }}" -PathToCodeCoverage "${{ runner.temp }}/_artifacts/coverageResults" -Name "${{ runner.os }} Coverage Results" -Flags "${{ runner.os }}" + shell: pwsh + timeout-minutes: 3 + continue-on-error: true + if: env.codecov_token != '' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1820866..70b779b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,6 +10,7 @@ permissions: actions: read pages: write id-token: write + contents: read # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. diff --git a/.github/workflows/libtemplate-update.yml b/.github/workflows/libtemplate-update.yml index 0501d5e..f78c7e0 100644 --- a/.github/workflows/libtemplate-update.yml +++ b/.github/workflows/libtemplate-update.yml @@ -1,4 +1,4 @@ -name: Library.Template update +name: โ›œ Library.Template update # PREREQUISITE: This workflow requires the repo to be configured to allow workflows to create pull requests. # Visit https://github.com/USER/REPO/settings/actions diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d6f5cc4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,87 @@ +name: ๐ŸŽ Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + ship_run_id: + description: ID of the GitHub workflow run to ship + required: true + +run-name: ${{ github.ref_name }} + +permissions: + actions: read + contents: write + +jobs: + release: + runs-on: ubuntu-22.04 + steps: + - name: โš™๏ธ Initialization + shell: pwsh + run: | + if ('${{ secrets.NUGET_API_KEY }}') { + echo "NUGET_API_KEY_DEFINED=true" >> $GITHUB_ENV + } + + - name: ๐Ÿ”Ž Search for build of ${{ github.ref }} + shell: pwsh + id: findrunid + env: + GH_TOKEN: ${{ github.token }} + run: | + if ('${{ inputs.ship_run_id }}') { + $runid = '${{ inputs.ship_run_id }}' + } else { + $restApiRoot = '/repos/${{ github.repository }}' + + # Resolve the tag reference to a commit sha + $resolvedRef = gh api ` + -H "Accept: application/vnd.github+json" ` + -H "X-GitHub-Api-Version: 2022-11-28" ` + $restApiRoot/git/ref/tags/${{ github.ref_name }} ` + | ConvertFrom-Json + $commitSha = $resolvedRef.object.sha + + Write-Host "Resolved ${{ github.ref_name }} to $commitSha" + + $releases = gh run list -R ${{ github.repository }} -c $commitSha -w .github/workflows/build.yml -s success --json databaseId,startedAt ` + | ConvertFrom-Json | Sort-Object startedAt -Descending + + if ($releases.length -eq 0) { + Write-Error "No successful builds found for ${{ github.ref }}." + } elseif ($releases.length -gt 1) { + Write-Warning "More than one successful run found for ${{ github.ref }}. Artifacts from the most recent successful run will ship." + } + + $runid = $releases[0].databaseId + } + + Write-Host "Using artifacts from run-id: $runid" + + Echo "runid=$runid" >> $env:GITHUB_OUTPUT + + - name: ๐Ÿ”ป Download deployables artifacts + uses: actions/download-artifact@v4 + with: + name: deployables-Linux + path: ${{ runner.temp }}/deployables + run-id: ${{ steps.findrunid.outputs.runid }} + github-token: ${{ github.token }} + + - name: ๐Ÿ’ฝ Upload artifacts to release + shell: pwsh + if: ${{ github.event.release.assets_url }} != '' + env: + GH_TOKEN: ${{ github.token }} + run: | + Get-ChildItem '${{ runner.temp }}/deployables' |% { + Write-Host "Uploading $($_.Name) to release..." + gh release -R ${{ github.repository }} upload "${{ github.ref_name }}" $_.FullName + } + + - name: ๐Ÿš€ Push NuGet packages + run: dotnet nuget push ${{ runner.temp }}/deployables/*.nupkg --source https://api.nuget.org/v3/index.json -k '${{ secrets.NUGET_API_KEY }}' + if: ${{ env.NUGET_API_KEY_DEFINED == 'true' }} diff --git a/azure-pipelines/artifacts/coverageResults.ps1 b/azure-pipelines/artifacts/coverageResults.ps1 index a6c8f42..8c68216 100644 --- a/azure-pipelines/artifacts/coverageResults.ps1 +++ b/azure-pipelines/artifacts/coverageResults.ps1 @@ -3,14 +3,16 @@ $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") $coverageFiles = @(Get-ChildItem "$RepoRoot/test/*.cobertura.xml" -Recurse | Where {$_.FullName -notlike "*/In/*" -and $_.FullName -notlike "*\In\*" }) # Prepare code coverage reports for merging on another machine -if ($env:SYSTEM_DEFAULTWORKINGDIRECTORY) { - Write-Host "Substituting $env:SYSTEM_DEFAULTWORKINGDIRECTORY with `"{reporoot}`"" +$repoRoot = $env:SYSTEM_DEFAULTWORKINGDIRECTORY +if (!$repoRoot) { $repoRoot = $env:GITHUB_WORKSPACE } +if ($repoRoot) { + Write-Host "Substituting $repoRoot with `"{reporoot}`"" $coverageFiles |% { - $content = Get-Content -LiteralPath $_ |% { $_ -Replace [regex]::Escape($env:SYSTEM_DEFAULTWORKINGDIRECTORY), "{reporoot}" } + $content = Get-Content -LiteralPath $_ |% { $_ -Replace [regex]::Escape($repoRoot), "{reporoot}" } Set-Content -LiteralPath $_ -Value $content -Encoding UTF8 } } else { - Write-Warning "coverageResults: Azure Pipelines not detected. Machine-neutral token replacement skipped." + Write-Warning "coverageResults: Cloud build not detected. Machine-neutral token replacement skipped." } if (!((Test-Path $RepoRoot\bin) -and (Test-Path $RepoRoot\obj))) { return }