Add NativeAOT aggregate executable shims#128553
Conversation
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/69bd1576-bcad-4ee1-aa5e-af9513609eea Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/69bd1576-bcad-4ee1-aa5e-af9513609eea Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/69bd1576-bcad-4ee1-aa5e-af9513609eea Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/69bd1576-bcad-4ee1-aa5e-af9513609eea Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/69bd1576-bcad-4ee1-aa5e-af9513609eea Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/69bd1576-bcad-4ee1-aa5e-af9513609eea Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
|
Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib |
Yeah, fat executables are a thing https://www.bing.com/search?q=fat+executables . I had to re-read the PR description a few times to figure out that this is completely unrelated. Some naming suggestions "aggregate executable", "publish as aggregate", "multi app executable". On Unix, it is not unusual to bundle multiple tools into a single binary. It is typically achieved by creating a single binary and linking the single binary to different file names. For example, https://busybox.net/ uses this technique. This PR is trying to provide equivalent characteristics by working around lack of first-class support for links in NuGet and other .NET tooling. Does this sound accurate? We have similar asks for non-NativeAOT too #53834 . I am wondering whether we should think about unified UX for this that can work for different form factors. |
Yup, I believe this is called a "polymorphic executable" in Unix-land. It does seem like a useful pattern. The symlinks are very nice because you don't even pay a few KB per exe, it's more like a few hundred bytes. And when you're busybox with 100+ binaries, the difference is meaningful |
|
One note, since I hit this when trying to support symlinks for the muxer on windows: the windows dynamic loader always resolves from the link directory. So a hard link/symlink solution for Windows would be hard to support if the app has dynamic dependencies. |
|
Another reason why symlinks are a poor choice on Windows is that on Windows we have |
|
I agree that executable links/symlinks do not work well on Windows and in .NET in general. I am ok with building it without links/symlinks. I think this needs:
|
I did not think about this as a publicly documented feature. We have immediate need for this in .NET 11 since the goals to shrink the SDK download and making the SDK more responsive are at odds with each other. For native AOT, extending the "publish a native library" concept works naturally since the native library is the thing we're going to publish. It just adds the exports on top of whatever exports the user already has, and creates the shims. Feature switches, optimization settings, etc. will be set at this level. The experience doesn't exactly match the PublishTrimmed and PublishAot promises (the native library is not runnable without a publish and the settings the library is published with may not match the settings the individual exe projects have when the user debugs them). I think we'd need a new .bundeproj project type or something like that with first-class SDK support to have something that generalizes to non-publish AOT experiences (and makes the experience consistent for native AOT too - e.g. forces the individual EXEs to have the same feature switch settings). I'll make copilot gate this behind EnablePreviewFeatures property. Then we're free to break it in a couple years from now when we add .bundleproj. |
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f0f54e39-2f6a-48af-88ed-f5724228b103 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f0f54e39-2f6a-48af-88ed-f5724228b103 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f0f54e39-2f6a-48af-88ed-f5724228b103 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
|
@copilot the test still doesn't work on non-Windows: Looks like we're embedding the full file path to the .so library on the build machine (that doesn't exist on the test machine but the library is next to the executable by convention). |
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ff586a1f-b533-4271-a092-8a030ea016ec Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
|
On Unix, even shim executable isn't necessary. If we want an extra-diet mode, we can set to enable $ ./libc.so.6 --help
GNU C Library (Ubuntu GLIBC 2.43-2ubuntu2) stable release version 2.43.
Copyright (C) 2026 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 15.2.0.
libc ABIs: UNIQUE ABSOLUTE
Minimum supported kernel: 3.7.0
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>. |
You mean to save one shim executable? I'm not sure if it's worth complicating things to save ~5 kB. |
|
@copilot still doesn't work on non-Windows. Should something |
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/5b3bca38-fa3b-46a4-840c-50b496db204a Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>

Enable NativeAOT shared library projects to aggregate multiple executable entry points into one native library and emit small per-app shim executables.
This addresses scenarios where shipping multiple NativeAOT executables as a bundle would otherwise duplicate megabytes of code and data across each executable. A shared NativeAOT library can now mark executable
ProjectReferenceitems withIncludeInAggregateExecutable="true". ILC exports a distinct startup entry point for each referenced executable, and MSBuild generates platform-specific shims that dispatch to the appropriate exported entry point.The aggregate executable mode is inferred automatically when
NativeLib=Sharedand one or more project references are marked withIncludeInAggregateExecutable="true".Compiler plumbing
--aggregateexe:assemblyName=MainEntryPointNamehandling in ILC.Build integration
Regression coverage
Example project shape: