From 732de4acf056db2191e9b91b0b8ca8f93202aaa5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 3 Jan 2026 06:55:58 +0100 Subject: [PATCH] fix: make Sparkle builds numeric + universal --- docs/RELEASING.md | 1 + docs/mac/release.md | 11 +++- scripts/package-mac-app.sh | 117 +++++++++++++++++++++++++++++++------ 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index ebab1f444..9a5e3a04a 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -37,6 +37,7 @@ Use `pnpm` (Node 22+) from the repo root. Keep the working tree clean before tag - [ ] Generate the Sparkle appcast (HTML notes via `scripts/make_appcast.sh`) and update `appcast.xml`. - [ ] Keep the app zip (and optional dSYM zip) ready to attach to the GitHub release. - [ ] Follow `docs/mac/release.md` for the exact commands and required env vars. + - `APP_BUILD` must be numeric + monotonic (no `-beta`) so Sparkle compares versions correctly. - If notarizing, use the `clawdis-notary` keychain profile created from App Store Connect API env vars (see `docs/mac/release.md`). 6) **Publish (npm)** diff --git a/docs/mac/release.md b/docs/mac/release.md index 73b042daa..9d882c7e3 100644 --- a/docs/mac/release.md +++ b/docs/mac/release.md @@ -21,11 +21,16 @@ This app now ships Sparkle auto-updates. Release builds must be Developer IDโ€“s - Sparkle tools are fetched automatically via SwiftPM at `apps/macos/.build/artifacts/sparkle/Sparkle/bin/` (`sign_update`, `generate_appcast`, etc.). ## Build & package +Notes: +- `APP_BUILD` maps to `CFBundleVersion`/`sparkle:version`; keep it numeric + monotonic (no `-beta`), or Sparkle compares it as equal. +- Universal by default (`arm64 x86_64`). Override with `BUILD_ARCHS="arm64"` if you need arm-only. + ```bash -# From repo root; set release IDs so Sparkle feed is enabled +# From repo root; set release IDs so Sparkle feed is enabled. +# APP_BUILD must be numeric + monotonic for Sparkle compare. BUNDLE_ID=com.steipete.clawdis \ APP_VERSION=0.1.0 \ -APP_BUILD=0.1.0 \ +APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: Peter Steinberger (Y5PE65HELJ)" \ scripts/package-mac-app.sh @@ -43,7 +48,7 @@ scripts/create-dmg.sh dist/Clawdis.app dist/Clawdis-0.1.0.dmg NOTARIZE=1 NOTARYTOOL_PROFILE=clawdis-notary \ BUNDLE_ID=com.steipete.clawdis \ APP_VERSION=0.1.0 \ -APP_BUILD=0.1.0 \ +APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: Peter Steinberger (Y5PE65HELJ)" \ scripts/package-mac-dist.sh diff --git a/scripts/package-mac-app.sh b/scripts/package-mac-app.sh index cb8fe219c..620dc4d3e 100755 --- a/scripts/package-mac-app.sh +++ b/scripts/package-mac-app.sh @@ -6,7 +6,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" APP_ROOT="$ROOT_DIR/dist/Clawdis.app" -BUILD_PATH="$ROOT_DIR/apps/macos/.build" +BUILD_ROOT="$ROOT_DIR/apps/macos/.build" PRODUCT="Clawdis" BUNDLE_ID="${BUNDLE_ID:-com.steipete.clawdis.debug}" PKG_VERSION="$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")" @@ -16,6 +16,9 @@ GIT_BUILD_NUMBER=$(cd "$ROOT_DIR" && git rev-list --count HEAD 2>/dev/null || ec APP_VERSION="${APP_VERSION:-$PKG_VERSION}" APP_BUILD="${APP_BUILD:-$GIT_BUILD_NUMBER}" BUILD_CONFIG="${BUILD_CONFIG:-debug}" +BUILD_ARCHS_VALUE="${BUILD_ARCHS:-arm64 x86_64}" +IFS=' ' read -r -a BUILD_ARCHS <<< "$BUILD_ARCHS_VALUE" +PRIMARY_ARCH="${BUILD_ARCHS[0]}" SPARKLE_PUBLIC_ED_KEY="${SPARKLE_PUBLIC_ED_KEY:-AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=}" SPARKLE_FEED_URL="${SPARKLE_FEED_URL:-https://raw.githubusercontent.com/steipete/clawdis/main/appcast.xml}" AUTO_CHECKS=true @@ -23,6 +26,59 @@ if [[ "$BUNDLE_ID" == *.debug ]]; then SPARKLE_FEED_URL="" AUTO_CHECKS=false fi +if [[ "$AUTO_CHECKS" == "true" && ! "$APP_BUILD" =~ ^[0-9]+$ ]]; then + echo "ERROR: APP_BUILD must be numeric for Sparkle compare (CFBundleVersion). Got: $APP_BUILD" >&2 + exit 1 +fi + +build_path_for_arch() { + echo "$BUILD_ROOT/$1" +} + +bin_for_arch() { + echo "$(build_path_for_arch "$1")/$BUILD_CONFIG/$PRODUCT" +} + +sparkle_framework_for_arch() { + echo "$(build_path_for_arch "$1")/$BUILD_CONFIG/Sparkle.framework" +} + +merge_framework_machos() { + local primary="$1" + local dest="$2" + shift 2 + local others=("$@") + while IFS= read -r -d '' file; do + if /usr/bin/file "$file" | /usr/bin/grep -q "Mach-O"; then + local rel="${file#$primary/}" + local inputs=("$file") + for fw in "${others[@]}"; do + if [[ ! -f "$fw/$rel" ]]; then + echo "ERROR: Missing $rel in $fw" >&2 + exit 1 + fi + inputs+=("$fw/$rel") + done + /usr/bin/lipo -create "${inputs[@]}" -output "$dest/$rel" + fi + done < <(find "$primary" -type f -print0) +} + +build_relay_binary() { + local arch="$1" + local out="$2" + local define_arg="__CLAWDIS_VERSION__=\\\"$PKG_VERSION\\\"" + local -a cmd=(bun build "$ROOT_DIR/dist/macos/relay.js" --compile --bytecode --outfile "$out" -e electron --define "$define_arg") + if [[ "$arch" == "x86_64" ]]; then + if ! arch -x86_64 /usr/bin/true >/dev/null 2>&1; then + echo "ERROR: Rosetta is required to build the x86_64 relay. Install Rosetta and retry." >&2 + exit 1 + fi + arch -x86_64 "${cmd[@]}" + else + "${cmd[@]}" + fi +} echo "๐Ÿ“ฆ Ensuring deps (pnpm install)" (cd "$ROOT_DIR" && pnpm install --no-frozen-lockfile --config.node-linker=hoisted) @@ -42,11 +98,14 @@ fi cd "$ROOT_DIR/apps/macos" -echo "๐Ÿ”จ Building $PRODUCT ($BUILD_CONFIG)" -swift build -c "$BUILD_CONFIG" --product "$PRODUCT" --build-path "$BUILD_PATH" -Xlinker -rpath -Xlinker @executable_path/../Frameworks +echo "๐Ÿ”จ Building $PRODUCT ($BUILD_CONFIG) [${BUILD_ARCHS[*]}]" +for arch in "${BUILD_ARCHS[@]}"; do + BUILD_PATH="$(build_path_for_arch "$arch")" + swift build -c "$BUILD_CONFIG" --product "$PRODUCT" --build-path "$BUILD_PATH" --arch "$arch" -Xlinker -rpath -Xlinker @executable_path/../Frameworks +done -BIN="$BUILD_PATH/$BUILD_CONFIG/$PRODUCT" -echo "pkg: binary $BIN" >&2 +BIN_PRIMARY="$(bin_for_arch "$PRIMARY_ARCH")" +echo "pkg: binary $BIN_PRIMARY" >&2 echo "๐Ÿงน Cleaning old app bundle" rm -rf "$APP_ROOT" mkdir -p "$APP_ROOT/Contents/MacOS" @@ -77,15 +136,32 @@ else fi echo "๐Ÿšš Copying binary" -cp "$BIN" "$APP_ROOT/Contents/MacOS/Clawdis" +cp "$BIN_PRIMARY" "$APP_ROOT/Contents/MacOS/Clawdis" +if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then + BIN_INPUTS=() + for arch in "${BUILD_ARCHS[@]}"; do + BIN_INPUTS+=("$(bin_for_arch "$arch")") + done + /usr/bin/lipo -create "${BIN_INPUTS[@]}" -output "$APP_ROOT/Contents/MacOS/Clawdis" +fi chmod +x "$APP_ROOT/Contents/MacOS/Clawdis" # SwiftPM outputs ad-hoc signed binaries; strip the signature before install_name_tool to avoid warnings. /usr/bin/codesign --remove-signature "$APP_ROOT/Contents/MacOS/Clawdis" 2>/dev/null || true -SPARKLE_FRAMEWORK="$BUILD_PATH/$BUILD_CONFIG/Sparkle.framework" -if [ -d "$SPARKLE_FRAMEWORK" ]; then +SPARKLE_FRAMEWORK_PRIMARY="$(sparkle_framework_for_arch "$PRIMARY_ARCH")" +if [ -d "$SPARKLE_FRAMEWORK_PRIMARY" ]; then echo "โœจ Embedding Sparkle.framework" - cp -R "$SPARKLE_FRAMEWORK" "$APP_ROOT/Contents/Frameworks/" + cp -R "$SPARKLE_FRAMEWORK_PRIMARY" "$APP_ROOT/Contents/Frameworks/" + if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then + OTHER_FRAMEWORKS=() + for arch in "${BUILD_ARCHS[@]}"; do + if [[ "$arch" == "$PRIMARY_ARCH" ]]; then + continue + fi + OTHER_FRAMEWORKS+=("$(sparkle_framework_for_arch "$arch")") + done + merge_framework_machos "$SPARKLE_FRAMEWORK_PRIMARY" "$APP_ROOT/Contents/Frameworks/Sparkle.framework" "${OTHER_FRAMEWORKS[@]}" + fi chmod -R a+rX "$APP_ROOT/Contents/Frameworks/Sparkle.framework" fi @@ -106,14 +182,21 @@ if [[ "${SKIP_GATEWAY_PACKAGE:-0}" != "1" ]]; then echo "๐Ÿงฐ Building bundled relay (bun --compile)" mkdir -p "$RELAY_DIR" - RELAY_OUT="$RELAY_DIR/clawdis" - bun build "$ROOT_DIR/dist/macos/relay.js" \ - --compile \ - --bytecode \ - --outfile "$RELAY_OUT" \ - -e electron \ - --define "__CLAWDIS_VERSION__=\\\"$PKG_VERSION\\\"" - chmod +x "$RELAY_OUT" + RELAY_OUT="$RELAY_DIR/clawdis" + RELAY_BUILD_DIR="$RELAY_DIR/.relay-build" + rm -rf "$RELAY_BUILD_DIR" + mkdir -p "$RELAY_BUILD_DIR" + for arch in "${BUILD_ARCHS[@]}"; do + RELAY_ARCH_OUT="$RELAY_BUILD_DIR/clawdis-$arch" + build_relay_binary "$arch" "$RELAY_ARCH_OUT" + chmod +x "$RELAY_ARCH_OUT" + done + if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then + /usr/bin/lipo -create "$RELAY_BUILD_DIR"/clawdis-* -output "$RELAY_OUT" + else + cp "$RELAY_BUILD_DIR/clawdis-${BUILD_ARCHS[0]}" "$RELAY_OUT" + fi + rm -rf "$RELAY_BUILD_DIR" echo "๐ŸŽจ Copying gateway A2UI host assets" rm -rf "$RELAY_DIR/a2ui"