242 lines
11 KiB
Bash
242 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# NetDesk by Akronlogic -- iOS build (Flutter + Rust core).
|
|
#
|
|
# Runs on macOS ONLY (needs Xcode + the iOS SDKs). It:
|
|
# 1. adds the Rust iOS targets (aarch64-apple-ios + aarch64-apple-ios-sim)
|
|
# 2. builds the Rust core static lib for device AND simulator
|
|
# 3. assembles NetDeskCore.xcframework (xcodebuild -create-xcframework)
|
|
# 4. ensures ui/ios/ exists (flutter create --platforms=ios .)
|
|
# 5. wires the xcframework into the Flutter iOS project (CocoaPods podspec)
|
|
# 6. flutter build ipa (signed with your Apple team)
|
|
#
|
|
# The Rust FFI static lib comes from the workspace `core` crate
|
|
# (package netdesk-core, lib netdesk_core -> libnetdesk_core.a). When the
|
|
# Phase-3 flutter_rust_bridge wrapper crate lands under a different name
|
|
# (e.g. "netdesk-frb"), point the script at it with:
|
|
# CORE_CRATE=netdesk-frb LIB_NAME=netdesk_frb ./build-ios.sh ...
|
|
#
|
|
# See docs/build-ios.md for prerequisites (Apple Developer Program, signing
|
|
# cert + provisioning profile) and the full walkthrough.
|
|
#
|
|
# Usage:
|
|
# packaging/build-ios.sh --team ABCDE12345
|
|
# packaging/build-ios.sh --team ABCDE12345 --method ad-hoc --org com.akronlogic
|
|
# packaging/build-ios.sh --skip-ipa # just build the xcframework
|
|
# packaging/build-ios.sh --help
|
|
#
|
|
# Env overrides: CORE_CRATE LIB_NAME DEVICE_TARGET SIM_TARGET IOS_MIN
|
|
# FLUTTER TEAM_ID EXPORT_METHOD FLUTTER_ORG
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
# ---- configuration (env-overridable) ---------------------------------------
|
|
CORE_CRATE="${CORE_CRATE:-netdesk-core}" # cargo package to build
|
|
LIB_NAME="${LIB_NAME:-netdesk_core}" # -> lib<LIB_NAME>.a
|
|
DEVICE_TARGET="${DEVICE_TARGET:-aarch64-apple-ios}"
|
|
SIM_TARGET="${SIM_TARGET:-aarch64-apple-ios-sim}" # Intel Macs: x86_64-apple-ios
|
|
IOS_MIN="${IOS_MIN:-13.0}"
|
|
FLUTTER="${FLUTTER:-flutter}"
|
|
FLUTTER_ORG="${FLUTTER_ORG:-space.netdesk}" # -> bundle id <org>.netdesk
|
|
EXPORT_METHOD="${EXPORT_METHOD:-app-store}" # app-store | ad-hoc | development | enterprise
|
|
TEAM_ID="${TEAM_ID:-${DEVELOPMENT_TEAM:-}}" # 10-char Apple Team ID
|
|
XCFRAMEWORK="NetDeskCore.xcframework"
|
|
|
|
SKIP_IPA=0
|
|
|
|
# ---- args ------------------------------------------------------------------
|
|
usage() { sed -n '3,29p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; }
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--team) TEAM_ID="${2:?--team needs an Apple Team ID}"; shift 2 ;;
|
|
--method) EXPORT_METHOD="${2:?--method needs a value}"; shift 2 ;;
|
|
--org) FLUTTER_ORG="${2:?--org needs a value}"; shift 2 ;;
|
|
--skip-ipa|--xcframework-only) SKIP_IPA=1; shift ;;
|
|
-h|--help) usage 0 ;;
|
|
*) echo "unknown argument: $1" >&2; usage 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ---- paths -----------------------------------------------------------------
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
UI="$ROOT/ui"
|
|
IOSDIR="$UI/ios"
|
|
TEMPLATES="$SCRIPT_DIR/ios"
|
|
DIST="$SCRIPT_DIR/dist/ios"
|
|
DEV_LIB="$ROOT/target/$DEVICE_TARGET/release/lib$LIB_NAME.a"
|
|
SIM_LIB="$ROOT/target/$SIM_TARGET/release/lib$LIB_NAME.a"
|
|
|
|
say() { printf '\033[36m==> %s\033[0m\n' "$*"; }
|
|
ok() { printf '\033[32m %s\033[0m\n' "$*"; }
|
|
die() { printf '\033[31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }
|
|
|
|
cat <<BANNER
|
|
=== NetDesk iOS build (Akronlogic) ===
|
|
root : $ROOT
|
|
crate : $CORE_CRATE (lib$LIB_NAME.a)
|
|
targets: $DEVICE_TARGET + $SIM_TARGET
|
|
flutter: $FLUTTER org: $FLUTTER_ORG ios-min: $IOS_MIN
|
|
ipa : $([ "$SKIP_IPA" = 1 ] && echo "SKIPPED (--skip-ipa)" || echo "method=$EXPORT_METHOD team=${TEAM_ID:-<unset>}")
|
|
BANNER
|
|
|
|
# ---- 0. preflight ----------------------------------------------------------
|
|
say "0/6 preflight"
|
|
[ "$(uname -s)" = "Darwin" ] || die "iOS builds require macOS. Run this on the Mac."
|
|
command -v rustup >/dev/null || die "rustup not found. Install Rust: https://rustup.rs"
|
|
command -v cargo >/dev/null || die "cargo not found on PATH (try: source \$HOME/.cargo/env)"
|
|
command -v xcrun >/dev/null || die "xcrun not found. Install Xcode + command line tools (xcode-select --install)."
|
|
xcode-select -p >/dev/null 2>&1 || die "no active Xcode. Run: sudo xcode-select -s /Applications/Xcode.app"
|
|
command -v "$FLUTTER" >/dev/null || die "flutter not found. Install the Flutter SDK and add it to PATH (or set FLUTTER=/path/to/flutter)."
|
|
if [ "$SKIP_IPA" = 0 ] && [ -z "$TEAM_ID" ]; then
|
|
die "no Apple Team ID. Pass --team ABCDE12345 (or TEAM_ID=...), or use --skip-ipa to only build the xcframework."
|
|
fi
|
|
ok "macOS $(sw_vers -productVersion 2>/dev/null || true), Xcode $(xcodebuild -version 2>/dev/null | head -1 || true)"
|
|
|
|
# ---- 1. rust ios targets ---------------------------------------------------
|
|
say "1/6 rustup targets"
|
|
rustup target add "$DEVICE_TARGET" "$SIM_TARGET"
|
|
ok "$DEVICE_TARGET, $SIM_TARGET installed"
|
|
|
|
# ---- 2. build the rust core static lib for both targets --------------------
|
|
say "2/6 cargo build (release, staticlib) for device + simulator"
|
|
# Match Flutter's deployment target so the slices are link-compatible.
|
|
export IPHONEOS_DEPLOYMENT_TARGET="$IOS_MIN"
|
|
( cd "$ROOT" && cargo build --release -p "$CORE_CRATE" --target "$DEVICE_TARGET" )
|
|
( cd "$ROOT" && cargo build --release -p "$CORE_CRATE" --target "$SIM_TARGET" )
|
|
[ -f "$DEV_LIB" ] || die "expected device lib not found: $DEV_LIB"
|
|
[ -f "$SIM_LIB" ] || die "expected simulator lib not found: $SIM_LIB"
|
|
ok "device: $DEV_LIB"
|
|
ok "sim : $SIM_LIB"
|
|
|
|
# ---- 3. assemble the xcframework -------------------------------------------
|
|
say "3/6 assemble $XCFRAMEWORK"
|
|
mkdir -p "$DIST"
|
|
rm -rf "$DIST/$XCFRAMEWORK" # xcodebuild refuses to overwrite
|
|
# No -headers yet: the C ABI header (cbindgen over core/src/ffi.rs) is Phase 3.
|
|
# When it exists, add: -headers "$ROOT/target/include" to each -library pair.
|
|
xcodebuild -create-xcframework \
|
|
-library "$DEV_LIB" \
|
|
-library "$SIM_LIB" \
|
|
-output "$DIST/$XCFRAMEWORK"
|
|
ok "built $DIST/$XCFRAMEWORK"
|
|
|
|
if [ "$SKIP_IPA" = 1 ]; then
|
|
# Still stage it into ui/ios if that project already exists, so a later
|
|
# `flutter build ipa` picks up the fresh binary.
|
|
if [ -d "$IOSDIR" ]; then
|
|
cp -R "$DIST/$XCFRAMEWORK" "$IOSDIR/"
|
|
ok "staged into $IOSDIR/$XCFRAMEWORK"
|
|
fi
|
|
say "done (--skip-ipa). xcframework at: $DIST/$XCFRAMEWORK"
|
|
exit 0
|
|
fi
|
|
|
|
# ---- 4. ensure the Flutter iOS project exists ------------------------------
|
|
say "4/6 ensure ui/ios Flutter project"
|
|
if [ ! -d "$IOSDIR" ]; then
|
|
ok "ui/ios missing — generating with flutter create"
|
|
( cd "$UI" && "$FLUTTER" create --org "$FLUTTER_ORG" --project-name netdesk --platforms=ios . )
|
|
else
|
|
ok "ui/ios already present — leaving it in place"
|
|
fi
|
|
( cd "$UI" && "$FLUTTER" pub get )
|
|
|
|
# ---- 5. wire the xcframework into ui/ios (CocoaPods) -----------------------
|
|
say "5/6 wire xcframework into the iOS project"
|
|
cp -R "$DIST/$XCFRAMEWORK" "$IOSDIR/"
|
|
ok "copied $XCFRAMEWORK into ui/ios"
|
|
|
|
cp "$TEMPLATES/netdesk_core.podspec" "$IOSDIR/netdesk_core.podspec"
|
|
ok "installed ui/ios/netdesk_core.podspec"
|
|
|
|
# Podfile: install the template if there is none, otherwise inject the pod line.
|
|
if [ ! -f "$IOSDIR/Podfile" ]; then
|
|
cp "$TEMPLATES/Podfile" "$IOSDIR/Podfile"
|
|
ok "installed ui/ios/Podfile (from template)"
|
|
else
|
|
# Uncomment any commented `platform :ios` line so it matches the xcframework.
|
|
if grep -qE '^\s*#\s*platform :ios' "$IOSDIR/Podfile"; then
|
|
sed -i '' -E "s/^\s*#\s*platform :ios.*/platform :ios, '$IOS_MIN'/" "$IOSDIR/Podfile"
|
|
fi
|
|
# Inject the netdesk_core pod into the Runner target if it is not there yet.
|
|
if ! grep -q "pod 'netdesk_core'" "$IOSDIR/Podfile"; then
|
|
awk '
|
|
/target '\''Runner'\'' do/ && !done {
|
|
print
|
|
print " # NetDesk Rust core (added by packaging/build-ios.sh)."
|
|
print " pod '\''netdesk_core'\'', :path => '\''.'\''"
|
|
done = 1
|
|
next
|
|
}
|
|
{ print }
|
|
' "$IOSDIR/Podfile" > "$IOSDIR/Podfile.tmp" && mv "$IOSDIR/Podfile.tmp" "$IOSDIR/Podfile"
|
|
ok "injected pod 'netdesk_core' into existing Podfile"
|
|
else
|
|
ok "Podfile already references netdesk_core"
|
|
fi
|
|
fi
|
|
|
|
# Signing for the archive step: set the team + automatic signing via the
|
|
# Flutter xcconfig layer (applies because a fresh Runner target has no explicit
|
|
# DEVELOPMENT_TEAM in project.pbxproj). Idempotent.
|
|
for cfg in Release Debug; do
|
|
XC="$IOSDIR/Flutter/$cfg.xcconfig"
|
|
[ -f "$XC" ] || continue
|
|
if ! grep -q '^DEVELOPMENT_TEAM' "$XC"; then
|
|
{
|
|
echo ""
|
|
echo "// NetDesk signing (added by packaging/build-ios.sh)"
|
|
echo "DEVELOPMENT_TEAM = $TEAM_ID"
|
|
echo "CODE_SIGN_STYLE = Automatic"
|
|
} >> "$XC"
|
|
ok "set DEVELOPMENT_TEAM in Flutter/$cfg.xcconfig"
|
|
else
|
|
sed -i '' -E "s/^DEVELOPMENT_TEAM.*/DEVELOPMENT_TEAM = $TEAM_ID/" "$XC"
|
|
ok "updated DEVELOPMENT_TEAM in Flutter/$cfg.xcconfig"
|
|
fi
|
|
done
|
|
|
|
# ExportOptions.plist consumed by `flutter build ipa` for the export phase.
|
|
cat > "$IOSDIR/ExportOptions.plist" <<PLIST
|
|
<?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>method</key><string>$EXPORT_METHOD</string>
|
|
<key>teamID</key><string>$TEAM_ID</string>
|
|
<key>signingStyle</key><string>automatic</string>
|
|
<key>uploadSymbols</key><true/>
|
|
<key>stripSwiftSymbols</key><true/>
|
|
<key>destination</key><string>export</string>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
ok "wrote ui/ios/ExportOptions.plist (method=$EXPORT_METHOD)"
|
|
|
|
# Resolve pods now so any CocoaPods error surfaces before the long archive.
|
|
if command -v pod >/dev/null; then
|
|
( cd "$IOSDIR" && pod install )
|
|
ok "pod install complete"
|
|
else
|
|
ok "CocoaPods 'pod' not on PATH — flutter will run it during the build (sudo gem install cocoapods to install)"
|
|
fi
|
|
|
|
# ---- 6. flutter build ipa --------------------------------------------------
|
|
say "6/6 flutter build ipa"
|
|
( cd "$UI" && "$FLUTTER" build ipa --release --export-options-plist=ios/ExportOptions.plist )
|
|
|
|
IPA="$(ls -t "$UI/build/ios/ipa/"*.ipa 2>/dev/null | head -1 || true)"
|
|
echo ""
|
|
echo "=== DONE ==="
|
|
echo "xcframework : $DIST/$XCFRAMEWORK (also staged in ui/ios/)"
|
|
if [ -n "$IPA" ]; then
|
|
echo "ipa : $IPA"
|
|
echo ""
|
|
echo "Upload to App Store Connect / TestFlight with:"
|
|
echo " xcrun altool --upload-app -f \"$IPA\" -t ios --apiKey <KEY_ID> --apiIssuer <ISSUER_ID>"
|
|
echo "or open ui/build/ios/archive/Runner.xcarchive in Xcode > Organizer > Distribute App."
|
|
else
|
|
echo "ipa : (not found under ui/build/ios/ipa — check the flutter output above)"
|
|
fi
|