feat: Add macOS Universal Binary (Intel + Apple Silicon) build target #164

Closed
opened 2026-08-31 23:52:13 +01:00 by gronod · 0 comments
Owner

Overview & Objective

Add a macOS Universal Binary (universal-apple-darwin) build target combining x86_64 (Intel) and arm64 (Apple Silicon) architectures into a single fat Mach-O executable and .dmg package, while retaining dedicated single-architecture builds (x86_64 and arm64).


Upstream Dependency

Important

Blocked by Upstream: Depends on gronod/argyllcms#27 (macOS Universal Binary release archive).

ICCery's Universal build will directly vendor and bundle the universal build of ArgyllCMS (Argyll_V<version>_macOS_universal_bin.tgz), ensuring all bundled sidecar utilities (instlist, targen, printtarg, chartread, average, colprof, profcheck, iccgamut) are themselves universal fat Mach-O binaries.


Current Architecture & Limitations

  1. CI Build Matrix: Workflows currently build two separate packages:
    • Intel: target: x86_64-apple-darwin on macos-15-intel runner
    • Apple Silicon: target: aarch64-apple-darwin on macos-15 runner
  2. ArgyllCMS Sidecar Bundling:
    • scripts/fetch-argyll.mjs downloads and stages single-architecture binaries into src-tauri/argyll/<platform> (macos-x86_64 or macos-aarch64).
    • resolve_binary in src-tauri/src/commands.rs resolves binaries using the platform directory.
    • For the universal build, vendoring the universal ArgyllCMS release archive into src-tauri/argyll/macos-universal/ ensures native execution across all Macs with identical paths.

Required Changes

1. Build Script & Sidecar Preparation (scripts/fetch-argyll.mjs)

  • Add support for macos-universal platform in PLATFORMS:
    'macos-universal': {
      assetSuffix: '_macOS_universal_bin.tgz',
      markerBinary: 'instlist',
      destDir: path.join(TAURI_ARGYLL_DIR, 'macos-universal'),
      isWindows: false,
    },
    
  • When --platform=macos-universal is supplied, download and extract Argyll_V<version>_macOS_universal_bin.tgz from gronod/argyllcms releases into src-tauri/argyll/macos-universal/.
  • Ensure executable permissions (chmod 0755) are verified for all staged binaries.

2. Tauri Configuration & Binary Resolution

  • Update resolve_binary() in src-tauri/src/commands.rs:
    let platform = match (std::env::consts::OS, std::env::consts::ARCH) {
        ("linux", "x86_64") => "linux-x86_64",
        ("windows", "x86_64") => "windows-x86_64",
        ("macos", "aarch64") => {
            // Check for universal binary resource first, then fallback to arch-specific
            if app.path().resolve("argyll/macos-universal/instlist", tauri::path::BaseDirectory::Resource).map(|p| p.exists()).unwrap_or(false) {
                "macos-universal"
            } else {
                "macos-aarch64"
            }
        },
        ("macos", "x86_64") => {
            if app.path().resolve("argyll/macos-universal/instlist", tauri::path::BaseDirectory::Resource).map(|p| p.exists()).unwrap_or(false) {
                "macos-universal"
            } else {
                "macos-x86_64"
            }
        },
        _ => "linux-x86_64",
    };
    
  • Tauri v2 natively merges x86_64 and aarch64 into a single universal bundle when invoking:
    npm run tauri build -- --target universal-apple-darwin
    
  • Ensure tauri.conf.json resource globs ("argyll/**/*") bundle argyll/macos-universal/.

3. CI Workflow Updates (build-macos.yml)

  • Update the build matrix to include the universal target on macos-15 runner:
    matrix:
      platform:
        - name: Intel
          os: macos-15-intel
          target: x86_64-apple-darwin
          binary_dir: macos-x86_64
          arch: x86_64
        - name: Apple Silicon
          os: macos-15
          target: aarch64-apple-darwin
          binary_dir: macos-aarch64
          arch: arm64
        - name: Universal (Intel + Apple Silicon)
          os: macos-15
          target: universal-apple-darwin
          binary_dir: macos-universal
          arch: universal
    
  • Rust Toolchain Setup: When building universal-apple-darwin, install both target architectures on the runner:
    rustup target add x86_64-apple-darwin aarch64-apple-darwin
    
  • Argyll Fetch Step: For universal-apple-darwin, invoke npm run fetch-argyll -- --platform macos-universal.
  • Tauri Build Step: Execute npm run tauri build -- --target ${{ matrix.platform.target }}.
  • Packaging & Artifact Staging:
    • Stage ICCery_${TAG}-${SHORT_SHA}-macos-universal.dmg and ICCery_${TAG}-${SHORT_SHA}-macos-universal.zip.
    • Upload to release assets.

Verification & Acceptance Criteria

  1. Binary Architecture Confirmation:
    • Running file on both the main application executable and all bundled ArgyllCMS sidecar binaries confirms fat universal binaries:
      Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]
  2. Native Execution:
    • App and subprocesses execute natively without Rosetta 2 on Apple Silicon (M1/M2/M3/M4) and on Intel x86_64 Macs.
  3. Release Assets:
    • Release assets contain ICCery_<ver>-macos-universal.dmg and .zip alongside x86_64 and arm64 assets.
