Skip to content

Add NativeAOT + satellite assembly integration test#53971

Open
Copilot wants to merge 3 commits intomainfrom
copilot/add-native-aot-satellite-test
Open

Add NativeAOT + satellite assembly integration test#53971
Copilot wants to merge 3 commits intomainfrom
copilot/add-native-aot-satellite-test

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 17, 2026

  • Add NativeAot_app_publishes_with_app_and_package_satellite_assemblies test to GivenThatWeWantToPublishAnAotApp.cs
    • Custom Program.cs using ResourceManager + CultureInfo("fr") to verify French resource at runtime
    • App-defined Strings.resx (neutral fallback) and Strings.fr.resx (French) embedded resources
    • System.Spatial 5.8.3 package reference (ships de/es/fr/it/ja/ko/ru/zh-Hans/zh-Hant satellites)
    • SatelliteResourceLanguages=fr;de filter
    • PublishAot=true, UseCurrentRuntimeIdentifier=true, SelfContained=true
    • StripSymbols=true on non-Windows
    • Assert fr/{projectName}.resources.dll exists (app satellite)
    • Assert fr/System.Spatial.resources.dll and de/System.Spatial.resources.dll exist (package satellites)
    • Assert ja/System.Spatial.resources.dll does NOT exist (filtered)
    • Assert native image exists and is native
    • Run native exe and assert stdout contains "Bonjour"
    • Derive ResourceManager base name from projectName variable (use interpolated string) so the test stays self-consistent if projectName ever changes
Original prompt The conversation is about investigating test coverage for satellite assemblies in the dotnet/sdk repository, specifically under the context of a branch named `sdk-satellite-fix`. User progressively asked: 1. Are there tests for publishing with satellite assemblies (app-defined + package reference)? 2. Any NativeAOT tests? 3. Add a single NativeAOT test covering both app-defined and package-reference satellites. 4. Can the test run the native image to verify resources work at runtime?

I identified existing tests, then added a new test to test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs named NativeAot_app_publishes_with_app_and_package_satellite_assemblies. In the final step, I enhanced it to run the produced native exe and assert runtime resource loading.

Key technical details:

  • CreateHelloWorldTestProject helper creates a basic test project with a Program.cs that prints "Hello World"
  • TestProject has EmbeddedResources dict (auto-written like SourceFiles); SDK default globs include **/*.resx as EmbeddedResource
  • System.Spatial 5.8.3 ships satellites for de, es, fr, it, ja, ko, ru, zh-Hans, zh-Hant (per GivenThatWeWantToFilterSatelliteAssemblies)
  • Existing runnable AOT test pattern uses /p:UseCurrentRuntimeIdentifier=true, records NETCoreSdkPortableRuntimeIdentifier, sets StripSymbols=true on non-Windows, and uses RunExeCommand
  • Build verification via ./build.sh was attempted but aborted (long bootstrap); system dotnet alone is insufficient because repo tests depend on redist

Recent operations: edited the file twice. First edit added a basic file-existence test. Second (final) edit replaced it with a runnable version that customizes Program.cs to use ResourceManager + CultureInfo("fr") and runs the native exe asserting "Bonjour".

1. Conversation Overview: - Primary Objectives: - "are there tests for publishing with satellite assemblies (either defined by the app or from package references)?" - "any nativeaot tests?" - "Ah can we add a nativeaot test that has satellite assemblies? both satellites defined by the app and from package reference? a single test ideally." - "can the test run the native image to ensure it can process the resources? (do other tests run)?" - Session Context: Investigation of satellite-assembly test coverage in the dotnet/sdk repo (branch `sdk-satellite-fix`), culminating in authoring a new NativeAOT publish test that exercises both app-defined satellites and package-reference satellites, then upgrading it to run the native image. - User Intent Evolution: From discovery (what tests exist?) → gap identification (no AOT+satellite coverage) → implementation (add single covering test) → strengthen (execute native image to validate runtime resource resolution).
  1. Technical Foundation:

    • Repo: /home/sven/src/sdk-satellite-fix (dotnet/sdk)
    • Test framework: xUnit via SdkTest base class; TestAssetsManager.CreateTestProject(TestProject); PublishCommand, RunExeCommand, GetValuesCommand
    • TestProject model: SourceFiles, EmbeddedResources (both dumped to disk; SDK default globs pick up .resx), PackageReferences, AdditionalProperties, RecordProperties
    • NativeAOT publishing: /p:PublishAot=true, /p:UseCurrentRuntimeIdentifier=true, /p:SelfContained=true, NETCoreSdkPortableRuntimeIdentifier property recording, StripSymbols=true on non-Windows, IsNativeImage() helper
    • Satellite filtering: SatelliteResourceLanguages MSBuild property
    • Reference NuGet: System.Spatial 5.8.3 (ships de/es/fr/it/ja/ko/ru/zh-Hans/zh-Hant satellites)
  2. Codebase Status:

    • test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs:
      • Purpose: Primary NativeAOT publish test suite
      • Current State: Added new test method NativeAot_app_publishes_with_app_and_package_satellite_assemblies inserted just before the private helper CheckIlcVersions (around line 1061)
      • Key Code Segments: New [RequiresMSBuildVersionFact("17.0.0.32901")] test that:
        • Calls CreateHelloWorldTestProject(CurrentTargetFramework, "NativeAotAppWithSatellites", true)
        • Records NETCoreSdkPortableRuntimeIdentifier, sets PublishAot=true, conditionally StripSymbols=true on non-Windows
        • Sets SatelliteResourceLanguages=fr;de
        • Adds new TestPackageReference("System.Spatial", "5.8.3")
        • Overrides SourceFiles[$"{projectName}.cs"] with a Main that uses new ResourceManager("{projectName}.Strings", typeof(Test).Assembly).GetString("HelloWorld", CultureInfo.GetCultureInfo("fr")), prints value, returns 0 iff equals "Bonjour"
        • Adds EmbeddedResources["Strings.resx"] (value "Hello") and EmbeddedResources["Strings.fr.resx"] (value "Bonjour") using minimal resx content
        • Publishes with /p:UseCurrentRuntimeIdentifier=true /p:SelfContained=true
        • Gets rid from recorded properties,...

Created from Copilot CLI via the copilot delegate command.

Copilot AI changed the title [WIP] Add NativeAOT test for satellite assemblies in publishing Add NativeAOT + satellite assembly integration test Apr 17, 2026
Copilot AI requested a review from sbomer April 17, 2026 18:23
@sbomer sbomer marked this pull request as ready for review April 17, 2026 18:35
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

Adds end-to-end test coverage in the publish test suite for NativeAOT publish scenarios involving satellite assemblies, ensuring both app-authored and package-provided satellites are produced and usable at runtime.

Changes:

  • Add NativeAot_app_publishes_with_app_and_package_satellite_assemblies to validate satellite assembly publish output under PublishAot=true.
  • Validate SatelliteResourceLanguages filtering for package satellites (presence for fr/de, absence for ja).
  • Execute the produced native binary and assert the French resource value is resolved at runtime.

Comment thread test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs Outdated
@sbomer
Copy link
Copy Markdown
Member

sbomer commented Apr 17, 2026

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 17, 2026

@copilot apply changes based on the comments in this thread

Done in 981ab55. The source file assignment now uses an interpolated string ($@"...") with {projectName}.Strings so the ResourceManager base name stays in sync with the project name automatically.

sbomer added a commit to dotnet/runtime that referenced this pull request Apr 21, 2026
…em 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants