Skip to content

Lift the netstandard2.0 floor off the tooling stack via an out-of-process MSBuild bridge - #447

Draft
ChrisonSimtian wants to merge 10 commits into
Fallout-build:developfrom
ChrisonSimtian:tooling/lift-ns20-floor
Draft

Lift the netstandard2.0 floor off the tooling stack via an out-of-process MSBuild bridge#447
ChrisonSimtian wants to merge 10 commits into
Fallout-build:developfrom
ChrisonSimtian:tooling/lift-ns20-floor

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

⚠️Breaking changeFallout.Tooling / Fallout.Tooling.Generator drop netstandard2.0 (now net8.0;net10.0 and net10.0) and Fallout.MSBuildTasks drops net472. A consumer referencing those assemblies directly from a netstandard2.0/net472 project must move to net8+/net10. The MSBuild integration's behaviour is preserved for VS2019/2022 via a new out-of-process bridge (below).

Implements Lever 2 of #441 — frees the tooling stack from the ns2.0 floor so Fallout.Core (ns2.1) can absorb Configure<T> / AbsolutePath / the tool contracts (de-static Phase 2b/2c/2e). Design recorded in ADR-0009 (in this PR).

How

netstandard2.0 is the only TFM that can't consume Fallout.Core (ns2.1), and the floor came solely from Fallout.MSBuildTasks's net472 target (loaded by full-framework VS MSBuild) referencing Fallout.Tooling.

  • Task logic extracted to a net10 Fallout.MSBuildTasks.Engine.
  • Core MSBuild (dotnet/SDK): in-process net10 task — the hot path, keeps ALC isolation.
  • Full-framework MSBuild (VS2019/2022): a thin net472 ToolTask bridge that shells out to a standalone net10 worker (Fallout.MSBuildTasks.Worker) — zero Fallout deps, so Tooling no longer needs ns2.0.
  • .targets selects by $(MSBuildRuntimeType); packaged into build\netcore (task) / build\netfx (bridge + worker).
  • Fallout.Tooling keeps a net8 flavour for Fallout.ProjectModel's down-level targets — net8 consumes ns2.1, so the floor is still lifted.

The bridge is time-boxed: delete it once VS2026's .NET MSBuild can load net10 tasks in-process.

Verification ⚠️ (why this is a draft)

  • ✅ Full solution build + entire test suite green; the worker is smoke-tested out-of-process.
  • Not yet verified: the consumer task path + Pack output, and the net472 VS full-framework bridge — these aren't exercised on non-Windows or by this repo's dogfood (which runs FalloutTasksEnabled=False). Needs a Windows + VS + Pack smoke test before un-drafting.

🤖 Generated with Claude Code

ChrisonSimtianand others added 6 commits June 30, 2026 20:17
…MSBuild bridge
Decision record for freeing Fallout.Tooling + Fallout.Tooling.Generator from the ns2.0
floor that Fallout.MSBuildTasks's net472 target imposes. Extract task logic into a net10
engine; serve dotnet/SDK builds with an in-proc net10 task and VS2019/2022 full-framework
MSBuild with a thin net472 ToolTask bridge that shells out to a standalone net10 worker.
Unblocks lifting AbsolutePath / Host / IFalloutBuild into Fallout.Core. Implements Lever 2
of Fallout-build#441 (Lever 1 / SourceGenerators cone is Fallout-build#442, independent). Bridge is time-boxed and
deletable once VS2026's .NET MSBuild lands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First step of ADR-0009. Lifts the bodies of CodeGenerationTask / EmbedPackagesForSelfContained
/ PackPackageTools into an MSBuild-free, net10-only engine (CodeGenerationEngine +
PackageToolingEngine, plain data in/out via PackagedToolFile). This is the shared home both the
in-process net10 task and the out-of-process worker will call, so the net472 host can later become
a thin bridge and Fallout.Tooling can drop the ns2.0 floor.
Additive — nothing consumes the engine yet; the existing tasks are untouched. Solution + full
build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second step of ADR-0009. Fallout.MSBuildTasks.Protocol is a dependency-free contract
(netstandard2.0;net10) of request/response POCOs + System.Text.Json helpers, referenced by
both the net472 bridge and the net10 worker. Fallout.MSBuildTasks.Worker is the standalone
net10 console the bridge shells out to: <verb> <input.json> <output.json>, dispatching codegen
/ embed-packages / pack-tools to the engine and writing a WorkerResponse (exit 0/1/2).
Additive — nothing invokes the worker yet. Smoke-tested out-of-process: bad-invocation → exit 2;
embed-packages against a real project.assets.json → exit 0 with a valid package list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Third step of ADR-0009. Fallout.MSBuildTasks.Bridge (net472, RootNamespace Fallout.MSBuildTasks
so the task type names match the .targets UsingTask) is three ToolTask shims —
CodeGenerationTask / EmbedPackagesForSelfContainedTask / PackPackageToolsTask — over a base
WorkerBridgeTask that writes inputs to a temp JSON, shells out to `dotnet
Fallout.MSBuildTasks.Worker.dll <verb> in out` (dotnet host via DOTNET_HOST_PATH, else PATH),
and reads item outputs back. It references only the dependency-free Protocol — no Fallout.Tooling.
To suit ToolTask, the worker now emits messages on stdout and errors on stderr (LogStandardErrorAsError),
with the exit code as success; the output JSON carries only item outputs. WorkerResponse trimmed accordingly.
Builds green (worker net10, bridge net472). The flip (TFMs net10-only + .targets runtime switch) is next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… .targets
The flip (final step of ADR-0009). The in-proc CodeGeneration / EmbedPackages / PackPackageTools
tasks now delegate to Fallout.MSBuildTasks.Engine and Fallout.MSBuildTasks goes net10-only.
Fallout.Tooling drops netstandard2.0 (now net8.0;net10.0 — net8 kept for Fallout.ProjectModel's
down-level targets; net8 consumes ns2.1, so Core absorption is unblocked) and
Fallout.Tooling.Generator goes net10-only. The .targets picks the assembly by $(MSBuildRuntimeType):
Full (VS2019/2022) -> the net472 bridge, Core (dotnet/SDK) -> the in-proc net10 task.
ns2.0 — the only TFM that can't consume Fallout.Core (ns2.1) — is gone from the tooling stack, so
AbsolutePath / Host / Configure<T> / the tool contracts can now move into Core (Phase 2b/2c/2e).
Full solution build + test suite green (StronglyTypedSolution snapshot refreshed for the four new
projects). VS full-framework path can't be exercised on this OS; the net10/CLI path is verified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x folder
Fallout.Common (the consumer-facing build package) packed MSBuildTasks's net472 publish output
into build\netfx; with MSBuildTasks now net10-only that glob is dead. Replace it with the net472
bridge plus the net10 worker (same folder, so the bridge finds Fallout.MSBuildTasks.Worker.dll
beside itself). build\netcore still ships the in-proc net10 task. Also point the dogfood's inert
FalloutTasksDirectory at the real net10 publish path (was a stale net8.0).
NOTE: the consumer task path (especially full-framework VS) and `Pack` output are not exercised by
this repo's build/test (the dogfood runs with FalloutTasksEnabled=False), and the net472/VS path
can't be smoke-tested on non-Windows — both need a Windows + Pack verification before merge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisonSimtianChrisonSimtian added breaking-change Change is breaking — requires major version bump per CLAUDE.md semver policy. target/vCurrent Targets the current version labels Jun 30, 2026
@ChrisonSimtian

Copy link
Copy Markdown
CollaboratorAuthor

Bridge smoke test (Windows + VS2019/2022 full-framework MSBuild): the Full path fails — STJ binding.

Ran the consumer codegen path on this branch (commit 7220ab5) across all three runtimes:

  • dotnet / SDK MSBuild (Core, in-proc net10 task): ✅ generates valid output
  • VS2022 MSBuild.exe (Full, net472 bridge): ❌
  • VS2019 MSBuild.exe (Full, net472 bridge): ❌ (identical)

The bridge throws before it ever spawns the worker:

error MSB6003: System.IO.FileNotFoundException: Could not load file or assembly
'System.Text.Json, Version=10.0.0.0, ... PublicKeyToken=cc7b13ffcd2ddd51'
at Fallout.MSBuildTasks.Protocol.WorkerJson.Serialize[T]
at CodeGenerationTask.BuildRequestJson() -> WorkerBridgeTask.GenerateCommandLineCommands()

