Skip to content

fix: fire analyzer-filter guards before _HandlePackageFileConflicts (2.0.2)#24

Merged
MarcelRoozekrans merged 4 commits into
mainfrom
fix/buildtransitive-conflict-resolution-202
May 22, 2026
Merged

fix: fire analyzer-filter guards before _HandlePackageFileConflicts (2.0.2)#24
MarcelRoozekrans merged 4 commits into
mainfrom
fix/buildtransitive-conflict-resolution-202

Conversation

@MarcelRoozekrans

Copy link
Copy Markdown
Contributor

Summary

2.0.1's buildTransitive guards used `BeforeTargets="CoreCompile"`. By that point MSBuild's `_HandlePackageFileConflicts` target has already deduped `@(Analyzer)`, discarding one of the two copies of `ZeroAlloc.Authorization.Generator.dll` (bundled-in-main vs standalone). Whichever survived, our `DisableBundled` guard then removed it on schedule — leaving no analyzer at all, no generator output, and CS0234 `namespace ZeroAlloc.Authorization.Generated does not exist` in consumer code.

2.0.2 moves the guard to `BeforeTargets="_HandlePackageFileConflicts"` so the bundled analyzer is removed before conflict resolution runs. No conflict triggers, the standalone Generator analyzer survives, the recipe works.

Why it slipped past 2.0.1's PackSmoke

The 2.0.1 PackSmoke test packed both packages in a single `dotnet pack` invocation without explicit `-p:Version` overrides → both DLLs ended up with the same default AssemblyVersion (1.0.0.0). MSBuild conflict resolution with equal versions doesn't fire the "Choosing X" branch consistently; the test passed because the local case never hit the dedupe path the published 2.0.1 packages hit. The publish pipeline ran `release-please.yml` with `-p:Version=2.0.1` (bundled DLL → 2.0.1.0) and `publish-from-manifest.yml` without it (standalone Generator DLL → 1.0.0.0), so consumers saw the conflict that the test scenario didn't.

Changes

  • `src/ZeroAlloc.Authorization/buildTransitive/...targets`: `BeforeTargets="_HandlePackageFileConflicts"` + documenting comment.
  • `src/ZeroAlloc.Authorization.Generator/buildTransitive/...targets`: same, for symmetry.
  • `.github/workflows/publish-from-manifest.yml`: pass `-p:Version=$VERSION` to `dotnet build` so the standalone Generator's AssemblyVersion matches the bundled-in-main going forward (hygiene; the BeforeTargets fix is the actual fix).
  • `tests/ZeroAlloc.Authorization.PackSmoke/PackSmokeTests.cs`: new `Split_package_pattern_survives_assemblyversion_mismatch` test that packs the two packages with deliberately-mismatched `-p:Version` values to reproduce the production failure. Verified: this test fails when the BeforeTargets fix is reverted, passes with it in place.
  • `release-please-config.json`: one-shot `release-as: "2.0.2"`.

Followups

  1. Template PR ZeroAlloc-Net/ZeroAlloc.Templates#105 bumps to 2.0.2 after this publishes.
  2. Patch `release-please.yml` publish job to walk `src//.csproj` (still hardcoded to one csproj — tech debt, not blocking).
  3. Drop the `release-as: "2.0.2"` override in a small followup PR after publish confirms.

Test plan

  • Local PackSmoke: 3/3 tests pass with fix (including new regression test).
  • Regression test verified to catch the bug when the fix is reverted.
  • CI: build + test + apicompat + aot-smoke green.
  • After publish: template PR #105 bumps + builds.

🤖 Generated with Claude Code

MarcelRoozekrans and others added 4 commits May 22, 2026 09:01
…icts

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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
…t-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) <noreply@anthropic.com>
@MarcelRoozekrans
MarcelRoozekrans merged commit d7e30a6 into main May 22, 2026
4 checks passed
@MarcelRoozekrans
MarcelRoozekrans deleted the fix/buildtransitive-conflict-resolution-202 branch May 22, 2026 07:15
MarcelRoozekrans added a commit to ZeroAlloc-Net/ZeroAlloc.Templates that referenced this pull request May 22, 2026
2.0.1's buildTransitive guards fired too late (BeforeTargets="CoreCompile"),
so MSBuild's _HandlePackageFileConflicts had already discarded the standalone
Generator analyzer before our DisableBundled guard ran. Net effect: no
analyzer at all, no generator output, CS0234 'namespace
ZeroAlloc.Authorization.Generated does not exist' in MyApp.Api.

