754 lines
25 KiB
Bash
Executable File
754 lines
25 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# build-ios.sh
|
||
# Author: Matthew Raymer
|
||
# Description: iOS build script for TimeSafari application
|
||
# Date: 2025-07-11
|
||
|
||
# Exit on any error
|
||
set -e
|
||
|
||
# Source common utilities
|
||
source "$(dirname "$0")/common.sh"
|
||
|
||
# Default values
|
||
BUILD_MODE="development"
|
||
BUILD_TYPE="debug"
|
||
OPEN_STUDIO=false
|
||
BUILD_IPA=false
|
||
BUILD_APP=false
|
||
CLEAN_ONLY=false
|
||
SYNC_ONLY=false
|
||
ASSETS_ONLY=false
|
||
DEPLOY_APP=false
|
||
AUTO_RUN=false
|
||
CUSTOM_API_IP=""
|
||
|
||
# Function to print iOS-specific usage
|
||
print_ios_usage() {
|
||
echo "Usage: $0 [options]"
|
||
echo ""
|
||
echo "iOS Build Options:"
|
||
echo " --dev, --development Build for development environment"
|
||
echo " --test Build for testing environment"
|
||
echo " --prod, --production Build for production environment"
|
||
echo " --debug Build debug app (default)"
|
||
echo " --release Build release app"
|
||
echo " --studio Open Xcode after build"
|
||
echo " --ipa Build IPA file"
|
||
echo " --app Build app bundle"
|
||
echo " --clean Clean build artifacts only"
|
||
echo " --sync Sync Capacitor only"
|
||
echo " --assets Generate assets only"
|
||
echo " --deploy Deploy app to connected device"
|
||
echo " --auto-run Auto-run app after build"
|
||
echo " --api-ip <ip> Custom IP address for claim API (uses Capacitor default)"
|
||
echo ""
|
||
echo "Common Options:"
|
||
echo " -h, --help Show this help message"
|
||
echo " -v, --verbose Enable verbose logging"
|
||
echo ""
|
||
echo "Examples:"
|
||
echo " $0 --dev --studio # Development build + open Xcode"
|
||
echo " $0 --prod --ipa # Production IPA build"
|
||
echo " $0 --test --app # Testing app build"
|
||
echo " $0 --test --auto-run # Test build + auto-run"
|
||
echo " $0 --clean # Clean only"
|
||
echo " $0 --sync # Sync only"
|
||
echo " $0 --deploy # Build and deploy to device"
|
||
echo " $0 --dev # Dev build with Capacitor default"
|
||
echo " $0 --dev --api-ip 192.168.1.100 # Dev build with custom API IP"
|
||
echo ""
|
||
}
|
||
|
||
# Function to parse iOS-specific arguments
|
||
parse_ios_args() {
|
||
local args=("$@")
|
||
local i=0
|
||
|
||
while [ $i -lt ${#args[@]} ]; do
|
||
local arg="${args[$i]}"
|
||
|
||
case $arg in
|
||
--dev|--development)
|
||
BUILD_MODE="development"
|
||
;;
|
||
--test)
|
||
BUILD_MODE="test"
|
||
;;
|
||
--prod|--production)
|
||
BUILD_MODE="production"
|
||
;;
|
||
--debug)
|
||
BUILD_TYPE="debug"
|
||
;;
|
||
--release)
|
||
BUILD_TYPE="release"
|
||
;;
|
||
--studio)
|
||
OPEN_STUDIO=true
|
||
;;
|
||
--ipa)
|
||
BUILD_IPA=true
|
||
;;
|
||
--app)
|
||
BUILD_APP=true
|
||
;;
|
||
--clean)
|
||
CLEAN_ONLY=true
|
||
;;
|
||
--sync)
|
||
SYNC_ONLY=true
|
||
;;
|
||
--assets)
|
||
ASSETS_ONLY=true
|
||
;;
|
||
--deploy)
|
||
DEPLOY_APP=true
|
||
;;
|
||
--auto-run)
|
||
AUTO_RUN=true
|
||
;;
|
||
--api-ip)
|
||
if [ $((i + 1)) -lt ${#args[@]} ]; then
|
||
CUSTOM_API_IP="${args[$((i + 1))]}"
|
||
i=$((i + 1)) # Skip the next argument
|
||
else
|
||
log_error "Error: --api-ip requires an IP address"
|
||
exit 1
|
||
fi
|
||
;;
|
||
--api-ip=*)
|
||
CUSTOM_API_IP="${arg#*=}"
|
||
;;
|
||
-h|--help)
|
||
print_ios_usage
|
||
exit 0
|
||
;;
|
||
-v|--verbose)
|
||
set -x
|
||
;;
|
||
*)
|
||
log_warn "Unknown argument: $arg"
|
||
;;
|
||
esac
|
||
i=$((i + 1))
|
||
done
|
||
}
|
||
|
||
# Function to validate iOS environment
|
||
validate_ios_environment() {
|
||
log_info "Validating iOS build environment..."
|
||
|
||
# Check for Xcode
|
||
if ! command -v xcodebuild &> /dev/null; then
|
||
log_error "Xcode not found. Please install Xcode and command line tools."
|
||
exit 1
|
||
fi
|
||
|
||
# Check for iOS Simulator
|
||
if ! command -v xcrun &> /dev/null; then
|
||
log_error "Xcode command line tools not found. Please install with: xcode-select --install"
|
||
exit 1
|
||
fi
|
||
|
||
# Check for Capacitor
|
||
if ! command -v npx &> /dev/null; then
|
||
log_error "npx not found. Please install Node.js and npm."
|
||
exit 1
|
||
fi
|
||
|
||
# Check for iOS platform
|
||
if [ ! -d "ios" ]; then
|
||
log_error "iOS platform not found. Please run: npx cap add ios"
|
||
exit 1
|
||
fi
|
||
|
||
log_success "iOS build environment validated"
|
||
}
|
||
|
||
# Function to check iOS resources
|
||
check_ios_resources() {
|
||
log_info "Checking iOS resources..."
|
||
|
||
# Check for required assets
|
||
if [ ! -f "resources/icon.png" ]; then
|
||
log_warn "App icon not found at resources/icon.png"
|
||
fi
|
||
|
||
if [ ! -f "resources/splash.png" ]; then
|
||
log_warn "Splash screen not found at resources/splash.png"
|
||
fi
|
||
|
||
# Check for iOS-specific files
|
||
if [ ! -f "ios/App/App/Info.plist" ]; then
|
||
log_warn "Info.plist not found"
|
||
fi
|
||
|
||
if [ ! -f "ios/App/App/AppDelegate.swift" ]; then
|
||
log_warn "AppDelegate.swift not found"
|
||
fi
|
||
|
||
log_success "iOS resource check completed"
|
||
}
|
||
|
||
# iOS app icon appearance variants (Dark, Tinted, …).
|
||
# Luminosity appearance values match Apple's asset catalog format (iOS 18+).
|
||
readonly _IOS_APP_ICON_APPEARANCE_LUMINOSITY="luminosity"
|
||
readonly _IOS_APP_ICON_APPEARANCE_DARK="dark"
|
||
readonly _IOS_APP_ICON_APPEARANCE_TINTED="tinted"
|
||
|
||
# Each row: source_file|luminosity_value|dest_filename
|
||
# To add a variant: append one row and place the source PNG under resources/ios/.
|
||
readonly _IOS_APP_ICON_APPEARANCE_VARIANTS=(
|
||
"resources/ios/icon/icon-dark.png|${_IOS_APP_ICON_APPEARANCE_DARK}|AppIcon-Dark.png"
|
||
"resources/ios/icon/icon-tinted.png|${_IOS_APP_ICON_APPEARANCE_TINTED}|AppIcon-Tinted.png"
|
||
)
|
||
|
||
# Update AppIcon.appiconset/Contents.json with one 1024×1024 appearance entry.
|
||
# Preserves all other images (including capacitor-assets output); replaces any
|
||
# existing entry for the same luminosity appearance.
|
||
_update_appicon_contents_for_appearance() {
|
||
local contents_json="$1"
|
||
local dest_filename="$2"
|
||
local luminosity_value="$3"
|
||
|
||
local tmp_file
|
||
tmp_file="$(mktemp)"
|
||
|
||
if ! jq --arg filename "$dest_filename" \
|
||
--arg appearance "$_IOS_APP_ICON_APPEARANCE_LUMINOSITY" \
|
||
--arg value "$luminosity_value" \
|
||
'
|
||
.images |= map(select((.appearances[0].value // "") != $value))
|
||
| .images += [{
|
||
"appearances": [{
|
||
"appearance": $appearance,
|
||
"value": $value
|
||
}],
|
||
"filename": $filename,
|
||
"idiom": "universal",
|
||
"platform": "ios",
|
||
"size": "1024x1024"
|
||
}]
|
||
' "$contents_json" > "$tmp_file"; then
|
||
rm -f "$tmp_file"
|
||
log_error "Failed to update AppIcon Contents.json for appearance: $luminosity_value"
|
||
return 1
|
||
fi
|
||
|
||
mv "$tmp_file" "$contents_json"
|
||
}
|
||
|
||
# Install one appearance variant when its source PNG exists.
|
||
_apply_ios_app_icon_appearance_variant() {
|
||
local appiconset_dir="$1"
|
||
local contents_json="$2"
|
||
local source_file="$3"
|
||
local luminosity_value="$4"
|
||
local dest_filename="$5"
|
||
|
||
if [ ! -f "$source_file" ]; then
|
||
log_info "iOS app icon appearance variant not found ($source_file) — skipping"
|
||
return 0
|
||
fi
|
||
|
||
if [ ! -d "$appiconset_dir" ]; then
|
||
log_warn "AppIcon.appiconset not found — skipping appearance variant ($luminosity_value)"
|
||
return 0
|
||
fi
|
||
|
||
if ! cp "$source_file" "$appiconset_dir/$dest_filename"; then
|
||
log_error "Failed to copy $source_file to $appiconset_dir/$dest_filename"
|
||
return 1
|
||
fi
|
||
|
||
log_info "Installed iOS app icon appearance variant: $luminosity_value ($dest_filename)"
|
||
_update_appicon_contents_for_appearance "$contents_json" "$dest_filename" "$luminosity_value"
|
||
}
|
||
|
||
# Post-process capacitor-assets iOS icons: copy optional Dark/Tinted sources and
|
||
# register them in AppIcon.appiconset/Contents.json.
|
||
apply_ios_app_icon_appearances() {
|
||
local appiconset_dir="ios/App/App/Assets.xcassets/AppIcon.appiconset"
|
||
local contents_json="$appiconset_dir/Contents.json"
|
||
|
||
if [ ! -f "$contents_json" ]; then
|
||
log_info "AppIcon Contents.json not found — skipping appearance variants"
|
||
return 0
|
||
fi
|
||
|
||
if ! command -v jq &> /dev/null; then
|
||
log_error "jq is required to install iOS app icon appearance variants (install with: brew install jq)"
|
||
return 1
|
||
fi
|
||
|
||
local variant source_file luminosity_value dest_filename
|
||
for variant in "${_IOS_APP_ICON_APPEARANCE_VARIANTS[@]}"; do
|
||
IFS='|' read -r source_file luminosity_value dest_filename <<< "$variant"
|
||
_apply_ios_app_icon_appearance_variant \
|
||
"$appiconset_dir" "$contents_json" \
|
||
"$source_file" "$luminosity_value" "$dest_filename"
|
||
done
|
||
}
|
||
|
||
# Generate iOS assets (capacitor-assets), then apply optional appearance variants.
|
||
generate_ios_assets() {
|
||
ensure_ios_capacitor_asset_directories
|
||
if [ -d "assets" ]; then
|
||
log_warn "@capacitor/assets prioritizes the top-level assets/ directory over resources/."
|
||
log_warn "This project intentionally uses resources/ as the canonical asset source."
|
||
log_warn "Remove the legacy assets/ directory if it is empty or obsolete."
|
||
fi
|
||
npx capacitor-assets generate --ios
|
||
apply_ios_app_icon_appearances
|
||
}
|
||
|
||
# Function to clean iOS build
|
||
clean_ios_build() {
|
||
log_info "Cleaning iOS build artifacts..."
|
||
|
||
# Clean Xcode build (temporary output directory)
|
||
if [ -d "ios/App/build" ]; then
|
||
rm -rf ios/App/build/
|
||
log_debug "Cleaned ios/App/build/"
|
||
fi
|
||
|
||
# Clean DerivedData
|
||
if [ -d "ios/App/DerivedData" ]; then
|
||
rm -rf ios/App/DerivedData/
|
||
log_debug "Cleaned ios/App/DerivedData/"
|
||
fi
|
||
|
||
# Clean Capacitor (using npm script instead of invalid cap clean command)
|
||
npm run clean:ios || true
|
||
|
||
log_success "iOS build cleaned"
|
||
}
|
||
|
||
# Function to build iOS app
|
||
build_ios_app() {
|
||
local build_config=""
|
||
local scheme="App"
|
||
local destination=""
|
||
|
||
if [ "$BUILD_TYPE" = "debug" ]; then
|
||
build_config="Debug"
|
||
# Use device SDK — prebuilt MLKit frameworks (MLImage, MLKitBarcodeScanning) ship
|
||
# iOS device slices only and cannot link against the iOS Simulator SDK.
|
||
destination="generic/platform=iOS"
|
||
else
|
||
build_config="Release"
|
||
destination="platform=iOS,id=auto"
|
||
fi
|
||
|
||
log_info "Building iOS app (${build_config})..."
|
||
|
||
cd ios/App
|
||
|
||
# Prevent pkgx-managed libs (e.g. zlib) from leaking into the iOS SDK linker.
|
||
unset LIBRARY_PATH
|
||
unset DYLD_LIBRARY_PATH
|
||
unset DYLD_FALLBACK_LIBRARY_PATH
|
||
|
||
|
||
# Build the app:
|
||
# -quiet: skip the huge export VAR dump (compiler warnings still show unless suppressed below).
|
||
# SWIFT_SUPPRESS_WARNINGS / GCC_WARN_INHIBIT_ALL_WARNINGS: quiet CLI output from Pods + plugins;
|
||
# build in Xcode for full diagnostics. Real errors still fail the build.
|
||
local build_exit=0
|
||
xcodebuild -quiet \
|
||
-workspace App.xcworkspace \
|
||
-scheme "$scheme" \
|
||
-configuration "$build_config" \
|
||
-destination "$destination" \
|
||
build \
|
||
CODE_SIGN_IDENTITY="" \
|
||
CODE_SIGNING_REQUIRED=NO \
|
||
CODE_SIGNING_ALLOWED=NO \
|
||
SWIFT_SUPPRESS_WARNINGS=YES \
|
||
GCC_WARN_INHIBIT_ALL_WARNINGS=YES || build_exit=$?
|
||
|
||
cd ../..
|
||
|
||
if [ $build_exit -ne 0 ]; then
|
||
return $build_exit
|
||
fi
|
||
|
||
log_success "iOS app built successfully"
|
||
}
|
||
|
||
# Function to deploy to device
|
||
deploy_ios_app() {
|
||
log_info "Deploy-app mode: building app and deploying to device"
|
||
|
||
# Check for connected device
|
||
local devices=$(xcrun devicectl list devices --json | grep -c '"state":"booted"' || echo "0")
|
||
if [ "$devices" -eq 0 ]; then
|
||
log_error "No iOS device connected. Please connect a device and try again."
|
||
exit 1
|
||
fi
|
||
|
||
# Build app for device
|
||
BUILD_TYPE="debug"
|
||
build_ios_app
|
||
|
||
# Get device ID
|
||
local device_id=$(xcrun devicectl list devices --json | grep -o '"identifier":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||
|
||
if [ -z "$device_id" ]; then
|
||
log_error "Could not find device ID. Please ensure device is connected and unlocked."
|
||
exit 1
|
||
fi
|
||
|
||
# Install app on device
|
||
log_info "Installing app on device..."
|
||
xcrun devicectl device install app --device "$device_id" ios/App/build/Debug-iphoneos/App.app
|
||
|
||
log_success "iOS app deployed successfully to device!"
|
||
log_info "You can now run the app with: npx cap run ios"
|
||
}
|
||
|
||
# Function to auto-run iOS app
|
||
auto_run_ios_app() {
|
||
log_step "Auto-running iOS app..."
|
||
|
||
# Check if we're in debug mode (simulator) or release mode (device)
|
||
if [ "$BUILD_TYPE" = "debug" ]; then
|
||
log_info "Launching iOS Simulator and installing app"
|
||
safe_execute "Launching app" "npx cap run ios" || {
|
||
log_error "Failed to launch iOS app on simulator"
|
||
return 1
|
||
}
|
||
else
|
||
log_info "Building and installing on real device"
|
||
# For release builds, we need to use a different approach
|
||
# since npx cap run ios is primarily for simulators
|
||
log_warn "Auto-run for release builds requires manual device setup"
|
||
log_info "Please use Xcode to run the app on your device"
|
||
return 1
|
||
fi
|
||
|
||
log_success "iOS app launched successfully!"
|
||
}
|
||
|
||
# Parse command line arguments
|
||
parse_ios_args "$@"
|
||
|
||
# Print build header
|
||
print_header "TimeSafari iOS Build Process"
|
||
log_info "Starting iOS build process at $(date)"
|
||
log_info "Build mode: $BUILD_MODE"
|
||
log_info "Build type: $BUILD_TYPE"
|
||
|
||
# Setup environment for Capacitor build
|
||
setup_build_env "capacitor" "$BUILD_MODE"
|
||
|
||
# Setup application directories
|
||
setup_app_directories
|
||
|
||
# Load environment-specific .env file if it exists
|
||
env_file=".env.$BUILD_MODE"
|
||
if [ -f "$env_file" ]; then
|
||
load_env_file "$env_file"
|
||
else
|
||
log_debug "No $env_file file found, using default environment"
|
||
fi
|
||
|
||
# Load .env file if it exists (fallback)
|
||
if [ -f ".env" ]; then
|
||
load_env_file ".env"
|
||
fi
|
||
|
||
# Override API servers for iOS development when custom IP is specified
|
||
if [ "$BUILD_MODE" = "development" ] && [ -n "$CUSTOM_API_IP" ]; then
|
||
# Use custom IP for physical device development
|
||
export VITE_DEFAULT_ENDORSER_API_SERVER="http://${CUSTOM_API_IP}:3000"
|
||
export VITE_DEFAULT_PARTNER_API_SERVER="http://${CUSTOM_API_IP}:3000"
|
||
log_info "iOS development mode: Using custom IP ${CUSTOM_API_IP} for physical device"
|
||
fi
|
||
|
||
# Validate iOS environment
|
||
validate_ios_environment
|
||
|
||
# Handle clean-only mode
|
||
if [ "$CLEAN_ONLY" = true ]; then
|
||
log_info "Clean-only mode: cleaning build artifacts"
|
||
clean_ios_build
|
||
safe_execute "Cleaning dist directory" "clean_build_artifacts dist" || exit 1
|
||
log_success "Clean completed successfully!"
|
||
exit 0
|
||
fi
|
||
|
||
# Xcode 26 / CocoaPods workaround for cap sync (used by sync-only and full build)
|
||
# Temporarily downgrade project.pbxproj objectVersion 70 -> 56 so pod install succeeds.
|
||
run_cap_sync_with_workaround() {
|
||
local PROJECT_FILE="ios/App/App.xcodeproj/project.pbxproj"
|
||
|
||
if [ ! -f "$PROJECT_FILE" ]; then
|
||
log_error "Project file not found: $PROJECT_FILE (run full build first?)"
|
||
return 1
|
||
fi
|
||
|
||
local current_version
|
||
current_version=$(grep "objectVersion" "$PROJECT_FILE" | head -1 | grep -o "[0-9]\+" || echo "")
|
||
|
||
if [ -z "$current_version" ]; then
|
||
log_error "Could not determine project format version for Capacitor sync"
|
||
return 1
|
||
fi
|
||
|
||
if [ "$current_version" = "70" ]; then
|
||
log_debug "Applying Xcode 26 workaround for Capacitor sync: temporarily downgrading to format 56"
|
||
|
||
if ! sed -i '' 's/objectVersion = 70;/objectVersion = 56;/' "$PROJECT_FILE"; then
|
||
log_error "Failed to downgrade project format for Capacitor sync"
|
||
return 1
|
||
fi
|
||
|
||
log_info "Running Capacitor sync..."
|
||
if ! npx cap sync ios; then
|
||
log_error "Capacitor sync failed"
|
||
sed -i '' 's/objectVersion = 56;/objectVersion = 70;/' "$PROJECT_FILE" || true
|
||
return 1
|
||
fi
|
||
|
||
log_debug "Restoring project format to 70 after Capacitor sync..."
|
||
sed -i '' 's/objectVersion = 56;/objectVersion = 70;/' "$PROJECT_FILE" || true
|
||
log_success "Capacitor sync completed successfully"
|
||
else
|
||
log_debug "Project format is $current_version, running Capacitor sync normally"
|
||
if ! npx cap sync ios; then
|
||
log_error "Capacitor sync failed"
|
||
return 1
|
||
fi
|
||
log_success "Capacitor sync completed successfully"
|
||
fi
|
||
}
|
||
|
||
# Handle sync-only mode
|
||
if [ "$SYNC_ONLY" = true ]; then
|
||
log_info "Sync-only mode: syncing with Capacitor (with Xcode 26 workaround if needed)"
|
||
safe_execute "Syncing with Capacitor" "run_cap_sync_with_workaround" || exit 6
|
||
log_success "Sync completed successfully!"
|
||
exit 0
|
||
fi
|
||
|
||
# Handle assets-only mode
|
||
if [ "$ASSETS_ONLY" = true ]; then
|
||
log_info "Assets-only mode: generating assets"
|
||
safe_execute "Generating assets" "generate_ios_assets" || exit 7
|
||
log_success "Assets generation completed successfully!"
|
||
exit 0
|
||
fi
|
||
|
||
# Handle deploy-app mode
|
||
if [ "$DEPLOY_APP" = true ]; then
|
||
deploy_ios_app
|
||
exit 0
|
||
fi
|
||
|
||
# Step 1: Check iOS resources
|
||
check_ios_resources
|
||
|
||
# Step 2: Clean iOS build
|
||
safe_execute "Cleaning iOS build" "clean_ios_build" || exit 1
|
||
|
||
# Step 3: Clean dist directory
|
||
log_info "Cleaning dist directory..."
|
||
clean_build_artifacts "dist"
|
||
|
||
# Step 4: Run TypeScript type checking for test and production builds
|
||
if [ "$BUILD_MODE" = "production" ] || [ "$BUILD_MODE" = "test" ]; then
|
||
log_info "Running TypeScript type checking for $BUILD_MODE mode..."
|
||
|
||
if ! measure_time npm run type-check; then
|
||
log_error "TypeScript type checking failed for $BUILD_MODE mode!"
|
||
exit 2
|
||
fi
|
||
|
||
log_success "TypeScript type checking completed for $BUILD_MODE mode"
|
||
else
|
||
log_debug "Skipping TypeScript type checking for development mode"
|
||
fi
|
||
|
||
# Step 5: Build Capacitor version with mode
|
||
if [ "$BUILD_MODE" = "development" ]; then
|
||
safe_execute "Building Capacitor version (development)" "npm run build:capacitor" || exit 3
|
||
elif [ "$BUILD_MODE" = "test" ]; then
|
||
safe_execute "Building Capacitor version (test)" "npm run build:capacitor -- --mode test" || exit 3
|
||
elif [ "$BUILD_MODE" = "production" ]; then
|
||
safe_execute "Building Capacitor version (production)" "npm run build:capacitor -- --mode production" || exit 3
|
||
fi
|
||
|
||
# Step 6: Fix Daily Notification Plugin podspec name (must run before pod install)
|
||
# ===================================================================
|
||
# The Podfile expects TimesafariDailyNotificationPlugin.podspec, but the plugin
|
||
# package only includes CapacitorDailyNotification.podspec. This script creates
|
||
# the expected podspec file before CocoaPods tries to resolve dependencies.
|
||
# ===================================================================
|
||
log_info "Fixing Daily Notification Plugin podspec name..."
|
||
if [ -f "./scripts/fix-daily-notification-podspec.sh" ]; then
|
||
if ./scripts/fix-daily-notification-podspec.sh; then
|
||
log_success "Daily Notification Plugin podspec created"
|
||
else
|
||
log_warn "Failed to create podspec (may already exist)"
|
||
fi
|
||
else
|
||
log_warn "fix-daily-notification-podspec.sh not found, skipping"
|
||
fi
|
||
|
||
# Step 6.5: Install CocoaPods dependencies (with Xcode 26 workaround)
|
||
# ===================================================================
|
||
# WORKAROUND: Xcode 26 / CocoaPods Compatibility Issue
|
||
# ===================================================================
|
||
# Xcode 26 uses project format version 70, but CocoaPods' xcodeproj gem
|
||
# (1.27.0) only supports up to version 56. This causes pod install to fail.
|
||
#
|
||
# This workaround temporarily downgrades the project format to 56, runs
|
||
# pod install, then restores it to 70. Xcode will automatically upgrade
|
||
# it back to 70 when opened, which is fine.
|
||
#
|
||
# NOTE: Both explicit pod install AND Capacitor sync (which runs pod install
|
||
# internally) need this workaround. run_pod_install_with_workaround() is below;
|
||
# run_cap_sync_with_workaround() is defined earlier (used by --sync and Step 6.6).
|
||
#
|
||
# TO REMOVE THIS WORKAROUND IN THE FUTURE:
|
||
# 1. Check if xcodeproj gem has been updated: bundle exec gem list xcodeproj
|
||
# 2. Test if pod install works without the workaround
|
||
# 3. If it works, remove run_pod_install_with_workaround() and run_cap_sync_with_workaround()
|
||
# 4. Replace with:
|
||
# - safe_execute "Installing CocoaPods dependencies" "cd ios/App && bundle exec pod install && cd ../.." || exit 6
|
||
# - safe_execute "Syncing with Capacitor" "npx cap sync ios" || exit 6
|
||
# 5. Update this comment to indicate the workaround has been removed
|
||
# ===================================================================
|
||
run_pod_install_with_workaround() {
|
||
local PROJECT_FILE="ios/App/App.xcodeproj/project.pbxproj"
|
||
|
||
log_info "Installing CocoaPods dependencies (with Xcode 26 workaround)..."
|
||
|
||
# Check if project file exists
|
||
if [ ! -f "$PROJECT_FILE" ]; then
|
||
log_error "Project file not found: $PROJECT_FILE"
|
||
return 1
|
||
fi
|
||
|
||
# Check current format version
|
||
local current_version=$(grep "objectVersion" "$PROJECT_FILE" | head -1 | grep -o "[0-9]\+" || echo "")
|
||
|
||
if [ -z "$current_version" ]; then
|
||
log_error "Could not determine project format version"
|
||
return 1
|
||
fi
|
||
|
||
log_debug "Current project format version: $current_version"
|
||
|
||
# Only apply workaround if format is 70
|
||
if [ "$current_version" = "70" ]; then
|
||
log_debug "Applying Xcode 26 workaround: temporarily downgrading to format 56"
|
||
|
||
# Downgrade to format 56 (supported by CocoaPods)
|
||
if ! sed -i '' 's/objectVersion = 70;/objectVersion = 56;/' "$PROJECT_FILE"; then
|
||
log_error "Failed to downgrade project format"
|
||
return 1
|
||
fi
|
||
|
||
# Run pod install
|
||
log_info "Running pod install..."
|
||
if ! (cd ios/App && bundle exec pod install && cd ../..); then
|
||
log_error "pod install failed"
|
||
# Try to restore format even on failure
|
||
sed -i '' 's/objectVersion = 56;/objectVersion = 70;/' "$PROJECT_FILE" || true
|
||
return 1
|
||
fi
|
||
|
||
# Restore to format 70
|
||
log_debug "Restoring project format to 70..."
|
||
if ! sed -i '' 's/objectVersion = 56;/objectVersion = 70;/' "$PROJECT_FILE"; then
|
||
log_warn "Failed to restore project format to 70 (Xcode will upgrade it automatically)"
|
||
fi
|
||
|
||
log_success "CocoaPods dependencies installed successfully"
|
||
else
|
||
# Format is not 70, run pod install normally
|
||
log_debug "Project format is $current_version, running pod install normally"
|
||
if ! (cd ios/App && bundle exec pod install && cd ../..); then
|
||
log_error "pod install failed"
|
||
return 1
|
||
fi
|
||
log_success "CocoaPods dependencies installed successfully"
|
||
fi
|
||
}
|
||
|
||
safe_execute "Installing CocoaPods dependencies" "run_pod_install_with_workaround" || exit 6
|
||
|
||
# Step 6.6: Sync with Capacitor (uses run_cap_sync_with_workaround defined above for Xcode 26)
|
||
safe_execute "Syncing with Capacitor" "run_cap_sync_with_workaround" || exit 6
|
||
|
||
# Step 7: Generate assets
|
||
safe_execute "Generating assets" "generate_ios_assets" || exit 7
|
||
|
||
# Step 8: Build iOS app
|
||
safe_execute "Building iOS app" "build_ios_app" || exit 5
|
||
|
||
# Step 9: Build IPA/App if requested
|
||
if [ "$BUILD_IPA" = true ]; then
|
||
log_info "Building IPA package..."
|
||
cd ios/App
|
||
xcodebuild -quiet \
|
||
-workspace App.xcworkspace \
|
||
-scheme App \
|
||
-configuration Release \
|
||
-archivePath build/App.xcarchive \
|
||
archive \
|
||
CODE_SIGN_IDENTITY="" \
|
||
CODE_SIGNING_REQUIRED=NO \
|
||
CODE_SIGNING_ALLOWED=NO \
|
||
SWIFT_SUPPRESS_WARNINGS=YES \
|
||
GCC_WARN_INHIBIT_ALL_WARNINGS=YES
|
||
|
||
xcodebuild -quiet -exportArchive \
|
||
-archivePath build/App.xcarchive \
|
||
-exportPath build/ \
|
||
-exportOptionsPlist exportOptions.plist
|
||
cd ../..
|
||
log_success "IPA package built successfully"
|
||
fi
|
||
|
||
if [ "$BUILD_APP" = true ]; then
|
||
log_info "Building app bundle..."
|
||
# App bundle is already built in step 7
|
||
log_success "App bundle built successfully"
|
||
fi
|
||
|
||
# Step 10: Auto-run app if requested
|
||
if [ "$AUTO_RUN" = true ]; then
|
||
safe_execute "Auto-running iOS app" "auto_run_ios_app" || exit 9
|
||
fi
|
||
|
||
# Step 11: Open Xcode if requested
|
||
if [ "$OPEN_STUDIO" = true ]; then
|
||
safe_execute "Opening Xcode" "npx cap open ios" || exit 8
|
||
fi
|
||
|
||
# Print build summary
|
||
log_success "iOS build completed successfully!"
|
||
log_info "Build mode: $BUILD_MODE"
|
||
log_info "Build type: $BUILD_TYPE"
|
||
if [ "$BUILD_IPA" = true ]; then
|
||
log_info "IPA build: completed"
|
||
fi
|
||
if [ "$BUILD_APP" = true ]; then
|
||
log_info "App build: completed"
|
||
fi
|
||
if [ "$AUTO_RUN" = true ]; then
|
||
log_info "Auto-run: completed"
|
||
fi
|
||
if [ "$OPEN_STUDIO" = true ]; then
|
||
log_info "Xcode: opened"
|
||
fi
|
||
|
||
print_footer "iOS Build"
|
||
|
||
# Exit with success
|
||
exit 0 |