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 <tomb@block.xyz>
This commit is contained in:
Tom Brow
2026-10-16 14:06:53 -07:00
committed by GitHub
parent 60de1db0b8
commit d79446316b
5 changed files with 63 additions and 7 deletions

View File

@@ -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'

View File

@@ -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"

View File

@@ -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'

View File

@@ -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"]],

View File

@@ -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