|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Build free-threaded CPython (--disable-gil) as an iOS XCframework with |
| 3 | +# CPython's own tooling (Platforms/Apple). Usage: build-ios.sh [VERSION] |
| 4 | +# Needs macOS with a full Xcode and python3 >= 3.10. |
| 5 | +set -euo pipefail |
| 6 | +here="$(cd "$(dirname "$0")" && pwd)" |
| 7 | +# shellcheck source=common.sh |
| 8 | +source "$here/common.sh" |
| 9 | + |
| 10 | +PY_VERSION="${1:-3.15.0}" |
| 11 | +# The XCframework needs every slice Platforms/Apple declares: one device host |
| 12 | +# and two simulator hosts (arm64, x86_64). |
| 13 | +HOSTS=(arm64-apple-ios arm64-apple-ios-simulator x86_64-apple-ios-simulator) |
| 14 | + |
| 15 | +src="$(fetch_source "$PY_VERSION")" |
| 16 | +cd "$src" |
| 17 | +xcodebuild -version |
| 18 | + |
| 19 | +# Platforms/Apple names the standard library directory "python<major.minor>" |
| 20 | +# when it packages and in the Xcode build helper, but a free-threaded build |
| 21 | +# installs "python<major.minor>t" (LIBDEST has $(ABI_THREAD)). Without these two |
| 22 | +# edits `package` fails copying the xcprivacy files and the helper script would |
| 23 | +# not find the standard library. Each edit is asserted so a changed upstream |
| 24 | +# script fails loudly instead of silently skipping the patch. |
| 25 | +python3 -I - <<'PY' |
| 26 | +import pathlib |
| 27 | +def patch(path, old, new): |
| 28 | + p = pathlib.Path(path) |
| 29 | + s = p.read_text() |
| 30 | + assert s.count(old) == 1, f"{path}: expected exactly one {old!r}" |
| 31 | + p.write_text(s.replace(old, new)) |
| 32 | +patch("Platforms/Apple/__main__.py", |
| 33 | + 'version_tag = ".".join(version.split(".")[:2])', |
| 34 | + 'version_tag = ".".join(version.split(".")[:2]) + "t"') |
| 35 | +patch("Platforms/Apple/testbed/Python.xcframework/build/utils.sh", |
| 36 | + 'grep -E "^python3\\.\\d+$"', |
| 37 | + 'grep -E "^python3\\.[0-9]+t?$"') |
| 38 | +PY |
| 39 | + |
| 40 | +# --disable-gil goes to configure after `--`, for the build Python and each host. |
| 41 | +python3 Platforms/Apple configure-build -- --disable-gil |
| 42 | +python3 Platforms/Apple make-build |
| 43 | +python3 Platforms/Apple pythoninfo-build |
| 44 | +for host in "${HOSTS[@]}"; do |
| 45 | + python3 Platforms/Apple configure-host iOS "$host" -- --disable-gil |
| 46 | + python3 Platforms/Apple make-host iOS "$host" |
| 47 | +done |
| 48 | +python3 Platforms/Apple package iOS |
| 49 | + |
| 50 | +# ---- verification ------------------------------------------------------- |
| 51 | +minor="$(py_minor "$PY_VERSION")" |
| 52 | +archive="$(ls cross-build/dist/python-*-iOS-XCframework.tar.gz)" |
| 53 | +log "package: $archive" |
| 54 | + |
| 55 | +# On macOS the build interpreter is python.exe (cross-build/build/python is a directory). |
| 56 | +build_py=cross-build/build/python.exe; [ -x "$build_py" ] || build_py=cross-build/build/python |
| 57 | +"$build_py" -c 'import sys, sysconfig; assert sysconfig.get_config_var("Py_GIL_DISABLED") == 1; print("build python free-threaded:", sys.version)' |
| 58 | + |
| 59 | +check="$FT_WORK/ios-check" |
| 60 | +rm -rf "$check"; mkdir -p "$check" |
| 61 | +tar -xzf "$archive" -C "$check" |
| 62 | +xcf="$check/Python.xcframework" |
| 63 | +[ -d "$xcf" ] || die "no Python.xcframework in the package" |
| 64 | + |
| 65 | +[ -d "$xcf/lib/python${minor}t" ] || die "no lib/python${minor}t in the XCframework" |
| 66 | +slices=0 |
| 67 | +for slice in "$xcf"/ios-*; do |
| 68 | + slices=$((slices + 1)) |
| 69 | + # Headers/pyconfig.h is a wrapper that includes pyconfig-<arch>.h. |
| 70 | + found=0 |
| 71 | + for h in "$slice"/Python.framework/Headers/pyconfig-*.h; do |
| 72 | + require_gil_disabled "$h"; found=$((found + 1)) |
| 73 | + done |
| 74 | + [ "$found" -gt 0 ] || die "no pyconfig-<arch>.h in $slice" |
| 75 | + [ -e "$slice/lib/libpython${minor}t.dylib" ] || die "no libpython${minor}t.dylib in $slice" |
| 76 | + lipo -info "$slice/Python.framework/Python" |
| 77 | +done |
| 78 | +[ "$slices" -eq 2 ] || die "expected 2 slices (device, simulator), found $slices" |
| 79 | + |
| 80 | +cp "$archive" "$FT_OUT/" |
| 81 | +echo "iOS archive:" >> "${GITHUB_STEP_SUMMARY:-/dev/null}" |
| 82 | +record_sha256 "$FT_OUT/$(basename "$archive")" |
0 commit comments