ajhahn.de
← the-way-out
Shell 119 lines
#!/usr/bin/env bash
# Cross-build "The Way Out.app" for Intel Macs (x86_64), to be tested by
# a friend on macOS Monterey 12.7.6. The WHOLE toolchain runs under
# Rosetta so the produced bundle is pure x86_64. The arm64 build
# (build_mac.sh / .venv/) is deliberately left untouched.
#
# Prereqs (see ajhahnde/Pipeline.md — Schritt 1+2, both DONE+verified):
#   - Rosetta 2 installed and working
#   - /Library/Frameworks/Python.framework/Versions/3.12 — universal2,
#     python.org 3.12.10, x86_64 slice min-OS 10.13 (<= Monterey 12.7)
#   - .venv-intel/ created from it under Rosetta
#     (pygame 2.6.1, pyinstaller 6.20.0, x86_64-clean, no arm64 leak)
#
# This script self-verifies the result (no arm64 anywhere + min-OS
# <= 12.7) and fails loudly if the bundle would not run on the friend's
# Mac — that check is the whole point of the exercise.
set -euo pipefail
cd "$(dirname "$0")"

PY=".venv-intel/bin/python"
[ -x "$PY" ] || { echo "missing $PY — run ajhahnde/Pipeline.md Schritt 1+2 first"; exit 1; }

# Robust Rosetta probe. NOT 'arch -x86_64 uname -m': uname can report
# the real hardware (arm64) and that one-liner gives a false negative.
/usr/bin/arch -x86_64 /usr/bin/true 2>/dev/null \
  || { echo "Rosetta 2 not working (arch -x86_64 failed)"; exit 1; }
MACH="$(arch -x86_64 "$PY" -c 'import platform;print(platform.machine())')"
[ "$MACH" = "x86_64" ] \
  || { echo "venv python is not x86_64 under Rosetta (got: $MACH)"; exit 1; }

arch -x86_64 "$PY" -m pip install --quiet --upgrade pyinstaller certifi

APPNAME="The Way Out"

# 'rm -rf dist' below would destroy an existing arm64 artifact. Back the
# arm64 zip up OUTSIDE the repo first (so neither the rm nor the seed
# rsync can touch it). The two builds' final zips have different names,
# but dist/ itself is NOT shared — only one .app lives there at a time.
if [ -f "dist/TheWayOut-mac.zip" ]; then
  BK="../TheWayOut-mac-arm64-$(git rev-parse --short HEAD 2>/dev/null || date +%s).zip"
  cp -p "dist/TheWayOut-mac.zip" "$BK"
  echo "Backed up existing arm64 zip -> $BK"
fi

SEEDPARENT="$(mktemp -d)"; SEED="$SEEDPARENT/_seed"; mkdir -p "$SEED"
trap 'rm -rf "$SEEDPARENT"' EXIT
rsync -a --delete \
  --exclude '.git' --exclude '.venv' --exclude '.venv-intel' \
  --exclude '__pycache__' --exclude 'build' --exclude 'dist' \
  --exclude '*.spec' --exclude '.DS_Store' \
  --exclude 'ajhahnde' \
  ./ "$SEED/"                                # assets/ MUST be in (offline 1st run)

rm -rf build dist "$APPNAME.spec"
arch -x86_64 "$PY" -m PyInstaller --noconfirm --windowed --clean \
  --name "$APPNAME" \
  --osx-bundle-identifier de.ajhahn.thewayout \
  --icon assets/icon.icns \
  --collect-all pygame \
  --collect-all certifi \
  --target-architecture x86_64 \
  --add-data "$SEED:_seed" \
  launcher.py

# Declare the bundle as a game so macOS Sonoma+ auto-enables Game Mode
# (parity with build_mac.sh). Must run before codesign.
PLIST="dist/$APPNAME.app/Contents/Info.plist"
/usr/libexec/PlistBuddy \
  -c "Add :LSApplicationCategoryType string public.app-category-games" \
  "$PLIST" 2>/dev/null \
  || /usr/libexec/PlistBuddy \
  -c "Set :LSApplicationCategoryType public.app-category-games" "$PLIST"

codesign --force --deep --sign - "dist/$APPNAME.app"

# ---- Hardened verification: this is why the script exists ----
APP="dist/$APPNAME.app"
EXE="$APP/Contents/MacOS/$APPNAME"
echo "=== verify: main executable ==="
file "$EXE"

echo "=== verify: NO arm64 slice anywhere in the bundle ==="
LEAK="$(find "$APP" -type f \( -name '*.so' -o -name '*.dylib' -o -perm -111 \) \
  -exec sh -c 'lipo -archs "$1" 2>/dev/null | grep -qw arm64 && echo "$1"' _ {} \;)"
if [ -n "$LEAK" ]; then
  echo "FAIL: arm64 slices present — will NOT run on the Intel Mac:"
  echo "$LEAK"
  exit 2
fi
echo "OK: no arm64 slices (pure x86_64 bundle)"

echo "=== verify: min-OS <= Monterey 12.7 on key binaries ==="
OK=1
for b in "$EXE" \
  "$APP/Contents/Frameworks"/Python \
  "$APP/Contents/Frameworks"/Python.framework/Versions/*/Python \
  "$APP/Contents/Frameworks"/libpython*.dylib \
  "$APP/Contents/Frameworks"/SDL2*.dylib ; do
  [ -e "$b" ] || continue
  MIN="$(otool -l "$b" 2>/dev/null \
    | awk '/LC_VERSION_MIN_MACOSX|LC_BUILD_VERSION/{f=1} f&&/(minos|version) /{print $2;f=0}' \
    | head -1)"
  echo "  $(basename "$b"): minos=${MIN:-unknown}"
  case "$MIN" in
    ""|*[!0-9.]*) : ;;                       # unknown -> report only
    *) maj=${MIN%%.*}; rest=${MIN#*.}; mn=${rest%%.*}; mn=${mn:-0}
       if [ "$maj" -gt 12 ] || { [ "$maj" -eq 12 ] && [ "$mn" -gt 7 ]; }; then
         echo "  FAIL: $b requires macOS $MIN (> 12.7)"; OK=0
       fi ;;
  esac
done
[ "$OK" -eq 1 ] || { echo "FAIL: a binary needs newer than Monterey 12.7"; exit 2; }
echo "OK: all checked binaries run on Monterey 12.7.6"

( cd dist && ditto -c -k --keepParent "$APPNAME.app" "TheWayOut-mac-intel.zip" )
echo
echo "Done: dist/TheWayOut-mac-intel.zip  (x86_64, Monterey-verified)"
echo "Next: Pipeline.md Schritt 6 — AirDrop this zip + send the friend block."