StuDev.AotAnywhere 1.0.5

dotnet add package StuDev.AotAnywhere --version 1.0.5
                    
NuGet\Install-Package StuDev.AotAnywhere -Version 1.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="StuDev.AotAnywhere" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StuDev.AotAnywhere" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="StuDev.AotAnywhere" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add StuDev.AotAnywhere --version 1.0.5
                    
#r "nuget: StuDev.AotAnywhere, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package StuDev.AotAnywhere@1.0.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=StuDev.AotAnywhere&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=StuDev.AotAnywhere&version=1.0.5
                    
Install as a Cake Tool

AotAnywhere

Cross-compile Native AOT .NET apps to Linux, macOS and Windows — from any of them.

PublishAot normally refuses to build for another OS:

$ dotnet publish -r linux-x64
Microsoft.NETCore.Native.Publish.targets(59,5): error : Cross-OS native compilation is not supported.

AotAnywhere is a NuGet package that lifts that restriction. Add it to your project and dotnet publish -r <rid> just works for Linux, macOS and Windows RIDs, from a Windows, macOS or Linux machine. It uses Zig as the linker and sysroot, and brings everything it needs with it — no extra SDKs, cross toolchains or system packages to install on the build machine.

<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="docs/assets/platform-matrix-dark.svg"> <img alt="Diagram: builds on Windows (x64, x86), macOS (arm64, x64) and Linux (x64, arm64) hosts, through AotAnywhere (dotnet publish -r <rid>), and runs on Linux glibc and musl (x64, arm64, arm), macOS (osx-arm64, osx-x64) and Windows (win-x64, win-arm64). Every combination is CI-tested: 5 hosts × 10 target RIDs." src="docs/assets/platform-matrix-light.svg" width="880"> </picture> </p>

Quick start

  1. In a project that already uses Native AOT, add an Sdk reference to StuDev.AotAnywhere inside the <Project> element:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <Sdk Name="StuDev.AotAnywhere" Version="1.0.5" />
    

    (Or omit the Version and pin it once in global.json under msbuild-sdks.)

  2. Publish for one of the newly available RIDs:

    dotnet publish -r linux-x64        # or linux-arm64, linux-arm*
    dotnet publish -r linux-musl-x64   # or linux-musl-arm64, linux-musl-arm*
    dotnet publish -r osx-x64          # or osx-arm64
    dotnet publish -r win-x64          # or win-arm64
    

That's it — no other tools required, including symbol stripping (the package handles that itself, no LLVM install needed).

Why an Sdk reference and not a PackageReference? The package pulls in its Zig linker toolchain through a second, host-specific NuGet package, and NuGet cannot restore package references declared inside a package's build targets (NuGet/Home#4790). SDK props are evaluated during restore, so the Sdk form fetches everything the first time with no further setup. A plain PackageReference still works if you restore Zig yourself — see Advanced configuration.

Supported platforms

Host machines (where you run dotnet publish):

  • Windows (x86, x64)
  • macOS (x64, arm64)
  • Linux (x64, arm64)

Targets (what you can publish for), from any supported host:

Target RIDs Notes
Linux linux-x64, linux-arm64, linux-arm, linux-musl-x64, linux-musl-arm64, linux-musl-arm glibc and musl. The arm/ARMv7 RIDs require a net9.0+ target framework.
macOS osx-x64, osx-arm64 Links against bundled Apple linker stubs. See macOS targets.
Windows win-x64, win-arm64 From Linux/macOS hosts, via zig's MinGW-w64 import libraries. On a Windows host the SDK links these natively with MSVC. See Windows targets.

Things to be aware of

  • ARMv7 needs .NET 9+. The linux-arm and linux-musl-arm targets require a net9.0 or later target framework — .NET only ships ILCompiler runtime packs for them from .NET 9 onwards.

  • macOS binaries need signing before you distribute them. Out of the box the output only runs locally (osx-arm64 gets an ad-hoc signature, osx-x64 is unsigned). To hand it to other people you must sign it with a Developer ID certificate and notarize it — both doable from any host, no Mac required. See Signing and notarizing.

  • Windows output skips some MSVC hardening. Control Flow Guard and CET markers (/CETCOMPAT) are not carried over. For maximum-hardening release builds, link on Windows with MSVC. Details in Windows targets.

  • Windows on ARM64 is not supported as a host. zig 0.16's aarch64-windows code generation is broken, so Zig can't reliably run there. It will be revisited when a newer Zig fixes it. (win-arm64 as a target is fully supported.)

  • Linux ICU dependency. A cross-compiled binary may need the ICU library on the target machine, the same as any globalization-enabled .NET app. See Runtime dependencies on Linux below.

