From 8539949cdca8dc83e49ada66ce3580be0f660afb Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 24 May 2026 15:47:28 +1200 Subject: [PATCH] feat(shims): hand-bridge DelegateDisposable, document non-bridgeable types (#69 session 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DelegateDisposable falls in the no-accessible-ctor bucket (private ctor), but unlike the other 3 candidates it exposes its full surface via public static factories (CreateBracket × 2, SetAndRestore). A static-class shim re-exposing those factories actually helps real consumers — the factories return the canonical IDisposable, so calls flow through naturally. For the other 3 no-accessible-ctor types, hand-bridges would be empty or decorative: - AbsolutePath: dominant consumer usage is implicit string/AbsolutePath conversion and operator/ — neither can be carried by a static-class shim - AzureKeyVault, MSBuildProject: zero public statics, framework-constructed only; nothing to re-expose These three are documented as fallout-migrate rename targets in a new "What's NOT covered (and won't be)" README section. Drops 2 SHIM001 warnings (DelegateDisposable × 2 referenced-assembly paths) via the suppression mechanism from #141. Remaining no-accessible-ctor types in the live build (AbsolutePath, AzureKeyVault, MSBuildProject) are exactly the three documented fallout-migrate targets. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Shims/Nuke.Common/README.md | 14 +++++++--- .../Utilities/DelegateDisposable.cs | 27 +++++++++++++++++++ .../SampleConsumerBuild.cs | 14 ++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs diff --git a/src/Shims/Nuke.Common/README.md b/src/Shims/Nuke.Common/README.md index 649a94f45..670541d36 100644 --- a/src/Shims/Nuke.Common/README.md +++ b/src/Shims/Nuke.Common/README.md @@ -13,19 +13,27 @@ This package is a **partial transition shim** for projects mid-migration from NU | `[Solution]` | `Fallout.Common.ProjectModel.SolutionAttribute` | Subclass | | `[GitRepository]` | `Fallout.Common.Git.GitRepositoryAttribute` | Subclass | | `CI host singletons` (`GitHubActions`, `AzurePipelines`, `TeamCity`, `AppVeyor`, `GitLab`, `Jenkins`, `Bamboo`, `Bitbucket`, `Bitrise`, `TravisCI`) | `Fallout.Common.CI..` | Hand-written static class re-exposing `.Instance` (returns canonical type) | +| `DelegateDisposable` | `Fallout.Common.Utilities.DelegateDisposable` | Hand-written static class re-exposing `CreateBracket` / `SetAndRestore` | The MVP unblocks a `class Build : NukeBuild` declaration with parameter/secret/solution/git injection — the entry-point surface of most consumer Build.cs files. CI host shims cover the `GitHubActions.Instance.Workflow`-style access path. -### CI host shim limitation +### Hand-bridge limitation (CI hosts, `DelegateDisposable`) -Because CI hosts are framework-injected, `[CI] readonly GitHubActions GitHubActions;` field declarations and `Host is GitHubActions` type checks need consumers to use the canonical (`Fallout.Common.CI.GitHubActions.GitHubActions`) type directly — the shim's `Instance` accessor returns a canonical instance, but the shim type itself is `static class` so it can't be used as a field type. Use `fallout-migrate` to rewrite those references. +These types are framework-injected or use private/internal constructors. The shim is `static class`, so consumers receiving canonical-typed instances (e.g. `[CI] readonly GitHubActions GitHubActions;`, `Host is GitHubActions`, methods returning `DelegateDisposable`) need to switch the type reference to canonical. Use `fallout-migrate` for those rewrites; the shim covers static factory / `.Instance` access paths only. + +## What's NOT covered (and won't be) + +These types have private/internal ctors and no public static factories, **and** are mainly used as value types — meaning the hand-bridge would be empty (no public statics to re-expose) and a subclass shim can't carry implicit operators or be assignment-compatible with framework-returned canonical instances: + +- **`Fallout.Common.IO.AbsolutePath`** — used via `AbsolutePath path = "/foo"` (implicit `string`/`AbsolutePath` conversion), `parent / "child"` (`operator/`), and field-injection. None of those work through a static-class shim. Use `fallout-migrate` to rewrite `Nuke.Common.IO.AbsolutePath` → `Fallout.Common.IO.AbsolutePath`. +- **`Fallout.Common.Tools.AzureKeyVault.AzureKeyVault`** — zero public statics, framework-constructed only. `fallout-migrate` rename. +- **`Fallout.Common.Tools.MSBuild.MSBuildProject`** — zero public statics, framework-returned only. `fallout-migrate` rename. ## What's NOT covered (yet) - **CI attributes** (`[GitHubActions]`, `[AzurePipelines]`, `[TeamCity]`, `[AppVeyor]`, ...) — these take enum parameters (`GitHubActionsImage`, etc.) that the shim doesn't re-export. C# can't subclass enums; bridging them needs a source generator. Tracked in the follow-up issue. - **The `Target` delegate** — delegates can't be implicitly converted across types in C#, so the shim can't bridge `Nuke.Common.Target Compile => _ => _` cleanly. Use `fallout-migrate` for the source-level rename. - **Static helper classes** — `DotNetTasks`, `MSBuildTasks`, etc. require method-by-method delegation. Tracked for the source-generator follow-up. -- **IO types** — `AbsolutePath`, file globbing helpers — also need wrapper classes with mirrored members. ## Packaging diff --git a/src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs b/src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs new file mode 100644 index 000000000..91e0b9331 --- /dev/null +++ b/src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs @@ -0,0 +1,27 @@ +// Copyright 2026 Maintainers of Fallout. +// Originally based on NUKE by Matthias Koch and contributors. +// Distributed under the MIT License. +// https://github.com/ChrisonSimtian/Fallout/blob/main/LICENSE + +// Hand-written transition shim. The canonical DelegateDisposable has a private +// ctor and exposes itself only via static factories; the generator skips it as +// no-accessible-ctor, and a subclass wouldn't compile anyway. This static-class +// shim re-exposes the three factories so consumers keep compiling on +// `using Nuke.Common.Utilities;`. + +using System; +using System.Linq.Expressions; + +namespace Nuke.Common.Utilities; + +public static class DelegateDisposable +{ + public static IDisposable CreateBracket(Action setup = null, Action cleanup = null) + => global::Fallout.Common.Utilities.DelegateDisposable.CreateBracket(setup, cleanup); + + public static IDisposable CreateBracket(Func setup, Action cleanup) + => global::Fallout.Common.Utilities.DelegateDisposable.CreateBracket(setup, cleanup); + + public static IDisposable SetAndRestore(Expression> memberProvider, T value) + => global::Fallout.Common.Utilities.DelegateDisposable.SetAndRestore(memberProvider, value); +} diff --git a/tests/Nuke.Common.Shim.Tests/SampleConsumerBuild.cs b/tests/Nuke.Common.Shim.Tests/SampleConsumerBuild.cs index 6550924ea..f2c7fcdae 100644 --- a/tests/Nuke.Common.Shim.Tests/SampleConsumerBuild.cs +++ b/tests/Nuke.Common.Shim.Tests/SampleConsumerBuild.cs @@ -17,6 +17,7 @@ using Nuke.Common.CI.TeamCity; using Nuke.Common.Git; using Nuke.Common.ProjectModel; +using Nuke.Common.Utilities; namespace Nuke.Common.Shim.Tests; @@ -45,4 +46,17 @@ void TouchCiHostShims() _ = AppVeyor.Instance?.AccountName; _ = AppVeyor.MessageLimit; } + + // DelegateDisposable shim re-exposes the static factories. Consumer + // usage stays canonical-typed at runtime (the factories return + // canonical IDisposable instances). + void TouchDelegateDisposableShim() + { + using var bracket = DelegateDisposable.CreateBracket( + setup: () => { }, + cleanup: () => { }); + using var typed = DelegateDisposable.CreateBracket( + setup: () => 42, + cleanup: _ => { }); + } }