Repository navigation
Expand file tree
/
Copy pathsetup_mac.sh
More file actions
651 lines (599 loc) · 31.3 KB
/
Copy pathsetup_mac.sh
File metadata and controls
651 lines (599 loc) · 31.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
#!/bin/bash
#
# AutoCua — macOS one-click setup
# ================================
# Installs uv (if missing), creates a local .venv/, and installs
# requirements_mac.txt into it. No manual Python install required — uv will
# fetch a Python for you if this Mac doesn't already have a suitable one.
# Then installs the Rust toolchain (if missing) and builds the web agent's
# native extension, so the first "web use" run doesn't stop on a missing cargo.
# Last, it offers iPhone / iPad control (WebDriverAgent, Xcode's checks), one
# yes/no question that counts as no when nobody answers.
#
# How to run (any of these):
# bash setup_mac.sh # simplest, works right after clone
# bash setup_mac.sh --ios # also set up iPhone / iPad control, without asking
# bash setup_mac.sh --force # ... and discard the WebDriverAgent clone, re-fetch it
# bash setup_mac.sh --yes # ... and install Xcode's iOS platform without asking
# chmod +x setup_mac.sh && ./setup_mac.sh
# Finder → right-click setup_mac.sh → Open With → Terminal.app
#
# After it finishes:
# source .venv/bin/activate
# python app.py # launch the GUI app (macOS + Windows)
#
set -e
cd "$(dirname "$0")"
MIN_PYTHON="3.10"
# Exclusive upper bound. Some dependencies ship native wheels that lag behind
# the newest CPython (paddlepaddle, for one, stops at cp313), so a Mac whose
# newest interpreter is 3.14 must not be used for the venv.
MAX_PYTHON="3.14"
PYTHON_SPEC=">=$MIN_PYTHON,<$MAX_PYTHON"
# .venv/ is the name uv looks for by default, so `uv pip install`, `uv run` and
# `uv sync` all find it with no --python flag. VENV_PROMPT, passed to
# `uv venv --prompt` below, labels the activated shell "(auto-cua)" whatever
# this repo's folder is called, rather than ".venv" or the folder name.
VENV_DIR=".venv"
VENV_PROMPT="auto-cua"
# WebDriverAgent, for step 7. Pinned on purpose: WebDriverAgent ships breaking
# changes across majors (v15.0.0 alone carried several), and
# AutoCua/ios_connector/setup.py targets a specific scheme and target list —
# WebDriverAgentLib / WebDriverAgentRunner / IntegrationApp. Tracking
# upstream's default branch would hand new users a version this connector was
# never tested against. Bump this only after testing, together with
# AutoCua/utils/wda.py and THIRD_PARTY_NOTICES.md.
WDA_VERSION="v15.1.1"
WDA_REPO="https://github.com/appium/WebDriverAgent.git"
WDA_DIR="AutoCua/ios_connector/WebDriverAgent"
IOS_REQUIREMENTS="requirements_ios.txt"
# The flags are all step 7's. --force and --yes imply --ios: neither means
# anything without it.
IOS=0
FORCE=0
ASSUME_YES=0
for _arg in "$@"; do
case "$_arg" in
--ios) IOS=1 ;;
--force) IOS=1; FORCE=1 ;;
--yes|-y) IOS=1; ASSUME_YES=1 ;;
esac
done
# How long a yes/no question waits before it counts as no.
ASK_SECONDS=30
# -----------------------------------------------------------------------------
# Print helpers (match the style used across the project's build scripts)
# -----------------------------------------------------------------------------
print_step() { printf "\n============================================================\n %s\n============================================================\n\n" "$1"; }
print_sub() { printf "\n --- %s ---\n" "$1"; }
print_ok() { printf " [OK] %s\n" "$1"; }
print_info() { printf " [INFO] %s\n" "$1"; }
print_warn() { printf " [!] %s\n" "$1"; }
print_error() { printf " [ERROR] %s\n" "$1"; }
# GUI popup so failures are visible even when launched from Finder
gui_alert() {
osascript -e "display dialog \"$1\" buttons {\"OK\"} default button 1 with icon caution with title \"AutoCua setup\"" >/dev/null 2>&1 || true
}
# A yes/no question with a countdown: y or yes is yes. Anything else, Enter,
# no answer within $2 seconds, or no terminal to ask on (a piped run) is no.
ask_yes_no() {
local reply=""
[ -t 0 ] || return 1
printf " %s [y/N] (no in %s s) " "$1" "$2"
if ! read -r -t "$2" reply; then
printf "\n"
return 1
fi
case "$reply" in
y|Y|yes|YES|Yes) return 0 ;;
esac
return 1
}
# -----------------------------------------------------------------------------
# Step 1 — uv
# -----------------------------------------------------------------------------
# NOTE: Previous versions of this script required the user to install Python
# from python.org by hand before setup could proceed. uv removes that step —
# it resolves an existing Python or downloads one itself. It also replaces pip
# for the install in step 3 (same PyPI packages, much faster, and it resolves
# the whole tree at once instead of one package at a time).
#
# The old "sync shared files to macOS flavor" step is long gone too — main.py,
# cli.py, ui/index.html and ui/script.js detect the OS at runtime,
# so a single checkout runs on both macOS and Windows with zero file patching.
print_step "STEP 1: Checking for uv"
# Make sure the standard uv install locations are visible to this shell, in
# case uv was installed by a previous run but the user's shell profile hasn't
# been re-sourced yet.
export PATH="${XDG_BIN_HOME:-$HOME/.local/bin}:$PATH"
if command -v uv >/dev/null 2>&1; then
print_ok "Found uv at $(command -v uv) ($(uv --version))"
else
print_info "uv not found — installing it (https://astral.sh/uv)"
if ! curl -LsSf https://astral.sh/uv/install.sh | sh; then
print_error "Failed to install uv."
print_info "Check your internet connection and re-run this script."
gui_alert "Could not install uv.\n\nCheck your internet connection, then run this script again."
exit 1
fi
# The installer drops uv in \$XDG_BIN_HOME or ~/.local/bin and appends that
# to the shell profile — which does not affect the shell we're in now.
export PATH="${XDG_BIN_HOME:-$HOME/.local/bin}:$PATH"
if ! command -v uv >/dev/null 2>&1; then
print_error "uv was installed but isn't on PATH in this shell."
print_info "Open a new Terminal window and re-run this script."
gui_alert "uv was installed, but this Terminal session can't see it yet.\n\nOpen a new Terminal window and run the script again."
exit 1
fi
print_ok "Installed uv ($(uv --version))"
fi
# -----------------------------------------------------------------------------
# Step 2 — .venv/
# -----------------------------------------------------------------------------
print_step "STEP 2: Preparing $VENV_DIR/"
# An existing venv is only reusable if its interpreter is still inside the
# supported range — a leftover venv built on an unsupported Python would fail
# in step 3 with an unresolvable dependency tree instead of here.
if [ -x "$VENV_DIR/bin/python" ]; then
if MIN="$MIN_PYTHON" MAX="$MAX_PYTHON" "$VENV_DIR/bin/python" -c 'import os, sys; bound = lambda k: tuple(int(p) for p in os.environ[k].split(".")); sys.exit(0 if bound("MIN") <= sys.version_info[:2] < bound("MAX") else 1)' 2>/dev/null; then
print_info "$VENV_DIR/ already exists — reusing"
else
print_info "$VENV_DIR/ uses an unsupported Python — rebuilding it"
rm -rf "$VENV_DIR"
fi
fi
if [ ! -x "$VENV_DIR/bin/python" ]; then
# Prefer a Python already on this Mac (--no-managed-python), so the venv
# keeps using the interpreter the user already has. Only if nothing here
# satisfies $PYTHON_SPEC do we let uv download one.
if uv venv --no-managed-python --python "$PYTHON_SPEC" --prompt "$VENV_PROMPT" "$VENV_DIR" 2>/dev/null; then
print_ok "Created $VENV_DIR/ using a Python already installed on this Mac"
else
print_info "No local Python $PYTHON_SPEC found — letting uv fetch one"
if ! uv venv --python "$PYTHON_SPEC" --prompt "$VENV_PROMPT" "$VENV_DIR"; then
print_error "Could not create a virtual environment."
gui_alert "Could not create the Python environment.\n\nCheck your internet connection, then run this script again."
exit 1
fi
print_ok "Created $VENV_DIR/ with a uv-managed Python"
fi
fi
VENV_PYTHON="$VENV_DIR/bin/python"
print_info "Python: $("$VENV_PYTHON" -c 'import sys; print("%d.%d.%d" % sys.version_info[:3])')"
# -----------------------------------------------------------------------------
# Step 3 — install
# -----------------------------------------------------------------------------
print_step "STEP 3: Installing requirements_mac.txt"
if [ ! -f "requirements_mac.txt" ]; then
print_error "requirements_mac.txt not found in $(pwd)"
exit 1
fi
if ! uv pip install --python "$VENV_PYTHON" -r requirements_mac.txt; then
print_error "Dependency installation failed."
gui_alert "Installing dependencies failed.\n\nScroll up in Terminal for the error, then run this script again."
exit 1
fi
# -----------------------------------------------------------------------------
# Step 4 — Rust toolchain (for the web agent)
# -----------------------------------------------------------------------------
# The web agent is the one mode that isn't pure Python: AutoCua/web is a PyO3
# cdylib that AutoCua/web/agent/__init__.py compiles with cargo on first
# import. No cargo means
# python main.py
# dies with "cargo not found" the moment web use is selected, so the toolchain
# belongs in setup rather than in a README step. setup_windows.bat has done
# this since it shipped; this is the macOS half of the same step.
#
# Unlike the Windows script, nothing from here on is fatal. By this point the
# venv and every dependency are in place, and computer use, shell use and
# mobile use all run without cargo — a failed toolchain install should cost the
# user the web agent, not the whole setup.
print_step "STEP 4: Rust toolchain (web agent)"
# rustup installs into ~/.cargo/bin and appends that to the shell profile,
# which does not reach the shell we're in now — same situation as the uv
# install in step 1, same fix.
export PATH="$HOME/.cargo/bin:$PATH"
WEB_AGENT_READY=1
# Run cargo rather than look for it. ~/.cargo/bin/cargo is not a binary, it is
# a symlink to the rustup proxy, and rustup creates that symlink during its own
# self-install whether or not a toolchain was ever downloaded. An interrupted
# install, or a rustup set up with `--default-toolchain none`, leaves a cargo
# that exists and fails on every invocation — and a `command -v cargo` gate
# would skip the install forever, so re-running this script could never repair
# it. Worse, the PATH prepend above puts that broken shim in front of a working
# Homebrew cargo.
have_cargo() { cargo --version >/dev/null 2>&1; }
# Checked before rustup, not after: cargo links through Apple's cc, and ring
# (the SHA-256 that stages the browser extension) compiles C sources, so a Mac
# without the Command Line Tools cannot build this crate no matter how well
# Rust installs. Failing here beats failing later inside a cargo error nobody
# can read.
if ! xcode-select -p >/dev/null 2>&1; then
print_info "Command Line Tools not found — cargo needs them to link."
print_info "Opening Apple's installer. Accept it, let it finish, then re-run this script."
xcode-select --install >/dev/null 2>&1 || true
gui_alert "The web agent needs Apple's Command Line Tools.\n\nAccept the installer that just opened, then run this script again.\n\nEverything else is already installed."
WEB_AGENT_READY=0
fi
# The repo pins its Rust (rust-toolchain.toml at the root): the first cargo
# call, in have_cargo below, has rustup install that version, about 150 MB,
# once, with no output of its own.
if [ "$WEB_AGENT_READY" = "1" ] && command -v rustup >/dev/null 2>&1; then
print_info "Checking the Rust version the repo pins (rust-toolchain.toml); a first install is about 150 MB..."
fi
# A rustup that is installed but has no default toolchain is repaired in place.
# Re-downloading rustup-init would work too, but this is the common leftover
# state and one command fixes it.
if [ "$WEB_AGENT_READY" = "1" ] && ! have_cargo && command -v rustup >/dev/null 2>&1; then
print_info "rustup is installed but has no usable default toolchain — installing stable"
rustup toolchain install stable || true
rustup default stable || true
fi
if [ "$WEB_AGENT_READY" = "1" ] && ! have_cargo; then
print_info "cargo not found — installing the Rust toolchain (https://rustup.rs)"
# Downloaded to a file rather than piped straight into sh: in
# `curl ... | sh`, a curl that dies mid-transfer still leaves sh reading a
# truncated script and exiting 0, so the install looks like it worked.
#
# The template is spelled out instead of using `-t rustup-init`, because
# GNU coreutils' mktemp — which Homebrew's gnubin opt-in puts ahead of
# /usr/bin/mktemp — rejects that form for having too few X's. And the
# assignment is guarded: `set -e` DOES abort on a bare assignment whose
# command substitution fails, which would kill a step documented above as
# never fatal.
if ! RUSTUP_SH="$(mktemp "${TMPDIR:-/tmp}/rustup-init.XXXXXXXX")"; then
print_error "Could not create a temp file for the Rust installer."
print_info "Install Rust yourself from https://rustup.rs, then re-run this script."
WEB_AGENT_READY=0
else
# --profile minimal: rustc + cargo + std, no docs/clippy/rustfmt. -y
# keeps it non-interactive. The profile edit it makes is deliberate —
# the loader re-runs cargo whenever a .rs file changes, so later shells
# need cargo too.
if curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o "$RUSTUP_SH" \
&& sh "$RUSTUP_SH" -y --profile minimal; then
export PATH="$HOME/.cargo/bin:$PATH"
print_ok "Installed the Rust toolchain"
else
print_error "Could not install Rust."
print_info "Install it yourself from https://rustup.rs, then re-run this script."
WEB_AGENT_READY=0
fi
rm -f "$RUSTUP_SH" || true
fi
fi
if [ "$WEB_AGENT_READY" = "1" ]; then
# Captured, not interpolated inline: a cargo that exits non-zero prints
# nothing, and `print_ok "Found $(cargo --version)"` would report the empty
# string as success.
CARGO_VERSION="$(cargo --version 2>/dev/null || true)"
if [ -n "$CARGO_VERSION" ]; then
print_ok "Found $CARGO_VERSION"
print_info "Using: $(command -v cargo)"
else
print_error "cargo still isn't usable in this shell."
print_info "Open a new Terminal window and re-run this script."
WEB_AGENT_READY=0
fi
fi
# -----------------------------------------------------------------------------
# Step 5 — build the web agent
# -----------------------------------------------------------------------------
# Built here rather than left to the first "web use" run, so a compile error
# surfaces during setup while the user is still watching, instead of minutes
# into an agent run. Importing the package is the build: the loader compiles
# whenever the .so is missing or older than any .rs source.
print_step "STEP 5: Building the web agent and the Android agent"
if [ "$WEB_AGENT_READY" = "1" ]; then
print_info "The first build takes a few minutes..."
if "$VENV_PYTHON" -c "import AutoCua.web.agent"; then
print_ok "Web agent built"
else
print_error "The web agent did not build."
print_info "Everything else still works — computer use, shell use and"
print_info "mobile use don't need it. Fix the error above and re-run."
fi
# The Android agent (AutoCua/android, Rust like the web agent) is built the
# same way, so the first "mobile use, android" run does not stop for a
# build. The emulator and the Android images are NOT fetched here (2.5 GB,
# Google's terms): the first emulator run does that, or
# python -m AutoCua.android_connector.emulator ahead of time.
print_info "Building the Android agent, about a minute..."
if "$VENV_PYTHON" -c "import AutoCua.android.agent"; then
print_ok "Android agent built"
else
print_error "The Android agent did not build."
print_info "Everything else still works — only \"mobile use, android\" needs it."
print_info "Fix the error above and re-run."
fi
else
print_info "Skipped — see the step above."
print_info "Computer use, shell use and mobile use on iOS are unaffected."
fi
# -----------------------------------------------------------------------------
# Step 6 — the Android phone tool: adb
# -----------------------------------------------------------------------------
# A real phone needs Google's adb (platform-tools) to talk to it. It is not
# vendored in this repo: it is fetched from Google's dl.google.com,
# version-pinned and SHA-256 verified, and only after the user answers y. An
# adb already on PATH is used as it is. No (or no answer) skips it; only
# phones need it.
print_step "STEP 6: Android phone tool (adb)"
print_info "For an Android phone. adb comes from Google, only after your y, and is"
print_info "never shipped in this repo, under Google's Android SDK terms"
print_info "(developer.android.com/studio/terms)."
if "$VENV_PYTHON" -m AutoCua.android_connector.adb fetch; then
print_ok "adb is ready"
else
print_info "Skipped — only an Android phone needs it. Re-run this script, or:"
print_info " $VENV_PYTHON -m AutoCua.android_connector.adb fetch"
fi
# -----------------------------------------------------------------------------
# Step 7 — iPhone / iPad control (optional)
# -----------------------------------------------------------------------------
# Behind one question, because iOS needs full Xcode and, for the simulator, a
# one-time ~8.5 GB platform from Apple: a Mac that will never drive an iPhone
# should pay for none of it. WebDriverAgent is third-party software (Facebook,
# Inc. / the Appium project, BSD 3-Clause), deliberately NOT vendored in this
# repo: it is cloned from the Appium project at the pinned tag above, so it
# comes from its authors rather than from us. See THIRD_PARTY_NOTICES.md.
#
# Nothing here is fatal to the steps above. A hard blocker (no full Xcode, no
# clone) ends the step; soft ones are collected in IOS_MISSING, so one run
# reports everything that needs fixing. set -e does not apply inside
# setup_ios, which runs as an `if` condition, so every command checks itself.
# Whether Xcode can BUILD to a simulator, which is a different question from
# whether simctl can boot one — and the only one that matters here, because
# simulation mode compiles WebDriverAgent onto the device.
sim_buildable() {
xcodebuild -project "$WDA_DIR/WebDriverAgent.xcodeproj" \
-scheme WebDriverAgentRunner -showdestinations 2>/dev/null \
| grep -q "platform:iOS Simulator, arch"
}
setup_ios() {
local xcode_out xcode_path
print_sub "Xcode"
if [ "$(uname -s)" != "Darwin" ]; then
print_error "iOS control only works on macOS — signing an iOS app requires Xcode."
return 1
fi
if ! command -v git >/dev/null 2>&1; then
print_error "git not found."
print_info "Install the Command Line Tools, then re-run: xcode-select --install"
gui_alert "git was not found.\n\nRun: xcode-select --install\n\nThen run this script again."
return 1
fi
print_ok "git $(git --version | awk '{print $3}')"
# xcodebuild must come from a full Xcode.app. The Command Line Tools alone
# provide a stub that cannot build or sign an iOS app, and it fails much
# later with a confusing error if we let it through here. Captured whole,
# not piped through head: a pipeline's status is head's, so a failing
# xcodebuild (an unaccepted licence) would pass as a version.
if ! xcode_out="$(xcodebuild -version 2>&1)"; then
XCODE_VER="$(printf '%s\n' "$xcode_out" | head -1)"
print_error "xcodebuild failed: $XCODE_VER"
if printf '%s' "$xcode_out" | grep -qi "license"; then
print_info "Accept the Xcode license first: sudo xcodebuild -license accept"
else
print_info "Install Xcode from the App Store, then run:"
print_info " sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
fi
gui_alert "Xcode is not usable yet.\n\n$XCODE_VER\n\nSee the Terminal output for the fix."
return 1
fi
XCODE_VER="$(printf '%s\n' "$xcode_out" | head -1)"
xcode_path="$(xcode-select -p 2>/dev/null || true)"
case "$xcode_path" in
*CommandLineTools*)
print_error "xcode-select points at the Command Line Tools, not a full Xcode."
print_info "Install Xcode from the App Store, then run:"
print_info " sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
gui_alert "AutoCua needs full Xcode, not just the Command Line Tools.\n\nInstall Xcode, then run:\nsudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
return 1
;;
esac
print_ok "$XCODE_VER ($xcode_path)"
print_sub "WebDriverAgent $WDA_VERSION"
print_info "Source: $WDA_REPO"
print_info "License: BSD 3-Clause (Facebook, Inc. / Appium) — not covered by"
print_info " AutoCua's MIT license, never shipped in this repo."
print_info " See THIRD_PARTY_NOTICES.md."
if [ -d "$WDA_DIR" ] && [ "$FORCE" = "1" ]; then
print_info "--force given — removing the existing clone"
rm -rf "$WDA_DIR"
fi
if [ -d "$WDA_DIR/WebDriverAgent.xcodeproj" ]; then
if [ -d "$WDA_DIR/.git" ]; then
WDA_ACTUAL="$(git -C "$WDA_DIR" describe --tags --always 2>/dev/null || echo unknown)"
print_ok "Already present at $WDA_DIR (version: $WDA_ACTUAL) — reusing"
if [ "$WDA_ACTUAL" != "$WDA_VERSION" ]; then
print_warn "That is not the pinned $WDA_VERSION."
print_info "Re-fetch the tested version with: bash setup_mac.sh --force"
fi
else
# A hand-placed copy. Report it as unverified rather than claiming
# the pinned version — we have no way to know what it actually is.
WDA_ACTUAL="unverified (existing copy, not a git clone)"
print_ok "Already present at $WDA_DIR (not a git clone) — reusing"
print_info "Replace it with the pinned clone using: bash setup_mac.sh --force"
fi
print_info "Note: re-fetching resets project.pbxproj, so signing must be redone."
else
# --depth 1 on a single tag: ~4MB of working tree instead of the full history.
if ! git clone --depth 1 --branch "$WDA_VERSION" "$WDA_REPO" "$WDA_DIR"; then
print_error "Could not clone WebDriverAgent."
print_info "Check your internet connection, or clone it manually:"
print_info " git clone --depth 1 --branch $WDA_VERSION $WDA_REPO $WDA_DIR"
gui_alert "Could not download WebDriverAgent.\n\nCheck your internet connection, then run this script again."
return 1
fi
if [ ! -d "$WDA_DIR/WebDriverAgent.xcodeproj" ]; then
print_error "Clone finished but $WDA_DIR/WebDriverAgent.xcodeproj is missing."
print_info "Upstream layout may have changed for tag $WDA_VERSION."
return 1
fi
WDA_ACTUAL="$WDA_VERSION"
print_ok "Cloned WebDriverAgent $WDA_VERSION into $WDA_DIR"
fi
# ios_connector/setup.py rewrites WebDriverAgent.xcodeproj/project.pbxproj
# through Ruby's xcodeproj gem to switch the targets to automatic signing.
# No gem, no signing — so this is genuinely required, not a nicety.
print_sub "Signing toolchain"
if ! command -v ruby >/dev/null 2>&1; then
print_error "ruby not found — it normally ships with macOS."
IOS_MISSING=1
elif ruby -e "require 'xcodeproj'" >/dev/null 2>&1; then
print_ok "ruby $(ruby -e 'print RUBY_VERSION') with the xcodeproj gem"
else
print_info "The Ruby 'xcodeproj' gem is missing — installing it now."
# --user-install, deliberately NOT sudo: this lands in ~/.gem, which is
# already on Ruby's search path, instead of writing into the system
# Ruby that macOS owns and replaces on update. Every dependency is pure
# Ruby, so there is nothing to compile and nothing to elevate for.
printf "\n"
if gem install --user-install xcodeproj; then
printf "\n"
if ruby -e "require 'xcodeproj'" >/dev/null 2>&1; then
print_ok "xcodeproj installed in $(ruby -e 'require "rubygems"; print Gem.user_dir')"
else
print_warn "The gem installed but ruby still cannot load it."
print_info "Something is overriding the gem path — compare:"
printf " gem env | grep -A3 'GEM PATHS'\n"
IOS_MISSING=1
fi
else
printf "\n"
print_warn "Could not install the xcodeproj gem — signing cannot run without it."
print_info "Try it by hand: gem install --user-install xcodeproj"
IOS_MISSING=1
fi
fi
# Python packages for iOS are standalone in requirements_ios.txt (full app
# stack + device libs — not shared with requirements_mac.txt), installed
# into the same .venv/ on top of step 3's.
print_sub "Device tooling"
if [ ! -f "$IOS_REQUIREMENTS" ]; then
print_error "$IOS_REQUIREMENTS not found in $(pwd)"
print_info "That file lists the iOS Python packages."
IOS_MISSING=1
elif "$VENV_PYTHON" -c "import pymobiledevice3" >/dev/null 2>&1; then
print_ok "pymobiledevice3 available in $VENV_DIR/"
else
print_info "Installing iOS Python packages from $IOS_REQUIREMENTS …"
if uv pip install --python "$VENV_PYTHON" -r "$IOS_REQUIREMENTS"; then
if "$VENV_PYTHON" -c "import pymobiledevice3" >/dev/null 2>&1; then
print_ok "Installed packages from $IOS_REQUIREMENTS"
else
print_warn "Install finished but pymobiledevice3 still does not import."
IOS_MISSING=1
fi
else
print_warn "Could not install $IOS_REQUIREMENTS."
print_info "Install it by hand with:"
print_info " uv pip install --python $VENV_PYTHON -r $IOS_REQUIREMENTS"
IOS_MISSING=1
fi
fi
# Can Xcode actually TARGET a simulator? `xcrun simctl` reads the
# system-wide CoreSimulator runtime store, so it can list and boot devices
# that the SELECTED Xcode has no platform support for. xcodebuild then
# refuses those devices ("Unable to find a destination...") halfway
# through an agent run. One second here beats that every time.
print_sub "Simulator support"
if sim_buildable; then
print_ok "Xcode can build for the iOS Simulator"
return 0
fi
print_warn "Xcode cannot target any iOS Simulator on this Mac."
print_info "\`xcrun simctl\` may still list and boot simulators — that store is"
print_info "system-wide — but the selected Xcode has no iOS platform support,"
print_info "so builds fail with \"Unable to find a destination\"."
printf "\n"
print_info "The missing piece is Xcode's iOS platform: an ~8.5 GB download from"
print_info "Apple, once. This is the right moment for it — the agent will never"
print_info "start a download of this size mid-run."
printf "\n"
# Setup is where a big one-time install belongs, so offer it here, as a
# yes/no with a countdown like every question in this script: no answer,
# or a run nobody can answer (CI, a pipe), declines and reports.
if [ "$ASSUME_YES" = "1" ] || ask_yes_no "Download and install it now (~8.5 GB from Apple)?" "$ASK_SECONDS"; then
print_info "Running: xcodebuild -downloadPlatform iOS"
printf "\n"
if xcodebuild -downloadPlatform iOS; then
printf "\n"
if sim_buildable; then
print_ok "iOS platform installed — Xcode can build for the iOS Simulator"
else
print_warn "The download finished but Xcode still targets no simulator."
print_info "Check that xcode-select points at the Xcode you expect:"
printf " xcode-select -p\n"
printf " sudo xcodebuild -runFirstLaunch\n"
IOS_MISSING=1
fi
else
printf "\n"
print_warn "xcodebuild -downloadPlatform iOS did not complete."
print_info "Re-run this script to try again, or install it from"
print_info "Xcode > Settings > Components."
IOS_MISSING=1
fi
else
print_info "Skipped. Install it later with either of:"
printf " bash setup_mac.sh --yes # re-run and install without asking\n"
printf " xcodebuild -downloadPlatform iOS # or Xcode > Settings > Components\n"
printf " xcode-select -p # confirm the right Xcode is selected\n"
printf "\n"
print_info "Simulation mode (the default) needs this; hardware-only use does not."
IOS_MISSING=1
fi
return 0
}
print_step "STEP 7: iPhone / iPad control (optional)"
print_info "Drives an iPhone or iPad, or the iOS Simulator. It needs full Xcode and"
print_info "WebDriverAgent, cloned from the Appium project (BSD 3-Clause, never"
print_info "shipped in this repo); the simulator also needs Xcode's iOS platform,"
print_info "a one-time ~8.5 GB download from Apple, asked about separately."
IOS_STATE="skipped"
IOS_MISSING=0
if [ "$IOS" = "1" ] || ask_yes_no "Set up iPhone / iPad control too?" "$ASK_SECONDS"; then
if setup_ios; then
if [ "$IOS_MISSING" = "0" ]; then IOS_STATE="ready"; else IOS_STATE="incomplete"; fi
else
IOS_STATE="failed"
fi
else
print_info "Skipped. Set it up later with: bash setup_mac.sh --ios"
fi
# -----------------------------------------------------------------------------
# Done
# -----------------------------------------------------------------------------
print_step "SETUP COMPLETE"
print_ok "Dependencies installed into $VENV_DIR/"
printf "\n"
print_info "Next steps:"
printf " source %s/bin/activate\n" "$VENV_DIR"
[ -f "main.py" ] && printf " python main.py # run the CLI agent\n"
[ -f "app.py" ] && printf " python app.py # launch the GUI app\n"
[ -f "mac_binary_build.py" ] && printf " python mac_binary_build.py # produce AutoCua.dmg\n"
printf "\n"
case "$IOS_STATE" in
ready)
print_ok "iPhone / iPad: WebDriverAgent ready at $WDA_DIR (version: $WDA_ACTUAL)"
print_info "To sign it onto a device, open the sign & run UI, either way works:"
printf " python main.py # the app: Settings > Connect Device > iPhone\n"
printf " python AutoCua/ios_connector/setup.py # or standalone, on port 8765\n"
print_info "You will need an Apple ID added in Xcode (Settings > Accounts), and"
print_info "the iPhone on USB, unlocked and trusting this Mac. A free Apple ID's"
print_info "profiles expire after 7 days, so re-signing is weekly; a paid"
print_info "developer account lasts a year."
;;
incomplete|failed)
print_warn "iPhone / iPad: not ready yet — fix the items marked above, then re-run:"
printf " bash setup_mac.sh --ios # reuses everything already in place\n"
;;
*)
print_info "Want to drive an iPhone or iPad later? It needs Xcode:"
printf " bash setup_mac.sh --ios\n"
;;
esac
printf "\n"