From 47a45649c5038979475e9365922eaf7acfb95300 Mon Sep 17 00:00:00 2001 From: Tee8z Date: Wed, 4 Nov 2026 18:00:43 -0500 Subject: [PATCH] ci(gpui): cross-compile the macOS x86_64 release on Apple Silicon The macOS x86_64 package took about 61 minutes on macos-15-intel, 51 of them compiling, against 17 for the same build on macos-14. Build it on macos-14 with --target x86_64-apple-darwin instead. package-macos.sh takes BUZZ_GPUI_MACOS_ARCH. When it differs from the machine, it sets CARGO_BUILD_TARGET and PKG_CONFIG_ALLOW_CROSS (the universal GStreamer framework carries x86_64), and reads the binaries from target//release. build-package.sh stages the relay provider in that same directory. Native builds are unchanged. The x86_64 job installs Rosetta, which its checks need to run the bundled x86_64 Node and provider, and the two architectures get separate Rust caches now that they share a runner image. Signed-off-by: Tee8z --- .../workflows/voltage-buzz-gpui-release.yml | 12 +++++++++- apps/buzz-gpui/scripts/build-package.sh | 5 +++- apps/buzz-gpui/scripts/package-macos.sh | 23 +++++++++++++++---- apps/buzz-gpui/scripts/test-package-build.py | 15 ++++++++++-- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/.github/workflows/voltage-buzz-gpui-release.yml b/.github/workflows/voltage-buzz-gpui-release.yml index 40c640b346a..9688d0254c5 100644 --- a/.github/workflows/voltage-buzz-gpui-release.yml +++ b/.github/workflows/voltage-buzz-gpui-release.yml @@ -130,10 +130,14 @@ jobs: include: - runner: macos-14 arch: arm64 - - runner: macos-15-intel + # Cross-compiled on Apple Silicon: the Intel runner took about + # 51 minutes to build what macos-14 builds in 17. + - runner: macos-14 arch: x86_64 + rust-target: x86_64-apple-darwin env: BUZZ_GPUI_RELEASE_VERSION: ${{ needs.version.outputs.version }} + BUZZ_GPUI_MACOS_ARCH: ${{ matrix.arch }} # The root rust-toolchain.toml pins block/buzz's Rust, and rustup reads # it from apps/buzz-gpui too, so name the GPUI toolchain explicitly. RUSTUP_TOOLCHAIN: 1.98.1 @@ -149,9 +153,15 @@ jobs: - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master with: toolchain: 1.98.1 + targets: ${{ matrix.rust-target }} + # The x86_64 checks run the bundled Node and provider under Rosetta. + - if: matrix.arch == 'x86_64' + run: sudo softwareupdate --install-rosetta --agree-to-license - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2.9.1 with: workspaces: apps/buzz-gpui + # Both architectures now share a runner image; keep their caches apart. + key: ${{ matrix.arch }} save-if: ${{ github.ref == 'refs/heads/main' }} # Keep the release build when packaging fails after it. cache-on-failure: true diff --git a/apps/buzz-gpui/scripts/build-package.sh b/apps/buzz-gpui/scripts/build-package.sh index 618efce1e00..37f25e600b5 100644 --- a/apps/buzz-gpui/scripts/build-package.sh +++ b/apps/buzz-gpui/scripts/build-package.sh @@ -5,6 +5,9 @@ here="$(cd "$(dirname "$0")/.." && pwd)" repo="$(cd "$here/../.." && pwd)" cd "$here" target="${CARGO_TARGET_DIR:-$here/target}" +# Cargo puts a cross build (CARGO_BUILD_TARGET) under target//. +out=release +if [ -n "${CARGO_BUILD_TARGET:-}" ]; then out="$CARGO_BUILD_TARGET/release"; fi features=() if [ "${BUZZ_GPUI_VOLTAGE:-0}" = 1 ]; then features=(--features voltage-sandbox) @@ -15,5 +18,5 @@ cargo build --locked --release -p buzz-gpui "${features[@]}" "$@" if [ "${BUZZ_GPUI_VOLTAGE:-0}" = 1 ]; then suffix="" if [ "${OS:-}" = Windows_NT ]; then suffix=.exe; fi - cp "$target/voltage-provider/release/buzz-backend-relay$suffix" "$target/release/" + cp "$target/voltage-provider/$out/buzz-backend-relay$suffix" "$target/$out/" fi diff --git a/apps/buzz-gpui/scripts/package-macos.sh b/apps/buzz-gpui/scripts/package-macos.sh index 353046b14dc..8b952b05d46 100755 --- a/apps/buzz-gpui/scripts/package-macos.sh +++ b/apps/buzz-gpui/scripts/package-macos.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Builds an ad-hoc signed Buzz.app and wraps it in a .dmg. Runs on macOS, -# for the architecture of the machine it runs on. The app carries the +# for the machine's architecture or BUZZ_GPUI_MACOS_ARCH (arm64 or x86_64), +# which it cross-compiles; the checks then run an x86_64 build under Rosetta. The app carries the # GStreamer it plays media with, copied from the official GStreamer # framework (the runtime and development .pkg installers from # gstreamer.freedesktop.org), so users need not install GStreamer. @@ -31,15 +32,29 @@ export RUSTFLAGS="${RUSTFLAGS:-} -C link-arg=-Wl,-headerpad_max_install_names" version="$(cargo metadata --no-deps --format-version 1 \ | python3 -c 'import json,sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "buzz-gpui"))')" -arch="$(uname -m)" +host_arch="$(uname -m)" +arch="${BUZZ_GPUI_MACOS_ARCH:-$host_arch}" +out=release +if [ "$arch" != "$host_arch" ]; then + case "$arch" in + arm64) triple=aarch64-apple-darwin ;; + x86_64) triple=x86_64-apple-darwin ;; + *) echo "Cannot build for $arch." >&2; exit 1 ;; + esac + export CARGO_BUILD_TARGET="$triple" + # pkg-config refuses a cross build unless told; the universal framework + # carries this architecture's libraries. + export PKG_CONFIG_ALLOW_CROSS=1 + out="$triple/release" +fi bash scripts/build-package.sh work="$(mktemp -d)" app="$work/Buzz.app" mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources" dist -cp target/release/buzz-gpui "$app/Contents/MacOS/" +cp "target/$out/buzz-gpui" "$app/Contents/MacOS/" if [ "${BUZZ_GPUI_VOLTAGE:-0}" = 1 ]; then - cp target/release/buzz-backend-relay "$app/Contents/MacOS/" + cp "target/$out/buzz-backend-relay" "$app/Contents/MacOS/" fi iconset="$work/buzz.iconset" diff --git a/apps/buzz-gpui/scripts/test-package-build.py b/apps/buzz-gpui/scripts/test-package-build.py index 588946c5f6c..bc07bd8d480 100644 --- a/apps/buzz-gpui/scripts/test-package-build.py +++ b/apps/buzz-gpui/scripts/test-package-build.py @@ -9,7 +9,7 @@ class PackageBuild(unittest.TestCase): - def run_build(self, voltage, windows=False, fail=False): + def run_build(self, voltage, windows=False, fail=False, cross=None): temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) root = Path(temporary.name) @@ -29,7 +29,7 @@ def run_build(self, voltage, windows=False, fail=False): if provider and os.environ.get("FAIL_PROVIDER") == "1": sys.exit(9) target = pathlib.Path(args[args.index("--target-dir")+1] if provider else os.environ["CARGO_TARGET_DIR"]) -release = target / "release" +release = target / os.environ.get("CARGO_BUILD_TARGET", "") / "release" release.mkdir(parents=True, exist_ok=True) name = "buzz-backend-relay" if provider else "buzz-gpui" if os.environ.get("OS") == "Windows_NT": @@ -41,11 +41,22 @@ def run_build(self, voltage, windows=False, fail=False): CALLS=str(root/"calls.jsonl"), CARGO_TARGET_DIR=str(root/"build"), BUZZ_GPUI_VOLTAGE="1" if voltage else "0", OS="Windows_NT" if windows else "Linux", FAIL_PROVIDER="1" if fail else "0") + env.pop("CARGO_BUILD_TARGET", None) + if cross: + env["CARGO_BUILD_TARGET"] = cross result = subprocess.run(["bash", str(scripts/"build-package.sh")], env=env, capture_output=True, text=True, timeout=10) calls = [json.loads(line) for line in (root/"calls.jsonl").read_text().splitlines()] return root, result, calls + def test_cross_build_stages_the_provider_beside_the_cross_built_app(self): + root, result, calls = self.run_build(True, cross="x86_64-apple-darwin") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(len(calls), 2) + staged = root/"build"/"x86_64-apple-darwin"/"release"/"buzz-backend-relay" + self.assertEqual(staged.read_text(), "fresh buzz-backend-relay") + self.assertFalse((root/"build"/"release"/"buzz-backend-relay").exists()) + def test_voltage_build_enables_remote_ui_and_stages_the_same_builds_provider(self): for windows in [False, True]: with self.subTest(windows=windows):