Skip to content

PublishAot / PublishSingleFile apps do not automatically strip resx satellite assemblies #127086

Description

@nagilson

Summary

When publishing an application with PublishAot=true, PublishSingleFile=true, or as self-contained, satellite resource assemblies (localized .resources.dll files in per-culture folders like cs/, de/, fr/, etc.) are not automatically excluded from the publish output directory. I believe these resources are already embedded in the single-file bundle or are unnecessary alongside a native AOT binary, yet they still appear as loose files in the output. This is confusing and made me think they were not correctly embedded.

Users must manually set <SatelliteResourceLanguages>en</SatelliteResourceLanguages> in their project file to suppress them. This property should not be required — the default behavior for PublishSingleFile and PublishAot scenarios should be to exclude satellite assembly folders from the publish output.

SatelliteResourceLanguages also does not seem to be properly excluding everything when I set the property - folders are still being generated when I set it to en in the dotnetup publish when I tried to set it locally.

Reproduction

  1. Create a console app with <PublishAot>true</PublishAot> that references any NuGet package shipping satellite assemblies (e.g., System.CommandLine, Spectre.Console).
  2. Run dotnet publish -r win-x64 -c Release.
  3. Observe the publish output directory contains 13+ language subdirectories (cs/, de/, es/, fr/, it/, ja/, ko/, pl/, pt-BR/, ru/, tr/, zh-Hans/, zh-Hant/), each containing .resources.dll files that are unnecessary alongside the native binary.

Current behavior

  • PublishAot: The AOT compiler produces a native binary. Satellite .resources.dll files from the project and its NuGet dependencies are still copied to the publish directory as loose files in per-culture subdirectories. They serve no purpose — the native binary does not load them.
  • PublishSingleFile: Satellite assemblies are bundled into the single-file binary (they are not marked ExcludeFromSingleFile=true), yet their per-culture folders and .resources.dll files are also emitted as loose files in the publish output.
  • Self-contained: Same issue — satellite assemblies from runtime packs and NuGet packages are copied out even when they are not needed.

In all three cases, the only way I found to suppress the extra output is to manually set this variable, which does not seem to always work:

<SatelliteResourceLanguages>en</SatelliteResourceLanguages>

Expected behavior

  1. SatelliteResourceLanguages should not be required. The SDK should automatically suppress satellite assembly output for PublishAot and PublishSingleFile scenarios, since the resources are either embedded or unused.
  2. The default for PublishSingleFile and PublishAot should behave as if SatelliteResourceLanguages is set to the project's neutral language (typically en), so that no extra per-culture folders are produced in the publish output.
  3. Users who explicitly need satellite assemblies alongside the binary should be able to opt back in.

Impact

Disk space and artifact bloat

For a project like dotnetup (PublishAot=true) that references System.CommandLine, Microsoft.Deployment.DotNet.Releases, and Spectre.Console, the publish output contains 13 language directories × 3 .resources.dll files = 39 extra files and their parent directories. This bloats CI artifacts and wastes disk space, particularly in constrained environments like cross-build containers.

XML documentation files

A related concern: XML documentation files (.xml) from NuGet packages should also be excluded from publish output by default. These are developer-time assets, not runtime assets, and should not ship in a self-contained or AOT-published app.

Related

  • Fixing this would clean up the dotnetup executable publish output (Produce AOT, Runtime Specific, Cross Build Executables of dotnetup sdk#53143), which currently ships 39 unnecessary satellite assembly files per RID alongside the native AOT binary.
  • Example CI artifacts showing the problem: official-dnup-ci build #2925196 — each per-RID dotnetup-executable-* artifact contains cs/, de/, es/, fr/, it/, ja/, ko/, pl/, pt-BR/, ru/, tr/, zh-Hans/, zh-Hant/ folders with satellite .resources.dll files that should not be present alongside the native AOT binary. Binlogs are also included in the build artifacts for investigation.

Activity

  1. baronfel commented on Mar 12, 2026

    @baronfel
    Member

    I think this is slightly backwards - the property should still be honored because it influences which DLLs (including resource DLLs) get passed to the linker.

    However, the process that empties the publish dir and copies over the newly-linked native application may need to do additional work to clean up those now-redunfant dlls. I actually think I have a copilot PR either in flight or recently merged for this exact issue.

  2. agocke commented on Mar 12, 2026

    @agocke
    Member

    Right, the trick is that all of these assemblies must actually make it to the Native AOT/single file bundling steps, otherwise the information is lost.

    The actual bug is that the SDK still produces folders/files afterwards.

  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Mar 12, 2026
  4. baronfel commented on Apr 16, 2026

    @baronfel
    Member

    Looks like this was a dupe of #124191, which the runtime team fixed in #124192!

  5. nagilson commented on Apr 16, 2026

    @nagilson
    MemberAuthor

    Thanks for helping with hygiene - though I wonder if this fix worked? The latest .NET 11 SDK being used to build dotnetup is still producing these extra folders in the publish output.

    We even have binlogs so this might be helpful.

    https://dev.azure.com/dnceng/internal/_build/results?buildId=2952946&view=artifacts&pathAsName=false&type=publishedArtifacts

    Image
  6. baronfel commented on Apr 16, 2026

    @baronfel
    Member

    My main question here is if the SDK repo on this branch has flowed a recent enough runtime to actually get the fix

  7. nagilson commented on Apr 16, 2026

    @nagilson
    MemberAuthor

    true, I just merged in the sdk back into dnup the other day, so lemme check

  8. nagilson commented on Apr 16, 2026

    @nagilson
    MemberAuthor

    yes, this run was from your latest PR which was after the newest SDK in main was merged with release/dnup - don't have more time to investigate atm but maybe will do so later

  9. baronfel commented on Apr 16, 2026

    @baronfel
    Member

    Taking a look at one of those binlogs now - it does appear like the change from that PR is in the targets being used, so there could still be a bug for sure!

    EDIT: Looks like this change did remove the satellite resource from this project from set of ResolvedFilesToPublish, but the SDK itself still copies the resources from a project's references over. These other-project resource references don't seem to be inputs to to the ILC invocation, so they (currently) don't seem like they would be inputs into trimming. Need to talk to someone on the runtime to untangle this I think, but I think you were right to reopen this @nagilson.

  10. self-assigned this
    on Apr 17, 2026
  11. transferred this issue fromdotnet/sdkon Apr 17, 2026
  12. dotnet-policy-service commented on Apr 17, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @agocke, @elinor-fung, @VSadov
    See info in area-owners.md if you want to be subscribed.

  13. elinor-fung commented on Jul 1, 2026

    @elinor-fung
    Member

    @sbomer this should be addressed with #127089, right?

  14. sbomer commented on Jul 1, 2026

    @sbomer
    Member

    Yup, fixed by #127089, thanks!

  15. locked and limited conversation to collaborators on Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions