From d79446316b9478ac092d7a18324f8e8802e9b8c4 Mon Sep 17 00:00:00 2001 From: Tom Brow Date: Fri, 16 Oct 2026 14:06:53 -0700 Subject: [PATCH] fix(mobile): support DeviceHub in mobile-dev (#8404) `just mobile-dev` fails on Xcode 27 machines that have DeviceHub instead of Simulator. Resolve the simulator frontend inside the selected Xcode installation: DeviceHub for Xcode 27, or Simulator for Xcode 26. Launch by full path so an older installed or running app cannot determine the choice. This extracts the still-needed DeviceHub portion of Wes's [#8330](https://github.com/block/buzz/pull/8330). The CocoaPods deployment-target work already landed in [#8379](https://github.com/block/buzz/pull/8379); this PR does not change Podfile or its lockfile. CI and pre-push mobile selection include the shared Justfile and mobile recipe regression script, so edits to either run the regression in the mobile gate. ### Validation - Nine production-recipe checks pass, covering side-by-side Xcode 26/27 selection with spaces in paths, missing apps, launch failure, and gateway argument forwarding. Mutations reject unqualified Launch Services lookup and removal of DeviceHub selection. - All 39 tests of the pinned paths-filter action pass, including the Justfile and recipe-test selection cases. Mobile formatting, analysis, and the full mobile suite pass. - Native macOS smoke checks exercised launch from the selected Xcode installation and reached the Flutter boundary. Flutter and identity setup were stubbed, so no app was built or installed. - The initial full local CI gate stopped on an unchanged ACP signal-cancellation test; that exact test passed in isolation. The initial pre-push run also hit missing dependencies and two unchanged desktop agent-discovery tests. Desktop lint and TypeScript checks passed after installing pinned dependencies. Publishing used the authorized local-hook exception. - Current-head [CI](https://github.com/block/buzz/actions/runs/38963831198) passed, including all nine recipe cases and native Swift validation. Owner approval and Codex code/security reviews are complete with no unresolved findings. buzz-review-completed --------- Signed-off-by: Tom Brow --- .github/workflows/ci.yml | 2 ++ Justfile | 10 ++++-- lefthook.yml | 2 +- scripts/ci-selection.test.mjs | 7 +++- scripts/test-mobile-gateway-recipes.sh | 49 ++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b7b2a7cd2..351c7bef6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,6 +88,8 @@ jobs: - 'pnpm-lock.yaml' mobile: - 'mobile/**' + - 'Justfile' + - 'scripts/test-mobile-gateway-recipes.sh' - 'scripts/mobile-release.sh' - 'scripts/mobile-worktree-overrides.sh' - 'scripts/mobile-worktree-clean.sh' diff --git a/Justfile b/Justfile index e8bdbccf49..369eb05680 100644 --- a/Justfile +++ b/Justfile @@ -1026,10 +1026,14 @@ mobile-build-android: mobile-dev: #!/usr/bin/env bash set -euo pipefail - if ! pgrep -x Simulator &>/dev/null; then - open -a Simulator - sleep 3 + developer_dir="$(xcode-select -p)" + simulator_app="$developer_dir/../Applications/DeviceHub.app" + if [[ ! -d "$simulator_app" ]]; then + simulator_app="$developer_dir/Applications/Simulator.app" fi + # Use the selected Xcode even when another version's app is already running. + open -a "$simulator_app" + sleep 3 ./scripts/mobile-worktree-overrides.sh gateway_url="${BUZZ_PUSH_GATEWAY_URL:-}" overrides_file="{{mobile_dir}}/ios/Flutter/AppOverrides.xcconfig" diff --git a/lefthook.yml b/lefthook.yml index 7b75acb715..529f1665f5 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -142,5 +142,5 @@ pre-push: # Run analysis and tests serially so parallel pre-push hooks do not # contend for shared Flutter state (.dart_tool, shader bundle). files: git diff --name-only origin/main...HEAD - glob: ["mobile/**"] + glob: ["mobile/**", "Justfile", "scripts/test-mobile-gateway-recipes.sh"] run: ./scripts/without-git-env.sh sh -c 'just mobile-check && just mobile-test' diff --git a/scripts/ci-selection.test.mjs b/scripts/ci-selection.test.mjs index 0ce88ef067..ccad2f604c 100644 --- a/scripts/ci-selection.test.mjs +++ b/scripts/ci-selection.test.mjs @@ -218,6 +218,11 @@ const scenarios = [ ], ["mobile lockfile", ["mobile/pubspec.lock"], ["mobile"]], ["mobile release script", ["scripts/mobile-release.sh"], ["mobile"]], + [ + "mobile gateway recipe tests", + ["scripts/test-mobile-gateway-recipes.sh"], + ["mobile"], + ], ["desktop", ["desktop/src/main.tsx"], ["desktop"]], ["Tauri", ["desktop/src-tauri/src/main.rs"], ["desktop", "desktop-rust"]], ["relay", ["crates/buzz-relay/src/main.rs"], ["rust"]], @@ -232,7 +237,7 @@ const scenarios = [ [".github/workflows/_ci-desktop.yml"], ["desktop", "mobile", "rust"], ], - ["Justfile", ["Justfile"], ["desktop", "rust"]], + ["Justfile", ["Justfile"], ["desktop", "mobile", "rust"]], ["CI apt retry", ["scripts/ci-apt-retry.sh"], ["desktop", "rust"]], ["integration services", ["docker-compose.yml"], ["rust"]], ["CI integration images", ["docker-compose.ci.yml"], ["rust"]], diff --git a/scripts/test-mobile-gateway-recipes.sh b/scripts/test-mobile-gateway-recipes.sh index ae6741eb1a..9374e0cdc8 100644 --- a/scripts/test-mobile-gateway-recipes.sh +++ b/scripts/test-mobile-gateway-recipes.sh @@ -15,7 +15,17 @@ cat > "$fixture/bin/flutter" <<'STUB' set -eu printf '%s\n' "$@" > "$CALL_LOG" STUB -cat > "$fixture/bin/pgrep" <<'STUB' +cat > "$fixture/bin/xcode-select" <<'STUB' +#!/bin/bash +[[ "$*" == "-p" ]] || exit 1 +printf '%s\n' "$SELECTED_DEVELOPER_DIR" +STUB +cat > "$fixture/bin/open" <<'STUB' +#!/bin/bash +printf '%s\n' "$@" >> "$OPEN_LOG" +[[ "$1" == -a && -d "$2" && "$OPEN_FAIL" == 0 ]] +STUB +cat > "$fixture/bin/sleep" <<'STUB' #!/bin/bash exit 0 STUB @@ -23,9 +33,14 @@ cat > "$fixture/scripts/mobile-worktree-overrides.sh" <<'STUB' #!/bin/bash exit 0 STUB -chmod +x "$fixture/bin/flutter" "$fixture/bin/pgrep" "$fixture/scripts/mobile-worktree-overrides.sh" +chmod +x "$fixture/bin/flutter" "$fixture/bin/xcode-select" "$fixture/bin/open" "$fixture/bin/sleep" "$fixture/scripts/mobile-worktree-overrides.sh" export PATH="$fixture/bin:$PATH" export CALL_LOG="$fixture/arguments" +export OPEN_LOG="$fixture/open-calls" +# Spaces also exercise quoting of the selected Xcode path. +export SELECTED_DEVELOPER_DIR="$fixture/Xcode 27.app/Contents/Developer" +export OPEN_FAIL=0 +mkdir -p "$SELECTED_DEVELOPER_DIR" "$SELECTED_DEVELOPER_DIR/../Applications/DeviceHub.app" unset BUZZ_PUSH_GATEWAY_URL for recipe in mobile-dev mobile-build-android; do @@ -56,3 +71,33 @@ printf '%s\n' 'BUZZ_PUSH_GATEWAY_URL = https:/$()/push.example' > "$fixture/mobi printf '%s\n' run '--dart-define=BUZZ_PUSH_GATEWAY_URL=https://push.example' > "$fixture/expected" diff -u "$fixture/expected" "$CALL_LOG" printf 'PASS mobile-dev Xcode override\n' + +# Both installations exist: selection must not depend on Launch Services order. +xcode26="$fixture/Xcode 26.app/Contents/Developer" +xcode27="$SELECTED_DEVELOPER_DIR" +mkdir -p "$xcode26/Applications/Simulator.app" +for scenario in selected-xcode26 selected-xcode27 launch-failure missing-app; do + SELECTED_DEVELOPER_DIR="$xcode27" + OPEN_FAIL=0 + case "$scenario" in + selected-xcode26) SELECTED_DEVELOPER_DIR="$xcode26" ;; + launch-failure) OPEN_FAIL=1 ;; + missing-app) SELECTED_DEVELOPER_DIR="$fixture/Missing Xcode.app/Contents/Developer" ;; + esac + expected_app="$SELECTED_DEVELOPER_DIR/../Applications/DeviceHub.app" + if [[ "$scenario" == selected-xcode26 || "$scenario" == missing-app ]]; then + expected_app="$SELECTED_DEVELOPER_DIR/Applications/Simulator.app" + fi + rm -f "$CALL_LOG" + : > "$OPEN_LOG" + printf '%s\n' -a "$expected_app" > "$fixture/expected-open" + if (cd "$fixture" && /bin/bash "$fixture/mobile-dev"); then + [[ "$scenario" != launch-failure && "$scenario" != missing-app ]] + diff -u "$fixture/expected" "$CALL_LOG" + else + [[ "$scenario" == launch-failure || "$scenario" == missing-app ]] + [[ ! -e "$CALL_LOG" ]] + fi + diff -u "$fixture/expected-open" "$OPEN_LOG" + printf 'PASS mobile-dev %s\n' "$scenario" +done