193 lines
5.6 KiB
Bash
Executable File
193 lines
5.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
PROJECT_PATH="$ROOT_DIR/HelloWorld.xcodeproj"
|
|
TARGET_NAME="HelloWorld"
|
|
TEAM_ID="${TEAM_ID:-HY3W4U256Z}"
|
|
BUNDLE_ID="${BUNDLE_ID:-local.proxmox.helloworld.e2e}"
|
|
CONFIGURATION="${CONFIGURATION:-Debug}"
|
|
DEVICE_UDID="${DEVICE_UDID:-}"
|
|
BUILD_ROOT="$ROOT_DIR/build/device"
|
|
BUILD_ONLY=0
|
|
LAUNCH=0
|
|
|
|
usage() {
|
|
echo "Usage: $0 [--udid UDID] [--bundle-id ID] [--team-id ID] [--configuration NAME] [--build-only] [--launch]"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--udid)
|
|
DEVICE_UDID="${2:?--udid requires a value}"
|
|
shift 2
|
|
;;
|
|
--bundle-id)
|
|
BUNDLE_ID="${2:?--bundle-id requires a value}"
|
|
shift 2
|
|
;;
|
|
--team-id)
|
|
TEAM_ID="${2:?--team-id requires a value}"
|
|
shift 2
|
|
;;
|
|
--configuration)
|
|
CONFIGURATION="${2:?--configuration requires a value}"
|
|
shift 2
|
|
;;
|
|
--build-only)
|
|
BUILD_ONLY=1
|
|
shift
|
|
;;
|
|
--launch)
|
|
LAUNCH=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
for command_name in xcodebuild security codesign python3; do
|
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
|
echo "Required command not found: $command_name" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if command -v pymobiledevice3 >/dev/null 2>&1; then
|
|
PMD=(pymobiledevice3)
|
|
elif python3 -c 'import pymobiledevice3' >/dev/null 2>&1; then
|
|
PMD=(python3 -m pymobiledevice3)
|
|
else
|
|
echo "pymobiledevice3 is required. Install it with: python3 -m pip install -U pymobiledevice3" >&2
|
|
exit 1
|
|
fi
|
|
|
|
run_pmd() {
|
|
PYTHONWARNINGS=ignore "${PMD[@]}" --no-color "$@"
|
|
}
|
|
|
|
if [[ -z "$DEVICE_UDID" ]]; then
|
|
device_json="$(run_pmd usbmux list)"
|
|
DEVICE_UDID="$(printf '%s' "$device_json" | python3 -c '
|
|
import json, sys
|
|
devices = json.load(sys.stdin)
|
|
print(devices[0].get("Identifier", "") if len(devices) == 1 else "")
|
|
')"
|
|
if [[ -z "$DEVICE_UDID" ]]; then
|
|
echo "Connect exactly one unlocked iPhone, or pass --udid explicitly." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "Building $BUNDLE_ID for device $DEVICE_UDID"
|
|
|
|
xcodebuild \
|
|
-project "$PROJECT_PATH" \
|
|
-target "$TARGET_NAME" \
|
|
-configuration "$CONFIGURATION" \
|
|
-sdk iphoneos \
|
|
-allowProvisioningUpdates \
|
|
-allowProvisioningDeviceRegistration \
|
|
PRODUCT_BUNDLE_IDENTIFIER="$BUNDLE_ID" \
|
|
DEVELOPMENT_TEAM="$TEAM_ID" \
|
|
SYMROOT="$BUILD_ROOT" \
|
|
OBJROOT="$BUILD_ROOT/Intermediates" \
|
|
build
|
|
|
|
APP_PATH="$BUILD_ROOT/$CONFIGURATION-iphoneos/$TARGET_NAME.app"
|
|
IPA_PATH="$BUILD_ROOT/$TARGET_NAME.ipa"
|
|
|
|
if [[ ! -d "$APP_PATH" ]]; then
|
|
echo "Expected app was not produced: $APP_PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
actual_bundle_id="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP_PATH/Info.plist")"
|
|
if [[ "$actual_bundle_id" != "$BUNDLE_ID" ]]; then
|
|
echo "Bundle ID mismatch: expected $BUNDLE_ID, found $actual_bundle_id" >&2
|
|
exit 1
|
|
fi
|
|
|
|
codesign --verify --deep --strict "$APP_PATH"
|
|
|
|
TEMP_DIR="$(mktemp -d /tmp/helloworld-install.XXXXXX)"
|
|
cleanup() {
|
|
if [[ -n "${TEMP_DIR:-}" && "$TEMP_DIR" == /tmp/helloworld-install.* ]]; then
|
|
rm -rf "$TEMP_DIR"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
PROFILE_PLIST="$TEMP_DIR/profile.plist"
|
|
security cms -D -i "$APP_PATH/embedded.mobileprovision" > "$PROFILE_PLIST"
|
|
|
|
if ! /usr/libexec/PlistBuddy -c 'Print :ProvisionedDevices' "$PROFILE_PLIST" 2>/dev/null | grep -Fq "$DEVICE_UDID"; then
|
|
echo "Provisioning profile does not contain device UDID $DEVICE_UDID." >&2
|
|
echo "Register the phone with the Apple team, then rerun this script." >&2
|
|
exit 1
|
|
fi
|
|
|
|
profile_name="$(/usr/libexec/PlistBuddy -c 'Print :Name' "$PROFILE_PLIST")"
|
|
profile_expiration="$(/usr/libexec/PlistBuddy -c 'Print :ExpirationDate' "$PROFILE_PLIST")"
|
|
|
|
mkdir -p "$TEMP_DIR/Payload"
|
|
ditto "$APP_PATH" "$TEMP_DIR/Payload/$TARGET_NAME.app"
|
|
ditto -c -k --keepParent "$TEMP_DIR/Payload" "$IPA_PATH"
|
|
|
|
echo "Signature and provisioning profile validated."
|
|
echo "Profile: $profile_name"
|
|
echo "Expires: $profile_expiration"
|
|
echo "IPA: $IPA_PATH"
|
|
|
|
if [[ "$BUILD_ONLY" -eq 1 ]]; then
|
|
echo "Build-only verification complete."
|
|
exit 0
|
|
fi
|
|
|
|
run_pmd apps install --udid "$DEVICE_UDID" "$APP_PATH"
|
|
|
|
query_output="$(run_pmd apps query --udid "$DEVICE_UDID" "$BUNDLE_ID")"
|
|
if ! printf '%s' "$query_output" | grep -Fq '"ProfileValidated": true'; then
|
|
echo "The app installed, but the device did not report a validated profile." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Installed and profile-validated: $BUNDLE_ID"
|
|
|
|
if [[ "$LAUNCH" -eq 1 ]]; then
|
|
if ! command -v xcrun >/dev/null 2>&1; then
|
|
echo "xcrun is required for --launch." >&2
|
|
exit 1
|
|
fi
|
|
|
|
device_list_json="$TEMP_DIR/devicectl.json"
|
|
xcrun devicectl list devices --json-output "$device_list_json" >/dev/null
|
|
core_device_id="$(python3 -c '
|
|
import json, sys
|
|
path, udid = sys.argv[1:]
|
|
with open(path, encoding="utf-8") as stream:
|
|
devices = json.load(stream).get("result", {}).get("devices", [])
|
|
for device in devices:
|
|
if device.get("hardwareProperties", {}).get("udid") == udid:
|
|
print(device.get("identifier", ""))
|
|
break
|
|
' "$device_list_json" "$DEVICE_UDID")"
|
|
|
|
if [[ -z "$core_device_id" ]]; then
|
|
echo "Unable to map UDID to a CoreDevice identifier for launch." >&2
|
|
exit 1
|
|
fi
|
|
|
|
xcrun devicectl device process launch --device "$core_device_id" "$BUNDLE_ID"
|
|
echo "Launched $BUNDLE_ID"
|
|
fi
|