Repository navigation
Port to .NET 10 SDK, rename to ICSharpCode.ReferenceAssemblyAnnotator, add GitHub Actions - #1
Conversation
- global.json: SDK 11 (roll forward, prerelease allowed); drop the pinned Microsoft.Net.Compilers.Toolset 3.4.0 in favor of the SDK compiler. - Annotate from Microsoft.NETCore.App.Ref 10.0.0; self-annotate with TunnelVisionLabs.ReferenceAssemblyAnnotator 1.0.0-alpha.160. - Update Mono.Cecil 0.11.6, StyleCop.Analyzers 1.2.0-beta.556, Nerdbank.GitVersioning 3.10.94. Compile the task against MSBuild 17.8.43 (compile-time only) so it still loads in SDK 8+ and VS 17.8+; MSBuild 18 would require System.Collections.Immutable 10. - Fix TypeReferenceEquivalenceComparer: Cecil's GetElementType() returns the innermost type, so by-ref/pointer/array element types lost their generic arguments (e.g. Nullable<bool>& matched Nullable<byte>&), which crashed annotation of net10.0 System.Runtime.InteropServices.JavaScript. - Nullable fixes for the newer compiler (as in tunnelvisionlabs#90). - WPF: only add NullableAttributes to _GeneratedCodeFiles when the markup compile project doesn't import package references (as in tunnelvisionlabs#90). - Tests: add net10.0 / net10.0-windows, WPF net40 -> net35, drop jnm2.ReferenceAssemblies.net35 (SDK provides net35 refs). Skip the explicit PackageDownload for netcoreapp3.x, where the SDK already downloads Microsoft.NETCore.App.Ref (NU1505). Silence EOL, NuGet audit and NETSDK1215 warnings in tests. No target frameworks were dropped. - Replace appveyor.yml with build/test.ps1 (-Engine dotnet|msbuild). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rename project, folder, namespace, build props/targets and ruleset to
ICSharpCode.ReferenceAssemblyAnnotator, so the package can be published to
nuget.org under the new ID.
- Remove the NewIdRequiredDueToNuGetBug PackageId workaround: the project no
longer shares an ID with the published TunnelVisionLabs package it uses to
annotate itself.
- Package metadata modeled on ICSharpCode.Decompiler: ic#code / ILSpy
Contributors, new repository URLs, packed README, tags; embedded PDBs with
Source Link instead of a legacy symbols package; ContinuousIntegrationBuild
on GitHub Actions.
- Version line 2.0.0-preview.{height}.
- tests/NuGet.Config: source mapping pins ICSharpCode.ReferenceAssemblyAnnotator
to the local build output.
- README and CLAUDE.md updated; remove codecov.yml (unused since AppVeyor).
Validated by building ILSpy's ICSharpCode.Decompiler (nullable warnings as
errors) against the new package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed MAX_PATH) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[3.0.0]" /> | ||
| <PackageReference Include="ICSharpCode.ReferenceAssemblyAnnotator" Version="2.0.0-*" PrivateAssets="all" /> | ||
| <!-- The SDK already downloads Microsoft.NETCore.App.Ref for netcoreapp3.x; a second PackageDownload fails with NU1505. --> | ||
| <PackageDownload Condition="!$(TargetFramework.StartsWith('netcoreapp3.'))" Include="Microsoft.NETCore.App.Ref" Version="[10.0.0]" /> |
There was a problem hiding this comment.
Correction: this duplicate-download failure reproduced with SDK 11.0.100-preview.4, but no longer reproduces after switching to SDK 11.0.100-rc.3. I am retracting this finding; the current SDK adds a compatible targeting-pack download.
Assisted-by: OpenCode:gpt-5.6-luna:OpenCode
There was a problem hiding this comment.
Confirmed, and it's broader than net10.0. Whether the SDK adds its own PackageDownload of Microsoft.NETCore.App.Ref depends on whether that targeting pack is installed. My machine and the CI image have 10.0.x installed, which is why it passed there. Forcing a download with -p:NetCoreTargetingPackRoot=<empty dir> reproduces NU1505 … [10.0.0], [10.0.14] locally.
Leaving out the explicit item alone (the first suggestion) fails the other way: with the pack installed nothing is downloaded, and the targets fail with RA0000. So no condition in the test project works on both kinds of machine.
Fixed in 4b2d45b at the source: the targets no longer annotate .NETCoreApp >= 3.0. Those reference assemblies are already annotated, so no PackageDownload is needed there. The test projects drop the item for netcoreapp3.x/net10.0, and the README shows how to condition it when multi-targeting.
Trade-off: those TFMs lose the weaver's removal of [DisallowNull] from EqualityComparer<T>.GetHashCode, and the refresh of EOL netcoreapp3.x refs with newer annotations. The test's GetHashCode line is now #if !NETCOREAPP3_0_OR_GREATER.
Verified: the full matrix passes on both engines, net10.0 and MultiTFM pass with an empty targeting-pack root, net10.0 passes on Linux with only SDK 11, and CI is green.
| <PackageReference Condition="'$(TargetFramework)' != 'net35'" Include="System.Data.SqlClient" Version="4.8.0" /> | ||
| <PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.*" PrivateAssets="all" /> | ||
| <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[3.0.0]" /> | ||
| <PackageReference Include="ICSharpCode.ReferenceAssemblyAnnotator" Version="2.0.0-*" PrivateAssets="all" /> |
There was a problem hiding this comment.
Correction: commit 4b2d45b fixes this failure by changing the local source path in tests/NuGet.Config to forward slashes. With CONFIGURATION=Release and SDK 11.0.100-rc.3, restore now succeeds for both netstandard2.0 and net10.0. I am retracting this finding.
Assisted-by: OpenCode:gpt-5.6-luna:OpenCode
There was a problem hiding this comment.
The failure is real, but the cause is different: 2.0.0-* does float to the highest 2.0.0 prerelease. On Windows your exact repro resolves 2.0.0-preview.5.g71a08c2c76, and CI resolves preview.N the same way.
I reproduced the NU1100 on Linux (WSL Ubuntu, SDK 11 rc.2) and bisected it:
- With source mapping removed,
2.0.0-*resolves preview.5, so the version range is fine. - With the mapping kept and the source path switched from
..\ICSharpCode…\bin\%CONFIGURATION%to forward slashes, it resolves as well.
On non-Windows NuGet still finds the backslash source (it's listed as "not considered"), but package source mapping doesn't match it, so the ID has no allowed source.
Fixed in 4b2d45b: forward slashes in tests/NuGet.Config, which Windows also accepts. Re-verified on Linux from the pushed branch (netstandard2.0 and net10.0). The version expressions in the tests and the README are unchanged.
Separately, dotnet pack … -p:TargetFramework=netstandard2.0 fails on its own (NU5026), because the package also expects the net472 output. Use dotnet build -c Release, which packs both.
This also turned up a bug in test.ps1: an inherited NUGET_PACKAGES overrides globalPackagesFolder, so a machine-wide cache could serve a stale package with the same version. The script now sets NUGET_PACKAGES itself.
There was a problem hiding this comment.
Re-reviewed commit 4b2d45b. The author fixes both previously reported restore failures: modern .NETCoreApp targets no longer require annotation or an explicit reference-pack download, and the local NuGet source path now works on Linux/macOS.
I verified the package build plus netstandard2.0 and net10.0 test restores/builds with SDK 11.0.100-rc.3. I found no remaining review findings.
Assisted-by: OpenCode:gpt-5.6-luna:OpenCode
…solation - Targets: don't annotate .NETCoreApp >= 3.0. Those reference assemblies are already annotated, and the Microsoft.NETCore.App.Ref PackageDownload the annotator requires can't be declared reliably there: when the targeting pack isn't installed the SDK adds its own download (NU1505 on a duplicate), and when it is installed, leaving it out failed with RA0000. This drops the GetHashCode [DisallowNull] removal and the annotation refresh for those TFMs (netcoreapp3.x is EOL). The test sources make that line conditional; the README explains how to condition the PackageDownload when multi-targeting. - tests/NuGet.Config: forward slashes in the local source path. With backslashes, package source mapping doesn't match that source on Linux/macOS and restore fails with NU1100. - test.ps1: set NUGET_PACKAGES explicitly (-PackagesPath, default tests/.isolatednugetcache). An inherited NUGET_PACKAGES overrides globalPackagesFolder, so a machine-wide cache served a stale annotator package with the same version. CI passes its short path through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
This PR brings the annotator from SDK 3.1 / C# compiler 3.4 / AppVeyor to SDK 11, renames it to
ICSharpCode.ReferenceAssemblyAnnotatorso it can be published to nuget.org under a new ID, and adds a GitHub Actions build. The goal was the fewest changes that get there. The package's behavior for consumers stays the same: same MSBuild properties and items, same package layout, and nullable attributes are still added as source.Earlier attempts this builds on: tunnelvisionlabs#90 (net6 port, never merged) contributed the WPF fix and the nullable code fixes. The PolySharp approach from tunnelvisionlabs#95 was evaluated and not taken (see below).
Validation:
dotnet msbuildand VS 2026msbuild.exe, locally and in CI.ICSharpCode.Decompiler(netstandard2.0,WarningsAsErrors=nullable) builds from scratch against the new package; 112 reference assemblies were annotated.Commits
1. Port to .NET 10 SDK and update dependencies
Tooling
global.json→11.0.0,rollForward: major,allowPrerelease: true(uses the newest installed SDK 11).Microsoft.Net.Compilers.Toolset 3.4.0; the SDK's compiler is used.Microsoft.NETCore.App.Ref10.0.0 (was 3.1.0), using the publishedTunnelVisionLabs.ReferenceAssemblyAnnotator1.0.0-alpha.160 (was alpha.138).Dependencies
ExcludeAssets="runtime"Why MSBuild 17.8 and not 18.x: the task runs inside the host's MSBuild, so the MSBuild packages are compile-time only. MSBuild 18.x on net472 depends on System.Collections.Immutable 10.0, which our task would then reference. VS 2022 and SDK 8/9 hosts carry older Immutable versions and binding redirects capped below 10, so the task would fail to load there. 17.8.43 (the latest servicing release of the VS 17.8 / SDK 8 line) brings Immutable 7.0, so the package works on SDK 8+ and VS 2022 17.8+. Bumping this later raises that floor.
Task TFMs are unchanged (netstandard2.0 + net472). netstandard2.0 is what
dotnet buildloads on any SDK; net472 is what VSmsbuild.exeloads. Moving tonet10.0would break consumers on SDK 8/9 for no functional gain.Bug fix in the weaver:
TypeReferenceEquivalenceComparerMono.Cecil's
TypeSpecification.GetElementType()returns the innermost element type, not the direct one. ForNullable<bool>&it returns the openNullable1, dropping the generic arguments. As a result, everyByReference/Pointer/Arrayof a generic instance compared equal to any other instantiation. Annotating net10.0 hit this onJSMarshalerArgument.ToManaged(out bool?)/ToManaged(out byte?)/ … inSystem.Runtime.InteropServices.JavaScript:SingleOrDefaultthrew and the build failed with MSB4018. The fix comparesTypeSpecification.ElementTypefor these three cases. TheGenericInstancebranch still usesGetElementType()`, which is correct there because the element of a generic instance is never itself a type specification.Smaller code fixes: nullable return types on
FindMatchingMethod/Property/Fieldandstring?inAnnotatorBuildTask(the newer compiler, plus the annotations we now build against, flag them; same as tunnelvisionlabs#90), and an SA1629 doc-comment fix.WPF:
NullableAttributesis added to_GeneratedCodeFilesonly whenIncludePackageReferencesDuringMarkupCompilation != 'true'(from tunnelvisionlabs#90). Modern SDKs import package references into the*_wpftmpproject by default, so the old workaround produced a duplicate compile item.Tests
net10.0everywhere (net10.0-windowsfor WPF). WPF net40 → net35, because the SDK now provides net35 reference assemblies, sojnm2.ReferenceAssemblies.net35is gone. The test XAML is simplified to avoid anMC1000 IAddChildInternalerror on net35 (same as Test using the .NET 6 SDK and stop adding a duplicate compile item for WPF tunnelvisionlabs/ReferenceAssemblyAnnotator#90).tests/Directory.Build.props:CheckEolTargetFramework=false,NuGetAudit=false(System.Data.SqlClient 4.8.0 advisories), and NETSDK1215 (".NET Standard < 2.0 not recommended") downgraded to a message. The tests cover legacy TFMs on purpose.PackageDownloadofMicrosoft.NETCore.App.Ref, because those TFMs are no longer annotated (see Review fixes below).build/test.ps1 -Configuration <cfg> -Engine dotnet|msbuildreplaces the 60-line command list inappveyor.yml, so CI and local runs execute the same thing. It runsgit cleanbefore each case (needed because of the isolated package cache), findsmsbuild.exeviavswhereif it isn't on PATH, and reports all failures at the end instead of stopping at the first.appveyor.ymlremoved.2. Rename to ICSharpCode.ReferenceAssemblyAnnotator and modernize packing
git mvof the folder, csproj,.props/.targets(NuGet only auto-importsbuild/<PackageId>.props/targets) and ruleset; namespace rename in all sources, theUsingTask, the.slnand the tests. The MIT copyright headers are unchanged.NewIdRequiredDueToNuGetBug/RestorePackageIdworkaround. It existed because the project and the package the repo builds itself with had the same ID. The project is nowICSharpCode.*while it still builds itself with the publishedTunnelVisionLabs.*, so the IDs no longer clash.ICSharpCode.Decompiler:Authors= ILSpy Contributors,Company= ic#code, copyright keeps the TVL line and adds ic#code, new project/repository URLs,PackageReadmeFile(README.md),PackageTags,PublishRepositoryUrl. Symbols:DebugType=embedded+EmbedUntrackedSourceswith the SDK's built-in Source Link replace the legacyIncludeSymbols/IncludeSource.symbols.nupkg.ContinuousIntegrationBuildis set on GitHub Actions.version.json→2.0.0-preview.{height}, a clear break from the1.0.0-alpha.*TunnelVisionLabs line.tests/NuGet.Config:<clear/>plus package source mapping that pinsICSharpCode.ReferenceAssemblyAnnotatorto the localbin/<Configuration>output, so the tests can never pick up a published package once one exists.[10.0.0], badges, migration note) and a newCLAUDE.md(contributor/agent notes);codecov.ymlremoved (unused since AppVeyor).Package contents:
build/ICSharpCode.ReferenceAssemblyAnnotator.{props,targets},build/NullableAttributes.{cs,vb},tools/{net472,netstandard2.0}/(task + Mono.Cecil + Mono.Cecil.Rocks),README.md.3. GitHub Actions (
.github/workflows/build.yml) + 2 CI fixeswindows-2025-vs2026, setup-dotnet 11.0.x preview and setup-msbuild; thendotnet build -c Release(which also packs),test.ps1with both engines, and upload of the.nupkgas thenuget-packageartifact. Windows is required for the net35/net48/WPF cases and formsbuild.exe. Only Release runs; a Debug leg would double the ~34 test builds for little extra coverage.Two problems only showed up on the runner, both because its checkout path (
D:\a\<repo>\<repo>\) is deeper than a typical local one and pushes paths in the isolated test package cache past MAX_PATH:git cleanfailed to delete the long paths →test.ps1now passes-c core.longpaths=true.msbuild.exe(.NET Framework) can't write them → the workflow passestest.ps1 -PackagesPathwith a short path outside the checkout. This is safe in CI because one run only ever contains one annotator version; local runs keep the per-case isolated cache.4. Review fixes (4b2d45b)
.targets: theResolveAssemblyReferencesDependsOnhook is conditioned on.NETCoreApp < 3.0). Whether the SDK adds its ownMicrosoft.NETCore.App.RefPackageDownloadfor these TFMs depends on whether the targeting pack is installed. With it installed, nothing is downloaded and leaving the item out failed with RA0000; without it, a project-level item failed with NU1505. Those reference assemblies are already annotated, so skipping them removes the conflict. Trade-off: those TFMs lose the weaver's[DisallowNull]removal onEqualityComparer<T>.GetHashCode(Program.IsExcludedAnnotation) and the refresh of EOL netcoreapp3.x refs with newer annotations. TheGetHashCodeline in the test sources is#if !NETCOREAPP3_0_OR_GREATER; the README explains how to condition thePackageDownloadwhen multi-targeting.tests/NuGet.Configuses forward slashes. With backslashes, package source mapping doesn't match the local source on Linux/macOS and restore fails with NU1100. The version range2.0.0-*was not the cause (bisected in WSL).test.ps1setsNUGET_PACKAGESitself (-PackagesPath, defaulttests/.isolatednugetcache). An inheritedNUGET_PACKAGESoverridesglobalPackagesFolder, so on machines with a machine-wide cache the tests could reuse a stale annotator package with the same version.Verified: the full matrix passes on both engines; net10.0 and MultiTFM pass with an empty
NetCoreTargetingPackRoot(no targeting packs); netstandard2.0 and net10.0 pass on Linux; CI is green.Not done, on purpose
MemberNotNull(When)tunnelvisionlabs/ReferenceAssemblyAnnotator#95): that PR used it to drop the shippedNullableAttributes.cs/.vb, which changes behavior for every consumer, and it isn't needed for the port. Add support for AOT attributes andMemberNotNull(When)tunnelvisionlabs/ReferenceAssemblyAnnotator#95's extra attributes (MemberNotNull*, trimming/AOT attributes) were also excluded on purpose.Follow-ups worth considering
ICSharpCode.ReferenceAssemblyAnnotatoris on nuget.org, switchDirectory.Build.propsto it.FindMatchingMethodre-throwsInvalidOperationExceptionafter logging candidates (at message importance), so any future comparer gap turns into an MSB4018 crash for consumers. Consider an RA1xxx warning and skipping the member instead; the same pattern applies toFindMatchingType(Program.cs:259) and the attribute constructor lookup (Program.cs:204).EquivalenceComparersandProgram.Annotate*, using in-memory Cecil modules, would be cheap and high-value.netcoreapp3.x consumers + NU1505: resolved in the review fixes (.NET Core 3.0+ is skipped).msbuild.exe(net472): tasks share one AppDomain there, so a different Mono.Cecil version loaded first by another task (Fody, ILLink, …) can win. Core MSBuild isolates tasks in an AssemblyLoadContext, so this only affects VS. Consider ILMerge/ILRepack or a renamed Cecil if it ever shows up.MemberNotNull,MemberNotNullWhen, …); that's what Add support for AOT attributes andMemberNotNull(When)tunnelvisionlabs/ReferenceAssemblyAnnotator#95 addressed. Consumers on netstandard2.0 lose them today.publicReleaseRefSpecis^refs/heads/master$, so branch builds get a-g<sha>suffix. Publish only from master builds, or add a release-tag ref spec plus a publish job (e.g. trusted publishing to nuget.org).11.0.x,preview). Once SDK 11 ships, dropdotnet-quality: previewandallowPrerelease.<frameworkAssembly assemblyName="mscorlib">for net472 (an artifact ofNoStdLib+ explicitmscorlibreference from building with the annotator itself); harmless but noise.CopyLocalLockFileAssemblies=truecopies MSBuild/Immutable DLLs intobinthat are never packed; only Cecil is picked explicitly in_GetFilesToPackage.🤖 Generated with Claude Code