From c66554eec068e12e32538693c684fb8376e68182 Mon Sep 17 00:00:00 2001 From: Marcel Roozerkans Date: Fri, 22 May 2026 09:01:54 +0200 Subject: [PATCH 1/4] fix(pack): fire analyzer-filter guards before _HandlePackageFileConflicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2.0.1 buildTransitive guards used BeforeTargets=\"CoreCompile\". By that point MSBuild's _HandlePackageFileConflicts target has already deduped @(Analyzer): if both the bundled-in-main analyzer and the standalone Generator analyzer reach the project (which is the entire point of the split-package recipe), the one with the higher AssemblyVersion wins and the other is discarded. The 2.0.1 publish pipeline emitted these two copies with different AssemblyVersions, so the bundled one (2.0.1.0) consistently won. Our DisableBundled guard then removed it on schedule, leaving no analyzer at all → no generator output → CS0234 'namespace ZeroAlloc.Authorization.Generated does not exist' in consumer code. Fixing BeforeTargets to _HandlePackageFileConflicts moves the guard ahead of conflict resolution. The bundled is removed first, no conflict ever triggers, and the standalone survives unconditionally. Both packages' guards updated for symmetry. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ZeroAlloc.Authorization.Generator.targets | 10 +++++++++- .../ZeroAlloc.Authorization.targets | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ZeroAlloc.Authorization.Generator/buildTransitive/ZeroAlloc.Authorization.Generator.targets b/src/ZeroAlloc.Authorization.Generator/buildTransitive/ZeroAlloc.Authorization.Generator.targets index fb9e487..4c7df28 100644 --- a/src/ZeroAlloc.Authorization.Generator/buildTransitive/ZeroAlloc.Authorization.Generator.targets +++ b/src/ZeroAlloc.Authorization.Generator/buildTransitive/ZeroAlloc.Authorization.Generator.targets @@ -16,8 +16,16 @@ Condition="'%(Analyzer.NuGetPackageId)' == '...'" does not survive the SDK's analyzer item flow reliably. --> + diff --git a/src/ZeroAlloc.Authorization/buildTransitive/ZeroAlloc.Authorization.targets b/src/ZeroAlloc.Authorization/buildTransitive/ZeroAlloc.Authorization.targets index f5ed359..7e6e4b2 100644 --- a/src/ZeroAlloc.Authorization/buildTransitive/ZeroAlloc.Authorization.targets +++ b/src/ZeroAlloc.Authorization/buildTransitive/ZeroAlloc.Authorization.targets @@ -30,8 +30,21 @@ Condition="'%(Analyzer.NuGetPackageId)' == '...'" does not survive the SDK's analyzer item flow reliably. --> + + BeforeTargets="_HandlePackageFileConflicts"> From 906e884211370a89a1329e148bb133e9961f8ef9 Mon Sep 17 00:00:00 2001 From: Marcel Roozerkans Date: Fri, 22 May 2026 09:02:05 +0200 Subject: [PATCH 2/4] test(pack): regression for split-package + AssemblyVersion mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2.0.1 publish pipeline emitted the bundled-in-main analyzer with -p:Version=2.0.1 (so AssemblyVersion 2.0.1.0) and the standalone Generator package without that flag (so AssemblyVersion 1.0.0.0). The original smoke tests packed both packages in the same invocation without explicit Version overrides, so both DLLs ended up with the same default AssemblyVersion — masking the conflict-resolution path that bit production. This test packs the main with -p:Version=9.9.9 and the Generator with -p:Version=1.0.0, then runs the split-package scenario. Confirmed by temporarily reverting the BeforeTargets fix: this test fails with the exact CS0234 hit in the field, and passes again with the fix in place. PackProject gains an optional assemblyVersion parameter so existing tests stay terse. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../PackSmokeTests.cs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/ZeroAlloc.Authorization.PackSmoke/PackSmokeTests.cs b/tests/ZeroAlloc.Authorization.PackSmoke/PackSmokeTests.cs index c0dbf84..271bfc3 100644 --- a/tests/ZeroAlloc.Authorization.PackSmoke/PackSmokeTests.cs +++ b/tests/ZeroAlloc.Authorization.PackSmoke/PackSmokeTests.cs @@ -61,6 +61,39 @@ public void Split_package_pattern_builds_without_CS0121() Assert.DoesNotContain("CS0121", build.StdOut, StringComparison.Ordinal); } + [Fact] + public void Split_package_pattern_survives_assemblyversion_mismatch() + { + // Regression test for the 2.0.1 publish-pipeline bug. The bundled-in-main + // analyzer was packed by release-please.yml with -p:Version=2.0.1 (high + // AssemblyVersion). The standalone Generator package was packed by + // publish-from-manifest.yml without -p:Version (default 1.0.0 + // AssemblyVersion). MSBuild's _HandlePackageFileConflicts target picked + // the bundled (higher AssemblyVersion wins) and discarded the standalone. + // The DisableBundled guard then removed the bundled too, leaving no + // analyzer at all. The fix moves the guard to fire BEFORE + // _HandlePackageFileConflicts so the bundled is gone before conflict + // resolution sees it; the standalone survives regardless of which copy + // would have won. + var repoRoot = LocateRepoRoot(); + + // Force a high AssemblyVersion on the bundled DLL (via main package) + // and a low one on the standalone Generator. Reproduces production. + PackProject(Path.Combine(repoRoot, "src/ZeroAlloc.Authorization/ZeroAlloc.Authorization.csproj"), + assemblyVersion: "9.9.9"); + PackProject(Path.Combine(repoRoot, "src/ZeroAlloc.Authorization.Generator/ZeroAlloc.Authorization.Generator.csproj"), + assemblyVersion: "1.0.0"); + + ScaffoldTemplate(useStandaloneGenerator: true); + + var apiCsproj = Path.Combine(_workDir, "src/TestApp.Api/TestApp.Api.csproj"); + var build = RunDotnet($"build \"{apiCsproj}\" -c Release", _workDir); + Assert.True(build.ExitCode == 0, + $"Build must succeed regardless of AssemblyVersion ordering between bundled and standalone analyzers.\nSTDOUT:\n{build.StdOut}\nSTDERR:\n{build.StdErr}"); + Assert.DoesNotContain("CS0121", build.StdOut, StringComparison.Ordinal); + Assert.DoesNotContain("CS0234", build.StdOut, StringComparison.Ordinal); + } + [Fact] public void Bundled_only_pattern_still_works_for_2_0_0_consumers() { @@ -80,9 +113,13 @@ public void Bundled_only_pattern_still_works_for_2_0_0_consumers() } private void PackProject(string csproj) + => PackProject(csproj, assemblyVersion: null); + + private void PackProject(string csproj, string? assemblyVersion) { + var versionArg = assemblyVersion is null ? "" : $" -p:Version={assemblyVersion}"; var result = RunDotnet( - $"pack \"{csproj}\" -c Release -p:PackageVersion={_testVersion} -o \"{_feed}\"", + $"pack \"{csproj}\" -c Release -p:PackageVersion={_testVersion}{versionArg} -o \"{_feed}\"", Environment.CurrentDirectory); Assert.True(result.ExitCode == 0, $"Pack failed for {csproj}:\n{result.StdOut}\n{result.StdErr}"); } From 09c0b7eb5620de0045e2e7dde7c8b4d692c0ca00 Mon Sep 17 00:00:00 2001 From: Marcel Roozerkans Date: Fri, 22 May 2026 09:02:17 +0200 Subject: [PATCH 3/4] ci: pass -p:Version to build in publish-from-manifest workflow The 2.0.1 publish flow produced an AssemblyVersion mismatch between the bundled analyzer (built by release-please.yml with -p:Version=$VERSION) and the standalone Generator package (built here without -p:Version, defaulting to Directory.Build.props' 1.0.0). Pass -p:Version to keep the AssemblyVersions in lockstep across publish paths. The BeforeTargets fix in the previous commit makes the recipe robust regardless of AssemblyVersion ordering, so this is hygiene rather than strictly required. Removes a future class of bugs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish-from-manifest.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-from-manifest.yml b/.github/workflows/publish-from-manifest.yml index f0bfe51..e3383ca 100644 --- a/.github/workflows/publish-from-manifest.yml +++ b/.github/workflows/publish-from-manifest.yml @@ -40,7 +40,9 @@ jobs: run: dotnet restore - name: Build - run: dotnet build --configuration Release --no-restore + env: + VERSION: ${{ steps.version.outputs.version }} + run: dotnet build --configuration Release --no-restore -p:Version="$VERSION" - name: Test run: dotnet test --configuration Release --no-build From 0c8aaf6921ddf83a4d257937acfcbcc32da1fbcf Mon Sep 17 00:00:00 2001 From: Marcel Roozerkans Date: Fri, 22 May 2026 09:02:26 +0200 Subject: [PATCH 4/4] chore(release): one-shot release-as 2.0.2 for analyzer-filter conflict-order fix 2.0.1 shipped with a buildTransitive guard that fires too late: by the time it ran (BeforeTargets=\"CoreCompile\"), MSBuild had already discarded the standalone Generator analyzer via package-file conflict resolution. 2.0.2 moves the guard ahead of conflict resolution so the split-package recipe actually works in consumer builds. Co-Authored-By: Claude Opus 4.7 (1M context) --- release-please-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-please-config.json b/release-please-config.json index 918a22d..d8c37f2 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -2,7 +2,7 @@ "packages": { ".": { "release-type": "simple", - "release-as": "2.0.1", + "release-as": "2.0.2", "changelog-sections": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" },