From c07ad3d4c5835c9488ccdbef16af35a35678c473 Mon Sep 17 00:00:00 2001 From: SangHun Lee Date: Tue, 8 Sep 2026 15:02:47 +0900 Subject: [PATCH 1/3] build(setup): preflight the Metal toolchain and document missing prerequisites --- CONTRIBUTING.md | 10 +++++++++- scripts/setup.sh | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c852abf71ebe..eec78571496b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,12 @@ - macOS 14+ - Xcode 26 (the pinned toolchain); Xcode 16.2 on Intel Macs running macOS 14 also builds the macOS app (best effort) - [Zig](https://ziglang.org/) (install via `brew install zig`) +- [Rust](https://rustup.rs) (install via `brew install rustup && rustup-init`) — `scripts/setup.sh` + requires `rustup`, and the app build compiles the bundled `cmux-cua` engine with `cargo` +- On Xcode 26, the Metal toolchain is a separate download and the build fails without it: + ```bash + xcodebuild -downloadComponent MetalToolchain + ``` ## Getting Started @@ -21,7 +27,9 @@ This will: - Initialize git submodules (ghostty, homebrew-cmux) - - Build the GhosttyKit.xcframework from source + - Install the pinned Rust toolchain + - Fetch a checksum-pinned prebuilt GhosttyKit.xcframework, falling back to building it + from source with Zig (force the source build with `CMUX_GHOSTTYKIT_NO_PREBUILT=1`) - Create the necessary symlinks 3. Build the debug app: diff --git a/scripts/setup.sh b/scripts/setup.sh index c8f3db06331b..9e930a033773 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -51,6 +51,18 @@ if ! command -v cargo &> /dev/null || ! cargo --version &> /dev/null; then exit 1 fi +# Xcode 26 ships the Metal compiler as a separately downloaded component rather +# than inside Xcode.app. Without it the app target fails partway through the +# build ("cannot execute tool 'metal' due to missing Metal Toolchain"), after +# the Swift modules have already compiled. Fail here instead, where the fix is +# one command. Older Xcode bundles metal, so this check simply passes there. +echo "==> Checking for the Metal toolchain..." +if ! xcrun metal --version &> /dev/null; then + echo "Error: the Metal toolchain is not installed." + echo "Install via: xcodebuild -downloadComponent MetalToolchain" + exit 1 +fi + # The Cloud tunnel system extension embeds wireguard-go, built by # scripts/build-wireguard-go.sh. Release builds require Go; a Debug build # without it gets a stub engine (the extension cannot load in a Debug build From 7d932f99bd04c28a2cd62fe6ad98c6a7e41e421e Mon Sep 17 00:00:00 2001 From: SangHun Lee Date: Tue, 8 Sep 2026 15:14:09 +0900 Subject: [PATCH 2/3] build(setup): correct the rustup hint and name both Metal failure causes --- CONTRIBUTING.md | 16 +++++++++++++--- scripts/setup.sh | 14 +++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eec78571496b..eafb33794e96 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,9 +5,19 @@ - macOS 14+ - Xcode 26 (the pinned toolchain); Xcode 16.2 on Intel Macs running macOS 14 also builds the macOS app (best effort) - [Zig](https://ziglang.org/) (install via `brew install zig`) -- [Rust](https://rustup.rs) (install via `brew install rustup && rustup-init`) — `scripts/setup.sh` - requires `rustup`, and the app build compiles the bundled `cmux-cua` engine with `cargo` -- On Xcode 26, the Metal toolchain is a separate download and the build fails without it: +- [Rust](https://rustup.rs) — `scripts/setup.sh` requires `rustup`, and every app build compiles + the bundled `cmux-cua` engine with `cargo`. The official installer puts both in `~/.cargo/bin`, + which is where `setup.sh` looks: + + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + + Homebrew's `rustup` formula works too, but it is keg-only and no longer ships `rustup-init`, so + add `$(brew --prefix rustup)/bin` to `PATH` and run `rustup default stable` yourself. +- On Xcode 26 the Metal compiler is a separately downloaded component, and the build fails + without it: + ```bash xcodebuild -downloadComponent MetalToolchain ``` diff --git a/scripts/setup.sh b/scripts/setup.sh index 9e930a033773..d2ca10b776de 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -47,7 +47,9 @@ rustup run "$DIFF_RUST_TOOLCHAIN" rustc --version echo "==> Checking for cargo (bundled cmux-cua)..." if ! command -v cargo &> /dev/null || ! cargo --version &> /dev/null; then echo "Error: a working Rust toolchain (cargo) is required to build the bundled cmux-cua." - echo "Install via rustup: https://rustup.rs (or \`brew install rustup && rustup-init\`)" + echo "Install via rustup: https://rustup.rs" + echo "(Homebrew's rustup is keg-only and ships no rustup-init: add" + echo " \"\$(brew --prefix rustup)/bin\" to PATH, then run \`rustup default stable\`.)" exit 1 fi @@ -56,10 +58,16 @@ fi # build ("cannot execute tool 'metal' due to missing Metal Toolchain"), after # the Swift modules have already compiled. Fail here instead, where the fix is # one command. Older Xcode bundles metal, so this check simply passes there. +# The Command Line Tools do not ship metal at all, so the message names both +# causes rather than assuming the component is merely missing. echo "==> Checking for the Metal toolchain..." if ! xcrun metal --version &> /dev/null; then - echo "Error: the Metal toolchain is not installed." - echo "Install via: xcodebuild -downloadComponent MetalToolchain" + echo "Error: the Metal compiler is not available." + echo "Active developer directory: $(xcode-select -p 2>/dev/null || echo 'unset')" + echo "If that is a full Xcode, install the toolchain component:" + echo " xcodebuild -downloadComponent MetalToolchain" + echo "If it is the Command Line Tools, select Xcode first:" + echo " sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer" exit 1 fi From b686b385935bc8546be7f9d517742ff5a8dca79e Mon Sep 17 00:00:00 2001 From: Austin Wang <38676809+austinywang@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:32:29 +0000 Subject: [PATCH 3/3] build(setup): check Metal before setup mutations --- CONTRIBUTING.md | 3 ++- scripts/setup.sh | 38 ++++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eafb33794e96..da97a194de23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,8 @@ Homebrew's `rustup` formula works too, but it is keg-only and no longer ships `rustup-init`, so add `$(brew --prefix rustup)/bin` to `PATH` and run `rustup default stable` yourself. - On Xcode 26 the Metal compiler is a separately downloaded component, and the build fails - without it: + without it. Select the intended full Xcode installation first (`DEVELOPER_DIR`, if + exported, overrides `xcode-select`), then install the component: ```bash xcodebuild -downloadComponent MetalToolchain diff --git a/scripts/setup.sh b/scripts/setup.sh index d2ca10b776de..af85c61f7169 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -6,6 +6,26 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")" cd "$PROJECT_DIR" +# Xcode 26 ships the Metal compiler as a separately downloaded component rather +# than inside Xcode.app. Without it the app target fails partway through the +# build ("cannot execute tool 'metal' due to missing Metal Toolchain"), after +# the Swift modules have already compiled. Fail here instead, where the fix is +# one command. Older Xcode bundles metal, so this check simply passes there. +# The Command Line Tools do not ship metal at all, so the message names both +# causes rather than assuming the component is merely missing. +echo "==> Checking for the Metal toolchain..." +if ! xcrun metal --version &> /dev/null; then + echo "Error: the Metal compiler is not available." + echo "Active developer directory: $(xcode-select -p 2>/dev/null || echo 'unset')" + echo "For Xcode 26 or later, install the toolchain component:" + echo " xcodebuild -downloadComponent MetalToolchain" + echo "If it is the Command Line Tools, select Xcode first:" + echo " sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer" + echo "If DEVELOPER_DIR is exported, unset it or point it to the intended Xcode;" + echo "it overrides the system selection." + exit 1 +fi + echo "==> Initializing submodules..." git submodule update --init --recursive @@ -53,24 +73,6 @@ if ! command -v cargo &> /dev/null || ! cargo --version &> /dev/null; then exit 1 fi -# Xcode 26 ships the Metal compiler as a separately downloaded component rather -# than inside Xcode.app. Without it the app target fails partway through the -# build ("cannot execute tool 'metal' due to missing Metal Toolchain"), after -# the Swift modules have already compiled. Fail here instead, where the fix is -# one command. Older Xcode bundles metal, so this check simply passes there. -# The Command Line Tools do not ship metal at all, so the message names both -# causes rather than assuming the component is merely missing. -echo "==> Checking for the Metal toolchain..." -if ! xcrun metal --version &> /dev/null; then - echo "Error: the Metal compiler is not available." - echo "Active developer directory: $(xcode-select -p 2>/dev/null || echo 'unset')" - echo "If that is a full Xcode, install the toolchain component:" - echo " xcodebuild -downloadComponent MetalToolchain" - echo "If it is the Command Line Tools, select Xcode first:" - echo " sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer" - exit 1 -fi - # The Cloud tunnel system extension embeds wireguard-go, built by # scripts/build-wireguard-go.sh. Release builds require Go; a Debug build # without it gets a stub engine (the extension cannot load in a Debug build