Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 066b87fe79 | |||
| dd8dd24098 |
@@ -0,0 +1,5 @@
|
|||||||
|
.DS_Store
|
||||||
|
build/
|
||||||
|
*.xcresult
|
||||||
|
xcuserdata/
|
||||||
|
*.ipa
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {};
|
||||||
|
objectVersion = 56;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
A10000000000000000000001 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* App.swift */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
A10000000000000000000002 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
||||||
|
A10000000000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
A10000000000000000000004 /* HelloWorld.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HelloWorld.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
A10000000000000000000005 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = ();
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
A10000000000000000000006 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A10000000000000000000007 /* Sources */,
|
||||||
|
A10000000000000000000008 /* Resources */,
|
||||||
|
A10000000000000000000009 /* Products */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
A10000000000000000000007 /* Sources */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (A10000000000000000000002 /* App.swift */,);
|
||||||
|
path = Sources;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
A10000000000000000000008 /* Resources */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (A10000000000000000000003 /* Info.plist */,);
|
||||||
|
path = Resources;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
A10000000000000000000009 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (A10000000000000000000004 /* HelloWorld.app */,);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
A1000000000000000000000A /* HelloWorld */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = A1000000000000000000000B /* Build configuration list for PBXNativeTarget "HelloWorld" */;
|
||||||
|
buildPhases = (
|
||||||
|
A1000000000000000000000C /* Sources */,
|
||||||
|
A10000000000000000000005 /* Frameworks */,
|
||||||
|
A1000000000000000000000D /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = ();
|
||||||
|
dependencies = ();
|
||||||
|
name = HelloWorld;
|
||||||
|
productName = HelloWorld;
|
||||||
|
productReference = A10000000000000000000004 /* HelloWorld.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
A1000000000000000000000E /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = 1;
|
||||||
|
LastSwiftUpdateCheck = 1620;
|
||||||
|
LastUpgradeCheck = 1620;
|
||||||
|
TargetAttributes = {
|
||||||
|
A1000000000000000000000A = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
DevelopmentTeam = HY3W4U256Z;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = A1000000000000000000000F /* Build configuration list for PBXProject "HelloWorld" */;
|
||||||
|
compatibilityVersion = "Xcode 14.0";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (en, Base,);
|
||||||
|
mainGroup = A10000000000000000000006;
|
||||||
|
productRefGroup = A10000000000000000000009 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (A1000000000000000000000A /* HelloWorld */,);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
A1000000000000000000000D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = ();
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
A1000000000000000000000C /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (A10000000000000000000001 /* App.swift in Sources */,);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
A10000000000000000000010 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
A10000000000000000000011 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
A10000000000000000000012 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
|
DEVELOPMENT_TEAM = HY3W4U256Z;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = Resources/Info.plist;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = local.proxmox.helloworld.e2e;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_VERSION = 6.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
A10000000000000000000013 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
|
DEVELOPMENT_TEAM = HY3W4U256Z;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = Resources/Info.plist;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = local.proxmox.helloworld.e2e;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
SWIFT_VERSION = 6.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
A1000000000000000000000B /* Build configuration list for PBXNativeTarget "HelloWorld" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (A10000000000000000000012 /* Debug */, A10000000000000000000013 /* Release */,);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
A1000000000000000000000F /* Build configuration list for PBXProject "HelloWorld" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (A10000000000000000000010 /* Debug */, A10000000000000000000011 /* Release */,);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = A1000000000000000000000E /* Project object */;
|
||||||
|
}
|
||||||
@@ -1,3 +1,94 @@
|
|||||||
# helloworld-ios-ota
|
# HelloWorld for iOS
|
||||||
|
|
||||||
Signed iOS OTA builds and installation manifests for HelloWorld
|
A minimal UIKit application plus a command-line build and installation workflow.
|
||||||
|
The workflow was tested with:
|
||||||
|
|
||||||
|
- Intel macOS Sonoma 14.8.7 running in Proxmox
|
||||||
|
- Xcode 16.2 and the iOS 18.2 SDK
|
||||||
|
- An iPhone 15 running iOS 26.5.2
|
||||||
|
- `pymobiledevice3` 9.36.0
|
||||||
|
|
||||||
|
The host Xcode does not need to support the phone's installed iOS version. The
|
||||||
|
script builds the Xcode **target** directly for `iphoneos`, which avoids Xcode's
|
||||||
|
destination compatibility check, and uses `pymobiledevice3` to install the
|
||||||
|
result through Apple's installation service.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Select Xcode and finish its first-launch setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
|
||||||
|
sudo xcodebuild -license accept
|
||||||
|
sudo xcodebuild -runFirstLaunch
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install a current `pymobiledevice3`. Version 2.30.0 is too old for current
|
||||||
|
AltServer JIT support because it does not expose the `mounter` command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install openssl@3
|
||||||
|
python3 -m pip install -U pymobiledevice3
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Sign in to the Apple account in Xcode, pair the iPhone, and enable
|
||||||
|
**Settings > Privacy & Security > Developer Mode** on the phone.
|
||||||
|
|
||||||
|
4. Ensure the phone is registered with the Personal Team. AltServer can perform
|
||||||
|
the initial registration. A free Personal Team profile expires after seven
|
||||||
|
days, so rerun the script to rebuild and reinstall before expiration.
|
||||||
|
|
||||||
|
## Build and install
|
||||||
|
|
||||||
|
Connect and unlock one iPhone, then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/build-install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
To select a device explicitly and launch the app after installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/build-install.sh \
|
||||||
|
--udid DEVICE_UDID \
|
||||||
|
--launch
|
||||||
|
```
|
||||||
|
|
||||||
|
The launch step requires the phone to remain unlocked. The build and signed IPA
|
||||||
|
are written under `build/device/`.
|
||||||
|
|
||||||
|
Useful overrides:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TEAM_ID=HY3W4U256Z \
|
||||||
|
BUNDLE_ID=local.proxmox.helloworld.e2e \
|
||||||
|
CONFIGURATION=Debug \
|
||||||
|
./scripts/build-install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--build-only` to compile, sign, package, and validate without installing.
|
||||||
|
When the phone is disconnected, pass its UDID explicitly so the script can
|
||||||
|
still verify the provisioning profile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/build-install.sh --build-only --udid DEVICE_UDID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why this works with an older Xcode
|
||||||
|
|
||||||
|
An ordinary scheme build aimed at an iOS 26 phone causes Xcode 16.2 to reject
|
||||||
|
the destination before compilation. This project instead uses a target-level
|
||||||
|
device build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xcodebuild -project HelloWorld.xcodeproj \
|
||||||
|
-target HelloWorld \
|
||||||
|
-sdk iphoneos \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
build
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting ARM64 app targets iOS 15 or later and therefore runs on iOS 26.
|
||||||
|
Installation and device queries are handled independently by
|
||||||
|
`pymobiledevice3`, which supports newer device protocols without requiring a
|
||||||
|
newer macOS release.
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Hello World</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>HelloWorld</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>HelloWorld</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>15.0</string>
|
||||||
|
<key>UILaunchScreen</key>
|
||||||
|
<dict/>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
var window: UIWindow?
|
||||||
|
|
||||||
|
func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||||
|
) -> Bool {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "Wireless install works!\nHello from Proxmox."
|
||||||
|
label.font = .systemFont(ofSize: 28, weight: .bold)
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 0
|
||||||
|
|
||||||
|
let viewController = UIViewController()
|
||||||
|
viewController.view.backgroundColor = .systemBackground
|
||||||
|
viewController.view.addSubview(label)
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
label.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor),
|
||||||
|
label.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
|
window.rootViewController = viewController
|
||||||
|
window.makeKeyAndVisible()
|
||||||
|
self.window = window
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+192
@@ -0,0 +1,192 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user