2.0.2 (ZeroAlloc-Net/ZeroAlloc.Authorization#24) moves the guards to
BeforeTargets="_HandlePackageFileConflicts" so the bundled is removed
first, no conflict triggers, and the standalone survives. New
Split_package_pattern_survives_assemblyversion_mismatch regression test
upstream prevents this from re-regressing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MarcelRoozekrans added a commit to ZeroAlloc-Net/ZeroAlloc.Templates that referenced this pull request May 22, 2026
2.0.1's buildTransitive guards fired too late (BeforeTargets="CoreCompile"),
so MSBuild's _HandlePackageFileConflicts had already discarded the standalone
Generator analyzer before our DisableBundled guard ran. Net effect: no
analyzer at all, no generator output, CS0234 'namespace
ZeroAlloc.Authorization.Generated does not exist' in MyApp.Api.

2.0.2 (ZeroAlloc-Net/ZeroAlloc.Authorization#24) moves the guards to
BeforeTargets="_HandlePackageFileConflicts" so the bundled is removed
first, no conflict triggers, and the standalone survives. New
Split_package_pattern_survives_assemblyversion_mismatch regression test
upstream prevents this from re-regressing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MarcelRoozekrans added a commit to ZeroAlloc-Net/ZeroAlloc.Templates that referenced this pull request May 22, 2026
…analyzer-leak workaround (#105)

* chore(deps): adopt ZA.Authorization 2.0.1 split-package recipe; drop workaround

ZA.Authorization 2.0.1 splits its source generator into a standalone
ZeroAlloc.Authorization.Generator package and ships a property-driven
buildTransitive guard that filters analyzers per-project based on two
consumer-set MSBuild properties. The za-clean template adopts the
recipe:

  - Directory.Build.props sets ZeroAllocAuthorizationDisableBundledAnalyzer=true
    so the bundled-in-main analyzer is removed from every project in the
    template (Application, Api, Infrastructure alike).
  - MyApp.Application.csproj sets ZeroAllocAuthorizationOwnsPolicies=true
    (it declares [Policy] / [RequirePolicy]) and adds the new
    ZeroAlloc.Authorization.Generator PackageReference. The Generator
    package's own buildTransitive guard removes its analyzer from any
    project that does not set OwnsPolicies, so Api and Infrastructure
    no longer pick the generator up via cross-project @(Analyzer)
    propagation.

Concrete result: the RemoveTransitiveZeroAllocAuthorizationAnalyzer
MSBuild Target previously shipped in MyApp.Api.csproj and
MyApp.Infrastructure.csproj is no longer necessary and has been
removed. The PrivateAssets=\"analyzers\" defense-in-depth on
MyApp.Application's ZA.Authorization PackageReference is also dropped
(the buildTransitive guards in 2.0.1 are the authoritative filter).

Both Directory.Packages.props files (repo root + content/za-clean)
bumped to ZeroAlloc.Authorization 2.0.1 and add the new
ZeroAlloc.Authorization.Generator 2.0.1 entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(deps): mirror Directory.Build.props at repo root (CI parity check)

Repo CI requires Directory.Build.props, Directory.Packages.props, and
global.json to be byte-identical between the repo root and
content/za-clean/. Mirrors the ZeroAllocAuthorizationDisableBundledAnalyzer
property added in the prior commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(deps): bump ZA.Authorization{,.Generator} 2.0.1 → 2.0.2

2.0.1's buildTransitive guards fired too late (BeforeTargets="CoreCompile"),
so MSBuild's _HandlePackageFileConflicts had already discarded the standalone
Generator analyzer before our DisableBundled guard ran. Net effect: no
analyzer at all, no generator output, CS0234 'namespace
ZeroAlloc.Authorization.Generated does not exist' in MyApp.Api.

2.0.2 (ZeroAlloc-Net/ZeroAlloc.Authorization#24) moves the guards to
BeforeTargets="_HandlePackageFileConflicts" so the bundled is removed
first, no conflict triggers, and the standalone survives. New
Split_package_pattern_survives_assemblyversion_mismatch regression test
upstream prevents this from re-regressing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant