Skip to content

Commit 9b871fa

Browse files
jguiceclaude
andcommitted
feat(macos): add Intel (x86_64) build with separate DMG
Pyke ort 2.0.0-rc.12 doesn't ship prebuilt ONNX Runtime binaries for x86_64-apple-darwin (their prebuilts cover arm64-darwin, linux, and windows only). To produce an Intel utter binary, we build ONNX Runtime v1.24.2 from source on a native Intel runner (macos-14-large), then point ort-sys at it via ORT_LIB_PATH so the multi-file static link path takes over. The pin matches what pyke validates against in their custom-static-link.yml. Silicon path is bit-for-bit unchanged: same macos-14 runner, same prebuilt-onnxruntime download, same DMG. The new Intel job runs in parallel and produces a separate utter-VERSION-macos-x86_64.dmg. The release job's existing dist/utter-*-macos-*.dmg glob picks up both. The from-source ONNX Runtime build is the slow step (~30 min cold). Cached by ONNXRUNTIME_VERSION so it only re-runs when we bump the pin. --skip_tests trims ~30% off the cold build time since we only need the .a libs for static linking, not the test executables. Validated end-to-end on a local Intel Mac: built, signed, launched, permissions granted, model downloaded, PTT transcription works. The CI cross-runner equivalence is on macos-14-large (slow but native — see actions/runner-images#12545). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c0da115 commit 9b871fa

2 files changed

Lines changed: 174 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,178 @@ jobs:
291291
path: dist/utter-*-macos-arm64.dmg
292292
if-no-files-found: error
293293

294+
macos-intel-build:
295+
name: Build macOS x86_64 (Intel, signed + notarized DMG)
296+
needs: test
297+
# macos-14-large is a native Intel (x86_64) runner. Mirrors the silicon job's
298+
# macOS version so eventual deprecation migrations stay coupled. Native is slower
299+
# than cross-compiling from arm64 (per actions/runner-images#12545) but avoids
300+
# the unknowns of cross-compiling ONNX Runtime.
301+
runs-on: macos-14-large
302+
env:
303+
APPLE_ID: ${{ secrets.APPLE_ID }}
304+
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
305+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
306+
UTTER_SIGN_IDENTITY: "Developer ID Application: Josh Guice (YKQ46WD7SL)"
307+
RUST_TARGET: x86_64-apple-darwin
308+
# Pyke ort 2.0.0-rc.12 doesn't ship prebuilt ONNX Runtime for x86_64-apple-darwin
309+
# (their prebuilts are arm64 + linux + windows). We build ONNX Runtime from source
310+
# and point ort-sys at the result via ORT_LIB_PATH. The pin must match what pyke
311+
# validates against in pykeio/ort .github/workflows/custom-static-link.yml.
312+
ONNXRUNTIME_VERSION: v1.24.2
313+
314+
steps:
315+
- name: Resolve checkout ref
316+
id: resolve_ref
317+
env:
318+
DISPATCH_TAG: ${{ inputs.tag }}
319+
EVENT_REF: ${{ github.ref }}
320+
run: |
321+
if [ -n "$DISPATCH_TAG" ]; then
322+
printf 'ref=%s\n' "$DISPATCH_TAG" >> "$GITHUB_OUTPUT"
323+
else
324+
printf 'ref=%s\n' "$EVENT_REF" >> "$GITHUB_OUTPUT"
325+
fi
326+
327+
- name: Checkout
328+
uses: actions/checkout@v4
329+
with:
330+
ref: ${{ steps.resolve_ref.outputs.ref }}
331+
332+
- name: Install Rust toolchain
333+
uses: dtolnay/rust-toolchain@stable
334+
with:
335+
targets: ${{ env.RUST_TARGET }}
336+
337+
- name: Cache cargo
338+
uses: Swatinem/rust-cache@v2
339+
with:
340+
shared-key: release-macos-x86_64
341+
342+
# The from-source ONNX Runtime build is the slow step (~30 min cold). Cache it
343+
# by version so the heavy work only runs when ONNXRUNTIME_VERSION changes.
344+
- name: Cache ONNX Runtime build
345+
id: cache-onnxruntime
346+
uses: actions/cache@v4
347+
with:
348+
path: onnxruntime/build/MacOS/Release
349+
key: onnxruntime-${{ env.ONNXRUNTIME_VERSION }}-x86_64-apple-darwin-v1
350+
351+
- name: Build ONNX Runtime from source (x86_64)
352+
if: steps.cache-onnxruntime.outputs.cache-hit != 'true'
353+
env:
354+
VERSION: ${{ env.ONNXRUNTIME_VERSION }}
355+
run: |
356+
set -euo pipefail
357+
git clone https://github.com/microsoft/onnxruntime --recursive \
358+
--branch "$VERSION" --single-branch --depth 1
359+
cd onnxruntime
360+
# --skip_tests trims ~30% off the build by not compiling test executables
361+
# (we only need the .a libs for ort-sys's static linking). Native build
362+
# on this Intel runner — no cross-compile flags needed.
363+
./build.sh --update --build --config Release --parallel \
364+
--compile_no_warning_as_error --skip_submodule_sync --skip_tests
365+
366+
- name: Import Developer ID cert into temp keychain
367+
env:
368+
CERT_B64: ${{ secrets.APPLE_DEV_ID_CERT_BASE64 }}
369+
CERT_PASS: ${{ secrets.APPLE_DEV_ID_CERT_PASSWORD }}
370+
run: |
371+
set -euo pipefail
372+
KEYCHAIN_PASS=$(uuidgen)
373+
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
374+
security create-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN_PATH"
375+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
376+
security unlock-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN_PATH"
377+
printf '%s' "$CERT_B64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
378+
security import "$RUNNER_TEMP/cert.p12" \
379+
-k "$KEYCHAIN_PATH" \
380+
-P "$CERT_PASS" \
381+
-T /usr/bin/codesign -T /usr/bin/security
382+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"//g')
383+
security set-key-partition-list \
384+
-S apple-tool:,apple:,codesign: \
385+
-s -k "$KEYCHAIN_PASS" "$KEYCHAIN_PATH"
386+
rm -f "$RUNNER_TEMP/cert.p12"
387+
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
388+
389+
- name: Build release binary (x86_64) against from-source ONNX Runtime
390+
env:
391+
ORT_LIB_PATH: ${{ github.workspace }}/onnxruntime/build/MacOS/Release
392+
run: cargo build --release --locked --target "$RUST_TARGET"
393+
394+
- name: Stage binary for bundle
395+
run: |
396+
mkdir -p target/release
397+
cp "target/${RUST_TARGET}/release/utter" target/release/utter
398+
399+
- name: Build + sign .app bundle
400+
run: ./scripts/make-bundle.sh
401+
402+
- name: Notarize + staple app bundle
403+
run: |
404+
set -euo pipefail
405+
ditto -c -k --keepParent target/release/utter.app "$RUNNER_TEMP/utter.zip"
406+
xcrun notarytool submit "$RUNNER_TEMP/utter.zip" \
407+
--apple-id "$APPLE_ID" \
408+
--password "$APPLE_APP_PASSWORD" \
409+
--team-id "$APPLE_TEAM_ID" \
410+
--wait
411+
xcrun stapler staple target/release/utter.app
412+
xcrun stapler validate target/release/utter.app
413+
414+
- name: Install create-dmg
415+
run: brew install create-dmg
416+
417+
- name: Create DMG
418+
env:
419+
RAW_VERSION: ${{ github.ref_name }}
420+
DISPATCH_TAG: ${{ inputs.tag }}
421+
run: |
422+
set -euo pipefail
423+
TAG="${DISPATCH_TAG:-$RAW_VERSION}"
424+
VERSION="${TAG#v}"
425+
DMG_NAME="utter-${VERSION}-macos-x86_64.dmg"
426+
mkdir -p dist
427+
STAGE="$RUNNER_TEMP/dmg-stage"
428+
rm -rf "$STAGE"
429+
mkdir -p "$STAGE"
430+
cp -R target/release/utter.app "$STAGE/"
431+
create-dmg \
432+
--volname "utter" \
433+
--window-pos 200 120 \
434+
--window-size 540 380 \
435+
--icon-size 100 \
436+
--icon "utter.app" 140 190 \
437+
--app-drop-link 400 190 \
438+
--hide-extension "utter.app" \
439+
--no-internet-enable \
440+
"dist/${DMG_NAME}" \
441+
"$STAGE"
442+
443+
- name: Sign + notarize + staple DMG
444+
run: |
445+
set -euo pipefail
446+
DMG=$(ls dist/utter-*-macos-x86_64.dmg)
447+
codesign --force --sign "$UTTER_SIGN_IDENTITY" --timestamp "$DMG"
448+
xcrun notarytool submit "$DMG" \
449+
--apple-id "$APPLE_ID" \
450+
--password "$APPLE_APP_PASSWORD" \
451+
--team-id "$APPLE_TEAM_ID" \
452+
--wait
453+
xcrun stapler staple "$DMG"
454+
xcrun stapler validate "$DMG"
455+
456+
- name: Upload DMG artifact
457+
uses: actions/upload-artifact@v4
458+
with:
459+
name: dist-macos-x86_64
460+
path: dist/utter-*-macos-x86_64.dmg
461+
if-no-files-found: error
462+
294463
release:
295464
name: Publish release
296-
needs: [build, macos-build]
465+
needs: [build, macos-build, macos-intel-build]
297466
runs-on: ubuntu-24.04
298467
steps:
299468
- name: Download build artifacts

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Local, no-cloud push-to-talk dictation for **macOS and Linux**. Hold a key, spea
1919

2020
**Change the PTT key:** open Terminal, run `/Applications/utter.app/Contents/MacOS/utter set-key`, press and hold the key you want, release. Pick **Quit utter** from the menu-bar icon and relaunch the app to apply.
2121

22+
### macOS (Intel)
23+
24+
Same flow as Apple Silicon, but download `utter-VERSION-macos-x86_64.dmg` instead of the arm64 build. The Intel build is functionally identical — same on-device transcription, same permission flow, same PTT behavior. Requires macOS 13 or later.
25+
2226
### Linux (Fedora, RHEL, Rocky, Debian, Ubuntu — `x86_64` / `aarch64`)
2327

2428
```bash

0 commit comments

Comments
 (0)