|
| 1 | +#!/usr/bin/env node |
| 2 | +/** |
| 3 | + * Lipo the extra cargo bin target(s) into the universal-apple-darwin build |
| 4 | + * so the Tauri bundler can copy them into the app bundle. |
| 5 | + * |
| 6 | + * Why this exists: |
| 7 | + * `tauri build --target universal-apple-darwin` compiles the crate for |
| 8 | + * both aarch64 and x86_64, then `lipo`-combines the two slices into one |
| 9 | + * fat binary — but the Tauri CLI (2.x) does this only for the *main* app |
| 10 | + * binary (`switchhosts`). Any additional cargo bin target — here |
| 11 | + * `swh_helper`, the privileged hosts-writing helper from |
| 12 | + * `src-tauri/src/bin/swh_helper.rs` — is left un-merged. It exists at |
| 13 | + * target/{aarch64,x86_64}-apple-darwin/release/swh_helper |
| 14 | + * but NOT at |
| 15 | + * target/universal-apple-darwin/release/swh_helper |
| 16 | + * so the bundler, which iterates over *all* bin targets and copies each |
| 17 | + * from the universal dir into `Contents/MacOS/`, fails with |
| 18 | + * failed to bundle project Failed to copy binary ... swh_helper ... |
| 19 | + * does not exist |
| 20 | + * The single-arch macOS jobs are unaffected — cargo emits swh_helper |
| 21 | + * straight into their per-arch release dir. |
| 22 | + * |
| 23 | + * Fix: wired up as tauri.conf.json `beforeBundleCommand`, which fires |
| 24 | + * AFTER the compile + main-binary lipo and BEFORE the bundler copies |
| 25 | + * binaries. We lipo each extra bin into the universal dir ourselves. |
| 26 | + * |
| 27 | + * Cross-platform & safe on every other build: `beforeBundleCommand` also |
| 28 | + * runs on the Windows and Linux release jobs (where `node` is present |
| 29 | + * but `lipo` and the universal dir are not) and on single-arch macOS |
| 30 | + * builds. It no-ops unless we are on macOS AND the universal target dir |
| 31 | + * exists, i.e. an actual universal build. |
| 32 | + */ |
| 33 | + |
| 34 | +import { existsSync, chmodSync } from 'fs' |
| 35 | +import { dirname, join } from 'path' |
| 36 | +import { fileURLToPath } from 'url' |
| 37 | +import { spawnSync } from 'child_process' |
| 38 | + |
| 39 | +const __dirname = dirname(fileURLToPath(import.meta.url)) |
| 40 | +const root = join(__dirname, '..') |
| 41 | + |
| 42 | +// Windows / Linux release jobs run this hook too — nothing to merge there. |
| 43 | +if (process.platform !== 'darwin') { |
| 44 | + process.exit(0) |
| 45 | +} |
| 46 | + |
| 47 | +const targetRoot = join(root, 'src-tauri', 'target') |
| 48 | +const universalDir = join(targetRoot, 'universal-apple-darwin', 'release') |
| 49 | + |
| 50 | +// Single-arch macOS builds (--target {aarch64,x86_64}-apple-darwin) never |
| 51 | +// create this dir; only a universal build does, and by the time this hook |
| 52 | +// fires the main binary has already been lipo'd into it. No dir → no-op. |
| 53 | +if (!existsSync(universalDir)) { |
| 54 | + process.exit(0) |
| 55 | +} |
| 56 | + |
| 57 | +// Extra cargo bin targets (besides the main `switchhosts` app binary) that |
| 58 | +// Tauri's universal lipo step skips. Keep in sync with src-tauri/src/bin/. |
| 59 | +const EXTRA_BINS = ['swh_helper'] |
| 60 | +const ARCHES = ['aarch64-apple-darwin', 'x86_64-apple-darwin'] |
| 61 | + |
| 62 | +for (const bin of EXTRA_BINS) { |
| 63 | + const out = join(universalDir, bin) |
| 64 | + |
| 65 | + // Idempotent: a re-run — or a future Tauri that learns to lipo extra |
| 66 | + // bins itself — leaves an already-fat binary untouched. |
| 67 | + if (existsSync(out)) { |
| 68 | + console.log(`[universal-helper] ${bin} already present in universal dir`) |
| 69 | + continue |
| 70 | + } |
| 71 | + |
| 72 | + const inputs = ARCHES.map((arch) => join(targetRoot, arch, 'release', bin)) |
| 73 | + const missing = inputs.filter((p) => !existsSync(p)) |
| 74 | + if (missing.length > 0) { |
| 75 | + // The per-arch builds must have produced this bin. If they didn't, |
| 76 | + // bundling would fail downstream with the same "does not exist"; fail |
| 77 | + // loudly here with a clearer message instead of shipping a broken app. |
| 78 | + console.error( |
| 79 | + `[universal-helper] cannot lipo ${bin}: missing per-arch build(s):\n ` + |
| 80 | + missing.join('\n '), |
| 81 | + ) |
| 82 | + process.exit(1) |
| 83 | + } |
| 84 | + |
| 85 | + console.log(`[universal-helper] lipo ${bin} → ${out}`) |
| 86 | + const res = spawnSync('lipo', ['-create', '-output', out, ...inputs], { |
| 87 | + stdio: 'inherit', |
| 88 | + }) |
| 89 | + if (res.error || res.status !== 0) { |
| 90 | + console.error( |
| 91 | + `[universal-helper] lipo failed for ${bin}` + |
| 92 | + (res.error ? `: ${res.error.message}` : ` (exit ${res.status})`), |
| 93 | + ) |
| 94 | + process.exit(res.status || 1) |
| 95 | + } |
| 96 | + |
| 97 | + // lipo's output may not carry the executable bit; the bundler copies and |
| 98 | + // code-signs this as Contents/MacOS/swh_helper, which must be runnable. |
| 99 | + chmodSync(out, 0o755) |
| 100 | +} |
0 commit comments