Refactor notarization process and enhance build scripts for macOS

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.
This commit is contained in:
2026-01-22 18:37:02 +09:00
parent c673fd2a76
commit 3d10414a88
3 changed files with 202 additions and 40 deletions

View File

@@ -7,6 +7,87 @@ 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:
@@ -87,11 +168,41 @@ echo "==> [$(ts)] Config: ${config}"
if [[ "$do_clean" -eq 1 ]]; then
echo "==> [$(ts)] Cleaning build dir"
rm -rf "$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:-}"
@@ -100,7 +211,6 @@ export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-12.0}"
cmake_prefix_path="${qt_prefix};${brew_prefix}"
echo "==> [$(ts)] Configuring (CMAKE_PREFIX_PATH=${cmake_prefix_path})"
cmake_extra_args=()
# Optional: override Sparkle update feed / key for your own published builds.
@@ -114,6 +224,7 @@ 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" \
@@ -122,7 +233,7 @@ cmake -S "$repo_root" -B "$build_dir" -G Ninja \
${cmake_extra_args+"${cmake_extra_args[@]}"} \
${icu_prefix:+-DICU_ROOT="$icu_prefix"}
echo "==> [$(ts)] Building"
run_with_heartbeat "Building" \
cmake --build "$build_dir" --parallel
if [[ "$do_deploy" -eq 1 ]]; then
@@ -140,12 +251,12 @@ if [[ "$do_deploy" -eq 1 ]]; then
fi
fi
echo "==> [$(ts)] Running: deploy"
run_with_heartbeat "Running: deploy" \
cmake --build "$build_dir" --target deploy
fi
if [[ "$do_dmg" -eq 1 ]]; then
echo "==> [$(ts)] Running: dmg"
run_with_heartbeat "Running: dmg" \
cmake --build "$build_dir" --target dmg
fi

View File

@@ -118,9 +118,10 @@ bin_path="${app_path}/Contents/MacOS/strawberry"
zip_path="${build_dir}/strawberry-notarize.zip"
dmg_path=""
notarize_and_staple() {
notarize_and_maybe_staple() {
local file_path="$1"
local label="$2"
local do_staple="${3:-1}"
echo "==> [$(ts)] Notarizing ${label}"
local out
@@ -146,8 +147,10 @@ notarize_and_staple() {
exit 1
fi
if [[ "$do_staple" -eq 1 ]]; then
echo "==> [$(ts)] Stapling ${label}"
xcrun stapler staple "$file_path"
fi
}
if [[ -z "$identity" ]]; then
@@ -167,9 +170,6 @@ 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
# Let CMake (deploy/macdeployqt/create-dmg) know the signing identity if it wants it.
export APPLE_DEVELOPER_ID="$identity"
"${repo_root}/build_tools/macos/build_app.sh" "${build_args[@]}"
if [[ ! -x "$bin_path" ]]; then
@@ -183,6 +183,16 @@ 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),
@@ -191,6 +201,7 @@ fi
# 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/*" \
@@ -211,6 +222,7 @@ done
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
@@ -233,15 +245,44 @@ rm -f "$zip_path"
ditto -c -k --sequesterRsrc --keepParent "$app_path" "$zip_path"
if [[ "$skip_notarize" -eq 0 ]]; then
notarize_and_staple "$zip_path" "ZIP"
# 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"
cmake --build "$build_dir" --target dmg
dmg_path="$(ls -1t "$build_dir"/strawberry-*.dmg 2>/dev/null | head -n 1 || true)"
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
@@ -251,7 +292,7 @@ if [[ "$do_dmg" -eq 1 ]]; then
codesign --force --timestamp --sign "$identity" "$dmg_path"
if [[ "$skip_notarize" -eq 0 ]]; then
notarize_and_staple "$dmg_path" "DMG"
notarize_and_maybe_staple "$dmg_path" "DMG" 1
fi
fi

View File

@@ -53,16 +53,9 @@ endif()
if(MACDEPLOYQT_EXECUTABLE)
# Allow build scripts to provide a codesign identity via environment variable.
# This is used to optionally pass -codesign=... to macdeployqt and --codesign to create-dmg.
if(NOT APPLE_DEVELOPER_ID AND DEFINED ENV{APPLE_DEVELOPER_ID})
set(APPLE_DEVELOPER_ID "$ENV{APPLE_DEVELOPER_ID}")
endif()
if(APPLE_DEVELOPER_ID)
set(MACDEPLOYQT_CODESIGN -codesign=${APPLE_DEVELOPER_ID})
set(CREATEDMG_CODESIGN --codesign ${APPLE_DEVELOPER_ID})
endif()
# Note: We intentionally do NOT codesign during the CMake 'deploy'/'dmg' targets.
# macdeployqt can optionally sign, but passing identities safely through Ninja's /bin/sh wrapper is brittle.
# This repo's signing/notarization pipeline is handled in build_tools/macos/build_sign_notarize.sh instead.
if(CREATEDMG_SKIP_JENKINS)
set(CREATEDMG_SKIP_JENKINS_ARG "--skip-jenkins")
endif()
@@ -82,14 +75,30 @@ if(MACDEPLOYQT_EXECUTABLE)
list(APPEND _deploy_commands
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh ${CMAKE_BINARY_DIR}/strawberry.app
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner
)
add_custom_target(deploy
# Make 'deploy' incremental:
# - add_custom_target() is always out-of-date, so it reruns every time.
# - using a stamp file makes Ninja/Make skip deploy when inputs haven't changed.
set(_deploy_stamp "${CMAKE_BINARY_DIR}/deploy_app_bundle.stamp")
add_custom_command(
OUTPUT "${_deploy_stamp}"
${_deploy_commands}
COMMAND ${CMAKE_COMMAND} -E touch "${_deploy_stamp}"
COMMENT "Deploying app bundle (bundling Sparkle/GStreamer + macdeployqt)"
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS strawberry
DEPENDS
strawberry
"${CMAKE_BINARY_DIR}/dist/macos/Info.plist"
"${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns"
"${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh"
"${CMAKE_SOURCE_DIR}/dist/macos/bundle_sparkle.sh"
)
add_custom_target(deploy DEPENDS "${_deploy_stamp}")
if(MACDEPLOYCHECK_EXECUTABLE)
add_custom_target(deploycheck
COMMAND ${MACDEPLOYCHECK_EXECUTABLE} strawberry.app
@@ -97,8 +106,9 @@ if(MACDEPLOYQT_EXECUTABLE)
endif()
if(CREATEDMG_EXECUTABLE)
add_custom_target(dmg
COMMAND ${CREATEDMG_EXECUTABLE} --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 ${CREATEDMG_CODESIGN} ${CREATEDMG_SKIP_JENKINS_ARG} strawberry-${STRAWBERRY_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app
COMMAND ${CREATEDMG_EXECUTABLE} --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 ${CREATEDMG_SKIP_JENKINS_ARG} strawberry-${STRAWBERRY_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS deploy
)
endif()
endif()