Skip to content
Merged
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
50 changes: 50 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Build

on:
push:
branches: '**'
pull_request:
branches: [ master ]

permissions:
contents: read

jobs:
Build:
# net35/net48 and WPF test projects plus the msbuild.exe engine need Windows with Visual Studio.
runs-on: windows-2025-vs2026
env:
Configuration: Release
# The in-repo isolated test cache exceeds MAX_PATH under msbuild.exe on the runner's deep checkout path.
# One run only ever sees one annotator version, so a shared short-path cache is safe here.
TestPackages: ${{ github.workspace }}\..\p

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Nerdbank.GitVersioning needs the history
persist-credentials: false

- uses: actions/setup-dotnet@v5
with:
dotnet-version: '11.0.x'
dotnet-quality: 'preview'

- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3

- name: Build and pack
run: dotnet build ReferenceAssemblyAnnotator.sln -c $env:Configuration

- name: Test (dotnet msbuild)
run: ./build/test.ps1 -Configuration $env:Configuration -Engine dotnet -PackagesPath $env:TestPackages

- name: Test (msbuild.exe)
run: ./build/test.ps1 -Configuration $env:Configuration -Engine msbuild -PackagesPath $env:TestPackages

- name: Upload NuGet package
uses: actions/upload-artifact@v7
with:
name: nuget-package
path: ICSharpCode.ReferenceAssemblyAnnotator/bin/${{ env.Configuration }}/*.nupkg
if-no-files-found: error
57 changes: 57 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

An IL weaver shipped as a NuGet package (`ICSharpCode.ReferenceAssemblyAnnotator`, the continuation of `TunnelVisionLabs.ReferenceAssemblyAnnotator`). At build time in a consuming project it takes un-annotated reference assemblies (.NET Framework, .NET Standard, .NET Core < 3.0; .NET Core 3.0+ is skipped because it is already annotated) and copies nullability attributes onto them from the annotated `Microsoft.NETCore.App.Ref` package the consumer pulls via `<PackageDownload>`. The project then compiles against the annotated copies.

## Build

`global.json` selects the newest installed SDK 11 (prerelease allowed). Windows is required for the full test matrix (net35/net48, WPF, msbuild.exe).

```
dotnet build ReferenceAssemblyAnnotator.sln -c Release
```

- `GeneratePackageOnBuild` is on: the build writes the `.nupkg` to `ICSharpCode.ReferenceAssemblyAnnotator/bin/<Configuration>/`. The version comes from Nerdbank.GitVersioning (`version.json`), so it only reflects committed changes.
- `TreatWarningsAsErrors` is on outside Visual Studio; StyleCop.Analyzers (`stylecop.json`) and `ICSharpCode.ReferenceAssemblyAnnotator.ruleset` apply. Assemblies are strong-name signed (`build/Key.snk`).
- The repo annotates its own references with the *published* `TunnelVisionLabs.ReferenceAssemblyAnnotator` (see `Directory.Build.props`), not the local build. That old version still has the `GetElementType()` comparer bug fixed here. It only works because this repo's own TFMs never hit the bug, so switch to the published `ICSharpCode.*` package once it exists.
- The task targets netstandard2.0 (dotnet MSBuild) and net472 (VS MSBuild) and compiles against MSBuild 17.8 (compile-only). Don't raise that to MSBuild 18: it drags in System.Collections.Immutable 10, which older hosts can't load.

## Tests

There is no unit test project. The tests are sample projects under `tests/` that must build cleanly with warnings as errors against the locally built package. Build the solution first, then:

```
./build/test.ps1 -Configuration Release -Engine dotnet # dotnet msbuild
./build/test.ps1 -Configuration Release -Engine msbuild # VS msbuild.exe (found via vswhere)
```

The script sets `NUGET_PACKAGES` to the isolated cache `tests/.isolatednugetcache` (an inherited machine-wide `NUGET_PACKAGES` would override `globalPackagesFolder` and serve a stale package with the same version), runs `git clean -dxf tests` before every case, and sets `CONFIGURATION` for the local source in `tests/NuGet.Config`. Source mapping forces `ICSharpCode.ReferenceAssemblyAnnotator` to come from `ICSharpCode.ReferenceAssemblyAnnotator/bin/<Configuration>`. To run a single case by hand:

```
$env:CONFIGURATION='Release'; $env:NUGET_PACKAGES="$PWD\tests\.isolatednugetcache"; git clean -dxf tests
dotnet msbuild -restore tests/SingleTFM -p:TestFramework=netstandard2.0 -warnaserror -nr:false -v:m
```

- `tests/SingleTFM` is parameterized by `-p:TestFramework=` (net35 … net10.0); MultiTFM, RefVersion3.1.0, NoDisabledWarnings (uses `-p:TreatWarningsAsErrors=true`, since `-warnaserror` ignores `WarningsNotAsErrors`) and WPF cover the rest.
- The test projects leave out the `Microsoft.NETCore.App.Ref` `PackageDownload` for .NET Core 3.0+ (netcoreapp3.x, net10.0). Those aren't annotated, and the SDK adds its own download whenever the targeting pack isn't installed, so a second one fails with NU1505. The `GetHashCode` line in the test sources is `#if !NETCOREAPP3_0_OR_GREATER` because only annotated TFMs get `[DisallowNull]` removed (`Program.IsExcludedAnnotation`).
- Keep the local source path in `tests/NuGet.Config` with forward slashes: with backslashes, source mapping doesn't match it on Linux/macOS (NU1100).

CI: `.github/workflows/build.yml` runs the same build and test script with both engines (Release only) and uploads the nupkg. On the runner, paths in the isolated cache exceed MAX_PATH under `msbuild.exe`. So the workflow passes `-PackagesPath` with a short path outside the checkout; that cache isn't wiped between cases, which is safe because a run only ever contains one annotator version. `test.ps1` also passes `core.longpaths=true` to `git clean`.

## Architecture

**MSBuild integration (most of the logic lives here)**: `ICSharpCode.ReferenceAssemblyAnnotator.props`/`.targets`, packed into `build/`. The file names must match the package ID.
- `.props` picks the task DLL: `tools/net472/` for full MSBuild, `tools/netstandard2.0/` for `dotnet` (Core) MSBuild. The Mono.Cecil DLLs are packed next to it via `_GetFilesToPackage`.
- `.targets` hooks `ResolveAssemblyReferencesDependsOn` with `AnnotateReferenceAssemblies` → `UpdateReferences`, except for .NETCoreApp >= 3.0. It resolves the annotated source directory from the `Microsoft.NETCore.App.Ref` `PackageDownload` (errors RA0000–RA0002) and decides which references to annotate per target framework family: a hard-coded list for .NETFramework, framework-package references for .NETStandard/.NETCoreApp, and `ResolvedCompileFileDefinitionsToAdd` below 2.0. It writes the outputs to `$(IntermediateOutputPath)annotated\`, then swaps the `Reference` items and prepends that folder to `TargetFrameworkDirectory`.
- It also adds `NullableAttributes.cs`/`.vb` (internal copies of the nullable attributes) as `Compile` items unless the TFM already has them or `GenerateNullableAttributes=false`. The WPF `_GeneratedCodeFiles` line works around dotnet/wpf#810.

**Weaver (C#)** in `ICSharpCode.ReferenceAssemblyAnnotator/`:
- `AnnotatorBuildTask`: the MSBuild task, invoked once per unannotated assembly; it calls `Program.Main`.
- `Program`: loads both assemblies with Mono.Cecil, walks types, methods, properties and fields, finds the matching member in the annotated assembly, and copies the nullability-related custom attributes. The `EquivalenceComparers+*` classes compare signatures structurally across different assembly identities; note that Cecil's `GetElementType()` returns the *innermost* type, so compare `TypeSpecification.ElementType` instead. It also makes sure the output carries `ReferenceAssemblyAttribute`.
- `WellKnownTypes` + `WellKnownTypes+*ProvidedType`: when the target assembly doesn't define an attribute type, it is synthesized into the output assembly as an internal embedded type via `CustomAttributeFactory`/`MethodFactory`.
- `SuppressibleLoggingHelper`: RA1xxx warnings that honor the consumer's `NoWarn`, which is passed in as `DisabledWarnings`.

**`CecilBasedAnnotator`**: a small net472 console wrapper around `Program.Main` for debugging the weaver outside MSBuild. Arguments: `<unannotated.dll> <tfm-dirs;-separated> <annotated.dll> <output.dll>`; sample profiles are in `Properties/launchSettings.json`.
4 changes: 2 additions & 2 deletions CecilBasedAnnotator/CecilBasedAnnotator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
</PropertyGroup>

<PropertyGroup>
<CodeAnalysisRuleSet>..\TunnelVisionLabs.ReferenceAssemblyAnnotator.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRuleSet>..\ICSharpCode.ReferenceAssemblyAnnotator.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\TunnelVisionLabs.ReferenceAssemblyAnnotator\TunnelVisionLabs.ReferenceAssemblyAnnotator.csproj" />
<ProjectReference Include="..\ICSharpCode.ReferenceAssemblyAnnotator\ICSharpCode.ReferenceAssemblyAnnotator.csproj" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion CecilBasedAnnotator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal class Program
{
private static void Main(string[] args)
{
TunnelVisionLabs.ReferenceAssemblyAnnotator.Program.Main(
ICSharpCode.ReferenceAssemblyAnnotator.Program.Main(
log: null,
referenceAssembly: args[0],
targetFrameworkDirectories: args[1].Split(';').ToImmutableArray(),
Expand Down
4 changes: 2 additions & 2 deletions CecilBasedAnnotator/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"profiles": {
"Microsoft.Win32.Primitives (netstandard1.6)": {
"commandName": "Project",
"commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\Microsoft.Win32.Primitives.dll\" \"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\;$(NuGetPackageRoot)\\system.runtime\\4.3.0\\ref\\netstandard1.5\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0\\ref\\netcoreapp3.0\\Microsoft.Win32.Primitives.dll\" Microsoft.Win32.Primitives.dll"
"commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\Microsoft.Win32.Primitives.dll\" \"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\;$(NuGetPackageRoot)\\system.runtime\\4.3.0\\ref\\netstandard1.5\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\10.0.0\\ref\\net10.0\\Microsoft.Win32.Primitives.dll\" Microsoft.Win32.Primitives.dll"
},
"System.ComponentModel.DataAnnotations (netcoreapp2.0)": {
"commandName": "Project",
"commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\System.ComponentModel.DataAnnotations.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0\\ref\\netcoreapp3.0\\System.ComponentModel.DataAnnotations.dll\" System.ComponentModel.DataAnnotations.dll"
"commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\System.ComponentModel.DataAnnotations.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\10.0.0\\ref\\net10.0\\System.ComponentModel.DataAnnotations.dll\" System.ComponentModel.DataAnnotations.dll"
}
}
}
37 changes: 20 additions & 17 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,21 @@
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)build\Key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<!-- Use the 16.4 compiler -->
<ItemGroup>
<PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="3.4.0" PrivateAssets="all" />
</ItemGroup>

<!-- Enable nullable reference types -->
<PropertyGroup>
<Nullable>enable</Nullable>
<AnnotatedReferenceAssemblyVersion>3.1.0</AnnotatedReferenceAssemblyVersion>
<AnnotatedReferenceAssemblyVersion>10.0.0</AnnotatedReferenceAssemblyVersion>
<GenerateNullableAttributes>false</GenerateNullableAttributes>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.138" PrivateAssets="all" />
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" />
</ItemGroup>

<!-- Code style -->
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.66" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand All @@ -59,23 +54,27 @@

<!-- Versioning and packaging -->
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="2.2.13" PrivateAssets="all" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.10.94" PrivateAssets="all" />
</ItemGroup>

<PropertyGroup>
<Description>IL weaver for adding nullability annotations to .NET Framework and .NET Standard reference assemblies.</Description>
<Product>TunnelVisionLabs.ReferenceAssemblyAnnotator</Product>
<Company>Tunnel Vision Laboratories, LLC</Company>
<Copyright>Copyright © Tunnel Vision Laboratories, LLC 2019</Copyright>
<Product>ICSharpCode.ReferenceAssemblyAnnotator</Product>
<Company>ic#code</Company>
<Copyright>Copyright © Tunnel Vision Laboratories, LLC 2019; Copyright © ic#code $([System.DateTime]::Now.Year)</Copyright>
<NeutralLanguage>en-US</NeutralLanguage>
<Authors>sharwell</Authors>
<PackageProjectUrl>https://github.com/tunnelvisionlabs/ReferenceAssemblyAnnotator</PackageProjectUrl>
<Authors>ILSpy Contributors</Authors>
<PackageProjectUrl>https://github.com/icsharpcode/ReferenceAssemblyAnnotator</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/tunnelvisionlabs/ReferenceAssemblyAnnotator</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>nullable;nullability;reference-assemblies;msbuild;il-weaver</PackageTags>
<RepositoryUrl>https://github.com/icsharpcode/ReferenceAssemblyAnnotator.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PublishRepositoryUrl>true</PublishRepositoryUrl>

<IncludeSymbols>true</IncludeSymbols>
<IncludeSource>true</IncludeSource>
<!-- Symbols and sources travel inside the assembly (Source Link is built into the SDK). -->
<DebugType>embedded</DebugType>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!--
NU5105: The package version 'version' uses SemVer 2.0.0 or components of SemVer 1.0.0 that are not supported on
legacy clients. Change the package version to a SemVer 1.0.0 string. If the version contains a release
Expand All @@ -85,4 +84,8 @@
<NoWarn>$(NoWarn),NU5105</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for TunnelVisionLabs.ReferenceAssemblyAnnotator" Description="Code analysis rules for TunnelVisionLabs.ReferenceAssemblyAnnotator.csproj." ToolsVersion="14.0">
<RuleSet Name="Rules for ICSharpCode.ReferenceAssemblyAnnotator" Description="Code analysis rules for ICSharpCode.ReferenceAssemblyAnnotator.csproj." ToolsVersion="14.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1101" Action="None" />
<Rule Id="SA1202" Action="None" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System;
using System.Collections.Immutable;
Expand Down Expand Up @@ -74,7 +74,7 @@ public override bool Execute()
{
var log = new SuppressibleLoggingHelper(Log, requiredPrefix: "RA", DisabledWarnings);

string unannotatedReferenceAssembly = TargetFrameworkDirectories.Select(path => Path.Combine(path.ItemSpec, UnannotatedReferenceAssembly + ".dll")).FirstOrDefault(File.Exists);
string? unannotatedReferenceAssembly = TargetFrameworkDirectories.Select(path => Path.Combine(path.ItemSpec, UnannotatedReferenceAssembly + ".dll")).FirstOrDefault(File.Exists);
string annotatedReferenceAssembly = Path.Combine(AnnotatedReferenceAssemblyDirectory, UnannotatedReferenceAssembly + ".dll");
bool foundAnnotatedAssembly = File.Exists(annotatedReferenceAssembly);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System;
using System.Linq;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System.Collections.Generic;
using Mono.Cecil;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System.Collections.Generic;
using System.Linq;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System.Collections.Generic;
using Mono.Cecil;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System.Collections.Generic;
using Mono.Cecil;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System.Collections.Generic;
using Mono.Cecil;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace TunnelVisionLabs.ReferenceAssemblyAnnotator
namespace ICSharpCode.ReferenceAssemblyAnnotator
{
using System.Collections.Generic;
using Mono.Cecil;
Expand Down
Loading
Loading