From 0074b367e13575e78ec3d2bcf9e73cb6215c7689 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 12 Jul 2026 22:26:15 +1200 Subject: [PATCH] Consolidate and rename CI workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collapse the three per-OS build workflows and rename the hand-written ones for clarity. Behaviour is unchanged — same triggers, same required check. - build.yml (generated): Linux PR gate for PRs to main/release/support. Its job is still named `ubuntu-latest`, so the required status check and branch protection are untouched. - build-cross-platform.yml (generated): macOS + Windows in one workflow (a job per OS, via the attribute's params image list), gated to release intent — PRs to release/support and v* tag pushes only. Replaces macos-latest.yml + windows-latest.yml. - build-docs.yml: renamed from ubuntu-latest-docs.yml; still reports the `ubuntu-latest` check on docs-only PRs. - publish-packages-preview.yml / publish-packages-release.yml: renamed from preview.yml / release.yml. ReleaseWorkflow const in Build.CI.GitHubActions.cs updated to match the release workflow's new name (it gates CreateGitHubRelease). Regenerated the two generated workflows from build/Build.CI.GitHubActions.cs and updated the workflow-name references in README, CONTRIBUTING, architecture, branching-and-release, conventions, and release-and-versioning docs (also fixing some already-stale trigger descriptions). ADRs left as point-in-time records. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ws-latest.yml => build-cross-platform.yml} | 26 +++++- .github/workflows/build-docs.yml | 40 +++++++++ .../{ubuntu-latest.yml => build.yml} | 4 +- .github/workflows/macos-latest.yml | 58 ------------- ...eview.yml => publish-packages-preview.yml} | 12 +-- ...lease.yml => publish-packages-release.yml} | 9 +- .github/workflows/ubuntu-latest-docs.yml | 36 -------- CONTRIBUTING.md | 2 +- README.md | 13 ++- build/Build.CI.GitHubActions.cs | 83 ++++++++++--------- docs/agents/conventions.md | 12 +-- docs/agents/release-and-versioning.md | 6 +- docs/architecture.md | 11 +-- docs/branching-and-release.md | 12 +-- 14 files changed, 153 insertions(+), 171 deletions(-) rename .github/workflows/{windows-latest.yml => build-cross-platform.yml} (62%) create mode 100644 .github/workflows/build-docs.yml rename .github/workflows/{ubuntu-latest.yml => build.yml} (92%) delete mode 100644 .github/workflows/macos-latest.yml rename .github/workflows/{preview.yml => publish-packages-preview.yml} (88%) rename .github/workflows/{release.yml => publish-packages-release.yml} (95%) delete mode 100644 .github/workflows/ubuntu-latest-docs.yml diff --git a/.github/workflows/windows-latest.yml b/.github/workflows/build-cross-platform.yml similarity index 62% rename from .github/workflows/windows-latest.yml rename to .github/workflows/build-cross-platform.yml index bc97f4c73..23a6d71b1 100644 --- a/.github/workflows/windows-latest.yml +++ b/.github/workflows/build-cross-platform.yml @@ -9,12 +9,12 @@ # # - To trigger manual generation invoke: # -# fallout --generate-configuration GitHubActions_windows-latest --host GitHubActions +# fallout --generate-configuration GitHubActions_build-cross-platform --host GitHubActions # # # ------------------------------------------------------------------------------ -name: windows-latest +name: build-cross-platform on: push: @@ -34,6 +34,28 @@ concurrency: cancel-in-progress: true jobs: + macos-latest: + name: macos-latest + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: 'Cache: .fallout/temp, ~/.nuget/packages' + uses: actions/cache@v4 + with: + path: | + .fallout/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - name: 'Setup: .NET SDK' + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + - name: 'Restore: dotnet tools' + run: dotnet tool restore + - name: 'Run: Test, Pack' + run: dotnet fallout Test Pack windows-latest: name: windows-latest runs-on: windows-latest diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 000000000..6cfe52187 --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,40 @@ +# Hand-written (not auto-generated). Companion to build.yml. +# +# Why this exists: +# - main branch protection requires the `ubuntu-latest` status check (the job +# name emitted by build.yml — branch protection keys on the job, not the +# workflow file/name). +# - build.yml has `paths-ignore: docs/**, .assets/**, **/*.md` so it doesn't +# fire on docs-only PRs. +# - Without a substitute, docs-only PRs sit BLOCKED waiting for a check that +# never reports. +# +# This workflow fires on the inverse path set (docs-only changes), runs nothing +# of substance, and reports success under the same `ubuntu-latest` status-check +# context — satisfying the protection rule without spending CI minutes on a real +# build. +# +# Keep the job name `ubuntu-latest` aligned with build.yml so both files produce +# a status check named `ubuntu-latest`; the workflow `name:` mirrors build.yml's +# `build` so docs-only runs appear under the same workflow in the Actions tab. + +name: build + +on: + pull_request: + branches: + - main + - 'release/*' + - 'support/*' + paths: + - 'docs/**' + - '.assets/**' + - '**/*.md' + +jobs: + ubuntu-latest: + name: ubuntu-latest + runs-on: ubuntu-latest + steps: + - name: 'Skip: docs-only PR, no build needed' + run: echo "Docs-only change — ubuntu-latest validation skipped via .github/workflows/build-docs.yml." diff --git a/.github/workflows/ubuntu-latest.yml b/.github/workflows/build.yml similarity index 92% rename from .github/workflows/ubuntu-latest.yml rename to .github/workflows/build.yml index 0e591238a..c03380533 100644 --- a/.github/workflows/ubuntu-latest.yml +++ b/.github/workflows/build.yml @@ -9,12 +9,12 @@ # # - To trigger manual generation invoke: # -# fallout --generate-configuration GitHubActions_ubuntu-latest --host GitHubActions +# fallout --generate-configuration GitHubActions_build --host GitHubActions # # # ------------------------------------------------------------------------------ -name: ubuntu-latest +name: build on: pull_request: diff --git a/.github/workflows/macos-latest.yml b/.github/workflows/macos-latest.yml deleted file mode 100644 index 426d42ffa..000000000 --- a/.github/workflows/macos-latest.yml +++ /dev/null @@ -1,58 +0,0 @@ -# ------------------------------------------------------------------------------ -# -# -# This code was generated. -# -# - To turn off auto-generation set: -# -# [GitHubActions (AutoGenerate = false)] -# -# - To trigger manual generation invoke: -# -# fallout --generate-configuration GitHubActions_macos-latest --host GitHubActions -# -# -# ------------------------------------------------------------------------------ - -name: macos-latest - -on: - push: - tags: - - 'v*' - pull_request: - branches: - - 'release/*' - - 'support/*' - paths-ignore: - - 'docs/**' - - '.assets/**' - - '**/*.md' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - macos-latest: - name: macos-latest - runs-on: macos-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: 'Cache: .fallout/temp, ~/.nuget/packages' - uses: actions/cache@v4 - with: - path: | - .fallout/temp - ~/.nuget/packages - key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} - - name: 'Setup: .NET SDK' - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - - name: 'Restore: dotnet tools' - run: dotnet tool restore - - name: 'Run: Test, Pack' - run: dotnet fallout Test Pack diff --git a/.github/workflows/preview.yml b/.github/workflows/publish-packages-preview.yml similarity index 88% rename from .github/workflows/preview.yml rename to .github/workflows/publish-packages-preview.yml index 7a49b72d6..a2c24d615 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/publish-packages-preview.yml @@ -14,11 +14,13 @@ # # `main` is the sole continuous prerelease lane (ADR-0008 collapsed the former # `experimental` `-alpha` lane into `main`). Stable + legacy releases are -# deliberate and tag-triggered — see release.yml. -# Cross-platform validation on `main` pushes is covered by macos-latest.yml / -# windows-latest.yml; this workflow's job is publishing, not gating. +# deliberate and tag-triggered — see publish-packages-release.yml. +# Cross-platform validation (macOS/Windows) is gated to release intent +# (build-cross-platform.yml) and does NOT run on `main` pushes; on `main` the +# ubuntu-latest gate (build.yml) is the edge. This workflow's job is publishing, +# not gating. -name: preview +name: publish-packages-preview on: push: @@ -29,7 +31,7 @@ on: - '.assets/**' - '**/*.md' -# Cancel a superseded preview build when a newer commit lands (#322). Never on release.yml. +# Cancel a superseded preview build when a newer commit lands (#322). Never on publish-packages-release.yml. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/release.yml b/.github/workflows/publish-packages-release.yml similarity index 95% rename from .github/workflows/release.yml rename to .github/workflows/publish-packages-release.yml index eb0dd5d9a..23fcbf2ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/publish-packages-release.yml @@ -3,7 +3,7 @@ # NuGetApiKey parameter, which would otherwise have to share a name). # # This is the DELIBERATE (production) release path. The fast test lane — -# main (-preview), push → GitHub Packages — lives in preview.yml. See ADR-0004 +# main (-preview), push → GitHub Packages — lives in publish-packages-preview.yml. See ADR-0004 # (amended 2026-05-30), as further amended by ADR-0008 (which collapsed the # former experimental/-alpha lane into main). # @@ -33,7 +33,12 @@ # # See docs/branching-and-release.md for the full runbook. -name: release +# The workflow `name:` below is load-bearing: Build.cs gates +# ICreateGitHubRelease.CreateGitHubRelease on `github.workflow == +# ReleaseWorkflow` (the const in build/Build.CI.GitHubActions.cs). Keep the two +# in sync — rename here and you must rename the const, or GitHub Releases stop +# being cut. +name: publish-packages-release on: push: diff --git a/.github/workflows/ubuntu-latest-docs.yml b/.github/workflows/ubuntu-latest-docs.yml deleted file mode 100644 index 68a767c8f..000000000 --- a/.github/workflows/ubuntu-latest-docs.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Hand-written (not auto-generated). Companion to ubuntu-latest.yml. -# -# Why this exists: -# - main branch protection requires the `ubuntu-latest` status check. -# - ubuntu-latest.yml has `paths-ignore: docs/**, .assets/**, **/*.md` so -# it doesn't fire on docs-only PRs. -# - Without a substitute, docs-only PRs sit BLOCKED waiting for a check -# that never reports. -# -# This workflow fires on the inverse path set (docs-only changes), runs -# nothing of substance, and reports success under the same `ubuntu-latest` -# status-check context — satisfying the protection rule without spending -# CI minutes on a real build. -# -# Keep the workflow `name:` and the job name aligned with ubuntu-latest.yml -# so both files produce a status check named `ubuntu-latest`. - -name: ubuntu-latest - -on: - pull_request: - branches: - - main - - 'release/*' - paths: - - 'docs/**' - - '.assets/**' - - '**/*.md' - -jobs: - ubuntu-latest: - name: ubuntu-latest - runs-on: ubuntu-latest - steps: - - name: 'Skip: docs-only PR, no build needed' - run: echo "Docs-only change — ubuntu-latest validation skipped via .github/workflows/ubuntu-latest-docs.yml." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9bc712ca6..8e2c45c64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,7 +75,7 @@ Tool wrapper JSON lives under `src/Fallout.Common/Tools//.json`. Whe ### After opening a PR -- The PR gate is `ubuntu-latest` only — fires on PRs against `main`, `release/*`, or `support/*`. Docs-only PRs hit a no-op shim workflow that reports the same status check name. `windows-latest` and `macos-latest` run post-merge for cross-platform validation. +- The PR gate is the `ubuntu-latest` job (from `build.yml`) only — fires on PRs against `main`, `release/*`, or `support/*`. Docs-only PRs hit a no-op shim workflow (`build-docs.yml`) that reports the same status check name. `build-cross-platform.yml` runs Windows + macOS validation on `release/*` / `support/*` PRs and `v*` tag pushes (gated to release intent), not on routine `main` work. - **Review rises with the ladder.** PRs to `main` (preview) get ordinary review — it's the integration trunk. Promotion to a `release/YYYY` production train (and the GA cut) gets rigorous, unhurried review — that's the project's quality gate. Match your expectations to where the PR is headed. - Address review feedback in additional commits rather than force-pushing — easier to review the changes. - If CI fails on something unrelated to your change, ping a maintainer. diff --git a/README.md b/README.md index f173fdc2d..a033f2f30 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ > Build automation for C#/.NET — the hard-fork successor to NUKE. [![Docs](https://img.shields.io/badge/docs-docs.fallout.build-blue?logo=readthedocs&logoColor=white)](https://docs.fallout.build/) -[![CI](https://github.com/Fallout-build/Fallout/actions/workflows/ubuntu-latest.yml/badge.svg)](https://github.com/Fallout-build/Fallout/actions/workflows/ubuntu-latest.yml) +[![CI](https://github.com/Fallout-build/Fallout/actions/workflows/build.yml/badge.svg)](https://github.com/Fallout-build/Fallout/actions/workflows/build.yml) [![NuGet](https://img.shields.io/nuget/v/Fallout.Common?label=Fallout.Common)](https://www.nuget.org/packages/Fallout.Common) [![NuGet downloads](https://img.shields.io/nuget/dt/Fallout.Common?label=downloads)](https://www.nuget.org/packages/Fallout.Common) [![Latest release](https://img.shields.io/github/v/release/Fallout-build/Fallout?label=release)](https://github.com/Fallout-build/Fallout/releases/latest) @@ -84,15 +84,14 @@ Fallout (NUKE's successor) brings your build automation to an even level with ev ## Build Status -CI runs on every PR targeting `main`, `release/*`, or `support/*` across `ubuntu-latest` — the only required status check. After merge, post-merge validation runs on `windows-latest` and `macos-latest`, and a `…-preview` prerelease is published to **GitHub Packages** under the reserved `Fallout.*` prefix from `main`. **Stable** releases fire from `release/YYYY` tags via `.github/workflows/release.yml` (GitHub Packages + GitHub Releases by default; nuget.org opt-in per release). Docs-only PRs are served by a no-op companion workflow (`ubuntu-latest-docs`) so branch protection is satisfied without spending CI minutes on a real build. +CI runs on every PR targeting `main`, `release/*`, or `support/*` on `build.yml` (Linux) — its `ubuntu-latest` job is the only required status check. Cross-platform Test+Pack (Windows + macOS) is gated to release intent — PRs into `release/*` / `support/*` and `v*` tag pushes — via `build-cross-platform.yml`; it does not run on routine `main` work. A `…-preview` prerelease is published to **GitHub Packages** under the reserved `Fallout.*` prefix on every push to `main`. **Stable** releases fire from `release/YYYY` tags via `.github/workflows/publish-packages-release.yml` (GitHub Packages + GitHub Releases by default; nuget.org opt-in per release). Docs-only PRs are served by a no-op companion workflow (`build-docs`) so branch protection is satisfied without spending CI minutes on a real build. | Workflow | Status | Trigger | |---|---|---| -| [`ubuntu-latest`](.github/workflows/ubuntu-latest.yml) | [![ubuntu-latest](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/ubuntu-latest.yml?branch=main&label=&logo=ubuntu&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/ubuntu-latest.yml) | PR to `main` / `release/*` / `support/*` (code paths) — **required check** | -| [`windows-latest`](.github/workflows/windows-latest.yml) | [![windows-latest](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/windows-latest.yml?branch=main&label=&logo=windows&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/windows-latest.yml) | push to those branches (post-merge validation) | -| [`macos-latest`](.github/workflows/macos-latest.yml) | [![macos-latest](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/macos-latest.yml?branch=main&label=&logo=apple&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/macos-latest.yml) | push to those branches (post-merge validation) | -| [`preview`](.github/workflows/preview.yml) | [![preview](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/preview.yml?branch=main&label=&logo=githubactions&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/preview.yml) | push to `main` → `…-preview` prerelease to GitHub Packages | -| [`release`](.github/workflows/release.yml) | [![release](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/release.yml?branch=main&label=&logo=nuget&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/release.yml) | tag push on `release/YYYY` (stable) or `support/*` (legacy/retired) — nuget.org opt-in | +| [`build`](.github/workflows/build.yml) | [![build](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/build.yml?branch=main&label=&logo=ubuntu&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/build.yml) | PR to `main` / `release/*` / `support/*` (code paths) — job `ubuntu-latest` is the **required check** | +| [`build-cross-platform`](.github/workflows/build-cross-platform.yml) | [![build-cross-platform](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/build-cross-platform.yml?label=&logo=github&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/build-cross-platform.yml) | PR to `release/*` / `support/*` or `v*` tag push — Windows + macOS (release intent) | +| [`publish-packages-preview`](.github/workflows/publish-packages-preview.yml) | [![publish-packages-preview](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/publish-packages-preview.yml?branch=main&label=&logo=githubactions&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-preview.yml) | push to `main` → `…-preview` prerelease to GitHub Packages | +| [`publish-packages-release`](.github/workflows/publish-packages-release.yml) | [![publish-packages-release](https://img.shields.io/github/actions/workflow/status/Fallout-build/Fallout/publish-packages-release.yml?branch=main&label=&logo=nuget&logoColor=white&style=flat-square)](https://github.com/Fallout-build/Fallout/actions/workflows/publish-packages-release.yml) | tag push on `release/YYYY` (stable) or `support/*` (legacy/retired) — nuget.org opt-in | Multi-provider CI support (Azure Pipelines, GitLab, TeamCity, AppVeyor) was removed during the takeover and is being revived demand-driven — see [#8](https://github.com/Fallout-build/Fallout/issues/8). diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 360607b4c..28f1c35d4 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -1,30 +1,54 @@ using Fallout.Common.CI.GitHubActions; using Fallout.Components; -// Cross-platform (macOS/Windows) full Test+Pack is gated to RELEASE INTENT -// (#318/#326): it runs only on a PR into a production branch (release/YYYY, -// support/*) and on a release tag push (v*) — never on routine pushes to -// main, never on a per-merge basis. On main "we've got our edge": the -// ubuntu-latest PR gate + the preview pipeline (.github/workflows/preview.yml). -// (workflow_dispatch as a manual cross-platform trigger isn't emitted here — -// the generator only writes workflow_dispatch when it has inputs; GitHub's -// built-in run re-run covers the on-demand case.) +// Two generated build workflows. Both run Test+Pack; both are GENERATED from the +// attributes below — edit here and regenerate (`./build.sh`), never hand-edit the +// `.yml`. // -// concurrency cancel-in-progress (#322): superseded runs are cancelled rather -// than stacked. Never applied to release.yml (a publish must not be cancelled). +// build.yml — the Linux PR gate, and the ONLY required status +// check (job `ubuntu-latest`; branch protection keys on +// that job name, not the workflow file/name). PR-only: +// feature-branch pushes run zero CI until a PR is opened +// against a long-lived branch (#327), targeting main, +// release/YYYY, or support/*. CheckoutRef = github.head_ref +// pins checkout to the PR source branch instead of the +// merge SHA, keeping HEAD attached so +// GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest +// (which reads .git/HEAD via GitRepository.FromLocalDirectory) +// resolves a non-null branch. +// +// build-cross-platform.yml — macOS + Windows in ONE workflow (one job per image). +// Cross-platform full Test+Pack is gated to RELEASE +// INTENT (#318/#326): it runs only on a PR into a +// production branch (release/YYYY, support/*) and on a +// release tag push (v*) — never on routine pushes/PRs to +// main. On main "we've got our edge": the ubuntu-latest +// gate above + the preview pipeline +// (.github/workflows/publish-packages-preview.yml). +// (workflow_dispatch as a manual cross-platform trigger +// isn't emitted — the generator only writes +// workflow_dispatch when it has inputs; GitHub's built-in +// run re-run covers the on-demand case.) +// +// concurrency cancel-in-progress (#322): superseded runs are cancelled rather than +// stacked. Never applied to the publish-packages-release workflow (a publish must +// not be cancelled). [GitHubActions( - "macos-latest", - GitHubActionsImage.MacOsLatest, + "build", + GitHubActionsImage.UbuntuLatest, FetchDepth = 0, ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", ConcurrencyCancelInProgress = true, - OnPushTags = new[] { "v*" }, - OnPullRequestBranches = new[] { ReleaseBranchPattern, SupportBranchPattern }, + CheckoutRef = "${{ github.head_ref }}", + // PRs targeting main or any release/YYYY / support/* branch — all long-lived and + // protected; all require the ubuntu-latest check. + OnPullRequestBranches = new[] { MainBranch, ReleaseBranchPattern, SupportBranchPattern }, OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] [GitHubActions( - "windows-latest", + "build-cross-platform", + GitHubActionsImage.MacOsLatest, GitHubActionsImage.WindowsLatest, FetchDepth = 0, ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", @@ -34,31 +58,14 @@ OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] -// The Linux PR gate — the only required status check. pull_request only: -// feature-branch pushes run zero CI until a PR is opened against a long-lived -// branch (#327). CheckoutRef = github.head_ref pins checkout to the PR source -// branch instead of the merge SHA, keeping HEAD attached so -// GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads .git/HEAD -// via GitRepository.FromLocalDirectory) resolves a non-null branch. -[GitHubActions( - "ubuntu-latest", - GitHubActionsImage.UbuntuLatest, - FetchDepth = 0, - ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", - ConcurrencyCancelInProgress = true, - CheckoutRef = "${{ github.head_ref }}", - // Trigger for PRs targeting main or any release/YYYY / support/* branch — - // all are long-lived and protected; all require the ubuntu-latest check. - OnPullRequestBranches = new[] { MainBranch, ReleaseBranchPattern, SupportBranchPattern }, - OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, - InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, - PublishArtifacts = false)] partial class Build { // The release workflow is intentionally hand-written at - // .github/workflows/release.yml — that lets us name the GitHub secret - // NUGET_API_KEY (conventional screaming-snake-case) while keeping the + // .github/workflows/publish-packages-release.yml — that lets us name the GitHub + // secret NUGET_API_KEY (conventional screaming-snake-case) while keeping the // Build.cs property name NuGetApiKey (idiomatic C#). The NUKE attribute - // generator would force the two to match. - const string ReleaseWorkflow = "release"; + // generator would force the two to match. This constant must match that + // workflow's `name:` — it gates ICreateGitHubRelease.CreateGitHubRelease + // (Build.cs) to the release workflow only. + const string ReleaseWorkflow = "publish-packages-release"; } diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md index d14b74b7f..5deeadbb1 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -75,13 +75,13 @@ public string[] OnWorkflowDispatchOptionalInputs { get; set; } = new string[0]; Shaped by [milestone #18](https://github.com/Fallout-build/Fallout/milestone/18) and the [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md) ladder (amended by [ADR-0008](../adr/0008-collapse-experimental-into-main.md), which collapsed `experimental` into `main`). Invariants: - **Feature branches run zero CI until a PR is opened.** Push triggers list **only** long-lived branches; nothing fires on `feature/*`, `bugfix/*`, etc. until they're PR'd against `main`/`release/*`/`support/*`. Do **not** add a working-branch pattern to any `OnPush*`/`branches:` trigger. -- **The Linux PR gate (`ubuntu-latest`) is the only required check** — runs on PRs to the long-lived branches. -- **`main` (push) → `-preview`** to GitHub Packages (`preview.yml`) — the sole continuous publisher now that `experimental.yml` is deleted. -- **Cross-platform `windows`/`macos` are gated to release intent** — PR-to-`release/*`/`support/*` or a `v*` tag push only. They do **not** run on `main` pushes. ("On `main` we've got our edge.") -- **`concurrency: cancel-in-progress` on every build workflow except `release.yml`** — never cancel a publish mid-flight. +- **The Linux PR gate (job `ubuntu-latest`, from `build.yml`) is the only required check** — runs on PRs to the long-lived branches. (Branch protection keys on the job name, not the workflow file.) +- **`main` (push) → `-preview`** to GitHub Packages (`publish-packages-preview.yml`) — the sole continuous publisher now that `experimental.yml` is deleted. +- **Cross-platform `windows`/`macos` are gated to release intent** — one `build-cross-platform.yml` workflow (a job per OS), firing on PR-to-`release/*`/`support/*` or a `v*` tag push only. They do **not** run on `main` pushes. ("On `main` we've got our edge.") +- **`concurrency: cancel-in-progress` on every build workflow except `publish-packages-release.yml`** — never cancel a publish mid-flight. - **Canonical CI-ignore paths:** `docs/**`, `.assets/**`, `**/*.md` — applied to every PR/push trigger. -- The `ubuntu-latest` / `windows-latest` / `macos-latest` workflows are **generated** from `build/Build.CI.GitHubActions.cs` — edit the attributes + constants there and regenerate (`./build.sh`), never hand-edit the `.yml`. `preview.yml` / `release.yml` are hand-written. -- **Every publishing lane runs `Test` before it publishes** (#324). `preview.yml` and `release.yml` both run a single `dotnet fallout Test Pack` invocation — NUKE executes it as discrete internal stages (Restore → Compile → Test → Pack) and fails at the breaking stage, so a test failure stops the job before the push step. Don't split a lane into separate `dotnet fallout Compile`/`Test`/`Pack` steps — each invocation re-runs the dependency graph (double-compile); the single invocation *is* the staged build. +- The `build.yml` (Linux gate) and `build-cross-platform.yml` (macOS+Windows) workflows are **generated** from `build/Build.CI.GitHubActions.cs` — edit the attributes + constants there and regenerate (`./build.sh`), never hand-edit the `.yml`. `build-docs.yml`, `publish-packages-preview.yml`, and `publish-packages-release.yml` are hand-written. +- **Every publishing lane runs `Test` before it publishes** (#324). `publish-packages-preview.yml` and `publish-packages-release.yml` both run a single `dotnet fallout Test Pack` invocation — NUKE executes it as discrete internal stages (Restore → Compile → Test → Pack) and fails at the breaking stage, so a test failure stops the job before the push step. Don't split a lane into separate `dotnet fallout Compile`/`Test`/`Pack` steps — each invocation re-runs the dependency graph (double-compile); the single invocation *is* the staged build. - **Caching** (#328): every workflow caches `~/.nuget/packages` + `.fallout/temp`, keyed on `global.json` + `**/*.csproj` + `Directory.Packages.props` (the dependency-affecting set), with a `restore-keys:` prefix fallback for partial restores. There is no `packages.lock.json` to add to the key, and build outputs (`bin`/`obj`) are deliberately **not** cached (stale-artifact correctness risk). ## What not to do diff --git a/docs/agents/release-and-versioning.md b/docs/agents/release-and-versioning.md index 588a52638..42a3ecc66 100644 --- a/docs/agents/release-and-versioning.md +++ b/docs/agents/release-and-versioning.md @@ -35,7 +35,7 @@ CI providers in use: **GitHub Actions only** (others were dropped — see [#8](h Apply by mirroring `main`'s protection JSON to the new branch via the GitHub API (or via repo Settings → Branches). Tag protection for `v*` tags (restricting who can fire a release tag) is tracked separately under milestone #13. -**Validation workflows.** `ubuntu-latest` runs on every PR targeting `main`, `release/*`, or `support/*` (with `paths-ignore` for `docs/**`, `.assets/**`, `**/*.md`). `windows-latest` and `macos-latest` run on push to those branches — they're post-merge / release validation, not PR gates. This is a deliberate cost trade-off. (These three workflows are **generated** from `build/Build.CI.GitHubActions.cs` — change the branch lists in the `MainBranch`/`*BranchPattern` constants there and regenerate, don't hand-edit the `.yml`.) +**Validation workflows.** `build.yml` runs Test+Pack on Linux on every PR targeting `main`, `release/*`, or `support/*` (with `paths-ignore` for `docs/**`, `.assets/**`, `**/*.md`); its job `ubuntu-latest` is the only required status check. `build-cross-platform.yml` runs Test+Pack on Windows and macOS (one job each) only on PRs targeting `release/*` / `support/*` and on `v*` tag pushes — cross-platform is gated to release intent, not routine `main` work. This is a deliberate cost trade-off. (Both workflows are **generated** from `build/Build.CI.GitHubActions.cs` — change the branch lists in the `MainBranch`/`*BranchPattern` constants there and regenerate, don't hand-edit the `.yml`. The `build-docs.yml` no-op shim reports the `ubuntu-latest` check on docs-only PRs.) **Merging.** Rebase merge only — squash and plain merge commits are both disabled by repo setting (linear history). Every reviewed commit lands on `main` verbatim, so curate commits into a clean sequence before final approval. See [CONTRIBUTING.md → Merging](https://github.com/Fallout-build/Fallout/blob/main/CONTRIBUTING.md#merging) for the convention. @@ -88,7 +88,7 @@ If you only discover the breaking nature mid-review, apply all relevant steps be ## Release pipeline -`.github/workflows/release.yml` is **tag-triggered**: pushing a `v*` tag on a production branch (`release/YYYY` or `support/*`) fires the pipeline. The workflow validates the tag is reachable from such a branch, then fans out a Test+Pack job to three parallel publish jobs: +`.github/workflows/publish-packages-release.yml` is **tag-triggered**: pushing a `v*` tag on a production branch (`release/YYYY` or `support/*`) fires the pipeline. The workflow validates the tag is reachable from such a branch, then fans out a Test+Pack job to three parallel publish jobs: | Job | Environment | Fires on tag push? | What ships | Gating | |---|---|---|---|---| @@ -98,7 +98,7 @@ If you only discover the breaking nature mid-review, apply all relevant steps be ### Preview lane (from `main`) -Pushes to `main` publish **preview prereleases** (`YYYY.MINOR.PATCH-preview..g`) to **GitHub Packages only** — never nuget.org, never a GitHub Release. `main` is the sole continuous prerelease lane (per [ADR-0008](../adr/0008-collapse-experimental-into-main.md), which collapsed the former `experimental`/`-alpha` lane into `main`). It does not cause nuget.org Dependabot fan-out into consumer repos (GitHub Packages is opt-in for consumers — the reason this lane is non-publishing to nuget.org per ADR-0001/0002). Implemented in `.github/workflows/preview.yml` (the former `experimental.yml` is deleted). +Pushes to `main` publish **preview prereleases** (`YYYY.MINOR.PATCH-preview..g`) to **GitHub Packages only** — never nuget.org, never a GitHub Release. `main` is the sole continuous prerelease lane (per [ADR-0008](../adr/0008-collapse-experimental-into-main.md), which collapsed the former `experimental`/`-alpha` lane into `main`). It does not cause nuget.org Dependabot fan-out into consumer repos (GitHub Packages is opt-in for consumers — the reason this lane is non-publishing to nuget.org per ADR-0001/0002). Implemented in `.github/workflows/publish-packages-preview.yml` (the former `experimental.yml` is deleted). ### Why nuget.org stays opt-in diff --git a/docs/architecture.md b/docs/architecture.md index cc4a7e466..355c64c7a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -81,12 +81,13 @@ This file covers *layout*. For how the build orchestrator works inside — the s | Workflow | When it runs | What it does | |---|---|---| -| `ubuntu-latest.yml` | Every PR targeting `main` (with `paths-ignore` for docs/.assets/markdown) | Test + Pack. Fast Linux feedback loop. | -| `windows-latest.yml` | Push to `main` only | Test + Pack on Windows after merge. | -| `macos-latest.yml` | Push to `main` only | Test + Pack on macOS after merge. | -| `release.yml` | Push to `main` | Test + Pack + Publish to GitHub Packages. | +| `build.yml` (generated) | Every PR targeting `main`, `release/*`, or `support/*` (with `paths-ignore` for docs/.assets/markdown) | Test + Pack on Linux. Fast feedback loop; the job `ubuntu-latest` is the only required status check. | +| `build-docs.yml` | Docs-only PRs to the same branches | No-op that reports the `ubuntu-latest` check so docs-only PRs aren't blocked. | +| `build-cross-platform.yml` (generated) | PRs targeting `release/*` / `support/*`, and `v*` tag pushes | Test + Pack on macOS **and** Windows (one job each). Gated to release intent. | +| `publish-packages-preview.yml` | Push to `main` | Test + Pack + publish `-preview` to GitHub Packages only. | +| `publish-packages-release.yml` | `v*` tag push on a production branch (or `workflow_dispatch`) | Test + Pack + publish to GitHub Packages + GitHub Releases (nuget.org opt-in). | -Linux runs on PRs because it's cheap and fast; Windows and macOS are reserved for post-merge validation. If either breaks post-merge, it surfaces as a red commit on `main` and we fix forward. +Linux runs on every PR because it's cheap and fast; macOS and Windows are gated to release intent (release/support PRs and release tags) to save CI minutes. On `main` the Linux gate plus the preview pipeline are the edge; if cross-platform breaks it surfaces on a release PR or tag and we fix before shipping. ## What this doc deliberately does NOT cover diff --git a/docs/branching-and-release.md b/docs/branching-and-release.md index 2ad752093..ab92a5a2f 100644 --- a/docs/branching-and-release.md +++ b/docs/branching-and-release.md @@ -36,7 +36,7 @@ Releases fire to multiple channels, each with its own GitHub Environment: | `github-releases` env (bundled) | `release/*`, `support/*` tags | Same tag as the package publish | None | Same as the tag | | Docker local NuGet server | Per-PR / per-commit | None (local) | PR-derived | Available via `tests/integration/docker-compose.yml` | -**Defaults:** `main` (preview) publishes to GitHub Packages only — **never nuget.org, never a GH Release**. `preview.yml` (main → `-preview`) is the only continuous publisher; the former `experimental.yml` workflow has been deleted ([ADR-0008](adr/0008-collapse-experimental-into-main.md)). Production tag pushes (`release/YYYY`, `support/*`) publish to GitHub Packages + GitHub Releases. nuget.org is **always opt-in** via the `workflow_dispatch` `publish-to-nugetorg` flag — used when a `release/YYYY` is stabilised enough for the broader consumer audience, or for a `support/v10` security patch. See [`project_release_channels` in agent memory](https://github.com/Fallout-build/Fallout/issues/267#issuecomment-4570408325) and [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md). +**Defaults:** `main` (preview) publishes to GitHub Packages only — **never nuget.org, never a GH Release**. `publish-packages-preview.yml` (main → `-preview`) is the only continuous publisher; the former `experimental.yml` workflow has been deleted ([ADR-0008](adr/0008-collapse-experimental-into-main.md)). Production tag pushes (`release/YYYY`, `support/*`) publish to GitHub Packages + GitHub Releases. nuget.org is **always opt-in** via the `workflow_dispatch` `publish-to-nugetorg` flag — used when a `release/YYYY` is stabilised enough for the broader consumer audience, or for a `support/v10` security patch. See [`project_release_channels` in agent memory](https://github.com/Fallout-build/Fallout/issues/267#issuecomment-4570408325) and [ADR-0004](adr/0004-calendar-versioning-and-dual-pace-channels.md). ## Cutting a release @@ -60,7 +60,7 @@ gh release create v2026.1.X \ --generate-notes ``` -That tag push triggers `.github/workflows/release.yml`: +That tag push triggers `.github/workflows/publish-packages-release.yml`: 1. **`validate-ref`** confirms the tag points at a commit reachable from a production branch (`release/YYYY` or `support/*`). 2. **`test-and-pack`** runs `dotnet fallout Test Pack`, uploads `output/packages/*.nupkg` as an artifact. @@ -75,11 +75,11 @@ When a `release/2026` release is stabilised enough for nuget.org, or for cutting ```bash # Option A: via gh CLI -gh workflow run release.yml \ +gh workflow run publish-packages-release.yml \ -f tag=v2026.1.X \ -f publish-to-nugetorg=true -# Option B: via Actions UI → release → "Run workflow" → set publish-to-nugetorg to true +# Option B: via Actions UI → publish-packages-release → "Run workflow" → set publish-to-nugetorg to true ``` The workflow: @@ -98,10 +98,10 @@ Each `dotnet nuget push` uses `--skip-duplicate`. Re-running a publish job is id ```bash # Routine re-run — leave publish-to-nugetorg false -gh workflow run release.yml -f tag=v2026.1.X +gh workflow run publish-packages-release.yml -f tag=v2026.1.X # Stabilised re-run — include the flag if you want to retry the nuget.org push -gh workflow run release.yml -f tag=v2026.1.X -f publish-to-nugetorg=true +gh workflow run publish-packages-release.yml -f tag=v2026.1.X -f publish-to-nugetorg=true ``` ## Promotion and hotfixing