|
#!/bin/bash |
|
# patch-xcode27-sequoia.sh |
|
# FINAL PRODUCTION FREEZE — Xcode 27.0 (27A266a) on macOS 15.7.3 (Sequoia). |
|
# Ships exactly FOUR authorized IDEKit binary patches. |
|
# Patch 5 (Welcome onboarding OS guard) was tested on a disposable clone and is |
|
# NOT required for any supported workflow — WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE. |
|
# Integrates LSMinimumSystemVersion=15.7 into normal patch/verify/status. |
|
# Default launch opens the durable helper editor immediately (no Welcome timeout). |
|
# Does NOT use codesign --deep. Does NOT invent unproven patches. Does NOT patch SwiftUI. |
|
set -euo pipefail |
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
|
SCRIPT_PATH="${SCRIPT_DIR}/$(basename "$0")" |
|
STATE_ROOT="${HOME}/Library/Application Support/Xcode27SequoiaPatch" |
|
BUILD_ID="27A266a" |
|
STATE_DIR="${STATE_ROOT}/${BUILD_ID}" |
|
PLUGIN_BACKUP_DIR="${STATE_DIR}/plugins" |
|
VANILLA_BACKUP_DIR="${STATE_DIR}/vanilla" |
|
CF_HOME="${STATE_DIR}/cf-home" |
|
MANIFEST="${STATE_DIR}/manifest.txt" |
|
LOG_FILE="${STATE_DIR}/patch.log" |
|
HELPERS_DIR="${STATE_DIR}/helpers" |
|
GUI_HELPER="${HELPERS_DIR}/xcode27-gui-helper" |
|
DEFAULT_LAUNCH_PKG="${STATE_DIR}/DefaultLaunchPackage" |
|
DEFAULT_LAUNCH_DOC="${DEFAULT_LAUNCH_PKG}/Sources/Xcode27DefaultLaunch/main.swift" |
|
|
|
# Known-good identity (fail-closed) |
|
EXPECTED_VERSION="27.0" |
|
# Info.plist DTXcodeBuild is "27A266"; xcodebuild -version reports "27A266a". |
|
EXPECTED_BUILD="27A266a" |
|
EXPECTED_BUILD_ALT="27A266" |
|
EXPECTED_MAIN_SHA="64c1588d262ca2dadddbbcf7603982e65343d8b8802162308f16410faeae2edd" |
|
EXPECTED_IDEKIT_SHA="02dda214cace3f2bb53e187701e5b3764d91e5eb14764a8ed96ed223a0fd5922" |
|
|
|
# Production LS requirement (part of final product, not optional) |
|
REQUIRED_LS_MIN="15.7" |
|
LEGACY_LS_VAN="26.6" |
|
|
|
# Final FOUR authorized production binary patches (ARM64 only). |
|
# File offset == VM for __TEXT at 0 on this IDEKit. |
|
PATCH1_OFF="0x374aa0"; PATCH1_VAN="200b0034"; PATCH1_NEW="1f2003d5" # startup/FLE macOS 26 cbz->nop |
|
PATCH2_OFF="0x6d7c40"; PATCH2_VAN="40180036"; PATCH2_NEW="1f2003d5" # IDEInitialEditor.loadView tbz->nop |
|
PATCH3_OFF="0xa5bf08"; PATCH3_VAN="a00a0036"; PATCH3_NEW="1f2003d5" # IDERunStopToolbar tbz->nop |
|
PATCH4_OFF="0x2b73c"; PATCH4_VAN="f44fbea9"; PATCH4_NEW="10000014" # createProjectQuickly -> newProjectFromTemplate |
|
# PATCH5 REMOVED from production set: |
|
# 0x373e4c 60030034 -> 1f2003d5 (IDEWelcomeManager onboarding OS guard) |
|
# Classification on disposable clone: PATCH5_NOT_REQUIRED_FOR_SUPPORTED_WORKFLOW |
|
# Native Welcome remains WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE regardless. |
|
# Kept here only as documentation of the historical site; NOT applied by patch. |
|
|
|
FINAL_PATCH_COUNT=4 |
|
|
|
PLUGINS=( |
|
"IDEXCStringsSupport.framework" |
|
"IDETestPlanEditor.framework" |
|
) |
|
|
|
DEFAULT_XCODE="/Applications/Xcode-27.app" |
|
|
|
die() { echo "ERROR: $*" >&2; exit 1; } |
|
log() { echo "$*"; echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $*" >>"$LOG_FILE" 2>/dev/null || true; } |
|
require_command() { command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"; } |
|
|
|
ensure_state() { |
|
mkdir -p "$STATE_DIR" "$PLUGIN_BACKUP_DIR" "$VANILLA_BACKUP_DIR" \ |
|
"$CF_HOME/Library/Preferences" "$HELPERS_DIR" |
|
touch "$MANIFEST" "$LOG_FILE" |
|
} |
|
|
|
hex_to_upper() { printf '%s' "$1" | tr '[:lower:]' '[:upper:]'; } |
|
|
|
read_bytes() { |
|
local file="$1" off_hex="$2" |
|
local off_dec=$((off_hex)) |
|
python3 - "$file" "$off_dec" <<'PY' |
|
import sys |
|
from pathlib import Path |
|
p=Path(sys.argv[1]); off=int(sys.argv[2]) |
|
data=p.read_bytes() |
|
if off+4>len(data): |
|
print(""); sys.exit(2) |
|
print(data[off:off+4].hex()) |
|
PY |
|
} |
|
|
|
# Write exactly 4 bytes at file offset (r+b seek; preserves inode). |
|
write_bytes() { |
|
local file="$1" off_hex="$2" hexbytes="$3" |
|
local off_dec=$((off_hex)) |
|
python3 - "$file" "$off_dec" "$hexbytes" <<'PY' |
|
import os, sys |
|
from pathlib import Path |
|
p=Path(sys.argv[1]); off=int(sys.argv[2]); hx=sys.argv[3] |
|
raw=bytes.fromhex(hx) |
|
assert len(raw)==4 |
|
with open(p,'r+b') as f: |
|
f.seek(off) |
|
cur=f.read(4) |
|
if len(cur)!=4: |
|
print("short read at offset", file=sys.stderr); sys.exit(2) |
|
f.seek(off) |
|
f.write(raw) |
|
f.flush() |
|
os.fsync(f.fileno()) |
|
with open(p,'rb') as f: |
|
f.seek(off); new=f.read(4) |
|
if new!=raw: |
|
print(f"verify write failed got {new.hex()}", file=sys.stderr); sys.exit(3) |
|
print("seek_write_ok", cur.hex(), "->", new.hex()) |
|
PY |
|
} |
|
|
|
idekit_path() { echo "$1/Contents/Frameworks/IDEKit.framework/Versions/A/IDEKit"; } |
|
xcode_main_path() { echo "$1/Contents/MacOS/Xcode"; } |
|
plugins_dir() { echo "$1/Contents/PlugIns"; } |
|
xcode_plist() { echo "$1/Contents/Info.plist"; } |
|
|
|
resolve_xcode() { |
|
local p="${1:-$DEFAULT_XCODE}" |
|
p="${p%/}" |
|
[[ -d "$p" ]] || die "Xcode app not found: $p" |
|
[[ -x "$p/Contents/MacOS/Xcode" ]] || die "not an Xcode app (missing MacOS/Xcode): $p" |
|
echo "$p" |
|
} |
|
|
|
read_ls_min() { |
|
local plist |
|
plist="$(xcode_plist "$1")" |
|
/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$plist" 2>/dev/null || echo "ABSENT" |
|
} |
|
|
|
read_identity() { |
|
local xcode="$1" |
|
local plist |
|
plist="$(xcode_plist "$xcode")" |
|
local vers cfbuild pbuild |
|
vers=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$plist" 2>/dev/null || echo "") |
|
cfbuild=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$plist" 2>/dev/null || echo "") |
|
pbuild=$(/usr/libexec/PlistBuddy -c 'Print :DTXcodeBuild' "$plist" 2>/dev/null || true) |
|
if [[ -z "$pbuild" ]]; then |
|
pbuild=$(/usr/libexec/PlistBuddy -c 'Print :ProductBuildVersion' "$plist" 2>/dev/null || true) |
|
fi |
|
if [[ -z "$pbuild" ]]; then |
|
pbuild=$(/usr/libexec/PlistBuddy -c 'Print :ProductBuildVersion' \ |
|
"$xcode/Contents/Developer/AppVersion.plist" 2>/dev/null || true) |
|
fi |
|
if [[ -z "$pbuild" && -x "$xcode/Contents/Developer/usr/bin/xcodebuild" ]]; then |
|
pbuild=$(DEVELOPER_DIR="$xcode/Contents/Developer" "$xcode/Contents/Developer/usr/bin/xcodebuild" -version 2>/dev/null | awk '/^Build/{print $3}' || true) |
|
fi |
|
echo "VERSION=$vers CFBundleVersion=$cfbuild ProductBuildVersion=$pbuild" |
|
} |
|
|
|
check_identity() { |
|
local xcode="$1" |
|
local id vers pbuild xbbuild |
|
id=$(read_identity "$xcode") |
|
vers=$(echo "$id" | sed -n 's/.*VERSION=\([^ ]*\).*/\1/p') |
|
pbuild=$(echo "$id" | sed -n 's/.*ProductBuildVersion=\([^ ]*\).*/\1/p') |
|
xbbuild="" |
|
if [[ -x "$xcode/Contents/Developer/usr/bin/xcodebuild" ]]; then |
|
xbbuild=$(DEVELOPER_DIR="$xcode/Contents/Developer" "$xcode/Contents/Developer/usr/bin/xcodebuild" -version 2>/dev/null | awk '/^Build/{print $3}' || true) |
|
fi |
|
[[ "$vers" == "$EXPECTED_VERSION" ]] || die "unsupported CFBundleShortVersionString '$vers' (need $EXPECTED_VERSION)" |
|
local ok=0 |
|
[[ "$xbbuild" == "$EXPECTED_BUILD" ]] && ok=1 |
|
[[ "$pbuild" == "$EXPECTED_BUILD" || "$pbuild" == "$EXPECTED_BUILD_ALT" ]] && ok=1 |
|
[[ "$ok" == "1" ]] || die "unsupported build identity pbuild='$pbuild' xcodebuild='$xbbuild' (need $EXPECTED_BUILD or $EXPECTED_BUILD_ALT)" |
|
local main arch |
|
main=$(xcode_main_path "$xcode") |
|
arch=$(lipo -archs "$main" 2>/dev/null || file "$main") |
|
echo "$arch" | grep -q "arm64" || die "unsupported architecture: $arch" |
|
log "identity OK: Xcode $vers (pbuild=$pbuild xcodebuild=$xbbuild) arm64" |
|
} |
|
|
|
check_host() { |
|
local ver build |
|
ver=$(sw_vers -productVersion 2>/dev/null || echo "?") |
|
build=$(sw_vers -buildVersion 2>/dev/null || echo "?") |
|
log "host macOS $ver ($build) — known-good is 15.7.3 (24G419); other hosts are unsupported" |
|
} |
|
|
|
idekit_sha() { shasum -a 256 "$(idekit_path "$1")" | awk '{print $1}'; } |
|
main_sha() { shasum -a 256 "$(xcode_main_path "$1")" | awk '{print $1}'; } |
|
|
|
# Final production patch state (4 sites). |
|
# VANILLA = all 4 sites vanilla |
|
# LEGACY3 = patches 1-3 applied; 4 still vanilla (upgrade path) |
|
# PATCH5_ONLY_REMNANT = 1-4 patched but Patch5 still NOP (pre-minimization leftover) |
|
# PATCHED_FINAL = exactly the 4 production sites patched; Patch5 VANILLA |
|
# UNKNOWN = anything else |
|
classify_idekit_patches() { |
|
local xcode="$1" idk b1 b2 b3 b4 b5 |
|
idk=$(idekit_path "$xcode") |
|
b1=$(hex_to_upper "$(read_bytes "$idk" "$PATCH1_OFF")") |
|
b2=$(hex_to_upper "$(read_bytes "$idk" "$PATCH2_OFF")") |
|
b3=$(hex_to_upper "$(read_bytes "$idk" "$PATCH3_OFF")") |
|
b4=$(hex_to_upper "$(read_bytes "$idk" "$PATCH4_OFF")") |
|
# Historical Patch5 site — production requires VANILLA here |
|
b5=$(hex_to_upper "$(read_bytes "$idk" "0x373e4c")") |
|
local p1v p2v p3v p4v p5v p1n p2n p3n p4n p5n |
|
p1v=$(hex_to_upper "$PATCH1_VAN"); p2v=$(hex_to_upper "$PATCH2_VAN"); p3v=$(hex_to_upper "$PATCH3_VAN") |
|
p4v=$(hex_to_upper "$PATCH4_VAN"); p5v=$(hex_to_upper "60030034") |
|
p1n=$(hex_to_upper "$PATCH1_NEW"); p2n=$(hex_to_upper "$PATCH2_NEW"); p3n=$(hex_to_upper "$PATCH3_NEW") |
|
p4n=$(hex_to_upper "$PATCH4_NEW"); p5n=$(hex_to_upper "1f2003d5") |
|
if [[ "$b1" == "$p1v" && "$b2" == "$p2v" && "$b3" == "$p3v" && "$b4" == "$p4v" && "$b5" == "$p5v" ]]; then |
|
echo VANILLA |
|
elif [[ "$b1" == "$p1n" && "$b2" == "$p2n" && "$b3" == "$p3n" && "$b4" == "$p4n" && "$b5" == "$p5v" ]]; then |
|
echo PATCHED_FINAL |
|
elif [[ "$b1" == "$p1n" && "$b2" == "$p2n" && "$b3" == "$p3n" && "$b4" == "$p4n" && "$b5" == "$p5n" ]]; then |
|
echo PATCH5_ONLY_REMNANT |
|
elif [[ "$b1" == "$p1n" && "$b2" == "$p2n" && "$b3" == "$p3n" && "$b4" == "$p4v" && "$b5" == "$p5v" ]]; then |
|
echo LEGACY3 |
|
elif [[ "$b1" == "$p1n" && "$b2" == "$p2n" && "$b3" == "$p3n" && "$b4" == "$p4v" && "$b5" == "$p5n" ]]; then |
|
echo LEGACY3_PATCH5 |
|
else |
|
echo UNKNOWN |
|
fi |
|
} |
|
|
|
# Production-valid IDEKit byte state is PATCHED_FINAL only. |
|
# PATCH5_ONLY_REMNANT is accepted as an intermediate the patcher can minimize. |
|
is_production_idekit() { |
|
[[ "$1" == "PATCHED_FINAL" ]] |
|
} |
|
|
|
verify_patch_sites() { |
|
local xcode="$1" |
|
local idk b1 b2 b3 b4 b5 cls |
|
idk=$(idekit_path "$xcode") |
|
b1=$(read_bytes "$idk" "$PATCH1_OFF") |
|
b2=$(read_bytes "$idk" "$PATCH2_OFF") |
|
b3=$(read_bytes "$idk" "$PATCH3_OFF") |
|
b4=$(read_bytes "$idk" "$PATCH4_OFF") |
|
b5=$(read_bytes "$idk" "0x373e4c") |
|
cls=$(classify_idekit_patches "$xcode") |
|
echo "IDEKit@${PATCH1_OFF}=${b1} (van=${PATCH1_VAN} new=${PATCH1_NEW}) REQUIRED" |
|
echo "IDEKit@${PATCH2_OFF}=${b2} (van=${PATCH2_VAN} new=${PATCH2_NEW}) REQUIRED" |
|
echo "IDEKit@${PATCH3_OFF}=${b3} (van=${PATCH3_VAN} new=${PATCH3_NEW}) REQUIRED" |
|
echo "IDEKit@${PATCH4_OFF}=${b4} (van=${PATCH4_VAN} new=${PATCH4_NEW}) REQUIRED" |
|
echo "IDEKit@0x373e4c=${b5} (van=60030034 new=1f2003d5) NOT_REQUIRED_PRODUCTION (must be vanilla)" |
|
echo "IDEKIT_PATCH_STATE=${cls}" |
|
echo "FINAL_PATCH_COUNT=${FINAL_PATCH_COUNT}" |
|
} |
|
|
|
apply_patch_site() { |
|
local file="$1" off="$2" van="$3" new="$4" label="$5" |
|
local cur van_u new_u |
|
cur=$(hex_to_upper "$(read_bytes "$file" "$off")") |
|
van_u=$(hex_to_upper "$van"); new_u=$(hex_to_upper "$new") |
|
if [[ "$cur" == "$new_u" ]]; then |
|
log "$label $off already $new" |
|
return 0 |
|
fi |
|
if [[ "$cur" != "$van_u" ]]; then |
|
die "$label $off unexpected bytes $cur (want van=$van_u or new=$new_u)" |
|
fi |
|
log "write $label $off $van -> $new" |
|
write_bytes "$file" "$off" "$new" |
|
} |
|
|
|
restore_patch_site_to_vanilla() { |
|
local file="$1" off="$2" van="$3" new="$4" label="$5" |
|
local cur van_u new_u |
|
cur=$(hex_to_upper "$(read_bytes "$file" "$off")") |
|
van_u=$(hex_to_upper "$van"); new_u=$(hex_to_upper "$new") |
|
if [[ "$cur" == "$van_u" ]]; then |
|
log "$label $off already vanilla" |
|
return 0 |
|
fi |
|
if [[ "$cur" != "$new_u" ]]; then |
|
die "$label $off unexpected bytes $cur (want van=$van_u or new=$new_u for minimize)" |
|
fi |
|
log "restore $label $off $new -> $van (not required in production)" |
|
write_bytes "$file" "$off" "$van" |
|
} |
|
|
|
backup_plugin() { |
|
local xcode="$1" name="$2" |
|
local src dst |
|
src="$(plugins_dir "$xcode")/$name" |
|
dst="${PLUGIN_BACKUP_DIR}/${name}" |
|
if [[ -e "$src" && -e "$dst" ]]; then |
|
die "plugin exists at both original and backup: $src AND $dst — resolve manually" |
|
fi |
|
if [[ -e "$src" ]]; then |
|
log "backup+disable plugin: $src -> $dst" |
|
mv "$src" "$dst" |
|
echo "plugin_moved=$name" |
|
elif [[ -e "$dst" ]]; then |
|
log "plugin already disabled (backup present): $name" |
|
echo "plugin_already_disabled=$name" |
|
else |
|
die "plugin missing at both original and backup: $name" |
|
fi |
|
} |
|
|
|
disable_plugins() { |
|
local xcode="$1" |
|
local p |
|
for p in "${PLUGINS[@]}"; do |
|
backup_plugin "$xcode" "$p" |
|
done |
|
} |
|
|
|
# Minimal proven signing: ad-hoc IDEKit + outer bundle seal (no --deep) |
|
extract_entitlements() { |
|
local xcode="$1" |
|
local out="$STATE_DIR/main-entitlements.plist" |
|
codesign -d --entitlements :- "$(xcode_main_path "$xcode")" 2>/dev/null >"$out" || true |
|
if [[ ! -s "$out" ]]; then |
|
python3 - <<PY |
|
import plistlib |
|
from pathlib import Path |
|
Path("$out").write_bytes(plistlib.dumps({ |
|
"com.apple.security.get-task-allow": True, |
|
"com.apple.security.cs.disable-library-validation": True, |
|
})) |
|
print("wrote reduced entitlements") |
|
PY |
|
fi |
|
echo "$out" |
|
} |
|
|
|
sign_modified_components() { |
|
local xcode="$1" |
|
local fw ents |
|
fw="$xcode/Contents/Frameworks/IDEKit.framework" |
|
ents=$(extract_entitlements "$xcode") |
|
log "ad-hoc sign IDEKit (no --deep)" |
|
codesign --force --sign - --identifier com.apple.dt.IDEKit "$fw" 2>&1 | tail -3 |
|
log "ad-hoc seal outer app (no --deep)" |
|
codesign --force --sign - --identifier com.apple.dt.Xcode \ |
|
--entitlements "$ents" "$xcode" 2>&1 | tail -3 |
|
} |
|
|
|
# Integrated into normal patch flow — required for PATCHED_VALID. |
|
apply_ls_minimum() { |
|
local xcode="$1" |
|
local value="${2:-$REQUIRED_LS_MIN}" |
|
local plist before after |
|
plist="$(xcode_plist "$xcode")" |
|
before=$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$plist" 2>/dev/null || echo "ABSENT") |
|
if [[ "$before" == "$value" ]]; then |
|
log "LSMinimumSystemVersion already $value" |
|
return 0 |
|
fi |
|
log "set LSMinimumSystemVersion $before -> $value (required production state)" |
|
/usr/libexec/PlistBuddy -c "Set :LSMinimumSystemVersion $value" "$plist" \ |
|
|| /usr/libexec/PlistBuddy -c "Add :LSMinimumSystemVersion string $value" "$plist" |
|
after=$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$plist") |
|
[[ "$after" == "$value" ]] || die "LSMinimumSystemVersion verify failed: got $after want $value" |
|
echo "LS_MINIMUM=$after" |
|
} |
|
|
|
prepare_cf_home() { |
|
ensure_state |
|
local prefs="$CF_HOME/Library/Preferences/com.apple.dt.Xcode.plist" |
|
mkdir -p "$(dirname "$prefs")" |
|
python3 - <<PY |
|
import plistlib |
|
from pathlib import Path |
|
p=Path("$prefs") |
|
d={} |
|
if p.exists(): |
|
try: |
|
d=plistlib.loads(p.read_bytes()) |
|
except Exception: |
|
d={} |
|
prev=d.get("DVTEdgeOcclusionViewMode") |
|
d["DVTEdgeOcclusionViewMode"]="Empty" |
|
d.setdefault("IDEMostRecentPostFLEToolsVersion", "27.0") |
|
d.setdefault("IDEDidCompleteThemesFLEToolsVersion", "27.0") |
|
d.setdefault("IDELanguageModelSelectedDuringFirstLaunch", 1) |
|
d.setdefault("IDEMetalToolchainSelectedDuringFirstLaunch", 1) |
|
if "IDEMostRecentPostFLEDate" not in d: |
|
d["IDEMostRecentPostFLEDate"] = "2026-09-18 10:34:30 +0000" |
|
p.write_bytes(plistlib.dumps(d)) |
|
print("cf-home edge mode prev=%r now=Empty; FLE-complete flags seeded" % (prev,)) |
|
PY |
|
if ! grep -q '^GLOBAL_DVTEDGE_MODE=' "$MANIFEST" 2>/dev/null; then |
|
local g |
|
g=$(defaults read com.apple.dt.Xcode DVTEdgeOcclusionViewMode 2>/dev/null || echo ABSENT) |
|
echo "GLOBAL_DVTEDGE_MODE=$g" >>"$MANIFEST" |
|
fi |
|
echo "CF_HOME=$CF_HOME" |
|
} |
|
|
|
set_edge_preference() { |
|
prepare_cf_home >/dev/null |
|
log "DVTEdgeOcclusionViewMode=Empty applied in isolated CF home (not a binary patch)" |
|
} |
|
|
|
create_vanilla_backup() { |
|
local xcode="$1" |
|
local dest="${VANILLA_BACKUP_DIR}/Xcode-27.app" |
|
if [[ -d "$dest" ]]; then |
|
log "vanilla backup already exists: $dest" |
|
else |
|
log "creating APFS clonefile backup of vanilla Xcode -> $dest" |
|
if cp -cR "$xcode" "$dest" 2>/dev/null; then |
|
log "APFS clonefile backup OK" |
|
else |
|
die "APFS clonefile failed — refuse silent full copy without user awareness (disk: $(df -h "$HOME" | awk 'NR==2{print $4}') free). Re-run after ensuring space or copy manually." |
|
fi |
|
fi |
|
local bm |
|
bm=$(shasum -a 256 "$dest/Contents/MacOS/Xcode" 2>/dev/null | awk '{print $1}' || echo "") |
|
echo "$bm" >"${STATE_DIR}/vanilla-main.sha256" |
|
log "vanilla backup main sha256=$bm" |
|
[[ -d "$dest/Contents/Frameworks" ]] || die "vanilla backup incomplete" |
|
} |
|
|
|
record_manifest() { |
|
local xcode="$1" action="$2" |
|
ensure_state |
|
{ |
|
echo "ACTION=$action" |
|
echo "XCODE=$xcode" |
|
echo "DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)" |
|
echo "HOST=$(sw_vers -productVersion)-$(sw_vers -buildVersion)" |
|
echo "MAIN_SHA=$(main_sha "$xcode")" |
|
echo "IDEKIT_SHA=$(idekit_sha "$xcode")" |
|
echo "LS_MINIMUM=$(read_ls_min "$xcode")" |
|
echo "FINAL_PATCH_COUNT=$FINAL_PATCH_COUNT" |
|
echo "WELCOME_CLASSIFICATION=WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE" |
|
verify_patch_sites "$xcode" |
|
} >>"$MANIFEST" |
|
} |
|
|
|
cmd_patch() { |
|
local xcode="$1" dry="${2:-0}" |
|
require_command python3 |
|
require_command codesign |
|
require_command shasum |
|
require_command /usr/libexec/PlistBuddy |
|
ensure_state |
|
xcode=$(resolve_xcode "$xcode") |
|
check_host |
|
check_identity "$xcode" |
|
log "target: $xcode" |
|
log "native Welcome is UNSUPPORTED on Sequoia (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE)" |
|
log "final binary patch count: $FINAL_PATCH_COUNT" |
|
|
|
local idk cls mh ih ls |
|
idk=$(idekit_path "$xcode") |
|
[[ -f "$idk" ]] || die "IDEKit not found: $idk" |
|
mh=$(main_sha "$xcode") |
|
ih=$(idekit_sha "$xcode") |
|
ls=$(read_ls_min "$xcode") |
|
log "main sha256=$mh" |
|
log "idekit sha256=$ih" |
|
log "LSMinimumSystemVersion=$ls" |
|
cls=$(classify_idekit_patches "$xcode") |
|
log "IDEKit patch state: $cls" |
|
|
|
if [[ "$cls" == "UNKNOWN" ]]; then |
|
verify_patch_sites "$xcode" >&2 || true |
|
die "IDEKit patch sites in unexpected state — refusing to patch" |
|
fi |
|
if [[ "$cls" == "PATCHED_FINAL" ]]; then |
|
log "IDEKit already in final 4-patch production state — idempotent path" |
|
elif [[ "$cls" == "PATCH5_ONLY_REMNANT" ]]; then |
|
log "IDEKit has obsolete Patch5 NOP — will minimize to final 4-patch set" |
|
elif [[ "$cls" == "LEGACY3" ]]; then |
|
log "IDEKit legacy 3-NOP state — authorized upgrade to final 4 sites" |
|
elif [[ "$cls" == "LEGACY3_PATCH5" ]]; then |
|
log "IDEKit legacy 3-NOP + obsolete Patch5 — upgrade to final 4, restore Patch5 vanilla" |
|
else |
|
if [[ "$mh" != "$EXPECTED_MAIN_SHA" ]]; then |
|
die "main executable hash mismatch (got $mh expected $EXPECTED_MAIN_SHA) — unexpected identity" |
|
fi |
|
if [[ "$ih" != "$EXPECTED_IDEKIT_SHA" ]]; then |
|
die "vanilla IDEKit hash mismatch (got $ih expected $EXPECTED_IDEKIT_SHA) — unexpected identity" |
|
fi |
|
fi |
|
|
|
if [[ "$dry" == "1" ]]; then |
|
echo "DRY-RUN plan for $xcode" |
|
echo " state: $cls" |
|
echo " final production patches: $FINAL_PATCH_COUNT" |
|
case "$cls" in |
|
VANILLA) |
|
echo " would write $PATCH1_OFF $PATCH1_VAN -> $PATCH1_NEW" |
|
echo " would write $PATCH2_OFF $PATCH2_VAN -> $PATCH2_NEW" |
|
echo " would write $PATCH3_OFF $PATCH3_VAN -> $PATCH3_NEW" |
|
echo " would write $PATCH4_OFF $PATCH4_VAN -> $PATCH4_NEW" |
|
;; |
|
LEGACY3) |
|
echo " IDEKit patches 1-3 already applied" |
|
echo " would write $PATCH4_OFF $PATCH4_VAN -> $PATCH4_NEW" |
|
;; |
|
LEGACY3_PATCH5) |
|
echo " IDEKit patches 1-3 already applied" |
|
echo " would write $PATCH4_OFF $PATCH4_VAN -> $PATCH4_NEW" |
|
echo " would restore 0x373e4c 1f2003d5 -> 60030034 (remove obsolete Patch5)" |
|
;; |
|
PATCH5_ONLY_REMNANT) |
|
echo " would restore 0x373e4c 1f2003d5 -> 60030034 (remove obsolete Patch5)" |
|
;; |
|
PATCHED_FINAL) |
|
echo " IDEKit already final 4-patch state" |
|
;; |
|
esac |
|
echo " would set LSMinimumSystemVersion=$REQUIRED_LS_MIN (currently $ls)" |
|
local p |
|
for p in "${PLUGINS[@]}"; do |
|
if [[ -e "$(plugins_dir "$xcode")/$p" ]]; then |
|
echo " would move plugin $p -> $PLUGIN_BACKUP_DIR/$p" |
|
elif [[ -e "$PLUGIN_BACKUP_DIR/$p" ]]; then |
|
echo " plugin $p already disabled" |
|
else |
|
echo " plugin $p MISSING — would fail" |
|
fi |
|
done |
|
echo " would prepare CF home with DVTEdgeOcclusionViewMode=Empty" |
|
echo " would ad-hoc sign IDEKit + outer app (no --deep)" |
|
echo " would ensure durable vanilla backup under $VANILLA_BACKUP_DIR" |
|
return 0 |
|
fi |
|
|
|
if [[ "$cls" == "VANILLA" ]]; then |
|
create_vanilla_backup "$xcode" |
|
fi |
|
|
|
prepare_cf_home >/dev/null |
|
extract_entitlements "$xcode" >/dev/null |
|
|
|
if [[ "$cls" == "VANILLA" ]]; then |
|
log "pre-pass: ad-hoc sign IDEKit to rewrite compressed nested Mach-O" |
|
codesign --force --sign - --identifier com.apple.dt.IDEKit \ |
|
"$xcode/Contents/Frameworks/IDEKit.framework" >/dev/null 2>&1 || true |
|
log "applying 4 authorized IDEKit sites (seek-write, fail-closed)" |
|
apply_patch_site "$idk" "$PATCH1_OFF" "$PATCH1_VAN" "$PATCH1_NEW" "patch1" |
|
apply_patch_site "$idk" "$PATCH2_OFF" "$PATCH2_VAN" "$PATCH2_NEW" "patch2" |
|
apply_patch_site "$idk" "$PATCH3_OFF" "$PATCH3_VAN" "$PATCH3_NEW" "patch3" |
|
apply_patch_site "$idk" "$PATCH4_OFF" "$PATCH4_VAN" "$PATCH4_NEW" "patch4" |
|
elif [[ "$cls" == "LEGACY3" ]]; then |
|
log "upgrading LEGACY3 -> PATCHED_FINAL (site 4 only)" |
|
apply_patch_site "$idk" "$PATCH4_OFF" "$PATCH4_VAN" "$PATCH4_NEW" "patch4" |
|
elif [[ "$cls" == "LEGACY3_PATCH5" ]]; then |
|
log "upgrading LEGACY3_PATCH5 -> PATCHED_FINAL (site 4 + remove obsolete Patch5)" |
|
apply_patch_site "$idk" "$PATCH4_OFF" "$PATCH4_VAN" "$PATCH4_NEW" "patch4" |
|
restore_patch_site_to_vanilla "$idk" "0x373e4c" "60030034" "1f2003d5" "patch5_obsolete" |
|
elif [[ "$cls" == "PATCH5_ONLY_REMNANT" ]]; then |
|
log "minimizing PATCH5_ONLY_REMNANT -> PATCHED_FINAL" |
|
restore_patch_site_to_vanilla "$idk" "0x373e4c" "60030034" "1f2003d5" "patch5_obsolete" |
|
fi |
|
|
|
disable_plugins "$xcode" |
|
set_edge_preference |
|
apply_ls_minimum "$xcode" "$REQUIRED_LS_MIN" |
|
sign_modified_components "$xcode" |
|
|
|
cls=$(classify_idekit_patches "$xcode") |
|
is_production_idekit "$cls" || die "post-patch classification=$cls (expected PATCHED_FINAL)" |
|
ls=$(read_ls_min "$xcode") |
|
[[ "$ls" == "$REQUIRED_LS_MIN" ]] || die "post-patch LSMinimumSystemVersion=$ls (expected $REQUIRED_LS_MIN)" |
|
local p |
|
for p in "${PLUGINS[@]}"; do |
|
[[ ! -e "$(plugins_dir "$xcode")/$p" ]] || die "plugin still present: $p" |
|
done |
|
record_manifest "$xcode" "patch" |
|
log "PATCH COMPLETE: $xcode" |
|
log "LSMinimumSystemVersion=$ls" |
|
log "IDEKit state=PATCHED_FINAL patches=$FINAL_PATCH_COUNT" |
|
} |
|
|
|
cmd_verify() { |
|
local xcode="$1" |
|
xcode=$(resolve_xcode "$xcode") |
|
check_identity "$xcode" |
|
local cls b1 b2 b3 b4 b5 ls ok=1 |
|
b1=$(read_bytes "$(idekit_path "$xcode")" "$PATCH1_OFF") |
|
b2=$(read_bytes "$(idekit_path "$xcode")" "$PATCH2_OFF") |
|
b3=$(read_bytes "$(idekit_path "$xcode")" "$PATCH3_OFF") |
|
b4=$(read_bytes "$(idekit_path "$xcode")" "$PATCH4_OFF") |
|
b5=$(read_bytes "$(idekit_path "$xcode")" "0x373e4c") |
|
ls=$(read_ls_min "$xcode") |
|
cls=$(classify_idekit_patches "$xcode") |
|
echo "VERIFY Xcode=$xcode" |
|
echo " version/build: $(read_identity "$xcode")" |
|
echo " arch: arm64 (required)" |
|
echo " final_patch_count: $FINAL_PATCH_COUNT" |
|
echo " patch1 ${PATCH1_OFF}=${b1} (want $PATCH1_NEW)" |
|
echo " patch2 ${PATCH2_OFF}=${b2} (want $PATCH2_NEW)" |
|
echo " patch3 ${PATCH3_OFF}=${b3} (want $PATCH3_NEW)" |
|
echo " patch4 ${PATCH4_OFF}=${b4} (want $PATCH4_NEW)" |
|
echo " patch5_site 0x373e4c=${b5} (want vanilla 60030034 — NOT required production patch)" |
|
echo " idekit_state=$cls" |
|
echo " LSMinimumSystemVersion=$ls (want $REQUIRED_LS_MIN)" |
|
local p |
|
for p in "${PLUGINS[@]}"; do |
|
if [[ -e "$(plugins_dir "$xcode")/$p" ]]; then |
|
echo " plugin $p: STILL PRESENT — FAIL" |
|
ok=0 |
|
else |
|
echo " plugin $p: disabled" |
|
fi |
|
done |
|
echo " cf-home edge mode:" |
|
prepare_cf_home >/dev/null |
|
/usr/libexec/PlistBuddy -c 'Print :DVTEdgeOcclusionViewMode' \ |
|
"$CF_HOME/Library/Preferences/com.apple.dt.Xcode.plist" 2>/dev/null | sed 's/^/ /' || echo " (missing)" |
|
local edge |
|
edge=$(/usr/libexec/PlistBuddy -c 'Print :DVTEdgeOcclusionViewMode' \ |
|
"$CF_HOME/Library/Preferences/com.apple.dt.Xcode.plist" 2>/dev/null || echo "?") |
|
[[ "$edge" == "Empty" ]] || { echo " DVTEdge: FAIL want Empty got $edge"; ok=0; } |
|
echo " signing:" |
|
codesign -dv --verbose=2 "$xcode" 2>&1 | grep -E 'Identifier=|flags=|TeamIdentifier=|Signature=' | sed 's/^/ /' || true |
|
echo " toolchain:" |
|
DEVELOPER_DIR="$xcode/Contents/Developer" xcodebuild -version 2>&1 | sed 's/^/ /' || ok=0 |
|
# swift may live under toolchain when Developer/usr/bin/xcrun is absent on some clones |
|
local swiftv="" |
|
if [[ -x "$xcode/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift" ]]; then |
|
swiftv=$("$xcode/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift" --version 2>&1 | head -1 || true) |
|
elif command -v xcrun >/dev/null 2>&1; then |
|
swiftv=$(DEVELOPER_DIR="$xcode/Contents/Developer" xcrun swift --version 2>&1 | head -1 || true) |
|
fi |
|
echo " $swiftv" |
|
echo " welcome: UNSUPPORTED (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE) — not a verify failure" |
|
is_production_idekit "$cls" || { echo "VERIFY FAIL: IDEKit not in final 4-patch state (got $cls)"; ok=0; } |
|
[[ "$ls" == "$REQUIRED_LS_MIN" ]] || { echo "VERIFY FAIL: LSMinimumSystemVersion=$ls want $REQUIRED_LS_MIN"; ok=0; } |
|
[[ "$ok" == "1" ]] || { echo "VERIFY FAIL"; return 1; } |
|
echo "VERIFY PASS" |
|
} |
|
|
|
cmd_status() { |
|
local xcode="$1" |
|
xcode=$(resolve_xcode "$xcode") |
|
local overall cls ls |
|
local id |
|
id=$(read_identity "$xcode") |
|
cls=$(classify_idekit_patches "$xcode") |
|
ls=$(read_ls_min "$xcode") |
|
local pmiss=0 p edge_ok=1 sign_ok=1 cli_ok=1 |
|
for p in "${PLUGINS[@]}"; do |
|
[[ -e "$(plugins_dir "$xcode")/$p" ]] && pmiss=1 |
|
done |
|
prepare_cf_home >/dev/null |
|
local cfe |
|
cfe=$(/usr/libexec/PlistBuddy -c 'Print :DVTEdgeOcclusionViewMode' \ |
|
"$CF_HOME/Library/Preferences/com.apple.dt.Xcode.plist" 2>/dev/null || echo "?") |
|
[[ "$cfe" == "Empty" ]] || edge_ok=0 |
|
local flags |
|
flags=$(codesign -dv "$xcode" 2>&1 | grep -E 'flags=' || true) |
|
echo "$flags" | grep -q "adhoc" || sign_ok=0 |
|
local xb |
|
xb=$(DEVELOPER_DIR="$xcode/Contents/Developer" xcodebuild -version 2>/dev/null | awk '/^Build/{print $3}' || true) |
|
[[ "$xb" == "$EXPECTED_BUILD" ]] || cli_ok=0 |
|
local swiftv |
|
if [[ -x "$xcode/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift" ]]; then |
|
swiftv=$("$xcode/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift" --version 2>&1 | head -1 || echo "?") |
|
else |
|
swiftv=$(DEVELOPER_DIR="$xcode/Contents/Developer" xcrun swift --version 2>/dev/null | head -1 || echo "?") |
|
fi |
|
if [[ "$cls" == "PATCHED_FINAL" && "$pmiss" == "0" && "$ls" == "$REQUIRED_LS_MIN" && "$edge_ok" == "1" && "$sign_ok" == "1" && "$cli_ok" == "1" ]]; then |
|
overall="PATCHED_VALID" |
|
elif [[ "$cls" == "PATCHED_FINAL" && "$ls" != "$REQUIRED_LS_MIN" ]]; then |
|
overall="PATCHED_BUT_LS_NOT_PRODUCTION" |
|
elif [[ "$cls" == "PATCH5_ONLY_REMNANT" || "$cls" == "LEGACY3_PATCH5" ]]; then |
|
overall="NEEDS_PATCH5_MINIMIZE" |
|
elif [[ "$cls" == "LEGACY3" ]]; then |
|
overall="LEGACY3_NEEDS_UPGRADE" |
|
elif [[ "$cls" == "VANILLA" ]]; then |
|
local mh ih |
|
mh=$(main_sha "$xcode"); ih=$(idekit_sha "$xcode") |
|
if [[ "$mh" == "$EXPECTED_MAIN_SHA" && "$ih" == "$EXPECTED_IDEKIT_SHA" ]]; then |
|
overall="VANILLA_SUPPORTED_INPUT" |
|
else |
|
overall="BROKEN_OR_UNKNOWN_STATE" |
|
fi |
|
else |
|
overall="PARTIALLY_PATCHED" |
|
fi |
|
local edge |
|
edge=$(defaults read com.apple.dt.Xcode DVTEdgeOcclusionViewMode 2>/dev/null || echo "ABSENT(global)") |
|
echo "Xcode path: $xcode" |
|
echo "Version/build: $id" |
|
echo "Host: $(sw_vers -productVersion) ($(sw_vers -buildVersion)) arm64" |
|
echo "Final patch count: $FINAL_PATCH_COUNT" |
|
echo "Patch 1: ${PATCH1_OFF} $(read_bytes "$(idekit_path "$xcode")" "$PATCH1_OFF") REQUIRED" |
|
echo "Patch 2: ${PATCH2_OFF} $(read_bytes "$(idekit_path "$xcode")" "$PATCH2_OFF") REQUIRED" |
|
echo "Patch 3: ${PATCH3_OFF} $(read_bytes "$(idekit_path "$xcode")" "$PATCH3_OFF") REQUIRED" |
|
echo "Patch 4: ${PATCH4_OFF} $(read_bytes "$(idekit_path "$xcode")" "$PATCH4_OFF") REQUIRED" |
|
echo "Patch 5 site: 0x373e4c $(read_bytes "$(idekit_path "$xcode")" "0x373e4c") NOT_REQUIRED (must be vanilla)" |
|
echo "IDEKit state: $cls" |
|
echo "LSMinimumSystemVersion: $ls (required $REQUIRED_LS_MIN)" |
|
echo "DVTEdge mode global: $edge" |
|
echo "DVTEdge mode launch CF-home: $cfe" |
|
for p in "${PLUGINS[@]}"; do |
|
if [[ -e "$(plugins_dir "$xcode")/$p" ]]; then |
|
echo "Plugin $p: enabled" |
|
else |
|
echo "Plugin $p: disabled (backup=$PLUGIN_BACKUP_DIR/$p)" |
|
fi |
|
done |
|
echo "Signing: $(codesign -dv "$xcode" 2>&1 | grep -E 'flags=|TeamIdentifier=' | tr '\n' ' ')" |
|
echo "Swift: $swiftv" |
|
echo "xcodebuild build id: $xb" |
|
echo "Native Welcome: UNSUPPORTED (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE)" |
|
echo "Default launch: durable helper editor" |
|
echo "Overall: $overall" |
|
} |
|
|
|
# Native PID-specific AppKit/CGWindow helper (no python PyObjC dependency). |
|
# Activation/reopen must be PID-targeted only — never open -b com.apple.dt.Xcode. |
|
write_gui_helper_source() { |
|
local src="$HELPERS_DIR/xcode27-gui-helper.swift" |
|
mkdir -p "$HELPERS_DIR" |
|
cat >"$src" <<'SWIFT' |
|
import AppKit |
|
import CoreGraphics |
|
import Foundation |
|
|
|
func die(_ msg: String) -> Never { |
|
FileHandle.standardError.write((msg + "\n").data(using: .utf8)!) |
|
exit(2) |
|
} |
|
|
|
func dumpWindows(pid: Int32, opts: CGWindowListOption, label: String) -> (visible: Int, total: Int) { |
|
guard let list = CGWindowListCopyWindowInfo(opts, kCGNullWindowID) as? [[String: Any]] else { |
|
print("WINDOW_STATUS=LIST_UNAVAILABLE pid=\(pid) list=\(label)") |
|
return (0, 0) |
|
} |
|
var visible = 0 |
|
var total = 0 |
|
for w in list { |
|
guard let owner = w[kCGWindowOwnerPID as String] as? Int, Int32(owner) == pid else { continue } |
|
total += 1 |
|
let layer = (w[kCGWindowLayer as String] as? Int) ?? -1 |
|
let alpha = (w[kCGWindowAlpha as String] as? Double) ?? (w[kCGWindowAlpha as String] as? Int).map(Double.init) ?? 1 |
|
let onscreen = (w[kCGWindowIsOnscreen as String] as? Bool) ?? false |
|
let b = w[kCGWindowBounds as String] as? [String: Any] ?? [:] |
|
let width = (b["Width"] as? Double) ?? (b["Width"] as? Int).map(Double.init) ?? 0 |
|
let height = (b["Height"] as? Double) ?? (b["Height"] as? Int).map(Double.init) ?? 0 |
|
let x = (b["X"] as? Double) ?? (b["X"] as? Int).map(Double.init) ?? 0 |
|
let y = (b["Y"] as? Double) ?? (b["Y"] as? Int).map(Double.init) ?? 0 |
|
let name = (w[kCGWindowName as String] as? String) ?? "" |
|
let winid = (w[kCGWindowNumber as String] as? Int) ?? -1 |
|
print("WINDOW pid=\(pid) list=\(label) id=\(winid) name=\(name) width=\(Int(width)) height=\(Int(height)) x=\(Int(x)) y=\(Int(y)) layer=\(layer) alpha=\(alpha) onscreen=\(onscreen)") |
|
if layer == 0 && width >= 400 && height >= 300 && onscreen && alpha > 0.05 { |
|
visible += 1 |
|
} |
|
} |
|
return (visible, total) |
|
} |
|
|
|
let args = CommandLine.arguments |
|
guard args.count >= 3 else { die("usage: xcode27-gui-helper <activate|windows|windows-all|reopen|frontmost> <pid>") } |
|
let mode = args[1] |
|
guard let pid = Int32(args[2]) else { die("invalid pid: \(args[2])") } |
|
|
|
switch mode { |
|
case "activate": |
|
guard let app = NSRunningApplication(processIdentifier: pid) else { |
|
print("ACTIVATE_RESULT=NO_APP pid=\(pid)") |
|
exit(3) |
|
} |
|
let ok = app.activate(options: [.activateIgnoringOtherApps]) |
|
usleep(300_000) |
|
print("ACTIVATE_RESULT=\(ok ? "OK" : "FAILED") pid=\(pid) name=\(app.localizedName ?? "?") frontmost=\(app.isActive) bundle=\(app.bundleURL?.path ?? "?")") |
|
case "frontmost": |
|
guard let app = NSRunningApplication(processIdentifier: pid) else { |
|
print("FRONTMOST_RESULT=NO_APP pid=\(pid)") |
|
exit(3) |
|
} |
|
print("FRONTMOST_RESULT pid=\(pid) active=\(app.isActive) finishedLaunching=\(app.isFinishedLaunching) hidden=\(app.isHidden) terminated=\(app.isTerminated) bundle=\(app.bundleURL?.path ?? "?")") |
|
case "windows": |
|
let (visible, total) = dumpWindows(pid: pid, opts: [.optionOnScreenOnly, .excludeDesktopElements], label: "onscreen") |
|
if visible > 0 { |
|
print("WINDOW_STATUS=WINDOW_VISIBLE pid=\(pid) visible_count=\(visible) owned_count=\(total)") |
|
exit(0) |
|
} else { |
|
print("WINDOW_STATUS=WINDOW_NOT_DETECTED pid=\(pid) visible_count=0 owned_count=\(total)") |
|
exit(5) |
|
} |
|
case "windows-all": |
|
let on = dumpWindows(pid: pid, opts: [.optionOnScreenOnly, .excludeDesktopElements], label: "onscreen") |
|
let all = dumpWindows(pid: pid, opts: [.optionAll, .excludeDesktopElements], label: "all") |
|
print("WINDOW_SUMMARY pid=\(pid) onscreen_visible=\(on.visible) onscreen_owned=\(on.total) all_owned=\(all.total)") |
|
if on.visible > 0 { |
|
print("WINDOW_STATUS=WINDOW_VISIBLE pid=\(pid) visible_count=\(on.visible) owned_count=\(on.total)") |
|
exit(0) |
|
} |
|
print("WINDOW_STATUS=WINDOW_NOT_DETECTED pid=\(pid) visible_count=0 owned_count=\(on.total)") |
|
exit(5) |
|
case "reopen": |
|
guard let app = NSRunningApplication(processIdentifier: pid) else { |
|
print("REOPEN_RESULT=NO_APP pid=\(pid)") |
|
exit(3) |
|
} |
|
_ = app.activate(options: [.activateIgnoringOtherApps]) |
|
usleep(200_000) |
|
let target = NSAppleEventDescriptor(processIdentifier: pid) |
|
let event = NSAppleEventDescriptor( |
|
eventClass: AEEventClass(kCoreEventClass), |
|
eventID: AEEventID(kAEReopenApplication), |
|
targetDescriptor: target, |
|
returnID: AEReturnID(kAutoGenerateReturnID), |
|
transactionID: AETransactionID(kAnyTransactionID) |
|
) |
|
var sendOK = false |
|
var sendErr = "none" |
|
do { |
|
_ = try event.sendEvent(options: .noReply, timeout: 5.0) |
|
sendOK = true |
|
} catch { |
|
sendErr = String(describing: error) |
|
} |
|
usleep(400_000) |
|
print("REOPEN_RESULT=\(sendOK ? "SENT" : "FAILED") pid=\(pid) error=\(sendErr) active=\(app.isActive) bundle=\(app.bundleURL?.path ?? "?")") |
|
default: |
|
die("unknown mode \(mode)") |
|
} |
|
SWIFT |
|
echo "$src" |
|
} |
|
|
|
ensure_gui_helper() { |
|
ensure_state |
|
local src |
|
src=$(write_gui_helper_source) |
|
local rebuild=0 |
|
if [[ ! -x "$GUI_HELPER" ]]; then |
|
rebuild=1 |
|
elif [[ -f "$src" && "$src" -nt "$GUI_HELPER" ]]; then |
|
rebuild=1 |
|
fi |
|
if [[ "$rebuild" == "0" ]]; then |
|
return 0 |
|
fi |
|
log "compiling GUI helper -> $GUI_HELPER" |
|
local xc="${1:-$DEFAULT_XCODE}" |
|
local tc="$xc/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc" |
|
local sdk="" |
|
local sdkroot="$xc/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs" |
|
sdk=$(ls -d "$sdkroot"/MacOSX*.sdk 2>/dev/null | tail -1 || true) |
|
local target="arm64-apple-macos12.0" |
|
: >"$HELPERS_DIR/compile.log" |
|
if [[ -x "$tc" && -n "$sdk" ]]; then |
|
DEVELOPER_DIR="$xc/Contents/Developer" \ |
|
"$tc" -sdk "$sdk" -target "$target" -O "$src" -o "$GUI_HELPER" \ |
|
>>"$HELPERS_DIR/compile.log" 2>&1 && return 0 |
|
fi |
|
if DEVELOPER_DIR="$xc/Contents/Developer" xcrun -f swiftc >/dev/null 2>&1; then |
|
DEVELOPER_DIR="$xc/Contents/Developer" \ |
|
xcrun swiftc ${sdk:+-sdk "$sdk"} -target "$target" -O "$src" -o "$GUI_HELPER" \ |
|
>>"$HELPERS_DIR/compile.log" 2>&1 && return 0 |
|
fi |
|
# Fallback: any host xcode-select toolchain with a visible SDK |
|
local host_sdk="" |
|
host_sdk=$(xcrun --show-sdk-path 2>/dev/null || true) |
|
if xcrun swiftc ${host_sdk:+-sdk "$host_sdk"} -O "$src" -o "$GUI_HELPER" \ |
|
>>"$HELPERS_DIR/compile.log" 2>&1; then |
|
return 0 |
|
fi |
|
# Last resort: keep a previously proven helper rather than blocking launch |
|
if [[ -x "$GUI_HELPER" ]]; then |
|
log "WARNING: GUI helper rebuild failed; reusing existing binary $GUI_HELPER" |
|
log "compile log: $HELPERS_DIR/compile.log" |
|
return 0 |
|
fi |
|
die "failed to compile GUI helper; see $HELPERS_DIR/compile.log" |
|
} |
|
|
|
# Durable bare-launch document. Not ephemeral /tmp; not embedded in Xcode.app. |
|
ensure_default_launch_doc() { |
|
ensure_state |
|
local pkg="$DEFAULT_LAUNCH_PKG" |
|
local main_swift="$DEFAULT_LAUNCH_DOC" |
|
mkdir -p "$pkg/Sources/Xcode27DefaultLaunch" |
|
if [[ ! -f "$pkg/Package.swift" ]]; then |
|
cat >"$pkg/Package.swift" <<'EOF' |
|
// swift-tools-version: 6.0 |
|
// Durable bare-launch target for the Xcode 27 Sequoia patcher. |
|
// Native Welcome is UNSUPPORTED (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE). |
|
// This package exists only so `patch-xcode27-sequoia.sh launch` has a real |
|
// document to open immediately. Keep it outside Xcode.app and production repos. |
|
import PackageDescription |
|
|
|
let package = Package( |
|
name: "Xcode27DefaultLaunch", |
|
targets: [ |
|
.executableTarget( |
|
name: "Xcode27DefaultLaunch", |
|
path: "Sources/Xcode27DefaultLaunch" |
|
) |
|
] |
|
) |
|
EOF |
|
fi |
|
if [[ ! -f "$main_swift" ]]; then |
|
cat >"$main_swift" <<'EOF' |
|
// Xcode 27 Sequoia patcher — durable default-launch document. |
|
// Safe disposable source used to create a visible helper editor window. |
|
// Native Welcome is unsupported on Sequoia; this file is the daily path. |
|
import Foundation |
|
|
|
print("Xcode27DefaultLaunch ready on \(ProcessInfo.processInfo.operatingSystemVersionString)") |
|
EOF |
|
fi |
|
[[ -f "$main_swift" ]] || die "failed to create durable launch document: $main_swift" |
|
echo "$main_swift" |
|
} |
|
|
|
activate_pid() { |
|
local pid="$1" |
|
ensure_gui_helper "${2:-$DEFAULT_XCODE}" >/dev/null || true |
|
if [[ ! -x "$GUI_HELPER" ]]; then |
|
echo "ACTIVATE_RESULT=HELPER_MISSING pid=$pid" |
|
return 0 |
|
fi |
|
"$GUI_HELPER" activate "$pid" || true |
|
} |
|
|
|
detect_window_status() { |
|
local pid="$1" |
|
if [[ -x "$GUI_HELPER" ]]; then |
|
"$GUI_HELPER" windows "$pid" || true |
|
return 0 |
|
fi |
|
echo "WINDOW_STATUS=HELPER_MISSING pid=$pid" |
|
} |
|
|
|
cmd_launch() { |
|
local xcode="$1" |
|
shift |
|
xcode=$(resolve_xcode "$xcode") |
|
ensure_state |
|
prepare_cf_home >/dev/null |
|
ensure_gui_helper "$xcode" >/dev/null |
|
local tmp |
|
tmp=$(getconf DARWIN_USER_TEMP_DIR 2>/dev/null || echo /tmp) |
|
|
|
# Flags: |
|
# (default) open durable helper editor immediately; no Welcome attempt |
|
# --helper-doc explicit alias of the default durable helper path |
|
# --welcome-only diagnostic/historical: bare launch, no helper fallback |
|
local args=() |
|
local force_helper=0 |
|
local welcome_only=0 |
|
while [[ $# -gt 0 ]]; do |
|
case "$1" in |
|
--helper-doc) force_helper=1; shift ;; |
|
--welcome-only) welcome_only=1; shift ;; |
|
*) args+=("$1"); shift ;; |
|
esac |
|
done |
|
local launch_target_kind="explicit" |
|
|
|
if [[ "$welcome_only" == "1" ]]; then |
|
if [[ ${#args[@]} -gt 0 ]]; then |
|
die "--welcome-only must not be combined with document args" |
|
fi |
|
launch_target_kind="welcome_only_diagnostic" |
|
log "WELCOME-ONLY diagnostic/historical mode" |
|
log "Native Welcome is UNSUPPORTED on Sequoia (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE)" |
|
elif [[ ${#args[@]} -eq 0 ]]; then |
|
# FINAL daily behavior: always durable helper editor. No Welcome timeout. |
|
local default_doc |
|
default_doc=$(ensure_default_launch_doc) |
|
args=("$default_doc") |
|
if [[ "$force_helper" == "1" ]]; then |
|
launch_target_kind="durable_default_forced" |
|
else |
|
launch_target_kind="durable_default" |
|
fi |
|
log "Native Welcome is UNSUPPORTED on Sequoia (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE)" |
|
log "DEFAULT LAUNCH: durable helper editor — $default_doc" |
|
fi |
|
|
|
log "launching (isolated Swiftly-free env): $xcode" |
|
log "CFFIXED_USER_HOME=$CF_HOME" |
|
log "document args: ${args[*]:-<none>}" |
|
log "launch_target_kind=$launch_target_kind" |
|
|
|
launch_exec() { |
|
local -a dargs=("$@") |
|
env -i \ |
|
HOME="$CF_HOME" \ |
|
CFFIXED_USER_HOME="$CF_HOME" \ |
|
USER="$(id -un)" \ |
|
LOGNAME="$(id -un)" \ |
|
SHELL="${SHELL:-/bin/zsh}" \ |
|
TMPDIR="$tmp" \ |
|
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \ |
|
DEVELOPER_DIR="$xcode/Contents/Developer" \ |
|
TOOLCHAINS="com.apple.dt.toolchain.XcodeDefault" \ |
|
__CF_USER_TEXT_ENCODING="0x1F5:0x0:0x0" \ |
|
"$xcode/Contents/MacOS/Xcode" ${dargs[@]+"${dargs[@]}"} \ |
|
>/tmp/xcode27-sequoia-launch.out 2>/tmp/xcode27-sequoia-launch.err & |
|
echo $! |
|
} |
|
|
|
dump_all_windows() { |
|
local pid="$1" |
|
if [[ -x "$GUI_HELPER" ]]; then |
|
"$GUI_HELPER" windows-all "$pid" 2>/dev/null || true |
|
else |
|
echo "WINDOW_STATUS=HELPER_MISSING pid=$pid" |
|
fi |
|
"$GUI_HELPER" frontmost "$pid" 2>/dev/null || true |
|
} |
|
|
|
wait_for_window() { |
|
local pid="$1" |
|
local i wstatus="WINDOW_NOT_DETECTED" still_running=0 |
|
sleep 2 |
|
if ! kill -0 "$pid" 2>/dev/null; then |
|
wait "$pid" 2>/dev/null || true |
|
echo "LAUNCH_STATUS=PROCESS_EXITED" |
|
echo "WINDOW_STATUS=PROCESS_EXITED" |
|
echo "ERROR: Xcode exited immediately. See /tmp/xcode27-sequoia-launch.err" |
|
tail -40 /tmp/xcode27-sequoia-launch.err 2>/dev/null || true |
|
return 1 |
|
fi |
|
still_running=1 |
|
activate_pid "$pid" "$xcode" >/dev/null || true |
|
local max_rounds=8 |
|
local sleep_s=2 |
|
if [[ "$launch_target_kind" == "welcome_only_diagnostic" ]]; then |
|
max_rounds=30 |
|
sleep_s=2 |
|
fi |
|
for i in $(seq 1 "$max_rounds"); do |
|
sleep "$sleep_s" |
|
if ! kill -0 "$pid" 2>/dev/null; then |
|
still_running=0 |
|
break |
|
fi |
|
if [[ "$launch_target_kind" == "welcome_only_diagnostic" ]]; then |
|
echo "--- diagnostic poll $i/${max_rounds} ---" |
|
dump_all_windows "$pid" || true |
|
fi |
|
wstatus=$(detect_window_status "$pid" 2>/dev/null | awk -F= '/^WINDOW_STATUS=/{print $2}' | awk '{print $1}') |
|
if [[ "$wstatus" == "WINDOW_VISIBLE" ]]; then |
|
break |
|
fi |
|
done |
|
if [[ "$still_running" != "1" ]] || ! kill -0 "$pid" 2>/dev/null; then |
|
echo "LAUNCH_STATUS=PROCESS_EXITED" |
|
echo "WINDOW_STATUS=PROCESS_EXITED" |
|
tail -40 /tmp/xcode27-sequoia-launch.err 2>/dev/null || true |
|
return 1 |
|
fi |
|
echo "PROCESS_STATUS=PROCESS_RUNNING" |
|
echo "WINDOW_STATUS=${wstatus:-WINDOW_NOT_DETECTED}" |
|
if [[ "$launch_target_kind" == "welcome_only_diagnostic" ]]; then |
|
echo "===== FINAL WELCOME-ONLY WINDOW DUMP =====" |
|
dump_all_windows "$pid" || true |
|
echo "stderr: /tmp/xcode27-sequoia-launch.err" |
|
echo "NOTE: Native Welcome is UNSUPPORTED (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE)." |
|
echo "This mode is diagnostic/historical only." |
|
if [[ "$wstatus" == "WINDOW_VISIBLE" ]]; then |
|
echo "WELCOME_ONLY_RESULT=WINDOW_VISIBLE pid=$pid" |
|
echo "LAUNCH_STATUS=WINDOW_VISIBLE" |
|
return 0 |
|
fi |
|
echo "WELCOME_ONLY_RESULT=NO_USABLE_WINDOW pid=$pid (process left alive; not killed)" |
|
echo "LAUNCH_STATUS=WELCOME_ONLY_NO_USABLE_WINDOW" |
|
return 2 |
|
fi |
|
if [[ "$wstatus" == "WINDOW_VISIBLE" ]]; then |
|
echo "LAUNCH_STATUS=WINDOW_VISIBLE" |
|
echo "Xcode running: pid=$pid (visible window detected)" |
|
echo "Re-activate this PID only with:" |
|
echo " $SCRIPT_PATH activate $pid" |
|
return 0 |
|
fi |
|
echo "LAUNCH_STATUS=${wstatus:-WINDOW_NOT_DETECTED}" |
|
echo "Xcode running: pid=$pid but no usable window was detected." |
|
echo "Process survival alone is NOT a GUI pass." |
|
echo "stderr: /tmp/xcode27-sequoia-launch.err" |
|
return 2 |
|
} |
|
|
|
local pid |
|
pid=$(launch_exec ${args[@]+"${args[@]}"}) |
|
echo "XCODE_EXECUTABLE=$xcode/Contents/MacOS/Xcode" |
|
echo "XCODE_PID=$pid" |
|
echo "DOCUMENT_ARGS=${args[*]:-<none>}" |
|
echo "LAUNCH_TARGET_KIND=$launch_target_kind" |
|
echo "CFFIXED_USER_HOME=$CF_HOME" |
|
echo "NATIVE_WELCOME=UNSUPPORTED WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE" |
|
echo "stderr: /tmp/xcode27-sequoia-launch.err" |
|
|
|
local rc=0 |
|
wait_for_window "$pid" || rc=$? |
|
if [[ "$rc" == "0" ]]; then |
|
return 0 |
|
fi |
|
if [[ "$launch_target_kind" == "welcome_only_diagnostic" ]]; then |
|
return "$rc" |
|
fi |
|
return "$rc" |
|
} |
|
|
|
cmd_activate() { |
|
local pid="${1:-}" |
|
[[ -n "$pid" ]] || die "usage: $(basename "$0") activate <pid>" |
|
[[ "$pid" =~ ^[0-9]+$ ]] || die "pid must be numeric: $pid" |
|
ensure_gui_helper "$DEFAULT_XCODE" >/dev/null || true |
|
activate_pid "$pid" "$DEFAULT_XCODE" |
|
detect_window_status "$pid" || true |
|
} |
|
|
|
cmd_windows_all() { |
|
local pid="${1:-}" |
|
[[ -n "$pid" ]] || die "usage: $(basename "$0") windows-all <pid>" |
|
[[ "$pid" =~ ^[0-9]+$ ]] || die "pid must be numeric: $pid" |
|
ensure_gui_helper "$DEFAULT_XCODE" >/dev/null || true |
|
"$GUI_HELPER" windows-all "$pid" || true |
|
"$GUI_HELPER" frontmost "$pid" || true |
|
} |
|
|
|
cmd_reopen() { |
|
local pid="${1:-}" |
|
[[ -n "$pid" ]] || die "usage: $(basename "$0") reopen <pid>" |
|
[[ "$pid" =~ ^[0-9]+$ ]] || die "pid must be numeric: $pid" |
|
ensure_gui_helper "$DEFAULT_XCODE" >/dev/null || true |
|
"$GUI_HELPER" reopen "$pid" || true |
|
sleep 2 |
|
"$GUI_HELPER" windows-all "$pid" || true |
|
"$GUI_HELPER" frontmost "$pid" || true |
|
} |
|
|
|
cmd_restore() { |
|
local xcode="$1" |
|
xcode=$(resolve_xcode "$xcode") |
|
local bak="${VANILLA_BACKUP_DIR}/Xcode-27.app" |
|
[[ -d "$bak" ]] || die "no durable vanilla backup at $bak" |
|
log "RESTORE from pristine backup (not reverse-patch): $bak -> $xcode" |
|
log "NOTE: ad-hoc patched bundles cannot be restored to Apple identity by re-writing bytes." |
|
local tmpdest |
|
tmpdest="${xcode}.restoring.$$" |
|
if ! cp -cR "$bak" "$tmpdest" 2>/dev/null; then |
|
die "clonefile restore failed" |
|
fi |
|
local mh ih |
|
mh=$(shasum -a 256 "$tmpdest/Contents/MacOS/Xcode" | awk '{print $1}') |
|
ih=$(shasum -a 256 "$tmpdest/Contents/Frameworks/IDEKit.framework/Versions/A/IDEKit" | awk '{print $1}') |
|
[[ "$mh" == "$EXPECTED_MAIN_SHA" ]] || { rm -rf "$tmpdest"; die "restore copy main hash $mh != vanilla"; } |
|
[[ "$ih" == "$EXPECTED_IDEKIT_SHA" ]] || { rm -rf "$tmpdest"; die "restore copy IDEKit hash $ih != vanilla"; } |
|
mv "$xcode" "${xcode}.patched-old.$$" |
|
mv "$tmpdest" "$xcode" |
|
local p |
|
for p in "${PLUGINS[@]}"; do |
|
if [[ -e "$PLUGIN_BACKUP_DIR/$p" && ! -e "$(plugins_dir "$xcode")/$p" ]]; then |
|
mv "$PLUGIN_BACKUP_DIR/$p" "$(plugins_dir "$xcode")/$p" |
|
log "restored plugin $p" |
|
fi |
|
done |
|
log "restore complete — vanilla bundle has original LSMinimumSystemVersion and Apple-related signing materials as preserved" |
|
cmd_verify "$xcode" || true |
|
} |
|
|
|
# Maintenance/debug command. Production `patch` already sets LS=15.7 automatically. |
|
cmd_set_ls() { |
|
local xcode="$1" |
|
local value="${2:-$REQUIRED_LS_MIN}" |
|
xcode=$(resolve_xcode "$xcode") |
|
require_command python3 |
|
require_command codesign |
|
require_command /usr/libexec/PlistBuddy |
|
ensure_state |
|
check_identity "$xcode" |
|
local idk cls |
|
idk=$(idekit_path "$xcode") |
|
cls=$(classify_idekit_patches "$xcode") |
|
[[ "$cls" == "PATCHED_FINAL" || "$cls" == "PATCH5_ONLY_REMNANT" || "$cls" == "LEGACY3" || "$cls" == "LEGACY3_PATCH5" ]] \ |
|
|| die "set-ls requires patched IDEKit (state=$cls)" |
|
log "set-ls (maintenance): $xcode LSMinimumSystemVersion -> $value" |
|
apply_ls_minimum "$xcode" "$value" |
|
sign_modified_components "$xcode" |
|
cls=$(classify_idekit_patches "$xcode") |
|
# After re-seal, production state must be PATCHED_FINAL |
|
if [[ "$cls" == "PATCH5_ONLY_REMNANT" ]]; then |
|
log "minimizing obsolete Patch5 during set-ls re-seal" |
|
restore_patch_site_to_vanilla "$idk" "0x373e4c" "60030034" "1f2003d5" "patch5_obsolete" |
|
sign_modified_components "$xcode" |
|
cls=$(classify_idekit_patches "$xcode") |
|
fi |
|
is_production_idekit "$cls" || die "post set-ls IDEKit state=$cls (expected PATCHED_FINAL)" |
|
local p |
|
for p in "${PLUGINS[@]}"; do |
|
[[ ! -e "$(plugins_dir "$xcode")/$p" ]] || die "plugin reappeared after set-ls: $p" |
|
done |
|
record_manifest "$xcode" "set-ls:$value" |
|
log "SET_LS COMPLETE: LSMinimumSystemVersion=$value patches=PATCHED_FINAL plugins=disabled" |
|
echo "LS_MINIMUM=$(read_ls_min "$xcode")" |
|
echo "IDEKIT_PATCH_STATE=$cls" |
|
} |
|
|
|
cmd_help() { |
|
cat <<EOF |
|
Usage: $(basename "$0") <command> [--xcode /path/to/Xcode.app] [args] |
|
|
|
FINAL PRODUCTION FREEZE |
|
Host: macOS 15.7.3 (24G419) Apple Silicon |
|
Xcode: 27.0 / 27A266a arm64 |
|
Swift: 6.4.0 (swiftlang-6.4.0.34.1) |
|
Patches: ${FINAL_PATCH_COUNT} binary IDEKit sites |
|
Native Welcome: UNSUPPORTED (WELCOME_SWIFTUI_RUNTIME_INCOMPATIBLE) |
|
Default launch: durable helper editor (no Welcome timeout) |
|
LSMinimumSystemVersion: ${REQUIRED_LS_MIN} (required; applied by normal patch) |
|
|
|
Commands: |
|
patch [--dry-run] Apply ${FINAL_PATCH_COUNT} IDEKit sites, set LS=${REQUIRED_LS_MIN}, |
|
disable 2 plugins, prepare launch env, ad-hoc sign (no --deep) |
|
verify Identity, 4 patch bytes, LS, plugins, DVTEdge, signing, toolchain |
|
status Human-readable production state (PATCHED_VALID only if all match) |
|
set-ls [version] Maintenance: set LSMinimumSystemVersion (default ${REQUIRED_LS_MIN}) + re-seal |
|
launch [file...] Launch via direct executable + isolated CF home |
|
Bare launch opens durable helper editor immediately |
|
launch --helper-doc Explicit durable helper document (same as bare default) |
|
launch --welcome-only |
|
Diagnostic/historical only: bare native Welcome attempt. |
|
Native Welcome is unsupported on Sequoia. |
|
activate <pid> Bring a launched Xcode 27 PID to front (PID only) |
|
windows-all <pid> Dump every CGWindow owned by PID |
|
reopen <pid> PID-targeted Dock-equivalent reopen |
|
restore Restore pristine vanilla bundle from durable backup |
|
help This help |
|
|
|
Default Xcode path: ${DEFAULT_XCODE} |
|
|
|
Final authorized IDEKit sites (ARM64 only) — ${FINAL_PATCH_COUNT} patches: |
|
1 ${PATCH1_OFF} ${PATCH1_VAN} -> ${PATCH1_NEW} startup/FLE macOS 26 guard |
|
2 ${PATCH2_OFF} ${PATCH2_VAN} -> ${PATCH2_NEW} IDEInitialEditor.loadView guard |
|
3 ${PATCH3_OFF} ${PATCH3_VAN} -> ${PATCH3_NEW} IDERunStopToolbar guard |
|
4 ${PATCH4_OFF} ${PATCH4_VAN} -> ${PATCH4_NEW} createProjectQuickly -> newProjectFromTemplate |
|
(HUMAN VERIFIED New Project) |
|
|
|
NOT a production patch (must remain vanilla): |
|
historical 0x373e4c Welcome onboarding OS guard |
|
Classification: PATCH5_NOT_REQUIRED_FOR_SUPPORTED_WORKFLOW |
|
|
|
Launch notes: |
|
Bare launch opens durable helper editor immediately. |
|
Explicit file/project args open that exact target. |
|
Process survival alone is not a visible-GUI pass. |
|
Do NOT use open -b com.apple.dt.Xcode (shared id with Xcode 26.2). |
|
Do NOT click New Tab / Tahoe SwiftUI chrome. |
|
Do NOT patch SwiftUI or copy Tahoe Swift dylibs. |
|
|
|
State directory: $STATE_DIR |
|
Known-good: macOS 15.7.3 (24G419), Xcode 27.0 (27A266a) arm64 Swift 6.4.0 |
|
EOF |
|
} |
|
|
|
main() { |
|
local cmd="${1:-help}" |
|
shift || true |
|
local xcode="$DEFAULT_XCODE" |
|
local dry=0 |
|
while [[ $# -gt 0 ]]; do |
|
case "$1" in |
|
--xcode) xcode="${2:-}"; shift 2 || die "--xcode needs path" ;; |
|
--dry-run) dry=1; shift ;; |
|
*) break ;; |
|
esac |
|
done |
|
case "$cmd" in |
|
patch) cmd_patch "$xcode" "$dry" ;; |
|
verify) cmd_verify "$xcode" ;; |
|
status) cmd_status "$xcode" ;; |
|
set-ls) cmd_set_ls "$xcode" "${1:-$REQUIRED_LS_MIN}" ;; |
|
launch) cmd_launch "$xcode" "$@" ;; |
|
activate) cmd_activate "$@" ;; |
|
windows-all) cmd_windows_all "$@" ;; |
|
reopen) cmd_reopen "$@" ;; |
|
restore) cmd_restore "$xcode" ;; |
|
help|-h|--help) cmd_help ;; |
|
*) cmd_help; die "unknown command: $cmd" ;; |
|
esac |
|
} |
|
|
|
main "$@" |