#!/bin/bash set -euo pipefail cd "$(dirname "$0")/.." PROJECT="$PWD/FuelBoard.xcodeproj" SCHEME="FuelBoard" DERIVED_DATA="/tmp/fuelboard-build-ipas" SERVE_DIR="/tmp/ipa-serve" FULL_IPA="$SERVE_DIR/FuelBoard-watch.ipa" SIDELOAD_IPA="$SERVE_DIR/FuelBoard-sideload.ipa" HOST="192.168.1.131" PORT="8765" APP_PATH="$DERIVED_DATA/Build/Products/Release-iphoneos/FuelBoard.app" STAGE_FULL="/tmp/fuelboard-ipa-stage-full" STAGE_SIDELOAD="/tmp/fuelboard-ipa-stage-sideload" echo "==> Refresh bundled dump" "$PWD/scripts/refresh_bundled_dump.sh" echo "==> Clean build dirs" rm -rf "$DERIVED_DATA" "$STAGE_FULL" "$STAGE_SIDELOAD" mkdir -p "$SERVE_DIR" echo "==> Build Release app (generic iOS destination)" xcodebuild \ -project "$PROJECT" \ -scheme "$SCHEME" \ -configuration Release \ -destination 'generic/platform=iOS' \ -derivedDataPath "$DERIVED_DATA" \ CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGN_IDENTITY='' \ build >/tmp/fuelboard-build-ipas-xcodebuild.log if [ ! -d "$APP_PATH" ]; then echo "missing built app: $APP_PATH" >&2 exit 1 fi echo "==> Package full watch-capable IPA" python3 - <<'PY' import os, shutil, zipfile app = '/tmp/fuelboard-build-ipas/Build/Products/Release-iphoneos/FuelBoard.app' stage = '/tmp/fuelboard-ipa-stage-full' payload = os.path.join(stage, 'Payload') out = '/tmp/ipa-serve/FuelBoard-watch.ipa' if os.path.exists(stage): shutil.rmtree(stage) os.makedirs(payload, exist_ok=True) shutil.copytree(app, os.path.join(payload, 'FuelBoard.app')) tmp = out + '.tmp' if os.path.exists(tmp): os.remove(tmp) with zipfile.ZipFile(tmp, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf: for root, dirs, files in os.walk(stage): dirs.sort(); files.sort() for name in dirs: path = os.path.join(root, name) zf.write(path, os.path.relpath(path, stage)) for name in files: path = os.path.join(root, name) zf.write(path, os.path.relpath(path, stage)) os.replace(tmp, out) print(out) PY echo "==> Package sideload-safe IPA (strip Watch/)" python3 - <<'PY' import os, shutil, zipfile app = '/tmp/fuelboard-build-ipas/Build/Products/Release-iphoneos/FuelBoard.app' stage = '/tmp/fuelboard-ipa-stage-sideload' payload = os.path.join(stage, 'Payload') out = '/tmp/ipa-serve/FuelBoard-sideload.ipa' if os.path.exists(stage): shutil.rmtree(stage) os.makedirs(payload, exist_ok=True) dst = os.path.join(payload, 'FuelBoard.app') shutil.copytree(app, dst) watch_dir = os.path.join(dst, 'Watch') if os.path.exists(watch_dir): shutil.rmtree(watch_dir) tmp = out + '.tmp' if os.path.exists(tmp): os.remove(tmp) with zipfile.ZipFile(tmp, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf: for root, dirs, files in os.walk(stage): dirs.sort(); files.sort() for name in dirs: path = os.path.join(root, name) zf.write(path, os.path.relpath(path, stage)) for name in files: path = os.path.join(root, name) zf.write(path, os.path.relpath(path, stage)) os.replace(tmp, out) print(out) PY echo "==> Verify artifacts" ls -lh "$FULL_IPA" "$SIDELOAD_IPA" python3 - <<'PY' import os, plistlib, json base = '/tmp/fuelboard-build-ipas/Build/Products/Release-iphoneos/FuelBoard.app' paths = { 'host': os.path.join(base, 'Info.plist'), 'watch': os.path.join(base, 'Watch', 'FuelBoardWatch Watch App.app', 'Info.plist'), 'watch_ext': os.path.join(base, 'Watch', 'FuelBoardWatch Watch App.app', 'PlugIns', 'FuelBoardWatch Watch App Extension.appex', 'Info.plist'), } for name, path in paths.items(): print(f'## {name} {os.path.exists(path)}') if os.path.exists(path): with open(path, 'rb') as f: pl = plistlib.load(f) print(json.dumps({k: pl.get(k) for k in ['CFBundleIdentifier','CFBundleShortVersionString','CFBundleVersion']}, indent=2)) PY echo "==> URLs" echo "Watch-capable IPA: http://$HOST:$PORT/FuelBoard-watch.ipa" echo "Sideload-safe IPA: http://$HOST:$PORT/FuelBoard-sideload.ipa" echo "Xcode log: /tmp/fuelboard-build-ipas-xcodebuild.log"