Add architecture guidance for Mac App Store submissions in README_MAS.md
Some checks failed
Build / Build openSUSE (leap:15.6) (push) Has been cancelled
Build / Build openSUSE (leap:16.0) (push) Has been cancelled
Build / Build openSUSE (tumbleweed) (push) Has been cancelled
Build / Build Fedora (42) (push) Has been cancelled
Build / Build Fedora (43) (push) Has been cancelled
Build / Build Fedora (44) (push) Has been cancelled
Build / Build OpenMandriva (cooker) (push) Has been cancelled
Build / Build Mageia (9) (push) Has been cancelled
Build / Build Debian (bookworm) (push) Has been cancelled
Build / Build Debian (forky) (push) Has been cancelled
Build / Build Debian (trixie) (push) Has been cancelled
Build / Build Ubuntu (noble) (push) Has been cancelled
Build / Build Ubuntu (questing) (push) Has been cancelled
Build / Build Ubuntu (resolute) (push) Has been cancelled
Build / Upload Ubuntu PPA (noble) (push) Has been cancelled
Build / Upload Ubuntu PPA (questing) (push) Has been cancelled
Build / Upload Ubuntu PPA (resolute) (push) Has been cancelled
Build / Build FreeBSD (push) Has been cancelled
Build / Build OpenBSD (push) Has been cancelled
Build / Build macOS Public (release, macos-15) (push) Has been cancelled
Build / Build macOS Public (release, macos-15-intel) (push) Has been cancelled
Build / Build macOS Private (release, macos-arm64) (push) Has been cancelled
Build / Build Windows MinGW (i686, debug) (push) Has been cancelled
Build / Build Windows MinGW (i686, release) (push) Has been cancelled
Build / Build Windows MinGW (x86_64, debug) (push) Has been cancelled
Build / Build Windows MinGW (x86_64, release) (push) Has been cancelled
Build / Build Windows MSVC (arm64, debug, arm64 debug, windows-11-arm) (push) Has been cancelled
Build / Build Windows MSVC (arm64, release, arm64 release, windows-11-arm) (push) Has been cancelled
Build / Build Windows MSVC (x86, debug, x86 debug, windows-2022) (push) Has been cancelled
Build / Build Windows MSVC (x86, release, x86 release, windows-2022) (push) Has been cancelled
Build / Build Windows MSVC (x86_64, debug, x86_64 debug, windows-2022) (push) Has been cancelled
Build / Build Windows MSVC (x86_64, release, x86_64 release, windows-2022) (push) Has been cancelled
Build / Upload (push) Has been cancelled
Build / Attach to release (push) Has been cancelled
Some checks failed
Build / Build openSUSE (leap:15.6) (push) Has been cancelled
Build / Build openSUSE (leap:16.0) (push) Has been cancelled
Build / Build openSUSE (tumbleweed) (push) Has been cancelled
Build / Build Fedora (42) (push) Has been cancelled
Build / Build Fedora (43) (push) Has been cancelled
Build / Build Fedora (44) (push) Has been cancelled
Build / Build OpenMandriva (cooker) (push) Has been cancelled
Build / Build Mageia (9) (push) Has been cancelled
Build / Build Debian (bookworm) (push) Has been cancelled
Build / Build Debian (forky) (push) Has been cancelled
Build / Build Debian (trixie) (push) Has been cancelled
Build / Build Ubuntu (noble) (push) Has been cancelled
Build / Build Ubuntu (questing) (push) Has been cancelled
Build / Build Ubuntu (resolute) (push) Has been cancelled
Build / Upload Ubuntu PPA (noble) (push) Has been cancelled
Build / Upload Ubuntu PPA (questing) (push) Has been cancelled
Build / Upload Ubuntu PPA (resolute) (push) Has been cancelled
Build / Build FreeBSD (push) Has been cancelled
Build / Build OpenBSD (push) Has been cancelled
Build / Build macOS Public (release, macos-15) (push) Has been cancelled
Build / Build macOS Public (release, macos-15-intel) (push) Has been cancelled
Build / Build macOS Private (release, macos-arm64) (push) Has been cancelled
Build / Build Windows MinGW (i686, debug) (push) Has been cancelled
Build / Build Windows MinGW (i686, release) (push) Has been cancelled
Build / Build Windows MinGW (x86_64, debug) (push) Has been cancelled
Build / Build Windows MinGW (x86_64, release) (push) Has been cancelled
Build / Build Windows MSVC (arm64, debug, arm64 debug, windows-11-arm) (push) Has been cancelled
Build / Build Windows MSVC (arm64, release, arm64 release, windows-11-arm) (push) Has been cancelled
Build / Build Windows MSVC (x86, debug, x86 debug, windows-2022) (push) Has been cancelled
Build / Build Windows MSVC (x86, release, x86 release, windows-2022) (push) Has been cancelled
Build / Build Windows MSVC (x86_64, debug, x86_64 debug, windows-2022) (push) Has been cancelled
Build / Build Windows MSVC (x86_64, release, x86_64 release, windows-2022) (push) Has been cancelled
Build / Upload (push) Has been cancelled
Build / Attach to release (push) Has been cancelled
This commit enhances the `README_MAS.md` by providing detailed information on the differences between arm64-only, universal, and x86_64-only app architectures for Mac App Store uploads. It includes recommendations for achieving broad compatibility and clarifies the limitations of submitting multiple builds. This update aims to assist developers in making informed decisions regarding app architecture for their submissions.
This commit is contained in:
@@ -212,6 +212,51 @@ Outputs:
|
||||
|
||||
---
|
||||
|
||||
## Architecture note — arm64 vs universal (arm64+x86_64)
|
||||
|
||||
For Mac App Store uploads, your `.pkg` can contain either:
|
||||
|
||||
- **arm64-only** app (Apple Silicon only), or
|
||||
- **universal** app (arm64 + x86_64), or
|
||||
- **x86_64-only** app (runs on Apple Silicon under Rosetta 2, but native Intel only otherwise)
|
||||
|
||||
Apple does **not** require universal binaries for review. **arm64-only is allowed**, but:
|
||||
|
||||
- Intel Macs **cannot** run an arm64-only app.
|
||||
- If you ship arm64-only, App Store Connect will effectively make the app available only to Apple Silicon Macs (and your listing will reflect that).
|
||||
|
||||
### Recommendation
|
||||
|
||||
- If you want the broadest compatibility, aim for a **universal build**.
|
||||
- If you’re okay supporting only Apple Silicon Macs, arm64-only is the simplest path.
|
||||
|
||||
### Can I upload two different `.pkg`s (one arm64, one x86_64)?
|
||||
|
||||
Not in the way you want.
|
||||
|
||||
- In App Store Connect you can upload multiple builds over time, but for any given version/submission you ultimately pick **one build** to submit.
|
||||
- Apple will not “merge” two separate uploads (arm64-only + x86_64-only) into one app for customers.
|
||||
|
||||
If you want both Apple Silicon and Intel supported **natively**, you need to produce a **single universal** app bundle and package that into **one** `.pkg`.
|
||||
|
||||
If you don’t want to deal with universal yet, your practical choices are:
|
||||
|
||||
- **arm64-only**: Apple Silicon only.
|
||||
- **x86_64-only**: runs on Intel natively, and on Apple Silicon under **Rosetta 2** (slower, but widely compatible).
|
||||
|
||||
### Practical reality for this repo
|
||||
|
||||
This project depends on large native dependency stacks (Qt, GStreamer, plugins). If you build those via Homebrew, you typically end up with **single-architecture** libraries (arm64 under `/opt/homebrew`, x86_64 under `/usr/local`).
|
||||
|
||||
A true universal app requires **all bundled native code** (your executable *and* all `.dylib`/plugins/frameworks you ship) to be universal as well.
|
||||
|
||||
If you decide you want universal:
|
||||
|
||||
- You’ll need a universal build of **Qt** and **GStreamer** (and all bundled plugins), or
|
||||
- Build arm64 and x86_64 bundles separately and combine *matching* binaries where possible (advanced; easy to break signing / plugin loading).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting — `productbuild` fails with CSSM `-60008` (authorization)
|
||||
|
||||
If you see something like:
|
||||
@@ -303,3 +348,74 @@ xcrun iTMSTransporter -help
|
||||
CLI upload requires additional credentials (App Store Connect API key or Apple ID auth) and is easier to get wrong than the Transporter GUI.
|
||||
For most folks, **Transporter.app is the recommended path**.
|
||||
|
||||
---
|
||||
|
||||
## Creating a universal Mac App Store upload using two Macs (arm64 + x86_64)
|
||||
|
||||
If you have both an Apple Silicon Mac and an Intel Mac, the most reliable way to ship a universal app for this repo is:
|
||||
|
||||
1. Build + deploy the **unsigned** MAS app bundle on each machine (arm64 and x86_64).
|
||||
2. Copy both `.app` bundles to the machine that has your signing keys.
|
||||
3. Merge them with `lipo` and then **sign + package** once, producing a single universal `.pkg`.
|
||||
|
||||
### Step A — Build + deploy (arm64 machine)
|
||||
|
||||
On your Apple Silicon Mac:
|
||||
|
||||
```bash
|
||||
./build_tools/macos/build_app.sh --release --clean --mas --deploy --build-dir ./cmake-build-macos-release-mas-arm64
|
||||
```
|
||||
|
||||
This produces (unsigned):
|
||||
|
||||
- `cmake-build-macos-release-mas-arm64/strawberry.app`
|
||||
|
||||
### Step B — Build + deploy (x86_64 machine)
|
||||
|
||||
On your Intel Mac:
|
||||
|
||||
```bash
|
||||
./build_tools/macos/build_app.sh --release --clean --mas --deploy --build-dir ./cmake-build-macos-release-mas-x86_64
|
||||
```
|
||||
|
||||
This produces (unsigned):
|
||||
|
||||
- `cmake-build-macos-release-mas-x86_64/strawberry.app`
|
||||
|
||||
### Step C — Copy both app bundles to one “packaging” machine
|
||||
|
||||
Pick the Mac that has your **Apple Distribution** and **Installer** identities (private keys) installed.
|
||||
Copy both `.app` bundles onto that Mac, for example:
|
||||
|
||||
- `/path/to/inputs/strawberry-arm64.app`
|
||||
- `/path/to/inputs/strawberry-x86_64.app`
|
||||
|
||||
Tip: `rsync` works well for app bundles:
|
||||
|
||||
```bash
|
||||
rsync -a "/path/to/arm64/strawberry.app" "/path/to/inputs/strawberry-arm64.app"
|
||||
rsync -a "/path/to/x86_64/strawberry.app" "/path/to/inputs/strawberry-x86_64.app"
|
||||
```
|
||||
|
||||
### Step D — Merge + sign + build the universal `.pkg`
|
||||
|
||||
On the packaging machine:
|
||||
|
||||
```bash
|
||||
./build_tools/macos/build_mas_universal_pkg.sh --run \
|
||||
--arm-app "/path/to/inputs/strawberry-arm64.app" \
|
||||
--x86-app "/path/to/inputs/strawberry-x86_64.app" \
|
||||
--codesign-identity "Apple Distribution: Your Name (TEAMID)" \
|
||||
--installer-identity "3rd Party Mac Developer Installer: Your Name (TEAMID)" \
|
||||
--provisionprofile "/path/to/profile.provisionprofile"
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
- `cmake-build-macos-release-mas-universal/strawberry.app` (universal)
|
||||
- `cmake-build-macos-release-mas-universal/strawberry-mas-universal.pkg`
|
||||
|
||||
### Important constraints (don’t skip)
|
||||
|
||||
- The two input apps must be built from the **same commit** with the **same enabled features** so the app bundle layouts match.
|
||||
- Do **not** sign the per-arch apps first; `lipo` invalidates signatures. Sign **only after** merging.
|
||||
|
||||
241
build_tools/macos/build_mas_universal_pkg.sh
Normal file
241
build_tools/macos/build_mas_universal_pkg.sh
Normal file
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build a universal (arm64+x86_64) Mac App Store upload package by:
|
||||
# - merging two already-deployed Strawberry.app bundles (arm64 + x86_64) using lipo
|
||||
# - embedding a Mac App Store provisioning profile
|
||||
# - codesigning with Apple Distribution (+ entitlements)
|
||||
# - producing a signed .pkg with productbuild (Installer identity)
|
||||
#
|
||||
# Intended workflow with two Macs:
|
||||
# 1) On Apple Silicon Mac: build+deploy MAS app bundle (unsigned) → copy strawberry.app somewhere
|
||||
# 2) On Intel Mac: build+deploy MAS app bundle (unsigned) → copy strawberry.app somewhere
|
||||
# 3) On the Mac that holds your signing keys (either one): run THIS script to merge+sign+package
|
||||
|
||||
ts() { date +"%H:%M:%S"; }
|
||||
|
||||
if [[ -z "${BASH_VERSION:-}" ]]; then
|
||||
echo "Error: this script must be run with bash." >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "$(uname -s)" != "Darwin" ]]; then
|
||||
echo "Error: This script is for macOS only." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd -- "${script_dir}/../.." && pwd)"
|
||||
|
||||
ensure_keychain_search_list() {
|
||||
local login_kc="$HOME/Library/Keychains/login.keychain-db"
|
||||
local system_kc="/Library/Keychains/System.keychain"
|
||||
local roots_kc="/System/Library/Keychains/SystemRootCertificates.keychain"
|
||||
|
||||
local current
|
||||
current="$(security list-keychains -d user 2>/dev/null | tr -d '"' | tr -d ' ' || true)"
|
||||
if echo "$current" | grep -Fq "$system_kc"; then
|
||||
return 0
|
||||
fi
|
||||
echo "==> [$(ts)] Note: adding System keychains to the user keychain search list"
|
||||
security list-keychains -d user -s "$login_kc" "$system_kc" "$roots_kc" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
prepare_login_keychain_for_signing() {
|
||||
local keychain_path="$HOME/Library/Keychains/login.keychain-db"
|
||||
local pw="${1:-}"
|
||||
if [[ -z "$pw" ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo "==> [$(ts)] Preparing login keychain for signing (unlock + key partition list)"
|
||||
security unlock-keychain -p "$pw" "$keychain_path" >/dev/null 2>&1 || true
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$pw" "$keychain_path" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
preflight_identity() {
|
||||
local what="$1"
|
||||
local policy="$2"
|
||||
local identity="$3"
|
||||
if ! security find-identity -p "$policy" -v 2>/dev/null | grep -Fq "$identity"; then
|
||||
echo "Error: ${what} identity not found/usable in Keychain: $identity" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./build_tools/macos/build_mas_universal_pkg.sh --run [options]
|
||||
|
||||
Required:
|
||||
--run
|
||||
--arm-app <path> Path to arm64 Strawberry.app (already built+deployed, unsigned)
|
||||
--x86-app <path> Path to x86_64 Strawberry.app (already built+deployed, unsigned)
|
||||
--codesign-identity "<name>" Apple Distribution: ...
|
||||
--installer-identity "<name>" 3rd Party Mac Developer Installer: ...
|
||||
--provisionprofile <path> Mac App Store provisioning profile (*.provisionprofile)
|
||||
|
||||
Optional:
|
||||
--out-dir <path> Output directory (default: cmake-build-macos-release-mas-universal)
|
||||
--entitlements <plist> Codesign entitlements (default: dist/macos/entitlements.mas.plist)
|
||||
--pkg-out <path> Output .pkg path (default: <out-dir>/strawberry-mas-universal.pkg)
|
||||
--bundle-id <id> For display/logging only (does not rewrite Info.plist)
|
||||
--keychain-password <pw> Or set env var STRAWBERRY_KEYCHAIN_PASSWORD (quote it!)
|
||||
|
||||
Notes:
|
||||
- This script does NOT build Strawberry. It merges two pre-built app bundles.
|
||||
- After lipo-merging, the app must be re-signed (this script does that).
|
||||
EOF
|
||||
}
|
||||
|
||||
do_run=0
|
||||
arm_app=""
|
||||
x86_app=""
|
||||
out_dir=""
|
||||
codesign_identity=""
|
||||
installer_identity=""
|
||||
provisionprofile=""
|
||||
entitlements=""
|
||||
pkg_out=""
|
||||
bundle_id=""
|
||||
keychain_password="${STRAWBERRY_KEYCHAIN_PASSWORD:-}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--run) do_run=1; shift ;;
|
||||
--arm-app) arm_app="${2:-}"; shift 2 ;;
|
||||
--x86-app) x86_app="${2:-}"; shift 2 ;;
|
||||
--out-dir) out_dir="${2:-}"; shift 2 ;;
|
||||
--codesign-identity) codesign_identity="${2:-}"; shift 2 ;;
|
||||
--installer-identity) installer_identity="${2:-}"; shift 2 ;;
|
||||
--provisionprofile) provisionprofile="${2:-}"; shift 2 ;;
|
||||
--entitlements) entitlements="${2:-}"; shift 2 ;;
|
||||
--pkg-out) pkg_out="${2:-}"; shift 2 ;;
|
||||
--bundle-id) bundle_id="${2:-}"; shift 2 ;;
|
||||
--keychain-password) keychain_password="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown arg: $1" >&2; usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$do_run" -eq 0 ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$arm_app" || ! -d "$arm_app" ]]; then
|
||||
echo "Error: missing/invalid --arm-app: $arm_app" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "$x86_app" || ! -d "$x86_app" ]]; then
|
||||
echo "Error: missing/invalid --x86-app: $x86_app" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "$codesign_identity" ]]; then
|
||||
echo "Error: missing --codesign-identity" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "$installer_identity" ]]; then
|
||||
echo "Error: missing --installer-identity" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -z "$provisionprofile" || ! -f "$provisionprofile" ]]; then
|
||||
echo "Error: missing/invalid --provisionprofile: $provisionprofile" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "$entitlements" ]]; then
|
||||
entitlements="${repo_root}/dist/macos/entitlements.mas.plist"
|
||||
fi
|
||||
if [[ ! -f "$entitlements" ]]; then
|
||||
echo "Error: entitlements file not found: $entitlements" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "$out_dir" ]]; then
|
||||
out_dir="${repo_root}/cmake-build-macos-release-mas-universal"
|
||||
fi
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
universal_app="${out_dir}/strawberry.app"
|
||||
if [[ -e "$universal_app" ]]; then
|
||||
rm -rf "$universal_app"
|
||||
fi
|
||||
|
||||
echo "==> [$(ts)] Repo: $repo_root"
|
||||
echo "==> [$(ts)] arm app: $arm_app"
|
||||
echo "==> [$(ts)] x86 app: $x86_app"
|
||||
echo "==> [$(ts)] out dir: $out_dir"
|
||||
if [[ -n "$bundle_id" ]]; then
|
||||
echo "==> [$(ts)] bundle id (expected): $bundle_id"
|
||||
fi
|
||||
|
||||
echo "==> [$(ts)] Creating universal app bundle (lipo merge)"
|
||||
"${repo_root}/build_tools/macos/make_universal_app.sh" \
|
||||
--arm-app "$arm_app" \
|
||||
--x86-app "$x86_app" \
|
||||
--out-app "$universal_app" \
|
||||
--clean
|
||||
|
||||
echo "==> [$(ts)] Embedding provisioning profile"
|
||||
cp -f "$provisionprofile" "${universal_app}/Contents/embedded.provisionprofile"
|
||||
|
||||
ensure_keychain_search_list
|
||||
prepare_login_keychain_for_signing "$keychain_password"
|
||||
preflight_identity "codesign" "codesigning" "$codesign_identity"
|
||||
preflight_identity "installer" "basic" "$installer_identity"
|
||||
|
||||
echo "==> [$(ts)] Codesigning universal app (Mac App Store)"
|
||||
codesign_args=( --force --timestamp --options runtime --sign "$codesign_identity" --entitlements "$entitlements" )
|
||||
|
||||
# Clean up any leftover codesign temp files and xattrs.
|
||||
find "$universal_app" -name "*.cstemp" -print0 2>/dev/null | while IFS= read -r -d '' f; do rm -f "$f" || true; done
|
||||
xattr -dr com.apple.provenance "$universal_app" >/dev/null 2>&1 || true
|
||||
xattr -dr com.apple.quarantine "$universal_app" >/dev/null 2>&1 || true
|
||||
|
||||
# Sign nested code first, then frameworks, then the main app bundle.
|
||||
find "$universal_app" -type f \( -name "*.dylib" -o -name "*.so" -o -perm -111 \) \
|
||||
! -name "*.cstemp" \
|
||||
! -path "*/Contents/Frameworks/*.framework/*" \
|
||||
! -path "*/Contents/Frameworks/*.app/*" \
|
||||
! -path "*/Contents/Frameworks/*.xpc/*" \
|
||||
! -path "*/Contents/PlugIns/*.framework/*" \
|
||||
! -path "*/Contents/PlugIns/*.app/*" \
|
||||
! -path "*/Contents/PlugIns/*.xpc/*" \
|
||||
-print0 | while IFS= read -r -d '' f; do
|
||||
if /usr/bin/file -b "$f" | grep -q "Mach-O"; then
|
||||
codesign "${codesign_args[@]}" "$f" >/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
find "$universal_app" -type d \( -name "*.xpc" -o -name "*.app" \) -print0 2>/dev/null | while IFS= read -r -d '' b; do
|
||||
codesign "${codesign_args[@]}" "$b" >/dev/null
|
||||
done
|
||||
|
||||
find "$universal_app/Contents/Frameworks" "$universal_app/Contents/PlugIns" -type d -name "*.framework" -print0 2>/dev/null | while IFS= read -r -d '' fw; do
|
||||
codesign "${codesign_args[@]}" "$fw" >/dev/null
|
||||
done
|
||||
|
||||
codesign "${codesign_args[@]}" "$universal_app" >/dev/null
|
||||
|
||||
echo "==> [$(ts)] Verifying codesign"
|
||||
codesign --verify --deep --strict --verbose=2 "$universal_app"
|
||||
|
||||
if [[ -z "$pkg_out" ]]; then
|
||||
pkg_out="${out_dir}/strawberry-mas-universal.pkg"
|
||||
fi
|
||||
rm -f "$pkg_out" >/dev/null 2>&1 || true
|
||||
|
||||
echo "==> [$(ts)] Building signed .pkg for App Store upload"
|
||||
productbuild \
|
||||
--component "$universal_app" /Applications \
|
||||
--sign "$installer_identity" \
|
||||
"$pkg_out"
|
||||
|
||||
echo "==> [$(ts)] Verifying pkg signature"
|
||||
pkgutil --check-signature "$pkg_out" || true
|
||||
|
||||
echo
|
||||
echo "Done."
|
||||
echo "Universal app: $universal_app"
|
||||
echo "PKG: $pkg_out"
|
||||
|
||||
170
build_tools/macos/make_universal_app.sh
Normal file
170
build_tools/macos/make_universal_app.sh
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create a universal (arm64+x86_64) .app by merging two already-deployed app bundles
|
||||
# that have identical layouts, one built on Apple Silicon and one built on Intel.
|
||||
#
|
||||
# Usage:
|
||||
# ./build_tools/macos/make_universal_app.sh \
|
||||
# --arm-app /path/to/arm64/strawberry.app \
|
||||
# --x86-app /path/to/x86_64/strawberry.app \
|
||||
# --out-app /path/to/output/strawberry.app \
|
||||
# [--clean]
|
||||
#
|
||||
# Notes:
|
||||
# - Do NOT sign the per-arch apps first; signatures will be invalidated by lipo anyway.
|
||||
# - Both inputs must be the same app version/config with the same enabled features,
|
||||
# so the file lists match.
|
||||
|
||||
ts() { date +"%H:%M:%S"; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./build_tools/macos/make_universal_app.sh --arm-app <path> --x86-app <path> --out-app <path> [--clean]
|
||||
|
||||
What it does:
|
||||
- Copies the arm64 app to --out-app
|
||||
- For every Mach-O file in the copied app, finds the corresponding file in the x86_64 app
|
||||
- Uses lipo to combine the two slices into a universal binary at the same relative path
|
||||
|
||||
Required:
|
||||
--arm-app <path> Path to arm64 Strawberry.app (built+deployed on Apple Silicon)
|
||||
--x86-app <path> Path to x86_64 Strawberry.app (built+deployed on Intel)
|
||||
--out-app <path> Output path for universal Strawberry.app
|
||||
|
||||
Optional:
|
||||
--clean Delete --out-app if it already exists
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "$(uname -s)" != "Darwin" ]]; then
|
||||
echo "Error: This script is for macOS only." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arm_app=""
|
||||
x86_app=""
|
||||
out_app=""
|
||||
do_clean=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--arm-app) arm_app="${2:-}"; shift 2 ;;
|
||||
--x86-app) x86_app="${2:-}"; shift 2 ;;
|
||||
--out-app) out_app="${2:-}"; shift 2 ;;
|
||||
--clean) do_clean=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown arg: $1" >&2; usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$arm_app" || -z "$x86_app" || -z "$out_app" ]]; then
|
||||
echo "Error: missing required args." >&2
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! -d "$arm_app" ]]; then
|
||||
echo "Error: --arm-app not found: $arm_app" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! -d "$x86_app" ]]; then
|
||||
echo "Error: --x86-app not found: $x86_app" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for cmd in /usr/bin/file /usr/bin/lipo /usr/bin/ditto; do
|
||||
if [[ ! -x "$cmd" ]]; then
|
||||
echo "Error: required tool not found: $cmd" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
out_parent="$(cd -- "$(dirname -- "$out_app")" && pwd)"
|
||||
out_name="$(basename -- "$out_app")"
|
||||
out_app="${out_parent}/${out_name}"
|
||||
|
||||
if [[ -e "$out_app" && "$do_clean" -eq 1 ]]; then
|
||||
echo "==> [$(ts)] Removing existing output app: $out_app"
|
||||
rm -rf "$out_app"
|
||||
fi
|
||||
if [[ -e "$out_app" ]]; then
|
||||
echo "Error: output already exists: $out_app (use --clean to overwrite)" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "==> [$(ts)] Copying arm64 app to output"
|
||||
/usr/bin/ditto "$arm_app" "$out_app"
|
||||
|
||||
# Remove any existing signatures in the copied app; we'll re-sign after creating universal binaries.
|
||||
echo "==> [$(ts)] Removing existing code signature metadata (will be re-signed later)"
|
||||
find "$out_app" -type d -name "_CodeSignature" -print0 2>/dev/null | while IFS= read -r -d '' d; do
|
||||
rm -rf "$d" || true
|
||||
done
|
||||
|
||||
echo "==> [$(ts)] Merging Mach-O files with lipo"
|
||||
|
||||
merged=0
|
||||
skipped=0
|
||||
|
||||
# Traverse output app and lipo-merge any Mach-O file with its counterpart in the x86 app.
|
||||
while IFS= read -r -d '' f; do
|
||||
# Only operate on regular files.
|
||||
if [[ ! -f "$f" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
ft="$(/usr/bin/file -b "$f" 2>/dev/null || true)"
|
||||
if [[ "$ft" != *"Mach-O"* ]]; then
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
rel="${f#"$out_app"/}"
|
||||
other="${x86_app}/${rel}"
|
||||
if [[ ! -f "$other" ]]; then
|
||||
echo "Error: missing matching file in x86 app for:" >&2
|
||||
echo " $rel" >&2
|
||||
echo "Expected at:" >&2
|
||||
echo " $other" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
other_ft="$(/usr/bin/file -b "$other" 2>/dev/null || true)"
|
||||
if [[ "$other_ft" != *"Mach-O"* ]]; then
|
||||
echo "Error: file is Mach-O in arm app but not Mach-O in x86 app:" >&2
|
||||
echo " $rel" >&2
|
||||
echo "arm64: $ft" >&2
|
||||
echo "x86_64: $other_ft" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate architectures.
|
||||
arm_archs="$(/usr/bin/lipo -archs "$f" 2>/dev/null || true)"
|
||||
x86_archs="$(/usr/bin/lipo -archs "$other" 2>/dev/null || true)"
|
||||
if [[ "$arm_archs" != *"arm64"* ]]; then
|
||||
echo "Error: expected arm64 slice in arm app file:" >&2
|
||||
echo " $rel" >&2
|
||||
echo " archs: $arm_archs" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$x86_archs" != *"x86_64"* ]]; then
|
||||
echo "Error: expected x86_64 slice in x86 app file:" >&2
|
||||
echo " $rel" >&2
|
||||
echo " archs: $x86_archs" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp="$(mktemp -t strawberry-universal.XXXXXX)"
|
||||
/usr/bin/lipo -create "$f" "$other" -output "$tmp"
|
||||
chmod --reference="$f" "$tmp" 2>/dev/null || true
|
||||
mv -f "$tmp" "$f"
|
||||
merged=$((merged + 1))
|
||||
done < <(find "$out_app" -type f -print0 2>/dev/null)
|
||||
|
||||
echo "==> [$(ts)] Done"
|
||||
echo "Merged Mach-O files: $merged"
|
||||
echo "Non-Mach-O files skipped: $skipped"
|
||||
echo "Output app:"
|
||||
echo " $out_app"
|
||||
|
||||
Reference in New Issue
Block a user