### Overview & Objective Add a macOS **Universal Binary** (`universal-apple-darwin`) build target combining `x86_64` (Intel) and `arm64` (Apple Silicon) architectures into a single fat Mach-O executable and `.dmg` package, while retaining dedicated single-architecture builds (`x86_64` and `arm64`). --- ### Upstream Dependency > [!IMPORTANT] > **Blocked by Upstream**: Depends on **[gronod/argyllcms#27](https://git.i3omb.com/gronod/argyllcms/issues/27)** (macOS Universal Binary release archive). > > ICCery's Universal build will directly vendor and bundle the **universal build of ArgyllCMS** (`Argyll_V<version>_macOS_universal_bin.tgz`), ensuring all bundled sidecar utilities (`instlist`, `targen`, `printtarg`, `chartread`, `average`, `colprof`, `profcheck`, `iccgamut`) are themselves universal fat Mach-O binaries. --- ### Current Architecture & Limitations 1. **CI Build Matrix**: Workflows currently build two separate packages: - Intel: `target: x86_64-apple-darwin` on `macos-15-intel` runner - Apple Silicon: `target: aarch64-apple-darwin` on `macos-15` runner 2. **ArgyllCMS Sidecar Bundling**: - `scripts/fetch-argyll.mjs` downloads and stages single-architecture binaries into `src-tauri/argyll/<platform>` (`macos-x86_64` or `macos-aarch64`). - `resolve_binary` in `src-tauri/src/commands.rs` resolves binaries using the platform directory. - For the universal build, vendoring the universal ArgyllCMS release archive into `src-tauri/argyll/macos-universal/` ensures native execution across all Macs with identical paths. --- ### Required Changes #### 1. Build Script & Sidecar Preparation (`scripts/fetch-argyll.mjs`) - Add support for `macos-universal` platform in `PLATFORMS`: ```javascript 'macos-universal': { assetSuffix: '_macOS_universal_bin.tgz', markerBinary: 'instlist', destDir: path.join(TAURI_ARGYLL_DIR, 'macos-universal'), isWindows: false, }, ``` - When `--platform=macos-universal` is supplied, download and extract `Argyll_V<version>_macOS_universal_bin.tgz` from `gronod/argyllcms` releases into `src-tauri/argyll/macos-universal/`. - Ensure executable permissions (`chmod 0755`) are verified for all staged binaries. #### 2. Tauri Configuration & Binary Resolution - Update `resolve_binary()` in `src-tauri/src/commands.rs`: ```rust let platform = match (std::env::consts::OS, std::env::consts::ARCH) { ("linux", "x86_64") => "linux-x86_64", ("windows", "x86_64") => "windows-x86_64", ("macos", "aarch64") => { // Check for universal binary resource first, then fallback to arch-specific if app.path().resolve("argyll/macos-universal/instlist", tauri::path::BaseDirectory::Resource).map(|p| p.exists()).unwrap_or(false) { "macos-universal" } else { "macos-aarch64" } }, ("macos", "x86_64") => { if app.path().resolve("argyll/macos-universal/instlist", tauri::path::BaseDirectory::Resource).map(|p| p.exists()).unwrap_or(false) { "macos-universal" } else { "macos-x86_64" } }, _ => "linux-x86_64", }; ``` - Tauri v2 natively merges `x86_64` and `aarch64` into a single universal bundle when invoking: ```bash npm run tauri build -- --target universal-apple-darwin ``` - Ensure `tauri.conf.json` resource globs (`"argyll/**/*"`) bundle `argyll/macos-universal/`. #### 3. CI Workflow Updates (`build-macos.yml`) - Update the build matrix to include the universal target on `macos-15` runner: ```yaml matrix: platform: - name: Intel os: macos-15-intel target: x86_64-apple-darwin binary_dir: macos-x86_64 arch: x86_64 - name: Apple Silicon os: macos-15 target: aarch64-apple-darwin binary_dir: macos-aarch64 arch: arm64 - name: Universal (Intel + Apple Silicon) os: macos-15 target: universal-apple-darwin binary_dir: macos-universal arch: universal ``` - **Rust Toolchain Setup**: When building `universal-apple-darwin`, install both target architectures on the runner: ```bash rustup target add x86_64-apple-darwin aarch64-apple-darwin ``` - **Argyll Fetch Step**: For `universal-apple-darwin`, invoke `npm run fetch-argyll -- --platform macos-universal`. - **Tauri Build Step**: Execute `npm run tauri build -- --target ${{ matrix.platform.target }}`. - **Packaging & Artifact Staging**: - Stage `ICCery_${TAG}-${SHORT_SHA}-macos-universal.dmg` and `ICCery_${TAG}-${SHORT_SHA}-macos-universal.zip`. - Upload to release assets. --- ### Verification & Acceptance Criteria 1. **Binary Architecture Confirmation**: - Running `file` on both the main application executable and all bundled ArgyllCMS sidecar binaries confirms fat universal binaries: `Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]` 2. **Native Execution**: - App and subprocesses execute natively without Rosetta 2 on Apple Silicon (M1/M2/M3/M4) and on Intel x86_64 Macs. 3. **Release Assets**: - Release assets contain `ICCery_<ver>-macos-universal.dmg` and `.zip` alongside `x86_64` and `arm64` assets.
gronod added this to the Milestone 9: macOS Native Support & Enhanced Print Spooling (v0.4.0) milestone 2026-08-31 23:52:13 +01:00
gronod added the Kind/FeatureKind/Enhancement
Priority
Medium
3
labels 2026-08-31 23:52:13 +01:00
Sign in to join this conversation.