|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Buildkite step: build, sign, and notarize harper-desktop. |
| 3 | +# See .buildkite/pipeline.yml. |
| 4 | + |
| 5 | +set -euo pipefail |
| 6 | + |
| 7 | +echo "--- :rubygems: Install gems" |
| 8 | +install_gems |
| 9 | + |
| 10 | +echo "--- :rust: Install Rust toolchain" |
| 11 | +# A8C Mac VMs have no Rust tooling baked in. Install `rustup` non-interactively, |
| 12 | +# then add the targets we need. |
| 13 | +# `--no-modify-path` is intentional: we source `cargo/env` ourselves below so |
| 14 | +# nothing outside this build step picks up cargo on a shared agent. |
| 15 | +if ! command -v rustup >/dev/null 2>&1; then |
| 16 | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ |
| 17 | + | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal |
| 18 | +fi |
| 19 | +# shellcheck disable=SC1091 |
| 20 | +. "$HOME/.cargo/env" |
| 21 | + |
| 22 | +# Universal builds (master, tags) need both arches. PR/branch builds run |
| 23 | +# Apple-Silicon-only to skip the x86_64 half of the compile (~4m saved). |
| 24 | +if [ "${BUILDKITE_BRANCH:-}" = "master" ] || [ -n "${BUILDKITE_TAG:-}" ]; then |
| 25 | + TAURI_TARGET=universal-apple-darwin |
| 26 | + JUST_RECIPE=build-desktop-macos |
| 27 | + rustup target add aarch64-apple-darwin x86_64-apple-darwin wasm32-unknown-unknown |
| 28 | +else |
| 29 | + TAURI_TARGET=aarch64-apple-darwin |
| 30 | + JUST_RECIPE=build-desktop-macos-arm64 |
| 31 | + rustup target add aarch64-apple-darwin wasm32-unknown-unknown |
| 32 | +fi |
| 33 | +echo "Build target: $TAURI_TARGET (recipe: $JUST_RECIPE)" |
| 34 | + |
| 35 | +echo "--- :floppy_disk: Restore cargo caches" |
| 36 | +# Two cache layers, both keyed off Cargo.lock + rust-toolchain.toml so they |
| 37 | +# invalidate the moment any dep version moves. |
| 38 | +# 1. `~/.cargo/registry` + `~/.cargo/git/db` — downloaded crate sources / git |
| 39 | +# deps. ~340MB compressed. Saves the crate-download phase on cold builds. |
| 40 | +# 2. `target/` — compiled artifacts. Several GB compressed. Lets cargo's |
| 41 | +# incremental compilation reuse object files across builds. The target |
| 42 | +# cache also includes the Tauri target arch in its key, since universal |
| 43 | +# and arm64-only builds produce disjoint object trees. |
| 44 | +CARGO_DEPS_KEY="$BUILDKITE_PIPELINE_SLUG-cargo-deps-darwin-arm64-$(hash_file Cargo.lock)-$(hash_file rust-toolchain.toml)" |
| 45 | +CARGO_TARGET_KEY="$BUILDKITE_PIPELINE_SLUG-target-darwin-arm64-$TAURI_TARGET-$(hash_file Cargo.lock)-$(hash_file rust-toolchain.toml)" |
| 46 | +restore_cache "$CARGO_DEPS_KEY" |
| 47 | +restore_cache "$CARGO_TARGET_KEY" |
| 48 | + |
| 49 | +echo "--- :package: Install build tools" |
| 50 | +# Without Rust pre-baked there's no `cargo-binstall` either. Use cargo-binstall's |
| 51 | +# own curl installer to pull a prebuilt — about a second, vs ~1m15s of `cargo |
| 52 | +# install --locked` building it from source. |
| 53 | +if ! command -v cargo-binstall >/dev/null 2>&1; then |
| 54 | + curl -L --proto '=https' --tlsv1.2 -sSf \ |
| 55 | + https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh \ |
| 56 | + | bash |
| 57 | +fi |
| 58 | +cargo binstall --no-confirm --force just |
| 59 | +cargo binstall --no-confirm --force wasm-pack |
| 60 | +# Skip cargo-binstall'ing `tauri-cli` — Tauri doesn't publish GitHub release |
| 61 | +# prebuilts for the CLI, so binstall always falls back to a source compile |
| 62 | +# (~2 minutes). `@tauri-apps/cli` from npm is the same Rust binary distributed |
| 63 | +# via platform-specific subpackages — it's already a devDependency of |
| 64 | +# `harper-desktop/package.json`, so `pnpm install` in that directory will pick |
| 65 | +# it up. The justfile recipes invoke it as `pnpm tauri build` rather than |
| 66 | +# `cargo tauri build`. |
| 67 | + |
| 68 | +echo "--- :npm: Install pnpm" |
| 69 | +# Node is set up by the `automattic/nvm` Buildkite plugin from `.nvmrc`. |
| 70 | +# `pnpm` is not bundled — install it via `npm install -g`. Pin matches the |
| 71 | +# `packageManager` field in the root `package.json`. |
| 72 | +npm install -g pnpm@10.10.0 |
| 73 | +hash -r |
| 74 | + |
| 75 | +echo "--- :key: Fetch Developer ID certificate" |
| 76 | +bundle exec fastlane set_up_signing |
| 77 | + |
| 78 | +echo "--- :hammer: Build harper-desktop (signed)" |
| 79 | +# `just $JUST_RECIPE` runs `pnpm tauri build -b app,dmg --target $TAURI_TARGET` |
| 80 | +# in `harper-desktop/`. With `APPLE_SIGNING_IDENTITY` set, Tauri's bundler signs |
| 81 | +# the produced .app and .dmg. `TAURI_SIGNING_PRIVATE_KEY` enables the minisign |
| 82 | +# updater signing. |
| 83 | +export APPLE_SIGNING_IDENTITY="Developer ID Application: Automattic, Inc. (PZYM8XX95Q)" |
| 84 | +just "$JUST_RECIPE" |
| 85 | + |
| 86 | +echo "--- :floppy_disk: Save cargo caches" |
| 87 | +# `save_cache` is a no-op when the key already exists in S3, so this is cheap on |
| 88 | +# subsequent same-deps builds. Runs after the build (so what gets cached is |
| 89 | +# coherent) but before notarization (a flaky notary shouldn't lose us the |
| 90 | +# cache). The target/ save is the one to watch — several GB of upload on a cold |
| 91 | +# key. |
| 92 | +save_cache "$HOME/.cargo/registry" "$CARGO_DEPS_KEY" |
| 93 | +save_cache "$HOME/.cargo/git/db" "$CARGO_DEPS_KEY-git" || true |
| 94 | +save_cache target "$CARGO_TARGET_KEY" |
| 95 | + |
| 96 | +echo "--- :apple: Notarize and staple" |
| 97 | +# Tauri signs but does not notarize when only APPLE_SIGNING_IDENTITY is set. |
| 98 | +# Notarize the .app and .dmg ourselves so Gatekeeper accepts them with no warning. |
| 99 | +APP_BUNDLE=$(find "target/$TAURI_TARGET/release/bundle/macos" -maxdepth 1 -name '*.app' -type d | head -1) |
| 100 | +DMG_FILE=$(find "target/$TAURI_TARGET/release/bundle/dmg" -maxdepth 1 -name '*.dmg' -type f | head -1) |
| 101 | + |
| 102 | +[ -n "$APP_BUNDLE" ] || { echo "no .app produced"; exit 1; } |
| 103 | +[ -n "$DMG_FILE" ] || { echo "no .dmg produced"; exit 1; } |
| 104 | + |
| 105 | +bundle exec fastlane notarize_macos package:"$APP_BUNDLE" |
| 106 | +bundle exec fastlane notarize_macos package:"$DMG_FILE" |
| 107 | + |
| 108 | +if [ -n "${BUILDKITE_TAG:-}" ]; then |
| 109 | + echo "--- :rocket: Publish draft GitHub release" |
| 110 | + APP_TARBALL=$(find "target/$TAURI_TARGET/release/bundle/macos" -maxdepth 1 -name '*.app.tar.gz' -type f | head -1) |
| 111 | + APP_SIG=$(find "target/$TAURI_TARGET/release/bundle/macos" -maxdepth 1 -name '*.app.tar.gz.sig' -type f | head -1) |
| 112 | + [ -n "$APP_TARBALL" ] || { echo "no .app.tar.gz produced"; exit 1; } |
| 113 | + [ -n "$APP_SIG" ] || { echo "no .app.tar.gz.sig produced"; exit 1; } |
| 114 | + bundle exec fastlane create_desktop_github_release \ |
| 115 | + tag:"$BUILDKITE_TAG" \ |
| 116 | + dmg:"$DMG_FILE" \ |
| 117 | + app_tarball:"$APP_TARBALL" \ |
| 118 | + app_signature:"$APP_SIG" |
| 119 | +fi |
0 commit comments