Skip to content

targetPacks/Directory.Build.targets invokes ILAsm without -deterministic, causing non-reproducible reference assemblies #1662

Description

@mthalman

Summary

src/targetPacks/Directory.Build.targets (the BuildIlFile target) invokes ilasm without the -deterministic flag. As a result, every assembled .dll (netstandard.dll, mscorlib.dll, the ~115 netfx-redist reference assemblies under Microsoft.NET.Build.Extensions/*/lib/) gets a fresh, wall-clock-derived PE TimeDateStamp and a fresh GUID-derived MVID on each invocation. This breaks reproducibility of these assemblies and cascades into every downstream C# DLL that references them.

Current code

<Target Name="BuildIlFile" DependsOnTargets="ResolveIlToolPaths">
  <Error Condition="'$(IlasmPath)' == ''" Text="The 'IlasmPath' property must be set." />
  <Exec Command="$(IlasmPath) $(IlFile) -dll -quiet -nologo &quot;-output=$(OutputFile)&quot;"
        IgnoreStandardErrorWarningFormat="true"
        CustomErrorRegularExpression=": error : " />
</Target>

The <Exec> is missing -deterministic.

Proposed fix

-  <Exec Command="$(IlasmPath) $(IlFile) -dll -quiet -nologo &quot;-output=$(OutputFile)&quot;"
+  <Exec Command="$(IlasmPath) $(IlFile) -dll -quiet -nologo -deterministic &quot;-output=$(OutputFile)&quot;"
         IgnoreStandardErrorWarningFormat="true"
         CustomErrorRegularExpression=": error : " />

ILAsm has supported -deterministic since dotnet/runtime#109091 (merged 2025-01-09, fixing dotnet/runtime#8293). With this flag, ILAsm:

  • Computes the PE TimeDateStamp as a content hash with the upper bit set (per the deterministic-PE convention).
  • Derives the MVID deterministically from the input.
  • Emits a deterministic CodeView debug entry.

Cascade impact

This is a high-leverage fix. The reference assemblies produced by this target are referenced by virtually every Roslyn-compiled assembly in the SDK. Because Roslyn deterministic compilation hashes the byte content of every referenced metadata-resolved DLL into the consumer's MVID, every C# DLL compiled against these reference assemblies also drifts between builds — analyzers, source generators, the Razor toolchain, hot-reload assemblies, SBRP-built reference packages (Newtonsoft.Json, Microsoft.IdentityModel.*, Microsoft.Css.Parser, etc.), and so on.

In the dotnet/dotnet source-build SDK reproducibility test, this single root cause accounts for ~17 directly-affected reference assemblies and over a hundred cascade-affected downstream DLLs.

Long-term alternative

#1313 proposes replacing the IL/ILAsm-based targeting-pack generation with GenAPI/C# entirely. If that lands, this fix becomes moot. But until then, the one-line -deterministic addition is a strict improvement.

Context

Found while building the SDK reproducibility validation test for the dotnet/dotnet VMR (dotnet/source-build#5486). Resolution of this issue is important to meet the goal of reproducible builds: dotnet/source-build#4963

Metadata

Metadata

Labels

area-buildRelated to builds (developer, CI and PR validation)

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions