Skip to content

Deprecate per-repo bootstrappers; make Fallout.GlobalTool the canonical entry point #203

Description

@ChrisonSimtian

Why

Today every Fallout-using repo carries `build.cmd` (7 lines), `build.sh` (~70 lines), and `build.ps1` (~70 lines). They do two jobs:

  1. Provision the .NET SDK locally (`dotnet-install.sh/ps1` → `.fallout/temp/`) when it's missing.
  2. Compile + run `build/_build.csproj` via `dotnet build` + `dotnet run --project --no-build`.

The `fallout` global tool exists but, for target execution, just shells out to `bash ./build.sh` — it's a launcher, not a runner. We ship a .NET build tool whose canonical entry point is a shell script. That's the thing we want to fix.

Goal: one logical entry point (`fallout`), one C# code path for target execution, no `.cmd`, and thin shell shims only for the "I just cloned the repo without `dotnet` installed yet" case.

Approach

Three PRs, sequenced. The split keeps PR-A as a pure additive change (the in-tool runner ships dormant; CI doesn't depend on it yet) so it can be released independently. Downstream consumers who upgrade `Fallout.GlobalTool` pick up the new runner; their old fat shims still work; they can adopt thin shims on their own schedule by re-running `fallout :setup`.

PR-A — Add in-tool build runner (#201)

Status: open, code+tests done locally, waiting on GitHub Actions to recover (see "Known issues" below).

Adds a code path so `fallout ` discovers and runs the build project directly, instead of spawning `./build.sh`/`./build.ps1`.

  • New `src/Fallout.GlobalTool/BuildProjectResolver.cs` — reads optional `BuildProjectFile` field from `.fallout/parameters.json` if set; otherwise convention `build/_build.csproj`. 6 unit tests cover the resolution cases.
  • New `src/Fallout.GlobalTool/Program.Run.cs` — resolves `dotnet` (PATH first; `.fallout/temp/dotnet-{unix,win}` as fallback), seeds the same env vars the shim sets (`DOTNET_CLI_TELEMETRY_OPTOUT`, `DOTNET_NOLOGO`, `DOTNET_ROLL_FORWARD`, `FALLOUT_TELEMETRY_OPTOUT`, plus the existing `FALLOUT_GLOBAL_TOOL_*` markers), runs `dotnet build` then `dotnet run --project --no-build -- ` as subprocesses with inherited stdio.
  • Modified `src/Fallout.GlobalTool/Program.cs` — `Handle`'s no-`:` fallback now calls `Run(...)` instead of `Build(buildScript, ...)`. The `buildScript == null` branch of the "Could not find" prompt is removed.
  • New `tests/Fallout.GlobalTool.Tests/BuildProjectResolverTests.cs` — 6 tests, green locally.

`Build()` method is NOT deleted — still called from `Program.Complete.cs` as a bootstrap path for the first-time completion request. Removed in PR-B alongside the shim deletion.

PR-B — Switch the repo to thin shims + local tool manifest, regenerate workflows

Status: not started; blocked on PR-A landing and a `Fallout.GlobalTool` release that ships the new runner.

  • New `.config/dotnet-tools.json` pinning `Fallout.GlobalTool` as a local tool.
  • Replace `build.sh` and `build.ps1` with thin versions (~20 lines each): keep the dotnet-provisioning block verbatim from the current scripts (`build.sh:36-68`, `build.ps1:41-75`), drop the `BUILD_PROJECT_FILE` config and the explicit `dotnet build` / `dotnet run --project` lines, end with `tool restore` + `exec dotnet fallout "$@"`.
  • Delete `build.cmd`.
  • Update the `[GitHubActions]` generator at `src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsRunStep.cs:23` — currently emits `run: ./{BuildCmdPath} {targets}` as a single step; replace with three steps: `actions/setup-dotnet@v4` (reads `global.json`), `dotnet tool restore`, `dotnet fallout {targets}`. Mark `BuildCmdPath` property on `GitHubActionsAttribute` as `[Obsolete]`. This is a breaking change for downstream consumers using `[GitHubActions(InvokedTargets = ...)]` — their next regen produces three steps instead of one.
  • Regenerate workflows via `fallout GenerateConfiguration` — the four files `.github/workflows/{ubuntu-latest,macos-latest,windows-latest,release}.yml` get the new shape. Do NOT edit these by hand; they're auto-generated.
  • Update `fallout :setup` scaffolder at `src/Fallout.GlobalTool/Program.Setup.cs` `WriteBuildScripts()` (lines 102-240): emit thin `build.sh`/`build.ps1` from rewritten templates, emit `.config/dotnet-tools.json` from a new template, drop `build.cmd` emission, delete `src/Fallout.GlobalTool/templates/build.cmd`. Add merge logic for the case where consumer already has `.config/dotnet-tools.json`.
  • Update `.fallout/build.schema.json` generation at `src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs:45` (or thereabouts) — teach the schema-emitter about the new optional `BuildProjectFile` field so IDE IntelliSense works for users editing `parameters.json`. (Manual edits to the schema file don't stick because it's regenerated on every build run.)

PR-C — Docs cleanup

Status: tracked in #198. Re-scope #198 (or open a follow-up) to cover the new shim shape: `docs/01-getting-started/02-setup.md`, `docs/01-getting-started/03-execution.md`, `docs/05-cicd/github-actions.md` (and the rest of `docs/05-cicd/*` if revived per #8), `CONTRIBUTING.md`, `CLAUDE.md`.

Backwards compatibility

Scenario Behaviour
Existing repo, old fat shims, run `./build.sh Compile` Unchanged. Old shims do their own `dotnet build` + `dotnet run --project` — they don't depend on the global tool.
Existing repo, old fat shims, run `fallout Compile` Uses the new in-tool runner. Same end result, one fewer subprocess hop.
Existing repo with thin shims, old globally-installed `fallout` Thin shim does `dotnet tool restore` + `dotnet fallout`; manifest-pinned (newer) version takes over. Safe.
Empty directory, no `.fallout/` Prompt for `:setup` as before.

Known issues / things to watch

1. GitHub Actions degraded performance (active as of 2026-05-26 09:30 UTC)

`pull_request` workflows weren't triggering across the whole repo for the past 30+ minutes — confirmed via `githubstatus.com`:

```
Actions degraded_performance
Pages degraded_performance
```

This affected #201 and #202 — neither has CI signal yet. Verify GH is healthy first thing tomorrow; if checks haven't appeared on those PRs, an empty-commit push usually nudges things. We already burned the close-and-reopen trick on PR-200 (now closed); the work landed on `feat/globaltool-runner` instead.

2. `Nuke.Common.Shim.Tests` silently broke windows-latest on main (fixed in #202)

The project is compile-only by design — see `SampleConsumerBuild.cs` header. It had zero `[Fact]`/`[Theory]` methods. `dotnet test` against a project with no discoverable tests exits 0 on Linux/macOS but 1 on Windows, so every post-merge push to `main` was failing the `windows-latest` validation step. `ubuntu-latest` and `macos-latest` were green. No-one noticed until the in-tool runner work surfaced it.

#202 adds a trivial `[Fact]` (`Shim_Compiles_When_Test_Assembly_Loads`) — discovery now succeeds on all platforms. After it merges, the next push to main should go green on windows-latest. Verify that.

3. `build.schema.json` is regenerated on every build run

By `HandleShellCompletionAttribute` (`src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs`). Manual edits don't stick. Add the `BuildProjectFile` field to the schema-generation code in PR-B, not by hand.

4. Branch-name workaround for stuck CI

The original PR-A branch `feat/global-tool-in-tool-runner` never got workflow runs (likely a GH-side webhook hiccup amplified by the ongoing Actions degradation). PR-200 was closed; the code was re-pushed to a fresh branch `feat/globaltool-runner` and reopened as #201. If a branch ever gets stuck like that again, recreate under a different name as a first attempt.

Pickup checklist (Windows laptop, tomorrow)

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions