Files
SpeedBreaker ProjectandClaude Opus 5.5 34e1f75901 Docs for v0.1.2: screenshots from v0.1.2 (sunlit roads, the round sun glare), the IPA not yet installed on a device; iOS build.sh's symbol check
The README's screenshots are new captures from v0.1.2 at the same moments.
The README, scripts/ios/README.md and the release text now say the IPA
itself hasn't been installed on a device yet (the tested iPhones and iPad
ran Xcode-signed builds), which sideloading tools keep which entitlement
with a paid account, and that an IPA installed over a self-built app is a
separate app. THIRD_PARTY_NOTICES.md names the IPA's licenses folder.

scripts/ios/build.sh counts the vkGetInstanceProcAddr match instead of
using grep -q, which with pipefail could stop a complete build with a false
"stripped" alarm (building it yourself, and make_ipa.sh without --app).
The v0.1.2 downloads were built from the previous commit; this one changes
none of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:36:48 +00:00

106 lines
5.7 KiB
Bash
Executable File

#!/bin/bash
# SpeedBreaker. GPL-3.0-or-later (see COPYING).
#
# scripts/ios/build.sh [install]: SpeedBreaker.app for iPad and iPhone
# (arm64, iOS 17 or later), in build/ios/xcode/Build/Products/Release-iphoneos.
# 1. CMake and Ninja compile the game and the runtime for iOS into static
# libraries (build/ios).
# 2. xcodebuild links them with SDL3, glslang, FFmpeg-XMA and MoltenVK
# (static, no Vulkan loader) into the app and signs it (automatic
# signing: Xcode's account for the team, and the device registered).
# 3. "install": devicectl installs it on the connected device.
# Settings, from the environment:
# IOS_DEPS the iOS dependencies, made by build_deps.sh (README.md):
# IOS_DEPS/prefix (SDL3, glslang, FFmpeg-XMA) and
# IOS_DEPS/MoltenVK
# DEVELOPMENT_TEAM the Apple developer team that signs it
# IOS_BUNDLE_ID the app's bundle identifier (unique to the team)
# IOS_DEVICE the device to install on (`xcrun devicectl list
# devices`; the only connected one by default)
# IOS_UNSIGNED=1 an app nobody signed, for make_ipa.sh (an IPA that
# players sideload): no team, no account, no profile;
# in build/ios/xcode-unsigned. IOS_BUNDLE_ID is then
# optional (a generic one), and "install" refused.
# No game data: the game installs from the player's disc image on the device.
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
export DEVELOPER_DIR=${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}
: "${IOS_DEPS:?set IOS_DEPS: the folder scripts/ios/build_deps.sh made (scripts/ios/README.md)}"
UNSIGNED=${IOS_UNSIGNED:-0}
if [ "$UNSIGNED" = 1 ]; then
[ "${1:-}" = install ] && { echo "build.sh: IOS_UNSIGNED=1 makes an app nobody signed, which no device installs: make_ipa.sh packages it for sideloading" >&2; exit 2; }
# Sideloading tools give it an identifier of the player's own anyway.
IOS_BUNDLE_ID=${IOS_BUNDLE_ID:-io.github.speedbreakerproject.SpeedBreaker}
else
: "${DEVELOPMENT_TEAM:?set DEVELOPMENT_TEAM: your Apple developer team ID (scripts/ios/README.md)}"
: "${IOS_BUNDLE_ID:?set IOS_BUNDLE_ID: a bundle identifier of your own, such as com.yourname.SpeedBreaker}"
fi
PREFIX=$IOS_DEPS/prefix
MOLTENVK=$IOS_DEPS/MoltenVK
BUILD=$ROOT/build/ios
JOBS=${JOBS:-6}
if [ ! -f "$BUILD/build.ninja" ]; then
cmake -S "$ROOT" -B "$BUILD" -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=17.0 \
-DSDL3_DIR="$PREFIX/lib/cmake/SDL3" -Dglslang_DIR="$PREFIX/lib/cmake/glslang" \
-DFFMPEG_XMA="$PREFIX" -DMOLTENVK_DIR="$MOLTENVK"
fi
ninja -C "$BUILD" -j"$JOBS"
# The runtime's archive whole (static constructors register things nobody
# references by name); the rest as the linker needs them. SDL finds
# vkGetInstanceProcAddr in the app itself with dlsym, so it must survive
# dead stripping.
LIBS=(
-force_load "$BUILD/runtime/libSpeedBreaker.a"
"$BUILD/recomp/libNfsmwRecompLib.a" "$BUILD/runtime/libXenonUtilsLite.a" "$BUILD/runtime/libo1heap.a"
"$BUILD/runtime/libimgui.a"
"$PREFIX/lib/libSDL3.a"
"$PREFIX/lib/libglslang.a" "$PREFIX/lib/libglslang-default-resource-limits.a"
"$PREFIX/lib/libavcodec.a" "$PREFIX/lib/libavutil.a"
"$MOLTENVK/MoltenVK/static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a"
-Wl,-u,_vkGetInstanceProcAddr
-lc++
)
for lib in SPIRV MachineIndependent GenericCodeGen OSDependent; do
[ -f "$PREFIX/lib/lib$lib.a" ] && LIBS+=("$PREFIX/lib/lib$lib.a")
done
for fw in AVFoundation AudioToolbox CoreAudio CoreBluetooth CoreFoundation CoreGraphics CoreMedia CoreMotion \
CoreVideo Foundation GameController IOSurface Metal OpenGLES QuartzCore UIKit UniformTypeIdentifiers; do
LIBS+=(-framework "$fw")
done
LIBS+=(-weak_framework CoreHaptics)
# The app's version is project()'s (the top-level CMakeLists.txt); its build
# number the commit's number in the history, which grows from one release to
# the next.
VERSION=$(sed -n 's/^CMAKE_PROJECT_VERSION:STATIC=//p' "$BUILD/CMakeCache.txt")
BUILD_NUMBER=$(git -C "$ROOT" rev-list --count HEAD 2>/dev/null || echo 1)
# Signed for the team (Xcode's automatic signing makes the profile), or for
# make_ipa.sh not at all: then no account is asked anything.
if [ "$UNSIGNED" = 1 ]; then
DERIVED=$BUILD/xcode-unsigned
SIGNING=(CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= DEVELOPMENT_TEAM=)
else
DERIVED=$BUILD/xcode
SIGNING=(-allowProvisioningUpdates DEVELOPMENT_TEAM="$DEVELOPMENT_TEAM")
fi
xcodebuild -project "$ROOT/scripts/ios/SpeedBreaker.xcodeproj" -scheme SpeedBreaker -configuration Release \
-destination generic/platform=iOS -derivedDataPath "$DERIVED" -quiet "${SIGNING[@]}" \
PRODUCT_BUNDLE_IDENTIFIER="$IOS_BUNDLE_ID" \
MARKETING_VERSION="${VERSION:-0.0.0}" CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
OTHER_LDFLAGS="${LIBS[*]}" build
APP=$DERIVED/Build/Products/Release-iphoneos/SpeedBreaker.app
# Counted, not grep -q: grep -q stops reading nm's long output at the first
# match, and with pipefail nm's SIGPIPE then fails the pipe (a false alarm).
[ "$(nm -gU "$APP/SpeedBreaker" | grep -c ' _vkGetInstanceProcAddr$')" != 0 ] || { echo "build.sh: vkGetInstanceProcAddr was stripped" >&2; exit 1; }
echo "$APP (v${VERSION:-?}, build $BUILD_NUMBER, $(du -sh "$APP" | cut -f1))"
if [ "${1:-}" = install ]; then
DEVICE=${IOS_DEVICE:-$(xcrun devicectl list devices 2>/dev/null | grep 'available (paired)' | grep -oE '[0-9A-F]{8}(-[0-9A-F]{4}){3}-[0-9A-F]{12}' | head -1)}
: "${DEVICE:?no connected device}"
xcrun devicectl device install app --device "$DEVICE" "$APP"
fi