diff --git a/crates/dwg-thumbnailer/macos/Info.plist b/crates/dwg-thumbnailer/macos/Info.plist index 43b03fe66..8d48bd4a8 100644 --- a/crates/dwg-thumbnailer/macos/Info.plist +++ b/crates/dwg-thumbnailer/macos/Info.plist @@ -33,6 +33,14 @@ QLSupportedContentTypes com.autodesk.dwg + + com.autodesk.autocad.dwg QLThumbnailMinimumDimension 0 diff --git a/crates/dwg-thumbnailer/macos/entitlements.plist b/crates/dwg-thumbnailer/macos/entitlements.plist new file mode 100644 index 000000000..663747a82 --- /dev/null +++ b/crates/dwg-thumbnailer/macos/entitlements.plist @@ -0,0 +1,16 @@ + + + + + + com.apple.security.app-sandbox + + + diff --git a/packaging/Info.plist b/packaging/Info.plist index ecfb4d334..c6df654e1 100644 --- a/packaging/Info.plist +++ b/packaging/Info.plist @@ -40,6 +40,10 @@ LSItemContentTypes com.autodesk.dwg + + com.autodesk.autocad.dwg LSHandlerRank Alternate @@ -54,10 +58,62 @@ LSItemContentTypes com.autodesk.dxf + com.autodesk.autocad.dxf LSHandlerRank Alternate + + UTImportedTypeDeclarations + + + UTTypeIdentifier + com.autodesk.dwg + UTTypeDescription + AutoCAD Drawing + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + dwg + + public.mime-type + + image/vnd.dwg + + + + + UTTypeIdentifier + com.autodesk.dxf + UTTypeDescription + AutoCAD DXF + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + dxf + + public.mime-type + + image/vnd.dxf + + + + diff --git a/packaging/build_macos_signed.sh b/packaging/build_macos_signed.sh new file mode 100755 index 000000000..d4547f740 --- /dev/null +++ b/packaging/build_macos_signed.sh @@ -0,0 +1,136 @@ +#!/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-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)" +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 + +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/OpenCADStudio" "$APP/Contents/MacOS/OpenCADStudio" +chmod +x "$APP/Contents/MacOS/OpenCADStudio" +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. + codesign --force --deep --sign - --timestamp=none "$APP" +else + # Inside-out: the appex first (sandbox entitlement is REQUIRED for a + # QuickLook extension to run), then the outer bundle. Hardened runtime + # and a secure timestamp on every layer for notarization. + 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" +for i in 1 2 3 4 5; do + if hdiutil create -volname "Open CAD Studio" -srcfolder "$APP" -ov -format UDZO "$DMG"; then + break + fi + echo "hdiutil failed (attempt $i), retrying..." >&2 + sleep 3 +done +[ -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"