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.
267 lines
8.2 KiB
Bash
Executable File
267 lines
8.2 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)"
|
|
|
|
current_cmd_pid=""
|
|
current_hb_pid=""
|
|
|
|
kill_tree() {
|
|
local pid="$1"
|
|
[[ -z "${pid}" ]] && return 0
|
|
# Recurse into children first (best-effort).
|
|
local child
|
|
for child in $(pgrep -P "$pid" 2>/dev/null || true); do
|
|
kill_tree "$child"
|
|
done
|
|
kill -TERM "$pid" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
cleanup() {
|
|
# Never fail cleanup on errors.
|
|
set +e
|
|
if [[ -n "${current_hb_pid}" ]]; then
|
|
kill "${current_hb_pid}" >/dev/null 2>&1 || true
|
|
wait "${current_hb_pid}" >/dev/null 2>&1 || true
|
|
current_hb_pid=""
|
|
fi
|
|
if [[ -n "${current_cmd_pid}" ]]; then
|
|
# If still running, terminate process tree.
|
|
kill -0 "${current_cmd_pid}" >/dev/null 2>&1 && kill_tree "${current_cmd_pid}"
|
|
current_cmd_pid=""
|
|
fi
|
|
}
|
|
|
|
trap 'cleanup; exit 130' INT TERM
|
|
trap 'cleanup' EXIT
|
|
|
|
run_with_heartbeat() {
|
|
local desc="$1"
|
|
shift
|
|
|
|
local start now elapsed hb_pid
|
|
start="$(date +%s)"
|
|
|
|
echo "==> [$(ts)] ${desc}"
|
|
|
|
# Run the command in the background so we can reliably clean it up on Ctrl-C.
|
|
set +e
|
|
"$@" &
|
|
local cmd_pid=$!
|
|
set -e
|
|
current_cmd_pid="$cmd_pid"
|
|
|
|
(
|
|
while kill -0 "$cmd_pid" >/dev/null 2>&1; do
|
|
sleep 20
|
|
now="$(date +%s)"
|
|
elapsed="$((now - start))"
|
|
echo " [$(ts)] ... still working (${elapsed}s elapsed) ..."
|
|
done
|
|
) &
|
|
hb_pid="$!"
|
|
current_hb_pid="$hb_pid"
|
|
|
|
set +e
|
|
wait "$cmd_pid"
|
|
local rc=$?
|
|
set -e
|
|
|
|
# Clear globals before stopping heartbeat to avoid cleanup double-kill.
|
|
current_cmd_pid=""
|
|
kill "$hb_pid" >/dev/null 2>&1 || true
|
|
wait "$hb_pid" >/dev/null 2>&1 || true
|
|
current_hb_pid=""
|
|
|
|
now="$(date +%s)"
|
|
elapsed="$((now - start))"
|
|
|
|
if [[ $rc -ne 0 ]]; then
|
|
echo "Error: '${desc}' failed after ${elapsed}s (exit $rc)." >&2
|
|
return "$rc"
|
|
fi
|
|
|
|
echo "==> [$(ts)] Done: ${desc} (${elapsed}s)"
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
./build_tools/macos/build_app.sh [--debug|--release] [--deploy] [--dmg] [--clean] [--build-dir <path>]
|
|
|
|
What it does:
|
|
- Configures and builds Strawberry with CMake + Ninja
|
|
- Optional: runs CMake targets 'deploy' (bundle deps) and 'dmg' (create DMG)
|
|
|
|
Options:
|
|
--release Release build (default)
|
|
--debug Debug build
|
|
--deploy Run: cmake --build <builddir> --target deploy
|
|
--dmg Run: cmake --build <builddir> --target dmg (implies --deploy)
|
|
--clean Delete the build dir before configuring
|
|
--build-dir Override build directory (default: <repo>/cmake-build-macos-<config>)
|
|
-h, --help Show help
|
|
EOF
|
|
}
|
|
|
|
config="Release"
|
|
do_deploy=0
|
|
do_dmg=0
|
|
do_clean=0
|
|
build_dir=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--release) config="Release"; shift ;;
|
|
--debug) config="Debug"; shift ;;
|
|
--deploy) do_deploy=1; shift ;;
|
|
--dmg) do_dmg=1; do_deploy=1; shift ;;
|
|
--clean) do_clean=1; shift ;;
|
|
--build-dir) build_dir="${2:-}"; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown arg: $1" >&2; usage; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
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
|
|
|
|
if ! command -v brew >/dev/null 2>&1; then
|
|
echo "Error: Homebrew ('brew') not found in PATH." >&2
|
|
echo "Install Homebrew first: https://brew.sh/" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v cmake >/dev/null 2>&1; then
|
|
echo "Error: cmake not found. Did you run ./build_tools/macos/install_brew_deps.sh ?" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v ninja >/dev/null 2>&1; then
|
|
echo "Error: ninja not found. Did you run ./build_tools/macos/install_brew_deps.sh ?" >&2
|
|
exit 1
|
|
fi
|
|
|
|
brew_prefix="$(brew --prefix)"
|
|
qt_prefix="$(brew --prefix qt)"
|
|
icu_prefix="$(brew --prefix icu4c || true)"
|
|
|
|
if [[ -z "$build_dir" ]]; then
|
|
build_dir="${repo_root}/cmake-build-macos-$(lower "$config")"
|
|
fi
|
|
|
|
echo "==> [$(ts)] Repo: ${repo_root}"
|
|
echo "==> [$(ts)] Build dir: ${build_dir}"
|
|
echo "==> [$(ts)] Config: ${config}"
|
|
|
|
if [[ "$do_clean" -eq 1 ]]; then
|
|
echo "==> [$(ts)] Cleaning build dir"
|
|
# macOS 26+ can apply provenance metadata that blocks deletion even when permissions look normal.
|
|
# Clear common xattrs and immutable flags before deleting.
|
|
xattr -dr com.apple.provenance "$build_dir" >/dev/null 2>&1 || true
|
|
xattr -dr com.apple.quarantine "$build_dir" >/dev/null 2>&1 || true
|
|
chflags -R nouchg,noschg "$build_dir" >/dev/null 2>&1 || true
|
|
rm -rf "$build_dir" || {
|
|
echo "Error: failed to remove build dir: $build_dir" >&2
|
|
echo "This is usually due to macOS provenance/flags. Try:" >&2
|
|
echo " xattr -dr com.apple.provenance \"$build_dir\"" >&2
|
|
echo " chflags -R nouchg,noschg \"$build_dir\"" >&2
|
|
echo " rm -rf \"$build_dir\"" >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
mkdir -p "$build_dir"
|
|
|
|
# If you've run a previously-built app directly from the build directory, macOS can apply provenance
|
|
# metadata that makes the bundle effectively immutable (even when permissions look normal).
|
|
# That breaks CMake because it needs to update strawberry.app/Contents/Info.plist during configure/build.
|
|
app_bundle="${build_dir}/strawberry.app"
|
|
if [[ -d "${app_bundle}/Contents" ]]; then
|
|
# Try to clear provenance/quarantine metadata first (best effort).
|
|
xattr -dr com.apple.provenance "${app_bundle}" >/dev/null 2>&1 || true
|
|
xattr -dr com.apple.quarantine "${app_bundle}" >/dev/null 2>&1 || true
|
|
|
|
# If the bundle is still not writable, remove it so CMake can recreate it.
|
|
if ! ( : > "${app_bundle}/Contents/.cmake_write_test" ) 2>/dev/null; then
|
|
echo "==> [$(ts)] Existing ${app_bundle} is not writable (likely macOS provenance). Removing it."
|
|
rm -rf "${app_bundle}"
|
|
else
|
|
rm -f "${app_bundle}/Contents/.cmake_write_test" >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
|
|
# Make pkg-config more reliable with Homebrew.
|
|
export PKG_CONFIG_PATH="${brew_prefix}/lib/pkgconfig:${brew_prefix}/share/pkgconfig:${PKG_CONFIG_PATH:-}"
|
|
|
|
# For dist/CMakeLists.txt Info.plist minimum version logic.
|
|
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-12.0}"
|
|
|
|
cmake_prefix_path="${qt_prefix};${brew_prefix}"
|
|
|
|
cmake_extra_args=()
|
|
|
|
# Optional: override Sparkle update feed / key for your own published builds.
|
|
# Example:
|
|
# export SPARKLE_FEED_URL="https://example.com/appcast.xml"
|
|
# export SPARKLE_PUBLIC_ED25519_KEY="base64=="
|
|
if [[ -n "${SPARKLE_FEED_URL:-}" ]]; then
|
|
cmake_extra_args+=("-DSPARKLE_FEED_URL=${SPARKLE_FEED_URL}")
|
|
fi
|
|
if [[ -n "${SPARKLE_PUBLIC_ED25519_KEY:-}" ]]; then
|
|
cmake_extra_args+=("-DSPARKLE_PUBLIC_ED25519_KEY=${SPARKLE_PUBLIC_ED25519_KEY}")
|
|
fi
|
|
|
|
run_with_heartbeat "Configuring (CMAKE_PREFIX_PATH=${cmake_prefix_path})" \
|
|
cmake -S "$repo_root" -B "$build_dir" -G Ninja \
|
|
-DCMAKE_BUILD_TYPE="$config" \
|
|
-DCMAKE_PREFIX_PATH="$cmake_prefix_path" \
|
|
-DCMAKE_FRAMEWORK_PATH="${brew_prefix}/Frameworks;${brew_prefix}/opt/sparkle-framework/Frameworks" \
|
|
-DOPTIONAL_COMPONENTS_MISSING_DEPS_ARE_FATAL=OFF \
|
|
${cmake_extra_args+"${cmake_extra_args[@]}"} \
|
|
${icu_prefix:+-DICU_ROOT="$icu_prefix"}
|
|
|
|
run_with_heartbeat "Building" \
|
|
cmake --build "$build_dir" --parallel
|
|
|
|
if [[ "$do_deploy" -eq 1 ]]; then
|
|
echo "==> [$(ts)] Preparing env for 'deploy' target (GIO/GStreamer)"
|
|
export GIO_EXTRA_MODULES="${brew_prefix}/lib/gio/modules"
|
|
export GST_PLUGIN_SCANNER="${brew_prefix}/opt/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner"
|
|
export GST_PLUGIN_PATH="${brew_prefix}/lib/gstreamer-1.0"
|
|
|
|
# Optional, but helps dist/macos/macgstcopy.sh bundle libsoup which GStreamer loads dynamically.
|
|
libsoup_prefix="$(brew --prefix libsoup 2>/dev/null || true)"
|
|
if [[ -n "${libsoup_prefix}" ]]; then
|
|
libsoup_dylib="$(ls -1 "${libsoup_prefix}"/lib/libsoup-*.dylib 2>/dev/null | head -n 1 || true)"
|
|
if [[ -n "${libsoup_dylib}" ]]; then
|
|
export LIBSOUP_LIBRARY_PATH="${libsoup_dylib}"
|
|
fi
|
|
fi
|
|
|
|
run_with_heartbeat "Running: deploy" \
|
|
cmake --build "$build_dir" --target deploy
|
|
fi
|
|
|
|
if [[ "$do_dmg" -eq 1 ]]; then
|
|
run_with_heartbeat "Running: dmg" \
|
|
cmake --build "$build_dir" --target dmg
|
|
fi
|
|
|
|
echo "==> [$(ts)] Done"
|
|
echo "Built app:"
|
|
echo " ${build_dir}/strawberry.app"
|
|
|