From d0150ec4232966619553d9c9bb2c671b9b8a4f63 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Sun, 19 Jul 2026 07:21:41 +0200 Subject: [PATCH] Add VerifyGeneratedTools CI gate for tool wrapper drift GenerateTools only runs when a contributor remembers to invoke it, so a .json spec edited without regenerating its .Generated.cs could merge silently and ship stale wrapper code in the next release. VerifyGeneratedTools regenerates the tool wrappers from their .json specs and asserts the working copy stays clean, and is wired into the ubuntu-latest required PR gate ahead of Test and Pack. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .fallout/build.schema.json | 3 ++- .github/workflows/ubuntu-latest.yml | 4 ++-- build/Build.CI.GitHubActions.cs | 2 +- build/Build.CodeGeneration.cs | 27 ++++++++++++++++++++++----- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.fallout/build.schema.json b/.fallout/build.schema.json index 47c6a4f95..34edd2473 100644 --- a/.fallout/build.schema.json +++ b/.fallout/build.schema.json @@ -41,7 +41,8 @@ "RunTargetInDockerImageTest", "Test", "UpdateContributors", - "UpdateStargazers" + "UpdateStargazers", + "VerifyGeneratedTools" ] }, "Verbosity": { diff --git a/.github/workflows/ubuntu-latest.yml b/.github/workflows/ubuntu-latest.yml index 45f548d6e..ef2e3ec0b 100644 --- a/.github/workflows/ubuntu-latest.yml +++ b/.github/workflows/ubuntu-latest.yml @@ -55,5 +55,5 @@ jobs: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: Test, Pack' - run: dotnet fallout Test Pack + - name: 'Run: VerifyGeneratedTools, Test, Pack' + run: dotnet fallout VerifyGeneratedTools Test Pack diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 6a272c419..090c5fe7b 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -51,7 +51,7 @@ // branch — all are long-lived and protected; all require the ubuntu-latest check. OnPullRequestBranches = new[] { ExperimentalBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, - InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, + InvokedTargets = new[] { nameof(VerifyGeneratedTools), nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] partial class Build { diff --git a/build/Build.CodeGeneration.cs b/build/Build.CodeGeneration.cs index f8ab28165..29aa95616 100644 --- a/build/Build.CodeGeneration.cs +++ b/build/Build.CodeGeneration.cs @@ -22,12 +22,29 @@ partial class Build }); Target GenerateTools => _ => _ + .Executes(() => GenerateAllTools()); + + // CI gate: `GenerateTools` only runs when a contributor remembers to invoke it, so a .json + // spec edited without regenerating its .Generated.cs could merge silently and ship stale + // wrapper code. Regenerating from a known-clean checkout and asserting the working copy is + // still clean afterward catches that drift before merge. + Target VerifyGeneratedTools => _ => _ + .Requires(() => GitHasCleanWorkingCopy()) .Executes(() => { - SpecificationsDirectory.GlobFiles("*/*.json").ForEach(x => - GenerateCode( - x, - namespaceProvider: x => $"Fallout.Common.Tools.{x.Name}", - sourceFileProvider: x => GitRepository.SetBranch(MainBranch).GetGitHubBrowseUrl(x.SpecificationFile))); + GenerateAllTools(); + + Assert.True( + GitHasCleanWorkingCopy(), + "Generated tool wrappers are out of sync with their .json specs. Run './build.ps1 GenerateTools' locally and commit the result."); }); + + void GenerateAllTools() + { + SpecificationsDirectory.GlobFiles("*/*.json").ForEach(x => + GenerateCode( + x, + namespaceProvider: x => $"Fallout.Common.Tools.{x.Name}", + sourceFileProvider: x => GitRepository.SetBranch(MainBranch).GetGitHubBrowseUrl(x.SpecificationFile))); + } }