Skip to main content

Universal Binaries

Create macOS universal binaries (x86_64 + aarch64)

macOS universal binaries (also called "fat binaries") combine native code for both x86_64 (Intel) and aarch64 (Apple Silicon) into a single executable. A universal binary runs natively on both architectures without Rosetta translation, giving users a single download that works on any Mac.

Anodizer creates universal binaries by running Apple's lipo tool after both architecture-specific builds have completed.

Config

Add universal_binaries to a crate definition:

crates:
  - name: myapp
    targets:
      - x86_64-apple-darwin
      - aarch64-apple-darwin
    universal_binaries:
      - replace: true

Options

FieldTypeDefaultDescription
name_templatestringbinary nameTemplate for the output filename. Supports all standard template variables (ProjectName, Version, etc.).
replaceboolfalseWhen true, remove the individual per-architecture binaries from the artifact registry. Downstream stages (archives, release, publishers) will only see the universal binary.
idslist of stringsall binariesFilter which binaries to combine. Only artifacts whose binary name matches an entry in this list are considered. Useful when a crate produces multiple binaries and you only want universal builds for some of them.
hooksobjectnonePre/post hooks to run around universal binary creation. Uses the same pre/post hook format as build hooks.
mod_timestampstringnoneOverride the output file's modification timestamp for reproducible builds. Supports templates (e.g., {{ .CommitDate }}).

Examples

Basic usage

Produce a universal binary and keep the per-arch binaries as well:

crates:
  - name: myapp
    targets:
      - x86_64-apple-darwin
      - aarch64-apple-darwin
      - x86_64-unknown-linux-gnu
    universal_binaries:
      - {}

Replace per-arch binaries

Ship only the universal binary for macOS, removing the individual x86_64 and aarch64 artifacts from archives and releases:

crates:
  - name: myapp
    targets:
      - x86_64-apple-darwin
      - aarch64-apple-darwin
    universal_binaries:
      - replace: true

Custom output name

crates:
  - name: myapp
    targets:
      - x86_64-apple-darwin
      - aarch64-apple-darwin
    universal_binaries:
      - name_template: "{{ .ProjectName }}-{{ .Version }}-darwin-universal"
        replace: true

Filter by binary name

When a crate produces multiple binaries, combine only specific ones:

crates:
  - name: myapp
    targets:
      - x86_64-apple-darwin
      - aarch64-apple-darwin
    universal_binaries:
      - ids:
          - myapp
          - myapp-cli

Hooks

Run commands before and after the lipo step:

crates:
  - name: myapp
    targets:
      - x86_64-apple-darwin
      - aarch64-apple-darwin
    universal_binaries:
      - hooks:
          pre:
            - cmd: echo "Creating universal binary..."
          post:
            - cmd: codesign --sign "Developer ID" dist/myapp_darwin_all/myapp

How it works

  1. Anodizer builds both x86_64-apple-darwin and aarch64-apple-darwin targets as normal build artifacts.
  2. After all builds complete, anodizer iterates over each crate's universal_binaries entries.
  3. For each entry, it locates the matching aarch64-apple-darwin and x86_64-apple-darwin binary artifacts. If the ids filter is set, only binaries whose name appears in the list are considered.
  4. If both architecture binaries are found, anodizer runs lipo -create -output <output> <arm64> <x86_64>.
  5. The universal binary is placed in dist/<crate_name>_darwin_all/ and registered as an artifact with kind UniversalBinary and target darwin-universal.
  6. When replace: true, the individual per-architecture artifacts are removed from the registry so downstream stages (archives, checksums, release uploads, package publishers) only see the universal binary.

If either architecture binary is missing, anodizer logs a warning and skips universal binary creation for that crate rather than failing the build.

Limitations

  • macOS only. Universal binaries are an Apple concept. The lipo tool is required and is only available on macOS (ships with Xcode Command Line Tools).
  • Requires both architectures. Both x86_64-apple-darwin and aarch64-apple-darwin must be listed in targets and must build successfully. If either is missing, the universal binary step is skipped with a warning.
  • lipo must be on PATH. If lipo is not found and universal_binaries is configured, anodizer will fail with an error. Install Xcode Command Line Tools (xcode-select --install) to get lipo.
  • CI considerations. GitHub Actions macOS runners include lipo. If you cross-compile macOS targets on Linux, you will need to transfer the binaries to a macOS runner (or use a macOS cross-lipo tool) for the universal binary step.