Skip to content

Fix NativeAOT: pass package satellite assemblies to ILC and remove them from publish output#127089

Merged
sbomer merged 2 commits intomainfrom
copilot/fix-satellite-assembly-handling
Apr 21, 2026
Merged

Fix NativeAOT: pass package satellite assemblies to ILC and remove them from publish output#127089
sbomer merged 2 commits intomainfrom
copilot/fix-satellite-assembly-handling

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 17, 2026

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.

Original prompt 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 (Implement resource lookup in satellite assemblies #86689, 2023, MichalStrehovsky, original satellite support)
  • git show b134fa28352 → 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.

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.
  1. Technical Foundation:

    • .NET 11 preview 3 SDK (11.0.100-preview.3.26172.108) pinned via global.json in the repro
    • NativeAOT publish pipeline: _ComputeIlcCompileInputsNativeCompile (post PR Use SDK NativeCompile hook for ILC publish integration #125629 at HEAD) / ComputeLinkedFilesToPublish (pre Use SDK NativeCompile hook for ILC publish integration #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)
  2. 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
  3. 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
  4. Progress Tracking:

    • Completed: investigation, repro, fix, cleanup, end-to-end val...

Adding a test for this in dotnet/sdk#53971.

Created from Copilot CLI via the copilot delegate command.

Copilot AI review requested due to automatic review settings April 17, 2026 18:14
Copilot AI review requested due to automatic review settings April 17, 2026 18:14
Copilot AI requested review from Copilot and removed request for Copilot April 17, 2026 18:20
Copilot AI changed the title [WIP] Fix satellite assembly handling in SDK publishing Fix NativeAOT: pass package satellite assemblies to ILC and remove them from publish output Apr 17, 2026
Copilot AI requested a review from sbomer April 17, 2026 18:22
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
See info in area-owners.md if you want to be subscribed.

@sbomer sbomer marked this pull request as ready for review April 17, 2026 18:34
Copilot AI review requested due to automatic review settings April 17, 2026 18:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes NativeAOT publish handling of satellite resource assemblies from NuGet packages by ensuring they’re passed to ILC for embedding (via --satellite:) and removed from publish output so they don’t leak as loose .resources.dll files.

Changes:

  • Populate IlcSatelliteAssembly from @(ResolvedFileToPublish) where AssetType == resources (package satellites) in addition to project satellites from @(IntermediateSatelliteAssembliesWithTargetPath).
  • Remove satellites from @(ResolvedFileToPublish) using the unified @(IlcSatelliteAssembly) list (covers both package + project satellites).
  • Simplify ComputeManagedAssembliesToCompileToNative by removing the dead SatelliteAssemblies output and PE-based classification logic/usings.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/coreclr/tools/aot/ILCompiler.Build.Tasks/ComputeManagedAssembliesToCompileToNative.cs Removes unused satellite-assembly detection/output logic that no longer matches the SDK’s metadata contract.
src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets Correctly identifies package satellite assemblies via AssetType=resources, passes them to ILC, and prunes them from publish output.

Copy link
Copy Markdown
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM assuming you tried it and it works now.

@sbomer sbomer enabled auto-merge (squash) April 21, 2026 16:58
@sbomer sbomer merged commit bbf72b7 into main Apr 21, 2026
118 of 120 checks passed
@sbomer sbomer deleted the copilot/fix-satellite-assembly-handling branch April 21, 2026 19:19
@github-project-automation github-project-automation Bot moved this to Done in AppModel Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants