Files
strawberry/build_tools/macos/build_mas_pkg.sh
David Helkowski d4d805443e Enhance macOS build scripts with keychain management and error handling
This commit introduces functions to ensure the System keychains are included in the user keychain search list, addressing common codesigning errors related to keychain trust chains. Additionally, it adds preflight checks for codesigning and installer identities, improving error reporting and guidance for developers. The README_MAS.md is updated to include troubleshooting steps for keychain-related issues, enhancing the overall usability of the macOS build process.
2026-01-22 20:46:09 +09:00

242 lines
8.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ts() { date +"%H:%M:%S"; }
lower() { echo "$1" | tr '[:upper:]' '[:lower:]'; }
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "${script_dir}/../.." && pwd)"
usage() {
cat <<'EOF'
Usage:
./build_tools/macos/build_mas_pkg.sh --run [options]
What it does:
- Builds Strawberry in Mac App Store mode (BUILD_FOR_MAC_APP_STORE=ON)
- Runs deploy (macdeployqt + bundling) so the app bundle is self-contained
- Embeds a Mac App Store provisioning profile into the app bundle
- Codesigns the app with an Apple Distribution identity + entitlements
- Builds a signed .pkg suitable for uploading to App Store Connect
Required options:
--run
--codesign-identity "<name>" (e.g. "Apple Distribution: Dry Ark LLC (TEAMID)")
--installer-identity "<name>" (e.g. "3rd Party Mac Developer Installer: Dry Ark LLC (TEAMID)")
--provisionprofile <path> Path to a *Mac App Store* provisioning profile (*.provisionprofile)
Optional:
--release | --debug Build config (default: Release)
--clean Clean build dir before build
--build-dir <path> Override build directory
--entitlements <plist> Codesign entitlements (default: dist/macos/entitlements.mas.plist)
--bundle-id <id> Override CFBundleIdentifier (default: com.dryark.strawberry)
--pkg-out <path> Output .pkg path (default: <build-dir>/strawberry-mas.pkg)
Examples:
./build_tools/macos/build_mas_pkg.sh --run --release --clean \
--codesign-identity "Apple Distribution: Your Name (TEAMID)" \
--installer-identity "3rd Party Mac Developer Installer: Your Name (TEAMID)" \
--provisionprofile "$HOME/Library/MobileDevice/Provisioning Profiles/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.provisionprofile"
Notes:
- Mac App Store submissions do NOT use Developer ID notarization.
- You must create a Mac App Store provisioning profile for your App ID in Apple Developer.
EOF
}
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "Error: This script is for macOS only." >&2
exit 1
fi
do_run=0
config="Release"
do_clean=0
build_dir=""
codesign_identity=""
installer_identity=""
provisionprofile=""
entitlements=""
bundle_id="com.dryark.strawberry"
pkg_out=""
while [[ $# -gt 0 ]]; do
case "$1" in
--run) do_run=1; shift ;;
--release) config="Release"; shift ;;
--debug) config="Debug"; shift ;;
--clean) do_clean=1; shift ;;
--build-dir) build_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 ;;
--bundle-id) bundle_id="${2:-}"; shift 2 ;;
--pkg-out) pkg_out="${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
echo
echo "==> [$(ts)] Tip: list available signing identities:"
echo " security find-identity -p codesigning -v"
echo " security find-identity -p basic -v"
exit 0
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 "$build_dir" ]]; then
build_dir="${repo_root}/cmake-build-macos-$(lower "$config")-mas"
fi
if [[ -z "$pkg_out" ]]; then
pkg_out="${build_dir}/strawberry-mas.pkg"
fi
echo "==> [$(ts)] Repo: ${repo_root}"
echo "==> [$(ts)] Build dir: ${build_dir}"
echo "==> [$(ts)] Config: ${config}"
echo "==> [$(ts)] Bundle ID: ${bundle_id}"
echo "==> [$(ts)] Entitlements: ${entitlements}"
echo "==> [$(ts)] Provisioning profile: ${provisionprofile}"
echo "==> [$(ts)] Output pkg: ${pkg_out}"
echo "==> [$(ts)] Building (Mac App Store mode)"
build_args=( "--release" )
if [[ "$config" == "Debug" ]]; then build_args=( "--debug" ); fi
if [[ "$do_clean" -eq 1 ]]; then build_args+=( "--clean" ); fi
build_args+=( "--build-dir" "$build_dir" "--mas" "--deploy" )
# Provide bundle id via CMake cache variable.
export MACOS_BUNDLE_ID="$bundle_id"
"${repo_root}/build_tools/macos/build_app.sh" "${build_args[@]}"
app_path="${build_dir}/strawberry.app"
bin_path="${app_path}/Contents/MacOS/strawberry"
if [[ ! -x "$bin_path" ]]; then
echo "Error: built app not found at: $app_path" >&2
exit 1
fi
echo "==> [$(ts)] Embedding provisioning profile"
cp -f "$provisionprofile" "${app_path}/Contents/embedded.provisionprofile"
ensure_keychain_search_list
preflight_identity "codesign" "-p codesigning" "$codesign_identity"
preflight_identity "installer" "-p basic" "$installer_identity"
ensure_keychain_search_list() {
# codesign builds the cert chain using the user keychain search list.
# If the list is missing the System keychain, you can get:
# "unable to build chain to self-signed root" + errSecInternalComponent
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 (fixes common codesign chain errors)"
echo " (This changes the user keychain search list; run 'security list-keychains -d user' to view.)"
security list-keychains -d user -s "$login_kc" "$system_kc" "$roots_kc" >/dev/null 2>&1 || true
}
preflight_identity() {
local what="$1"
local predicate="$2"
local identity="$3"
if ! security find-identity "$predicate" -v 2>/dev/null | grep -Fq "$identity"; then
echo "Error: ${what} identity not found/usable in Keychain: $identity" >&2
echo "Run: ./build_tools/macos/check_signing_identities.sh" >&2
exit 2
fi
}
echo "==> [$(ts)] Codesigning app (Mac App Store)"
codesign_args=( --force --timestamp --options runtime --sign "$codesign_identity" --entitlements "$entitlements" )
# Clean up any leftover codesign temp files from previous interrupted runs.
find "$app_path" -name "*.cstemp" -print0 2>/dev/null | while IFS= read -r -d '' f; do
rm -f "$f" || true
done
# Clear macOS provenance/quarantine metadata which can interfere with modifying files in-place.
xattr -dr com.apple.provenance "$app_path" >/dev/null 2>&1 || true
xattr -dr com.apple.quarantine "$app_path" >/dev/null 2>&1 || true
# Sign nested code first, then frameworks, then the main app bundle.
find "$app_path" -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
# Only sign Mach-O binaries.
if file -b "$f" | grep -q "Mach-O"; then
codesign "${codesign_args[@]}" "$f" >/dev/null
fi
done
find "$app_path" -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 "$app_path/Contents/Frameworks" "$app_path/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[@]}" "$app_path" >/dev/null
echo "==> [$(ts)] Verifying codesign"
codesign --verify --deep --strict --verbose=2 "$app_path"
echo "==> [$(ts)] Building signed .pkg for App Store upload"
rm -f "$pkg_out" >/dev/null 2>&1 || true
productbuild \
--component "$app_path" /Applications \
--sign "$installer_identity" \
"$pkg_out"
echo "==> [$(ts)] Verifying pkg signature"
pkgutil --check-signature "$pkg_out" || true
echo
echo "Done."
echo "App: $app_path"
echo "PKG: $pkg_out"