forked from 1b-agent/opencadstudio-upstream
The ad-hoc codesign path signed the whole bundle with one --deep pass, which never carried --entitlements to the QuickLook appex, so PlugInKit silently rejected it as unsandboxed. Sign nested code individually instead, matching the Developer ID path. Separately, ThumbnailProvider drew the extracted preview at its native pixel size anchored at the CGContext origin (bottom-left) instead of scaling it to fill request.maximumSize, producing a small thumbnail stuck in one corner instead of a properly sized, centered icon. Note: Gatekeeper still rejects an ad-hoc or unnotarized Developer-ID signature outright, and PlugInKit periodically re-validates and drops any registered plugin that fails that check — so these fixes are necessary but not sufficient for thumbnails to render from a build that isn't both signed and notarized. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
165 lines
7.4 KiB
Bash
Executable File
165 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build, sign, notarize and package OpenCADStudio.app + .dmg for macOS arm64.
|
|
#
|
|
# Mirrors the `build-macos` job in .github/workflows/release.yml, then goes
|
|
# further: instead of the CI's ad-hoc signature it produces a Developer ID
|
|
# signed, hardened-runtime, notarized and stapled bundle. Ad-hoc signing is
|
|
# what breaks the QuickLook thumbnail extension and "Open with" registration
|
|
# in the field (#365): appexes must be sandboxed *and* validly signed before
|
|
# macOS will run them.
|
|
#
|
|
# Inputs (environment):
|
|
# DEVELOPER_ID "Developer ID Application: ..." identity. Auto-derived
|
|
# from the keychain when unset. Set to "-" for an ad-hoc
|
|
# build (CI parity, no notarization).
|
|
# NOTARY_PROFILE notarytool keychain profile. Unset → skip notarization.
|
|
# VERSION Bundle version. Default: version from Cargo.toml.
|
|
#
|
|
# Output: dist/OpenCADStudio.app and dist/OpenCADStudio-v<VERSION>-macos-arm64.dmg
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
TARGET=aarch64-apple-darwin
|
|
DIST=dist
|
|
VERSION="${VERSION:-$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)}"
|
|
|
|
if [ -z "${DEVELOPER_ID:-}" ]; then
|
|
DEVELOPER_ID="$(security find-identity -v -p codesigning \
|
|
| sed -n 's/.*"\(Developer ID Application: .*\)"/\1/p' | head -1)"
|
|
fi
|
|
[ -n "$DEVELOPER_ID" ] || { echo "No Developer ID identity found; set DEVELOPER_ID (or '-' for ad-hoc)." >&2; exit 1; }
|
|
echo "==> Version $VERSION, signing as: $DEVELOPER_ID"
|
|
|
|
echo "==> cargo build (app + thumbnailer staticlib + launcher)"
|
|
cargo build --release --target "$TARGET"
|
|
# The staticlib crate-type is only emitted when the crate is built as a
|
|
# target (as a plain dependency cargo produces just the rlib).
|
|
cargo build --release --target "$TARGET" -p dwg-thumbnailer
|
|
# CFBundleExecutable (#1039) — see src/bin/ocs_launcher.rs.
|
|
cargo build --release --target "$TARGET" --bin ocs_launcher
|
|
|
|
echo "==> icons"
|
|
rm -rf "$DIST" && mkdir -p "$DIST"
|
|
ICONSET="$DIST/OpenCADStudio.iconset"
|
|
mkdir -p "$ICONSET"
|
|
for SIZE in 16 32 64 128 256 512 1024; do
|
|
rsvg-convert -w $SIZE -h $SIZE assets/logo.svg -o "$ICONSET/icon_${SIZE}x${SIZE}.png"
|
|
done
|
|
for BASE in 16 32 128 256 512; do
|
|
cp "$ICONSET/icon_$((BASE * 2))x$((BASE * 2)).png" "$ICONSET/icon_${BASE}x${BASE}@2x.png"
|
|
done
|
|
iconutil -c icns "$ICONSET" -o "$DIST/AppIcon.icns"
|
|
for T in dwg dxf; do
|
|
SET="$DIST/$T.iconset"
|
|
mkdir -p "$SET"
|
|
for SIZE in 16 32 64 128 256 512 1024; do
|
|
rsvg-convert -w $SIZE -h $SIZE "assets/mimetypes/image-vnd.$T.svg" \
|
|
-o "$SET/icon_${SIZE}x${SIZE}.png"
|
|
done
|
|
for BASE in 16 32 128 256 512; do
|
|
cp "$SET/icon_$((BASE*2))x$((BASE*2)).png" "$SET/icon_${BASE}x${BASE}@2x.png"
|
|
done
|
|
iconutil -c icns "$SET" -o "$DIST/$(echo "$T" | tr a-z A-Z).icns"
|
|
done
|
|
|
|
echo "==> QuickLook thumbnail extension (.appex)"
|
|
EXT="$DIST/DWGThumbnail.appex"
|
|
rm -rf "$EXT" && mkdir -p "$EXT/Contents/MacOS"
|
|
swiftc \
|
|
-sdk "$(xcrun --sdk macosx --show-sdk-path)" \
|
|
-target arm64-apple-macos11 \
|
|
-O -parse-as-library -application-extension \
|
|
-module-name DWGThumbnail \
|
|
-import-objc-header crates/dwg-thumbnailer/macos/dwg_thumbnailer.h \
|
|
crates/dwg-thumbnailer/macos/ThumbnailProvider.swift \
|
|
-L "target/$TARGET/release" -ldwg_thumbnailer \
|
|
-framework QuickLookThumbnailing -framework CoreGraphics \
|
|
-framework ImageIO -framework Foundation -framework Security \
|
|
-framework SystemConfiguration -liconv \
|
|
-Xlinker -e -Xlinker _NSExtensionMain \
|
|
-o "$EXT/Contents/MacOS/DWGThumbnail"
|
|
sed "s/__VERSION__/$VERSION/g" crates/dwg-thumbnailer/macos/Info.plist > "$EXT/Contents/Info.plist"
|
|
|
|
echo "==> assemble .app"
|
|
APP="$DIST/OpenCADStudio.app"
|
|
rm -rf "$APP"
|
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" "$APP/Contents/PlugIns"
|
|
cp "target/$TARGET/release/ocs_launcher" "$APP/Contents/MacOS/OpenCADStudio"
|
|
cp "target/$TARGET/release/OpenCADStudio" "$APP/Contents/MacOS/OpenCADStudio-App"
|
|
chmod +x "$APP/Contents/MacOS/OpenCADStudio" "$APP/Contents/MacOS/OpenCADStudio-App"
|
|
cp "$DIST/AppIcon.icns" "$DIST/DWG.icns" "$DIST/DXF.icns" "$APP/Contents/Resources/"
|
|
cp -R "$EXT" "$APP/Contents/PlugIns/"
|
|
sed "s/__VERSION__/$VERSION/g" packaging/Info.plist > "$APP/Contents/Info.plist"
|
|
|
|
echo "==> codesign"
|
|
if [ "$DEVELOPER_ID" = "-" ]; then
|
|
# CI-parity ad-hoc signature; cannot be notarized. Still sign nested
|
|
# code individually, in the same order as the Developer ID path below,
|
|
# rather than one `--deep` pass over the outer bundle: `--deep` re-signs
|
|
# every nested binary with the *outer* invocation's own arguments, which
|
|
# here carried no --entitlements at all — so the appex ended up ad-hoc
|
|
# signed but missing its sandbox entitlement, and PlugInKit silently
|
|
# dropped it ("rejecting ... plug-ins must be sandboxed" in the system
|
|
# log, no error surfaced anywhere else) (#365). Ad-hoc signing itself is
|
|
# not the blocker here — carrying the entitlements through is.
|
|
codesign --force --sign - --timestamp=none \
|
|
"$APP/Contents/MacOS/OpenCADStudio-App"
|
|
codesign --force --sign - --timestamp=none \
|
|
--entitlements crates/dwg-thumbnailer/macos/entitlements.plist \
|
|
"$APP/Contents/PlugIns/DWGThumbnail.appex"
|
|
codesign --force --sign - --timestamp=none "$APP"
|
|
else
|
|
# Sign nested code before the outer bundle. The sandbox entitlement is
|
|
# required for the QuickLook extension. Use hardened runtime
|
|
# and a secure timestamp on every layer for notarization.
|
|
codesign --force --timestamp --options runtime \
|
|
-s "$DEVELOPER_ID" "$APP/Contents/MacOS/OpenCADStudio-App"
|
|
codesign --force --timestamp --options runtime \
|
|
--entitlements crates/dwg-thumbnailer/macos/entitlements.plist \
|
|
-s "$DEVELOPER_ID" "$APP/Contents/PlugIns/DWGThumbnail.appex"
|
|
codesign --force --timestamp --options runtime \
|
|
-s "$DEVELOPER_ID" "$APP"
|
|
fi
|
|
codesign --verify --strict --verbose=2 "$APP"
|
|
|
|
echo "==> dmg"
|
|
DMG="$DIST/OpenCADStudio-v$VERSION-macos-arm64.dmg"
|
|
rm -f "$DMG"
|
|
|
|
# Stage the app next to the install-location shortcut (#769).
|
|
STAGING="$DIST/dmg-staging"
|
|
rm -rf "$STAGING"
|
|
mkdir -p "$STAGING"
|
|
cp -R "$APP" "$STAGING/"
|
|
ln -s /Applications "$STAGING/Applications"
|
|
|
|
for i in 1 2 3 4 5; do
|
|
if hdiutil create -volname "Open CAD Studio" -srcfolder "$STAGING" -ov -format UDZO "$DMG"; then
|
|
break
|
|
fi
|
|
rm -f "$DMG"
|
|
echo "hdiutil failed (attempt $i), retrying..." >&2
|
|
sleep 3
|
|
done
|
|
rm -rf "$STAGING"
|
|
[ -f "$DMG" ] || { echo "hdiutil failed permanently" >&2; exit 1; }
|
|
|
|
if [ -n "${NOTARY_PROFILE:-}" ] && [ "$DEVELOPER_ID" != "-" ]; then
|
|
echo "==> notarize (this can take a while; a first submission from a new"
|
|
echo " Developer ID can be held for hours — that is normal)"
|
|
SUBMIT_OUT="$(xcrun notarytool submit "$DMG" --keychain-profile "$NOTARY_PROFILE" --wait 2>&1 | tee /dev/stderr)"
|
|
SUBMISSION_ID="$(echo "$SUBMIT_OUT" | sed -n 's/^[[:space:]]*id: \([0-9a-f-]*\)$/\1/p' | head -1)"
|
|
# Always pull the log — it names the exact per-file cause on Invalid.
|
|
[ -n "$SUBMISSION_ID" ] && xcrun notarytool log "$SUBMISSION_ID" \
|
|
--keychain-profile "$NOTARY_PROFILE" || true
|
|
echo "$SUBMIT_OUT" | grep -q "status: Accepted" || { echo "Notarization NOT accepted" >&2; exit 1; }
|
|
xcrun stapler staple "$DMG"
|
|
xcrun stapler staple "$APP"
|
|
spctl -a -vvv "$APP"
|
|
else
|
|
echo "==> NOTARY_PROFILE unset (or ad-hoc build): skipping notarization"
|
|
fi
|
|
|
|
shasum -a 256 "$DMG" > "$DMG.sha256"
|
|
echo "==> done: $DMG"
|