Runtime dependencies on target Linux systems

When running the cross-compiled binaries on Linux, you may hit a missing ICU library:

Process terminated. Couldn't find a valid ICU package installed on the system. Please install libicu (or icu-libs) using your package manager and try again.

Solution 1 (recommended): install ICU on the target system:

# Ubuntu/Debian
sudo apt-get install libicu-dev

# CentOS/RHEL/Fedora
sudo yum install libicu-devel   # or: sudo dnf install libicu-devel

# Alpine Linux
sudo apk add icu-libs

Solution 2: build with invariant globalization (no ICU dependency):

dotnet publish -r linux-x64 /p:InvariantGlobalization=true

Note that invariant globalization disables culture-specific formatting, sorting, and other globalization features.

Packing .NET tools for every platform (.NET 11+)

Since .NET 10, a .NET tool can ship native AOT binaries as one small "pointer" package plus one RID-specific package per platform — dotnet tool install downloads just the binary matching the user's machine. A single dotnet pack builds all of them, but the base SDK only packs the RIDs the host's native toolchain can build (e.g. a linux-x64 machine packs only linux-x64).

With AotAnywhere referenced, that limit goes away: dotnet pack on one machine produces packages for every RID listed in ToolPackageRuntimeIdentifiers (or RuntimeIdentifiers), via the SDK's tool-packaging extensibility point (dotnet/sdk#55250).

<PropertyGroup>
  <PackAsTool>true</PackAsTool>
  <PublishAot>true</PublishAot>
  <ToolPackageRuntimeIdentifiers>linux-x64;linux-arm64;linux-musl-x64;linux-musl-arm64;osx-x64;osx-arm64;win-x64;win-arm64</ToolPackageRuntimeIdentifiers>
</PropertyGroup>
dotnet pack   # one host, one command, a package per RID

Things to know:

  • Requires a .NET 11 SDK with the extensibility point; on older SDKs the hook is inert and the SDK's own host-capability rules apply.
  • Every requested RID is attempted, unfiltered — a RID this package can't build fails its inner publish with a clear error rather than being silently dropped from the set of produced packages.
  • Set AotAnywhereMultiRidToolPackaging=false to opt out and restore the SDK's default host-capability selection.

Compressing the output with UPX

The published binary can optionally be compressed with UPX (the same idea as PublishAotCompressed), typically halving its on-disk size. UPX produces a self-extracting executable that unpacks itself in memory at launch; the startup cost is usually not observable.

Install UPX on the build machine (from the releases page, or apt install upx-ucl / brew install upx / winget install upx), then:

dotnet publish -r linux-x64 /p:AotAnywhereUpxCompress=true

Knobs:

  • AotAnywhereUpxPath — path to the UPX executable if it is not on PATH.
  • AotAnywhereUpxLzma=true — LZMA compression: smaller output, slower startup.
  • AotAnywhereUpxArgs — extra arguments appended to the UPX command line (the default is --best).

Things to know:

  • Works for Linux and Windows x64/x86 targets. UPX cannot pack macOS binaries (its Mach-O support was removed; macOS 13+ refuses to run packed binaries) or win-arm64 (no ARM64 PE support) — publishing those with the option set fails with an error rather than producing broken output.
  • Windows antivirus false positives. UPX-packed executables are a common malware wrapper, so some scanners flag them. Weigh that before shipping compressed Windows binaries.
  • Executables only — native libraries (NativeLib) cannot be packed.

Documentation

Credits

AotAnywhere began as a fork of Michal Strehovsky's PublishAotCross, which first demonstrated that Zig could stand in as the linker and sysroot to make Native AOT cross-compilation work. It has since been substantially rewritten — the native shim is gone, links run directly through zig cc and managed MSBuild tasks, and it ships bundled Apple sysroot stubs, symbol stripping, and a full host × target CI matrix — but the original insight and inspiration are Michal's. Thank you.

License

MIT — see LICENSE.TXT.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.5 84 8/19/2026
1.0.4 595 7/11/2026
1.0.3 110 7/10/2026
1.0.2 116 7/10/2026
1.0.1 111 7/9/2026
1.0.0 176 7/6/2026
1.0.0-preview.2 121 7/6/2026
1.0.0-preview.1 109 7/6/2026