Skip to content

Commit 033cfb1

Browse files
Copilotsbomer
authored andcommitted
Fix NativeAOT: pass package satellite assemblies to ILC and remove them from publish output (#127089)
NativeAOT publish was leaking satellite `.dll` files from NuGet packages into the publish directory and failing to pass them to ILC via `--satellite:` (required for resource embedding at link time). **Root cause:** `ComputeManagedAssembliesToCompileToNative` classified satellites by PE-inspecting items flagged `PostprocessAssembly=true`. The SDK never sets this metadata on resource items (`AssetType=resources`), so the satellite branch was dead code — `SatelliteAssemblies` output was always empty. PR #111514 introduced this regression; PR #124192 partially fixed project-own satellites via `IntermediateSatelliteAssembliesWithTargetPath` but left the package case broken. ## Changes **`Microsoft.NETCore.Native.Publish.targets`** - Populate `IlcSatelliteAssembly` via `Condition="'%(ResolvedFileToPublish.AssetType)' == 'resources'"` (package satellites) in addition to the existing `IntermediateSatelliteAssembliesWithTargetPath` (project satellites) - Replace `Remove="@(IntermediateSatelliteAssembliesWithTargetPath)"` with `Remove="@(IlcSatelliteAssembly)"` so both package and project satellites are pruned from `ResolvedFileToPublish` - Drop the now-unused `<Output TaskParameter="SatelliteAssemblies" ...>` wire-up **`ComputeManagedAssembliesToCompileToNative.cs`** - Remove dead `SatelliteAssemblies` `[Output]` property, PE-reading culture classification loop, `PostprocessAssembly` guard block, and the now-unused `System.Reflection.Metadata` / `System.Reflection.PortableExecutable` usings (~45 lines removed, none of it ever ran) # Customer Impact NativeAOT apps that reference NuGet packages containing satellite assemblies (e.g. `System.CommandLine`) would fail to resolve localized resources at runtime and would have stray `.dll` files in the publish output, defeating the single-file native binary guarantee. # Regression Yes — regressed in PR #111514 ("Run ILC after ComputeResolvedFilesToPublishList"). PR #124192 closed the gap for project-own satellites but missed package satellites. # Testing End-to-end validated with a repro app (`PublishAot=true`, `System.CommandLine 2.0.6` package dependency, project `Strings.resx` + `Strings.es.resx`): publish output is clean (only native binary + debug symbols), ILC receives the expected `--satellite:` arguments for all cultures, and runtime correctly resolves `es`/`de`/`ja` resources from both package and project sources. # Risk Low. The targets change is purely additive for the satellite item population and replaces a narrower `Remove` with a broader one that is a strict superset of the previous behavior. The task change removes code that was provably never executed under the current SDK contract. # Package authoring no longer needed in .NET 9 IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version. Keep in mind that we still need package authoring in .NET 8 and older versions. <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> <analysis> Chronological review: 1. User asked to read dotnet/runtime PR #124192 + related SDK issue comments 2. Asked for a repro in /tmp 3. Asked to root cause 4. Asked for logging / both app and package resources in repro 5. Asked about runtime behavior 6. Asked how SDK populates ResolvedFileToPublish 7. Asked what gets passed as --satellite 8. Asked to fix 9. Asked if I tested 10. Asked about task's satellite filtering being moot → led to cleanup 11. Asked about `_IlcReferenceSatelliteAssemblies` naming → kept (consistency) 12. Then asked: "What if we consider IlcSatelliteAssembly to be the snapshot?" → simplified to inline include + `Remove="@(IlcSatelliteAssembly)"` 13. Asked to remove two comments 14. Asked to re-test 15. Asked to commit → committed as `e765e126dcb` 16. Most recent: asked "Why weren't we passing satellites, did that regress at some point?" → I investigated git history, found PR #111514 was the regression point. Recent commands and results: - `git log --oneline --all -S 'SatelliteAssemblies'` on task file → found b134fa2 (#86689, 2023, MichalStrehovsky, original satellite support) - `git show b134fa2` → showed original implementation: task read ALL items, PE-inspected, classified by Culture - `git log --oneline --all -S 'PostprocessAssembly'` → found e144870 "Skip non-PostprocessAssembly files..." (Feb 17 2026) and a5cee7c "Derive ILC inputs from ResolvedFileToPublish..." (Feb 25 2026), both by Sven Boemer - `git log --all --oneline --ancestry-path` → showed these were part of branch ilc-compile-order / ilcPublish - `git log main --grep='Run ILC after'` → confirmed squash-merged as `44e21b71149 Run ILC after ComputeResolvedFilesToPublishList (#111514)` Final answer given: PR #111514 regressed satellite passing. Original PR #86689 (2023) worked. PR #124192 partially fixed project-own case. This commit closes package case. Commit created: e765e126dcb on branch satellite-fix, not pushed. </analysis> <summary> 1. Conversation Overview: - Primary Objectives: Investigate dotnet/runtime PR #124192 / dotnet/sdk#53422 regression where NativeAOT publish leaks package satellite `.dll` files AND fails to embed them via ILC. Root-cause, fix, validate, commit. - Session Context: Progressed through reading comments → repro → root cause → logging → SDK trace → fix implementation → code review/cleanup iterations → test → commit → regression history. - User Intent Evolution: From fixing the surface bug to cleaning up dead code, then simplifying the fix (inlining `IlcSatelliteAssembly` as the snapshot), then historical attribution. 2. Technical Foundation: - .NET 11 preview 3 SDK (`11.0.100-preview.3.26172.108`) pinned via global.json in the repro - NativeAOT publish pipeline: `_ComputeIlcCompileInputs` → `NativeCompile` (post PR #125629 at HEAD) / `ComputeLinkedFilesToPublish` (pre #125629 in installed SDK) - SDK `_ComputeAssembliesToPostprocessOnPublish` deliberately excludes satellites from `PostprocessAssembly=true` metadata - Package/ref satellites land in `ResolvedFileToPublish` with `AssetType=='resources'`; project's own satellites arrive via `IntermediateSatelliteAssembliesWithTargetPath` - `IlcSatelliteAssembly` item → `--satellite:` ILC args (required for embedding; NAOT can't load from disk) 3. Codebase Status: - `/home/sven/src/satellite-fix/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets`: - Changed `<IlcSatelliteAssembly Include="@(_SatelliteAssembliesToPublish)" />` to `<IlcSatelliteAssembly Include="@(ResolvedFileToPublish)" Condition="'%(ResolvedFileToPublish.AssetType)' == 'resources'" />` - Changed `<ResolvedFileToPublish Remove="@(IntermediateSatelliteAssembliesWithTargetPath)" />` to `<ResolvedFileToPublish Remove="@(IlcSatelliteAssembly)" />` - Dropped `<Output TaskParameter="SatelliteAssemblies" ItemName="_SatelliteAssembliesToPublish" />` - `/home/sven/src/satellite-fix/src/coreclr/tools/aot/ILCompiler.Build.Tasks/ComputeManagedAssembliesToCompileToNative.cs`: - Removed `SatelliteAssemblies` `[Output]` property, PE-reading culture classification, `PostprocessAssembly` late-filter check, `System.Reflection.Metadata`/`PortableExecutable` usings - Net −45 lines; +3/−3 on targets file - Repro at `/tmp/aot-satellite-repro/app/` with diagnostic dump targets, `PublishAot=true`, `System.CommandLine 2.0.6`, `Strings.resx`+`Strings.es.resx` 4. Problem Resolution: - Root cause: Task's classification logic gated on `PostprocessAssembly=true` which SDK never sets on satellites, making satellite branch unreachable - Fix: Classify via `AssetType=='resources'` in MSBuild directly; use `IlcSatelliteAssembly` as snapshot consumed by both ILC and the `ResolvedFileToPublish` remove - Dead code cleanup: task's `SatelliteAssemblies` output never produced anything under current SDK contract 5. Progress Tracking: - Completed: investigation, repro, fix, cleanup, end-to-end val... </details> Adding a test for this in dotnet/sdk#53971. <!-- START COPILOT CODING AGENT SUFFIX --> Created from Copilot CLI via the copilot delegate command. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com>
1 parent af44505 commit 033cfb1

2 files changed

Lines changed: 2 additions & 48 deletions

File tree

src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<ItemGroup>
99
<_IlcManagedInputAssemblies Include="@(ResolvedFileToPublish)" Condition="'%(ResolvedFileToPublish.PostprocessAssembly)' == 'true'" />
1010
<IlcReference Include="@(_IlcManagedInputAssemblies)" />
11-
<IlcSatelliteAssembly Include="@(_SatelliteAssembliesToPublish)" />
11+
<IlcSatelliteAssembly Include="@(ResolvedFileToPublish)" Condition="'%(ResolvedFileToPublish.AssetType)' == 'resources'" />
1212
<IlcSatelliteAssembly Include="@(IntermediateSatelliteAssembliesWithTargetPath)" />
1313
</ItemGroup>
1414
</Target>
@@ -35,7 +35,7 @@
3535
<ResolvedFileToPublish Remove="@(_IlcManagedInputAssemblies)" />
3636
<!-- dotnet CLI produces managed debug symbols, which we will replace with native symbols instead -->
3737
<ResolvedFileToPublish Remove="@(_DebugSymbolsIntermediatePath)" />
38-
<ResolvedFileToPublish Remove="@(IntermediateSatelliteAssembliesWithTargetPath)" />
38+
<ResolvedFileToPublish Remove="@(IlcSatelliteAssembly)" />
3939
<!-- replace apphost with binary we generated during native compilation -->
4040
<ResolvedFileToPublish Include="$(NativeBinary)">
4141
<RelativePath>$(NativeBinaryPrefix)$(TargetName)$(NativeBinaryExt)</RelativePath>
@@ -122,7 +122,6 @@
122122
SdkAssemblies="@(PrivateSdkAssemblies)"
123123
FrameworkAssemblies="@(FrameworkAssemblies)">
124124

125-
<Output TaskParameter="SatelliteAssemblies" ItemName="_SatelliteAssembliesToPublish" />
126125
<Output TaskParameter="RuntimePackFilesToSkipPublish" ItemName="_RuntimePackFilesToSkipPublish" />
127126
</ComputeManagedAssembliesToCompileToNative>
128127

src/coreclr/tools/aot/ILCompiler.Build.Tasks/ComputeManagedAssembliesToCompileToNative.cs

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
using System.Collections.Generic;
88
using System.Diagnostics;
99
using System.IO;
10-
using System.Reflection.Metadata;
11-
using System.Reflection.PortableExecutable;
1210

1311

1412

@@ -74,13 +72,6 @@ public string DotNetHostPolicyLibraryName
7472
set;
7573
}
7674

77-
[Output]
78-
public ITaskItem[] SatelliteAssemblies
79-
{
80-
get;
81-
set;
82-
}
83-
8475
/// <summary>
8576
/// CoreCLR runtime pack files (apphost, native assets, managed assemblies replaced by NativeAOT equivalents)
8677
/// that should be removed from the publish output and replaced with NativeAOT runtime pack assemblies.
@@ -95,7 +86,6 @@ public ITaskItem[] RuntimePackFilesToSkipPublish
9586
public override bool Execute()
9687
{
9788
var runtimePackFilesToSkipPublish = new List<ITaskItem>();
98-
var satelliteAssemblies = new List<ITaskItem>();
9989
var nativeAotFrameworkAssembliesToUse = new Dictionary<string, ITaskItem>();
10090

10191
foreach (ITaskItem taskItem in SdkAssemblies)
@@ -159,44 +149,9 @@ public override bool Execute()
159149
runtimePackFilesToSkipPublish.Add(taskItem);
160150
continue;
161151
}
162-
163-
// Only classify files that the SDK has identified as managed runtime assemblies.
164-
// Other files (e.g. Content items that happen to be managed assemblies) should be
165-
// left alone and allowed to be published as-is.
166-
if (!taskItem.GetMetadata("PostprocessAssembly").Equals("true", StringComparison.OrdinalIgnoreCase))
167-
{
168-
continue;
169-
}
170-
171-
// Check if this is a satellite assembly by reading its culture metadata.
172-
// Non-managed files are silently skipped as a safety measure.
173-
try
174-
{
175-
using (FileStream moduleStream = File.OpenRead(itemSpec))
176-
using (var module = new PEReader(moduleStream))
177-
{
178-
if (module.HasMetadata)
179-
{
180-
MetadataReader moduleMetadataReader = module.GetMetadataReader();
181-
if (moduleMetadataReader.IsAssembly)
182-
{
183-
string culture = moduleMetadataReader.GetString(moduleMetadataReader.GetAssemblyDefinition().Culture);
184-
185-
if (culture != "" && !culture.Equals("neutral", StringComparison.OrdinalIgnoreCase))
186-
{
187-
satelliteAssemblies.Add(taskItem);
188-
}
189-
}
190-
}
191-
}
192-
}
193-
catch (BadImageFormatException)
194-
{
195-
}
196152
}
197153

198154
RuntimePackFilesToSkipPublish = runtimePackFilesToSkipPublish.ToArray();
199-
SatelliteAssemblies = satelliteAssemblies.ToArray();
200155

201156
return true;
202157

0 commit comments

Comments
 (0)