Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 1 addition & 51 deletions .fallout/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,7 @@
"ExecutableTarget": {
"type": "string",
"enum": [
"CheckoutExternalRepositories",
"Clean",
"Compile",
"CreateGitHubRelease",
"DeletePackages",
"DownloadLicenses",
"GeneratePublicApi",
"GenerateTools",
"Install",
"Pack",
"Publish",
"References",
"ReportCoverage",
"Restore",
"RunTargetInDockerImageTest",
"Test",
"UpdateContributors",
"UpdateStargazers"
"Default"
]
},
"Verbosity": {
Expand Down Expand Up @@ -122,42 +105,9 @@
"allOf": [
{
"properties": {
"CodecovToken": {
"type": "string",
"default": "Secrets must be entered via 'nuke :secrets [profile]'"
},
"Configuration": {
"type": "string",
"enum": [
"Debug",
"Release"
]
},
"GitHubReleaseGitHubToken": {
"type": "string",
"default": "Secrets must be entered via 'nuke :secrets [profile]'"
},
"IgnoreFailedSources": {
"type": "boolean",
"description": "Ignore unreachable sources during Restore"
},
"Major": {
"type": "boolean"
},
"NuGetApiKey": {
"type": "string",
"default": "Secrets must be entered via 'nuke :secrets [profile]'"
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
},
"TestDegreeOfParallelism": {
"type": "integer",
"format": "int32"
},
"UseHttps": {
"type": "boolean"
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions fallout.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
<Project Path="tests\Fallout.ProjectModel.Tests\Fallout.ProjectModel.Tests.csproj" />
<Project Path="tests\Fallout.Solution.Tests\Fallout.Solution.Tests.csproj" />
<Project Path="tests\Fallout.SourceGenerators.Tests\Fallout.SourceGenerators.Tests.csproj" />
<Project Path="tests\Consumers\Nuke.Consumer\Nuke.Consumer.csproj" />
<Project Path="tests\Consumers\Fallout.Consumer.Local\Fallout.Consumer.Local.csproj" />
<Project Path="tests\Consumers\Fallout.Consumer.NuGet\Fallout.Consumer.NuGet.csproj" />
<Project Path="tests\Fallout.Tooling.Tests\Fallout.Tooling.Tests.csproj" />
<Project Path="tests\Fallout.Utilities.Tests\Fallout.Utilities.Tests.csproj" />
</Solution>
26 changes: 26 additions & 0 deletions tests/Consumers/Fallout.Consumer.Local/Build.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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
//
// Fallout consumer against this repo's local source (smart-rewrite makes the
// PackageReferences resolve to ProjectReferences). Catches breakage of the
// public Fallout surface in the current PR.

using Fallout.Common;
using Fallout.Common.IO;
using Fallout.Solutions;

class Build : FalloutBuild
{
public static int Main() => Execute<Build>(x => x.Default);

[Solution] readonly Solution Solution;

Target Default => _ => _
.Executes(() =>
{
Serilog.Log.Information("hello from fallout consumer (local source)");
Serilog.Log.Information("solution name: {Name}", Solution?.Name ?? "<unbound>");
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<RootNamespace>Fallout.Consumer.Local</RootNamespace>
<NoWarn>$(NoWarn);CS0649</NoWarn>
</PropertyGroup>

<!--
Direct ProjectReferences against this repo's local source. Always tracks
HEAD. Catches breakage in the current PR.

Why not PackageReference + Directory.Build.targets smart-rewrite? The
rewrite relies on `nuke-global.sln` being present, and that file is
generated on demand (via GenerateGlobalSolution) — it's not in tree by
default. ProjectReference is the simpler / more deterministic shape.
-->
<ItemGroup>
<ProjectReference Include="..\..\..\src\Fallout.Common\Fallout.Common.csproj" />
<ProjectReference Include="..\..\..\src\Fallout.Build\Fallout.Build.csproj" />
<ProjectReference Include="..\..\..\src\Persistence\Fallout.Solution\Fallout.Solution.csproj" />
<ProjectReference Include="..\..\..\src\Fallout.Components\Fallout.Components.csproj" />
</ItemGroup>

</Project>
27 changes: 27 additions & 0 deletions tests/Consumers/Fallout.Consumer.NuGet/Build.cs
Original file line number Diff line number Diff line change
@@ -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
//
// Fallout consumer against PUBLISHED Fallout.* packages (pinned in the csproj).
// Validates that the most-recent release's surface is intact from a clean
// consumer's perspective. Pre-#254 namespaces (Fallout.Common.ProjectModel)
// because the pinned 11.0.8 release predates the rename.

using Fallout.Common;
using Fallout.Common.IO;
using Fallout.Common.ProjectModel;

class Build : FalloutBuild
{
public static int Main() => Execute<Build>(x => x.Default);

[Solution] readonly Solution Solution;

Target Default => _ => _
.Executes(() =>
{
Serilog.Log.Information("hello from fallout consumer (pinned nuget 11.0.8)");
Serilog.Log.Information("solution name: {Name}", Solution?.Name ?? "<unbound>");
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<RootNamespace>Fallout.Consumer.NuGet</RootNamespace>
<NoWarn>$(NoWarn);CS0649</NoWarn>

<!-- Opt out of Directory.Build.targets' smart-rewrite so PackageReferences
actually resolve against nuget.org instead of in-solution ProjectReferences.
This is the whole point of this project: validate against the LAST PUBLISHED
package surface, not the current source tree.

Also opt out of central package management so the inline Version="..."
pins below take effect. -->
<ReplacePackageReferences>false</ReplacePackageReferences>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>

<!--
Pinned to 11.0.8 (last release before the namespace rename in #248/#254).
BUMP THIS after each release to catch upgrade-direction breaking changes.
See tests/Consumers/README.md → "Bumping the NuGet pin".
-->
<ItemGroup>
<PackageReference Include="Fallout.Common" Version="11.0.8" />
<PackageReference Include="Fallout.Build" Version="11.0.8" />
<PackageReference Include="Fallout.SolutionModel" Version="11.0.8" />
<PackageReference Include="Fallout.Components" Version="11.0.8" />
</ItemGroup>

</Project>
35 changes: 35 additions & 0 deletions tests/Consumers/Nuke.Consumer/Build.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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
//
// Pre-rename NUKE consumer pattern, compiled against the Nuke.Common /
// Nuke.Components transition shims. If a typical NUKE 10.x Build.cs stops
// compiling against the latest Fallout, this fails — protecting upgrading
// users from silent breakage.

using Nuke.Common;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Components;

// The shim generator skips delegates by C# language limitation (see SHIM002 —
// can't subclass a delegate cross-assembly). `Target` is a delegate in
// Fallout.Common, so NUKE-era code referencing `Target` needs either
// `fallout-migrate` (which flips usings to Fallout.*) or this manual alias.
// Including it here keeps the rest of the file NUKE-shape.
using Target = Fallout.Common.Target;

class Build : NukeBuild
{
public static int Main() => Execute<Build>(x => x.Default);

[Solution] readonly Solution Solution;

Target Default => _ => _
.Executes(() =>
{
Serilog.Log.Information("hello from nuke consumer (via shim)");
Serilog.Log.Information("solution name: {Name}", Solution?.Name ?? "<unbound>");
});
}
18 changes: 18 additions & 0 deletions tests/Consumers/Nuke.Consumer/Nuke.Consumer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<RootNamespace>Nuke.Consumer</RootNamespace>
<!-- Build.cs uses readonly fields populated by Fallout's value injection. -->
<NoWarn>$(NoWarn);CS0649</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Shims\Nuke.Common\Nuke.Common.csproj" />
<ProjectReference Include="..\..\..\src\Shims\Nuke.Build\Nuke.Build.csproj" />
<ProjectReference Include="..\..\..\src\Shims\Nuke.Components\Nuke.Components.csproj" />
</ItemGroup>

</Project>
43 changes: 43 additions & 0 deletions tests/Consumers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Consumer compatibility tests

Three small build-project consumers that exercise Fallout's public surface from the perspectives of real downstream users. If any of these stop **compiling**, we've broken something consumers depend on.

## Projects

| Project | What it exercises | Reference style |
|---|---|---|
| [`Nuke.Consumer`](Nuke.Consumer/) | A pre-rename NUKE consumer using `class Build : NukeBuild`, `[Solution]`, and `Target` via the `Nuke.Common` / `Nuke.Build` / `Nuke.Components` transition shims. Catches breakage of **shim coverage** — if a NUKE-shape symbol stops resolving, an upgrading NUKE user breaks. | `ProjectReference` to `src/Shims/Nuke.*/` |
| [`Fallout.Consumer.Local`](Fallout.Consumer.Local/) | A Fallout consumer using `class Build : FalloutBuild`, `[Solution]`, `Target` against **this repo's current source**. Catches breakage of the public Fallout surface **in the current PR**. | Direct `ProjectReference` to `src/Fallout.*/` and `src/Persistence/Fallout.Solution/` |
| [`Fallout.Consumer.NuGet`](Fallout.Consumer.NuGet/) | A Fallout consumer against the **last published** `Fallout.*` packages (pinned in the csproj). Catches **packaging issues** (missing assemblies, wrong references) on the most-recent release, and catches upgrade-direction breakage when the pin is bumped after a release. | `PackageReference` to nuget.org with `<ReplacePackageReferences>false</ReplacePackageReferences>` and `<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>` so the smart-rewrite doesn't kick in |

## How they're validated

**Compile-time validation only.** All three projects are in `fallout.slnx`, so `dotnet build fallout.slnx` (the standard CI gate) compiles them. Any consumer-facing breaking change makes the build fail.

### Why not runtime validation?

Initial design had a `Consumers.SmokeTests/` xUnit project that spawned each consumer via `dotnet run` and asserted exit code `0`. That approach hits a Fallout framework runtime requirement: `FalloutBuild.cctor()` enumerates `Host` subclasses and reflects for a static `IsRunningHost` property — minimal consumers that don't pull in the full Fallout MSBuild props/targets (`Fallout.Common.props`, `Fallout.Common.targets`) trigger a `System.ArgumentException: Host type 'Host' defines no property 'IsRunningHost'` at activation. Reproducing the full build-app environment for a smoke consumer is meaningfully more setup than the test's value justifies; for now the consumer projects are compile-only sentinels.

If we ever need runtime validation: import `Fallout.Common.props` + `Fallout.Common.targets` from the consumer csprojs (the way `build/_build.csproj` does), provide a `FalloutRootDirectory`, and the framework should activate cleanly. Not done in this initial pass because the compile-time check already catches the bulk of breaking changes.

## Bumping the NuGet pin

After a new `Fallout.*` release ships, edit `Fallout.Consumer.NuGet/Fallout.Consumer.NuGet.csproj` to bump the pinned version. If the consumer source still compiles, the upgrade is non-breaking. If it fails, the new release introduces a consumer-facing breaking change — record it in `CHANGELOG.md` and the migration path under `[Unreleased] — <next-major>`.

The `Nuke.Consumer` doesn't pin anything (it references the in-repo shim assemblies directly), so no bump cadence there — it always tracks HEAD's shim coverage.

## What's deliberately tested

- **NUKE-era `class Build : NukeBuild, IPack`** — basic class identity through the shim
- **`[Solution] readonly Solution Solution`** — value-injection attribute + facade type via shim
- **`Target Default => _ => _.Executes(...)`** — delegate type, default target lambda
- **`static int Main() => Execute<Build>(x => x.Default)`** — framework entry point

## What's NOT tested

- Actual build *execution* (see above — runtime activation is fragile)
- CI-host integration (GitHubActions, AzurePipelines, etc.) — covered by `tests/Fallout.Common.Tests/CI/`
- Tool wrappers — covered by their own generated tests
- Source generator behaviour — covered by `tests/Fallout.SourceGenerators.Tests/`

These consumer projects are a **sentinel for shape changes**, not a place to demo features. Don't add consumer projects covering specific subsystems — those go in their own focused tests.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ internal class Solution(SolutionModel model, AbsolutePath path) : Fallout.Soluti
public Fallout.Solutions.Project Fallout_Common => this.GetProject("Fallout.Common");
public Fallout.Solutions.Project Fallout_Common_Tests => this.GetProject("Fallout.Common.Tests");
public Fallout.Solutions.Project Fallout_Components => this.GetProject("Fallout.Components");
public Fallout.Solutions.Project Fallout_Consumer_Local => this.GetProject("Fallout.Consumer.Local");
public Fallout.Solutions.Project Fallout_Consumer_NuGet => this.GetProject("Fallout.Consumer.NuGet");
public Fallout.Solutions.Project Fallout_Migrate => this.GetProject("Fallout.Migrate");
public Fallout.Solutions.Project Fallout_Migrate_Analyzers => this.GetProject("Fallout.Migrate.Analyzers");
public Fallout.Solutions.Project Fallout_Migrate_Analyzers_Tests => this.GetProject("Fallout.Migrate.Analyzers.Tests");
Expand Down Expand Up @@ -44,6 +46,7 @@ internal class Solution(SolutionModel model, AbsolutePath path) : Fallout.Soluti
public Fallout.Solutions.Project Nuke_Common_Shim_Tests => this.GetProject("Nuke.Common.Shim.Tests");
public Fallout.Solutions.Project Nuke_Components => this.GetProject("Nuke.Components");
public Fallout.Solutions.Project Nuke_Components_Shim_Tests => this.GetProject("Nuke.Components.Shim.Tests");
public Fallout.Solutions.Project Nuke_Consumer => this.GetProject("Nuke.Consumer");

public _misc misc => Unsafe.As<_misc>(this.GetSolutionFolder("misc"));

Expand Down