From b421a48a3327d78ed30b7b60b90e01c3aa2ce3ca Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:59:33 -0700 Subject: [PATCH] Add universal binaries section to macOS publishing docs (#55200) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- docs/core/deploying/macos.md | 54 ++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/docs/core/deploying/macos.md b/docs/core/deploying/macos.md index 190590ff20b42..63341ed526b45 100644 --- a/docs/core/deploying/macos.md +++ b/docs/core/deploying/macos.md @@ -1,9 +1,9 @@ --- title: Publish .NET apps for macOS -description: Learn how to publish .NET applications for macOS, including signing, notarization, and app entitlements. +description: Learn how to publish .NET applications for macOS, including signing, notarization, app entitlements, and universal binaries. author: agocke ms.author: angocke -ms.date: 10/22/2025 +ms.date: 07/31/2026 ms.topic: how-to ai-usage: ai-assisted --- @@ -45,3 +45,53 @@ The following entitlements enable additional debugging and diagnostic capabiliti > [!WARNING] > Failing to sign and notarize your app might result in the application crashing while executing a restricted operation. + +## Universal binaries + +macOS supports *universal binaries*: a single Mach-O file that contains machine code for more than one CPU architecture. macOS selects the slice that matches the current processor, so one file runs natively on both Apple silicon (arm64) and Intel (x64) machines. + +.NET doesn't produce universal binaries directly. Instead, you publish your app once per architecture and merge the two native executables with the Apple `lipo` tool. Two .NET publishing modes produce a self-contained native executable that you can merge this way: + +- [Single-file apps](single-file/overview.md) (`PublishSingleFile`) +- [Native AOT deployment](native-aot/index.md) (`PublishAot`) + +### Publish a single-file universal binary + +Publish the app once per architecture into separate directories: + +```bash +dotnet publish -c Release -r osx-arm64 --self-contained true \ + -p:PublishSingleFile=true -o out/osx-arm64 + +dotnet publish -c Release -r osx-x64 --self-contained true \ + -p:PublishSingleFile=true -o out/osx-x64 +``` + +Then merge the two executables and re-sign the result: + +```bash +mkdir -p out/universal +lipo -create -output out/universal/MyApp out/osx-arm64/MyApp out/osx-x64/MyApp +codesign --force --sign - out/universal/MyApp +``` + +A self-contained single-file publish embeds the runtime, the managed assemblies, and the native libraries in the executable itself, so the merged file is the complete app. The universal binary is roughly the sum of the two inputs. + +Copy any remaining files from one of the per-architecture output directories (for example, `appsettings.json` or files excluded from the bundle) next to the merged executable. + +### Publish a Native AOT universal binary + +The steps are the same as for single-file, but replace the single-file properties with `PublishAot`: + +```bash +dotnet publish -c Release -r osx-arm64 -p:PublishAot=true -o out/osx-arm64 +dotnet publish -c Release -r osx-x64 -p:PublishAot=true -o out/osx-x64 + +mkdir -p out/universal +lipo -create -output out/universal/MyApp out/osx-arm64/MyApp out/osx-x64/MyApp +codesign --force --sign - out/universal/MyApp +``` + +Native AOT produces much smaller output than single-file publishing, so the universal binary stays small as well. + +Native AOT also emits a `MyApp.dSYM` debug symbol bundle per architecture. To keep symbols for both slices, keep the per-architecture directories, or merge the `DWARF` binaries inside the bundles with `lipo`.