canary #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: canary | |
| # Early-warning that the documentation tooling still runs on the NEXT, not-yet-released .NET. | |
| # | |
| # The shipped tooling targets net8.0 and reaches every newer runtime purely by roll-forward (the worker uses | |
| # LatestMajor; see doc/handwritten/for-maintainers/adr/0002-floor-the-tooling-runtime.md). A runtime breaking change in a future | |
| # major would surface here first — on a schedule, against the current preview — instead of in a user's CI the | |
| # day that major ships. It is the upper-end counterpart of the `floor` job in ci.yml, which pins the lower end. | |
| # | |
| # Deliberately NOT a pull-request gate: preview runtimes are unstable and sometimes not published yet, and their | |
| # breakage is not this repo's bug. It runs on a schedule (and on demand); a real regression turns the scheduled | |
| # run red and GitHub notifies the maintainer, while an unavailable preview ends the run neutral. | |
| on: | |
| schedule: | |
| # Weekly, Monday 06:00 UTC. Preview builds drop roughly monthly, so weekly catches a new one within days | |
| # without adding noise. Scheduled workflows run from the default branch only. | |
| - cron: '0 6 * * 1' | |
| workflow_dispatch: | |
| concurrency: | |
| group: canary-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Least privilege: checkout + build only. | |
| permissions: | |
| contents: read | |
| env: | |
| DOTNET_NOLOGO: 'true' | |
| DOTNET_CLI_TELEMETRY_OPTOUT: 'true' | |
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 'true' | |
| jobs: | |
| preview: | |
| name: Documentation tooling on the next .NET preview | |
| runs-on: ubuntu-latest | |
| # Build (~20s) plus one doc-generation run; cap a hung run like the ci jobs. | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| # Best effort: a preview for the next major may not be published yet, or may fail to install. Marking this | |
| # step continue-on-error keeps that from reddening the canary — the build/run steps below are gated on its | |
| # outcome and simply skip, leaving the job neutral. Bump the major (11.0.x -> 12.0.x -> ...) once the | |
| # current preview reaches GA; see ADR 0002. | |
| - name: Setup the next .NET preview (best effort) | |
| id: preview | |
| continue-on-error: true | |
| uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 | |
| with: | |
| dotnet-version: '11.0.x' | |
| dotnet-quality: preview | |
| # The build SDK. net8.0 is only a target framework; global.json pins the .NET 10 SDK for the build itself. | |
| - name: Setup the build SDK (.NET 10) | |
| uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: No preview available — skip (not a failure) | |
| if: steps.preview.outcome != 'success' | |
| run: echo "::notice::no .NET preview was available to canary against; skipping this run" | |
| # The fce tool, its GenDoc worker (copied next to fce by the CLI build target) and a net8 build of the Usage | |
| # sample as a real target to document. | |
| - name: Build the net8 tooling and a net8 target | |
| if: steps.preview.outcome == 'success' | |
| run: | | |
| dotnet build FirstClassErrors.Cli/FirstClassErrors.Cli.csproj -c Release | |
| dotnet build FirstClassErrors.Usage/FirstClassErrors.Usage.csproj -c Release -f net8.0 | |
| # Force the net8 tooling onto the newest installed major, INCLUDING the prerelease preview: | |
| # * DOTNET_ROLL_FORWARD=LatestMajor overrides the CLI's Major (and, redundantly, the worker's LatestMajor) | |
| # so both processes select the highest installed major rather than staying on a stable one; | |
| # * DOTNET_ROLL_FORWARD_TO_PRERELEASE=1 lets a stable net8 app bind a *prerelease* runtime, which | |
| # roll-forward refuses by default. | |
| # The step PRINTS the installed runtimes and the highest major first, so the log shows the preview it is about | |
| # to exercise instead of leaving it to be inferred. It then guards: only a runtime newer than the .NET 10 build | |
| # SDK proves anything, so if the preview install did not yield one the run skips neutrally rather than | |
| # false-passing on .NET 10. fce runs with --verbose, so the worker additionally logs the exact runtime it bound | |
| # to (its "Documenting '…' on .NET <version>." banner); that banner is ASSERTED against the highest major, so | |
| # fce silently binding an older runtime it also had — e.g. if the roll-forward env were dropped — turns the run | |
| # red rather than passing green. | |
| - name: Generate documentation on the preview runtime | |
| if: steps.preview.outcome == 'success' | |
| env: | |
| DOTNET_ROLL_FORWARD: LatestMajor | |
| DOTNET_ROLL_FORWARD_TO_PRERELEASE: '1' | |
| run: | | |
| set -euo pipefail | |
| echo "Installed .NET runtimes:" | |
| dotnet --list-runtimes | |
| newest="$(dotnet --list-runtimes | sed -nE 's/^Microsoft\.NETCore\.App ([0-9]+)\..*/\1/p' | sort -n | tail -1)" | |
| echo "Highest installed .NET major: ${newest:-none}" | |
| if [ "${newest:-0}" -le 10 ]; then | |
| echo "::notice::no runtime newer than the .NET 10 build SDK is installed; skipping this run" | |
| exit 0 | |
| fi | |
| # Capture fce's diagnostics (stderr) so the worker's runtime banner can be asserted, not just read. The | |
| # set +e around the call keeps a genuine fce failure from aborting before those diagnostics are echoed. | |
| set +e | |
| dotnet FirstClassErrors.Cli/bin/Release/net8.0/fce.dll generate \ | |
| --assemblies FirstClassErrors.Usage/bin/Release/net8.0/FirstClassErrors.Usage.dll \ | |
| --format json --verbose > canary-catalog.json 2> canary-diag.log | |
| fce_rc=$? | |
| set -e | |
| cat canary-diag.log | |
| if [ "$fce_rc" -ne 0 ]; then | |
| echo "::error::fce failed on the .NET ${newest} preview runtime (exit ${fce_rc})" | |
| exit 1 | |
| fi | |
| # Proof 1: the worker loaded the target and extracted documented errors. | |
| if ! grep -q '"code"' canary-catalog.json; then | |
| echo "::error::the net8 tooling produced no documented errors on the .NET ${newest} preview runtime" | |
| cat canary-catalog.json | |
| exit 1 | |
| fi | |
| # Proof 2 (the whole point of the canary): the worker's own runtime banner confirms it bound to the NEWEST | |
| # installed major, not a lower one it also had available. Without this, fce silently running on .NET 10 | |
| # would still document the target and pass green. | |
| if ! grep -qE "Documenting .* on \.NET ${newest}\." canary-diag.log; then | |
| echo "::error::fce did not report running on .NET ${newest} (see the banner above); the preview roll-forward may be broken" | |
| exit 1 | |
| fi | |
| echo "ok: the net8 fce and worker documented a net8 target on the .NET ${newest} preview runtime" | |
| # Beyond the tooling above, layer BEHAVIOURAL coverage of the netstandard2.0 LIBRARIES on the preview. The fce | |
| # step already proves the library LOADS on the preview runtime (the worker reflects over a netstandard2.0 target | |
| # there); this runs the libraries' own unit and property suites on it, so a runtime regression that breaks | |
| # behaviour — not just loading — turns the scheduled run red before that major ships. The test projects target | |
| # net10.0; only the `dotnet test` execution is wrapped in DOTNET_ROLL_FORWARD, so the net10 test host binds the | |
| # highest installed major (the preview) while the build stays on the .NET 10 SDK. Best-effort and non-blocking | |
| # like the rest of the canary. RequestBinder.UnitTests IS included here (unlike the net472 floor job): DateOnly, | |
| # which its fixtures bind, exists on every modern .NET — it is only absent from .NET Framework. | |
| - name: Test the libraries on the preview runtime | |
| if: steps.preview.outcome == 'success' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| echo "Installed .NET runtimes:" | |
| dotnet --list-runtimes | |
| newest="$(dotnet --list-runtimes | sed -nE 's/^Microsoft\.NETCore\.App ([0-9]+)\..*/\1/p' | sort -n | tail -1)" | |
| if [ "${newest:-0}" -le 10 ]; then | |
| echo "::notice::no runtime newer than the .NET 10 build SDK is installed; skipping the library preview run" | |
| exit 0 | |
| fi | |
| for proj in \ | |
| FirstClassErrors.UnitTests \ | |
| FirstClassErrors.PropertyTests \ | |
| FirstClassErrors.RequestBinder.UnitTests \ | |
| FirstClassErrors.RequestBinder.PropertyTests ; do | |
| echo "::group::$proj on the .NET ${newest} preview" | |
| # Build on the .NET 10 SDK (no roll-forward), then run ONLY the test host under roll-forward so it binds | |
| # the preview major. The env is scoped inline to the test call so it never touches build/evaluation. | |
| dotnet build "$proj/$proj.csproj" -c Release | |
| DOTNET_ROLL_FORWARD=LatestMajor DOTNET_ROLL_FORWARD_TO_PRERELEASE=1 \ | |
| dotnet test "$proj/$proj.csproj" -c Release --no-build --logger "console;verbosity=normal" | |
| echo "::endgroup::" | |
| done |