name: Build NativeAOT on: push: branches: - main - staging pull_request: branches: - main - staging workflow_dispatch: # Read-only unless a job says otherwise; only the release writes. Actions are # pinned to full commits - a tag can be moved under us, a commit cannot - and # checkout leaves no token behind for the pull request code that runs next. permissions: contents: read env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: true PROJECT_PATH: StarterNG/StarterNG.csproj TEST_PROJECT_PATH: StarterNG.Tests/StarterNG.Tests.csproj jobs: # On both systems the packages are built for: paths, encodings and the file # system differ, and that is where these tests earn their keep. test: name: Tests (${{ matrix.name }}) strategy: fail-fast: false matrix: include: - name: Windows x64 os: windows-2022 - name: Linux x64 os: ubuntu-22.04 runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Setup .NET 9 uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 9.0.x - name: Test run: > dotnet test "${{ env.TEST_PROJECT_PATH }}" -c Release # A known vulnerability in a NuGet package, direct or pulled in, fails the run. - name: Audit packages if: runner.os == 'Linux' shell: bash run: | set -euo pipefail dotnet list StarterNG.sln package --vulnerable --include-transitive 2>&1 | tee audit.txt if grep -q "has the following vulnerable packages" audit.txt; then echo "::error::Vulnerable NuGet packages, listed above" exit 1 fi # Major and minor are set by hand in the project file; the patch is the number # of commits, so every build of main is higher than the one before and no bot # has to commit a bump back to the branch. version: name: Version runs-on: ubuntu-latest timeout-minutes: 5 outputs: number: ${{ steps.number.outputs.number }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false - name: Number this build id: number shell: bash run: | set -euo pipefail prefix=$(sed -n 's:.*\([0-9][0-9]*\.[0-9][0-9]*\).*:\1:p' "$PROJECT_PATH") if [ -z "$prefix" ]; then echo "::error file=$PROJECT_PATH::No major.minor in the project file" exit 1 fi number="$prefix.$(git rev-list --count HEAD)" echo "number=$number" >> "$GITHUB_OUTPUT" echo "Version $number" publish: name: ${{ matrix.name }} # A failing test stops the packages, and with them the release. needs: [test, version] strategy: fail-fast: false matrix: include: - name: Windows x64 os: windows-2022 rid: win-x64 artifact: StarterNG-windows-x64 package: artifacts/StarterNG-windows-x64.zip - name: Linux x64 os: ubuntu-22.04 rid: linux-x64 artifact: StarterNG-linux-x64 package: artifacts/StarterNG-linux-x64.tar.gz runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Setup .NET 9 uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 9.0.x # NativeAOT on Linux needs a native compiler/linker toolchain. - name: Install NativeAOT dependencies if: runner.os == 'Linux' shell: bash run: | sudo apt-get update sudo apt-get install -y clang zlib1g-dev - name: Restore run: > dotnet restore "${{ env.PROJECT_PATH }}" -r "${{ matrix.rid }}" - name: Publish NativeAOT shell: bash env: VERSION: ${{ needs.version.outputs.number }} run: > dotnet publish "${{ env.PROJECT_PATH }}" -c Release -r "${{ matrix.rid }}" --self-contained true --no-restore -p:PublishAot=true -p:PublishTrimmed=true -p:Version="$VERSION" -o "publish/${{ matrix.rid }}" - name: Package Windows build if: runner.os == 'Windows' shell: pwsh run: | New-Item -ItemType Directory -Force -Path "artifacts" | Out-Null Compress-Archive ` -Path "publish/${{ matrix.rid }}/*" ` -DestinationPath "${{ matrix.package }}" - name: Package Linux build if: runner.os == 'Linux' shell: bash run: | mkdir -p artifacts tar \ -C "publish/${{ matrix.rid }}" \ -czf "${{ matrix.package }}" \ . # A fork's pull request is built, to prove it builds, but its binaries are not # handed out under the repository's name. - name: Upload artifact if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ matrix.artifact }} path: ${{ matrix.package }} if-no-files-found: error archive: false # Workflow artifacts are only reachable for signed-in users with repository # access, so every build of a release branch also lands in a release, whose # assets anyone can download. main gets a release of its own per build, tagged # with its version - the newest is what the starter checks itself against: # https://github.com/${{ github.repository }}/releases/latest/download/ # staging keeps one rolling pre-release, offered only to starters set to the # testing channel, which tell its builds apart by commit. release: name: Release needs: [publish, version] if: github.event_name == 'push' runs-on: ubuntu-latest timeout-minutes: 10 # Two pushes in a row release one after the other, not over each other. concurrency: group: release-${{ github.ref }} cancel-in-progress: false permissions: contents: write steps: # v8, not v7: the packages are uploaded with archive: false, and only v8 # can pull those non-zipped artifacts back down. skip-decompress keeps the # Windows .zip a file - unpacked, its contents would end up in the release # instead of the package. - name: Collect the packages uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist merge-multiple: true skip-decompress: true - name: Checksum the packages working-directory: dist run: sha256sum StarterNG-windows-x64.zip StarterNG-linux-x64.tar.gz | tee SHA256SUMS.txt - name: Publish the release if: github.ref_name == 'main' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: v${{ needs.version.outputs.number }} VERSION: ${{ needs.version.outputs.number }} run: | set -euo pipefail ls -l dist # A re-run of the same commit finds its release already there and refreshes # it; a release of the same number for another commit - main rewritten - is # left alone rather than given packages that are not its own. if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then tagged=$(gh api "repos/$GITHUB_REPOSITORY/commits/$TAG" --jq .sha) if [ "$tagged" != "$GITHUB_SHA" ]; then echo "::error::$TAG already belongs to $tagged, not to $GITHUB_SHA" exit 1 fi gh release upload "$TAG" dist/* --repo "$GITHUB_REPOSITORY" --clobber exit 0 fi gh release create "$TAG" \ dist/StarterNG-windows-x64.zip \ dist/StarterNG-linux-x64.tar.gz \ dist/SHA256SUMS.txt \ --repo "$GITHUB_REPOSITORY" \ --target "$GITHUB_SHA" \ --title "StarterNG $VERSION" \ --generate-notes \ --latest # Recreated rather than edited, so the tag follows its branch and no asset # from an older build is left behind. - name: Replace the staging pre-release if: github.ref_name == 'staging' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ needs.version.outputs.number }} run: | set -euo pipefail ls -l dist gh release delete staging --repo "$GITHUB_REPOSITORY" --yes --cleanup-tag || true gh release create staging \ dist/StarterNG-windows-x64.zip \ dist/StarterNG-linux-x64.tar.gz \ dist/SHA256SUMS.txt \ --repo "$GITHUB_REPOSITORY" \ --target "$GITHUB_SHA" \ --prerelease \ --title "Staging build $VERSION" \ --notes "Automatic build of $GITHUB_REF_NAME (${GITHUB_SHA::7}), $(date -u '+%Y-%m-%d %H:%M UTC')."