This commit updates the build_sign_notarize.sh script to improve the notarization process by introducing a conditional stapling option. It also cleans up temporary files and clears macOS provenance metadata to prevent issues during builds. The Dmg.cmake script is modified to remove the reliance on environment variables for codesigning, streamlining the build process. Additionally, the build_app.sh script is enhanced with heartbeat logging for long-running commands and improved cleanup procedures for build directories.
310 lines
11 KiB
Bash
Executable File
310 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ts() { date +"%H:%M:%S"; }
|
|
|
|
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_root="$(cd -- "${script_dir}/../.." && pwd)"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
./build_tools/macos/build_sign_notarize.sh # list local signing identities + notary profiles
|
|
./build_tools/macos/build_sign_notarize.sh --run [options] # build, sign, notarize, staple
|
|
|
|
Common options:
|
|
--run Perform build/sign/notarize (otherwise list identities/profiles)
|
|
--release | --debug Build config (default: Release)
|
|
--clean Clean build dir before build
|
|
--deploy Run CMake 'deploy' target before signing (default: on)
|
|
--no-deploy Do not run 'deploy' (not recommended for distribution)
|
|
--dmg Build a DMG after app notarization, then notarize+staple the DMG too
|
|
--build-dir <path> Override build directory
|
|
|
|
Signing options:
|
|
--identity "<name>" Codesign identity (e.g. "Developer ID Application: Your Name (TEAMID)")
|
|
--entitlements <plist> Optional entitlements plist for codesign
|
|
|
|
Notarization options (recommended):
|
|
--notary-profile <name> notarytool keychain profile name (created via `xcrun notarytool store-credentials <name> ...`)
|
|
--skip-notarize Skip notarization
|
|
|
|
Outputs:
|
|
- Signed app: <build-dir>/strawberry.app
|
|
- Zip for notarization: <build-dir>/strawberry-notarize.zip
|
|
- DMG (optional): <build-dir>/strawberry-*.dmg
|
|
|
|
Notes:
|
|
- This script is intended for Developer ID distribution (outside Mac App Store).
|
|
- If you want Sparkle updates, you'll typically ship a notarized .zip + an appcast feed.
|
|
EOF
|
|
}
|
|
|
|
list_identities_and_profiles() {
|
|
echo "==> [$(ts)] macOS code signing identities (Keychain)"
|
|
security find-identity -p codesigning -v || true
|
|
|
|
echo
|
|
echo "==> [$(ts)] notarytool credential profiles"
|
|
echo "Note: this Xcode notarytool version does not provide a 'list-profiles' command."
|
|
echo "If you forgot the profile name you created, check Keychain Access or re-run:"
|
|
echo " xcrun notarytool store-credentials \"<profile-name>\" --apple-id \"you@example.com\" --team-id \"TEAMID\""
|
|
|
|
echo
|
|
echo "==> [$(ts)] Provisioning profiles (macOS)"
|
|
prof_dir="${HOME}/Library/MobileDevice/Provisioning Profiles"
|
|
if [[ -d "${prof_dir}" ]]; then
|
|
ls -la "${prof_dir}" | head -n 50
|
|
else
|
|
echo "(none found at '${prof_dir}')"
|
|
fi
|
|
}
|
|
|
|
if [[ "$(uname -s)" != "Darwin" ]]; then
|
|
echo "Error: This script is for macOS only." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v xcode-select >/dev/null 2>&1 || ! xcode-select -p >/dev/null 2>&1; then
|
|
echo "Error: Xcode Command Line Tools not found." >&2
|
|
echo "Install them first: xcode-select --install" >&2
|
|
exit 1
|
|
fi
|
|
|
|
do_run=0
|
|
config="Release"
|
|
do_clean=0
|
|
do_deploy=1
|
|
do_dmg=0
|
|
build_dir=""
|
|
identity=""
|
|
entitlements=""
|
|
notary_profile=""
|
|
skip_notarize=0
|
|
|
|
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 ;;
|
|
--deploy) do_deploy=1; shift ;;
|
|
--no-deploy) do_deploy=0; shift ;;
|
|
--dmg) do_dmg=1; shift ;;
|
|
--build-dir) build_dir="${2:-}"; shift 2 ;;
|
|
--identity) identity="${2:-}"; shift 2 ;;
|
|
--entitlements) entitlements="${2:-}"; shift 2 ;;
|
|
--notary-profile) notary_profile="${2:-}"; shift 2 ;;
|
|
--skip-notarize) skip_notarize=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown arg: $1" >&2; usage; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ "$do_run" -eq 0 ]]; then
|
|
usage
|
|
echo
|
|
list_identities_and_profiles
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -z "$build_dir" ]]; then
|
|
lc_config="$(echo "$config" | tr '[:upper:]' '[:lower:]')"
|
|
build_dir="${repo_root}/cmake-build-macos-${lc_config}"
|
|
fi
|
|
|
|
app_path="${build_dir}/strawberry.app"
|
|
bin_path="${app_path}/Contents/MacOS/strawberry"
|
|
zip_path="${build_dir}/strawberry-notarize.zip"
|
|
dmg_path=""
|
|
|
|
notarize_and_maybe_staple() {
|
|
local file_path="$1"
|
|
local label="$2"
|
|
local do_staple="${3:-1}"
|
|
|
|
echo "==> [$(ts)] Notarizing ${label}"
|
|
local out
|
|
out="$(mktemp -t notarytool-submit.XXXXXX)"
|
|
xcrun notarytool submit "$file_path" --keychain-profile "$notary_profile" --wait --output-format plist --no-progress >"$out"
|
|
|
|
local submit_id submit_status
|
|
submit_id="$(/usr/bin/plutil -extract id raw -o - "$out" 2>/dev/null || true)"
|
|
submit_status="$(/usr/bin/plutil -extract status raw -o - "$out" 2>/dev/null || true)"
|
|
rm -f "$out" || true
|
|
|
|
if [[ -z "$submit_id" ]]; then
|
|
echo "Error: could not parse notarization submission id for ${label}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> [$(ts)] Notary submission id: $submit_id"
|
|
echo "==> [$(ts)] Notary status: $submit_status"
|
|
|
|
if [[ "$submit_status" != "Accepted" ]]; then
|
|
echo "Error: notarization failed for ${label} with status '$submit_status'. Fetching log..." >&2
|
|
xcrun notarytool log "$submit_id" --keychain-profile "$notary_profile" || true
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$do_staple" -eq 1 ]]; then
|
|
echo "==> [$(ts)] Stapling ${label}"
|
|
xcrun stapler staple "$file_path"
|
|
fi
|
|
}
|
|
|
|
if [[ -z "$identity" ]]; then
|
|
echo "Error: Missing --identity (Developer ID Application identity)." >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ "$skip_notarize" -eq 0 && -z "$notary_profile" ]]; then
|
|
echo "Error: Missing --notary-profile (or pass --skip-notarize)." >&2
|
|
exit 2
|
|
fi
|
|
|
|
echo "==> [$(ts)] Building Strawberry"
|
|
build_args=( "--release" )
|
|
if [[ "$config" == "Debug" ]]; then build_args=( "--debug" ); fi
|
|
if [[ "$do_clean" -eq 1 ]]; then build_args+=( "--clean" ); fi
|
|
if [[ -n "$build_dir" ]]; then build_args+=( "--build-dir" "$build_dir" ); fi
|
|
if [[ "$do_deploy" -eq 1 ]]; then build_args+=( "--deploy" ); fi
|
|
|
|
"${repo_root}/build_tools/macos/build_app.sh" "${build_args[@]}"
|
|
|
|
if [[ ! -x "$bin_path" ]]; then
|
|
echo "Error: built app not found at: $app_path" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> [$(ts)] Codesigning (hardened runtime)"
|
|
codesign_args=( --force --timestamp --options runtime --sign "$identity" )
|
|
if [[ -n "$entitlements" ]]; then
|
|
codesign_args+=( --entitlements "$entitlements" )
|
|
fi
|
|
|
|
# Clean up any leftover codesign temp files from previous interrupted runs.
|
|
# codesign may create *.cstemp alongside binaries while updating signatures.
|
|
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.
|
|
#
|
|
# Important: do NOT codesign individual files *inside* a .framework bundle (e.g. Sparkle.framework/Sparkle),
|
|
# because codesign expects frameworks to be signed as bundles and may error with
|
|
# "bundle format is ambiguous (could be app or framework)".
|
|
|
|
# 1) Sign dylibs and standalone executables that are NOT inside a .framework/.app/.xpc 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
|
|
codesign "${codesign_args[@]}" "$f" >/dev/null
|
|
done
|
|
|
|
# 2) Sign nested helper apps and XPC services (Sparkle ships these inside its framework).
|
|
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
|
|
|
|
# 2b) Sparkle.framework contains a standalone helper executable "Autoupdate" under Versions/* that is
|
|
# not inside an .app or .xpc bundle. Notarization requires it be signed with Developer ID + timestamp.
|
|
sparkle_fw="$app_path/Contents/Frameworks/Sparkle.framework"
|
|
if [[ -d "$sparkle_fw" ]]; then
|
|
find "$sparkle_fw/Versions" -type f -perm -111 \
|
|
! -name "*.cstemp" \
|
|
! -path "*/_CodeSignature/*" \
|
|
-print0 2>/dev/null | while IFS= read -r -d '' f; do
|
|
codesign "${codesign_args[@]}" "$f" >/dev/null
|
|
done
|
|
fi
|
|
|
|
# 3) Sign frameworks as bundles.
|
|
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
|
|
|
|
# 4) Finally sign the main app.
|
|
codesign "${codesign_args[@]}" "$app_path" >/dev/null
|
|
|
|
echo "==> [$(ts)] Verifying codesign"
|
|
codesign --verify --deep --strict --verbose=2 "$app_path"
|
|
|
|
echo "==> [$(ts)] Creating zip for notarization"
|
|
rm -f "$zip_path"
|
|
ditto -c -k --sequesterRsrc --keepParent "$app_path" "$zip_path"
|
|
|
|
if [[ "$skip_notarize" -eq 0 ]]; then
|
|
# ZIP archives cannot be stapled; notarization is still useful for distribution and Sparkle.
|
|
notarize_and_maybe_staple "$zip_path" "ZIP" 0
|
|
echo "==> [$(ts)] Stapling app"
|
|
xcrun stapler staple "$app_path"
|
|
fi
|
|
|
|
if [[ "$do_dmg" -eq 1 ]]; then
|
|
echo "==> [$(ts)] Building DMG (from already-signed app; no redeploy)"
|
|
if ! command -v create-dmg >/dev/null 2>&1; then
|
|
echo "Error: create-dmg not found. Install it with Homebrew (it's in Brewfile):" >&2
|
|
echo " ./build_tools/macos/install_brew_deps.sh" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Build a versioned DMG name using Info.plist (falls back to Strawberry version constant).
|
|
plist="${app_path}/Contents/Info.plist"
|
|
bundle_version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$plist" 2>/dev/null || true)"
|
|
if [[ -z "${bundle_version}" ]]; then
|
|
bundle_version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$plist" 2>/dev/null || true)"
|
|
fi
|
|
if [[ -z "${bundle_version}" ]]; then
|
|
bundle_version="unknown"
|
|
fi
|
|
arch="$(uname -m)"
|
|
dmg_path="${build_dir}/strawberry-${bundle_version}-${arch}.dmg"
|
|
|
|
rm -f "$dmg_path"
|
|
(
|
|
cd "$build_dir"
|
|
create-dmg \
|
|
--volname strawberry \
|
|
--background "${repo_root}/dist/macos/dmg_background.png" \
|
|
--app-drop-link 450 218 \
|
|
--icon strawberry.app 150 218 \
|
|
--window-size 600 450 \
|
|
"$(basename "$dmg_path")" \
|
|
strawberry.app
|
|
)
|
|
if [[ -z "$dmg_path" ]]; then
|
|
echo "Error: DMG was not created in $build_dir" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> [$(ts)] Codesigning DMG"
|
|
codesign --force --timestamp --sign "$identity" "$dmg_path"
|
|
|
|
if [[ "$skip_notarize" -eq 0 ]]; then
|
|
notarize_and_maybe_staple "$dmg_path" "DMG" 1
|
|
fi
|
|
fi
|
|
|
|
echo "==> [$(ts)] Gatekeeper assessment"
|
|
spctl -a -vv --type execute "$app_path" || true
|
|
|
|
echo
|
|
echo "Done."
|
|
echo "App: $app_path"
|
|
echo "Zip: $zip_path"
|
|
if [[ -n "${dmg_path}" ]]; then
|
|
echo "DMG: $dmg_path"
|
|
fi
|
|
|