Root cause:Fallout.MSBuildTasks.Protocol serializes with System.Text.Json. The packaged STJ is assembly version 10.0.0.8; the code references 10.0.0.0. The bridge runs in-process inside .NET-Framework MSBuild.exe, where fusion needs an exact version or a binding redirect — neither is present (and a Bridge.dll.config redirect isn't honored for an in-proc task; only the host MSBuild.exe.config is). On .NET Core, roll-forward hides this for the in-proc task and the worker, so only Full breaks.

This contradicts ADR-0009 §3 ("the bridge references onlyMicrosoft.Build.* + the BCL — zero Fallout dependencies"): the Protocol's STJ dependency reintroduces exactly the version-sensitive in-proc dependency the bridge design set out to avoid.

Fix (pushed):WorkerJson now uses the in-box DataContractJsonSerializer (System.Runtime.Serialization); STJ PackageReference dropped from the Protocol project. STJ no longer ships in build/netfx. Re-ran all three drivers: Core, VS2019, VS2022 now produce byte-identical generated output.

Repro runbook: docs/agents/msbuild-bridge-smoke-test.md.

ChrisonSimtianand others added 4 commits July 1, 2026 10:12
…net472 bridge loads
The net472 bridge loads Fallout.MSBuildTasks.Protocol in-process inside full-framework
MSBuild.exe (VS2019/2022). The Protocol serialized with System.Text.Json, whose packaged
assembly version (10.0.0.8) never matches the compile-time reference (10.0.0.0); .NET
Framework fusion needs an exact match or a host binding redirect, and MSBuild.exe has
neither for STJ. The bridge threw FileNotFoundException in WorkerJson.Serialize before it
could spawn the worker. .NET Core roll-forward masked this on the in-proc task and the
worker, so only the Full path broke and CI (which runs FalloutTasksEnabled=False) missed it.
Switch WorkerJson to the in-box DataContractJsonSerializer (System.Runtime.Serialization)
and drop the System.Text.Json PackageReference. The DTOs are trivial POCOs that round-trip
cleanly, both ends share WorkerJson so the wire format stays symmetric, and STJ no longer
ships in build/netfx. This makes the bridge genuinely "Microsoft.Build.* + BCL only" as
ADR-0009 intends. Verified: dotnet (Core), VS2019 and VS2022 (Full) now produce
byte-identical codegen output.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documents how to verify the full-framework (VS2019/2022) MSBuild bridge from ADR-0009:
which MSBuild runtime exercises which path, the homelab VM spec, VS version currency, the
exact repro steps, and the System.Text.Json binding defect plus its fix. This path is
Windows-only and not covered by CI (the dogfood runs FalloutTasksEnabled=False), so it
needs a manual smoke test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers the wire contract the net472 bridge and net10 worker share via WorkerJson:
- round-trip each DTO (CodegenRequest, EmbedPackagesRequest, PackToolsRequest, WorkerResponse,
including empty/default collections) through DataContractJsonSerializer;
- assert the Protocol assembly references no System.Text.Json — the regression guard for the
bridge binding failure (STJ can't load in-process under full-framework MSBuild.exe). A round-trip
test alone can't catch that (net10 roll-forward and the test host's binding redirects both mask
it); the dependency invariant does, cross-platform. End-to-end bridge behaviour stays covered by
the Windows-only smoke test in docs/agents/msbuild-bridge-smoke-test.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adding Fallout.MSBuildTasks.Protocol.Specs to fallout.slnx adds one project to the
StronglyTypedSolutionGenerator output; refresh the Verify snapshot to match (one new line).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisonSimtian

Copy link
Copy Markdown
CollaboratorAuthor

TLDR; in order to break up the whole god-class cluster, I need to break a few things open, this being one of them.
Isolated this, broke out the legacy stuff into a standalone-bridge to service windows vs2019 and windows 2022 (macos vs2022 uses netcore already Oo)

Also, interesting read on VS support:
https://learn.microsoft.com/en-us/visualstudio/releases/2026/servicing-vs#support-for-older-versions
VS2026: End of Support = 2028
VS2022: End of Support = 2032 (!)
VS2019: End of Support = 2029 (!)

Classic Microsoft move... I'd say with that in mind: we do a bridge. We keep it side-by-side as long as we can but drop it latest 2032 (oh god thats in 6 years from now!)

@ChrisonSimtianChrisonSimtian added the help wanted Extra attention is needed label Jul 1, 2026
@ChrisonSimtian

Copy link
Copy Markdown
CollaboratorAuthor

This also needs some testing. So please be my guest, run it against all sorts of pre 2026 VS instances :-)

@ChrisonSimtian

Copy link
Copy Markdown
CollaboratorAuthor

@dennisdoomen not quite tidy but have a look and let me know what you think.
I want to lift this into its own little library to unlock us doing a lift & shift for the rest of fallout. Plus this whole thing is going away with vs2026 on windows (already gone on vs2022 for macos funnily enough, I assume rider and vscode never had this problem because they're not based on a 100yr old codebase ...)

@ChrisonSimtianChrisonSimtian added the Dont Merge This cant be merged for various reasons (i.e. its being hold back, its broken, etc) label Jul 10, 2026
@ITaluoneITaluone mentioned this pull request Jul 10, 2026
@ChrisonSimtianChrisonSimtian added the enhancement New feature or request label Jul 11, 2026
@ChrisonSimtianChrisonSimtian added target/vNext Targets the next calendar-version target/vCurrent Targets the current version and removed target/vCurrent Targets the current version target/vNext Targets the next calendar-version labels Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-changeChange is breaking — requires major version bump per CLAUDE.md semver policy.Dont MergeThis cant be merged for various reasons (i.e. its being hold back, its broken, etc)enhancementNew feature or requesthelp wantedExtra attention is neededtarget/vCurrentTargets the current version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@ChrisonSimtian