fix(macos): declare DWG/DXF UTIs, sandbox the QL appex, add signed-build script (#365)
Finder thumbnails and "Open with" registration fail in the field for three compounding reasons: the app claims com.autodesk.dwg/.dxf but never declares them (UTImportedTypeDeclarations was missing, so on systems with no other declarer Launch Services can't bind the extensions at all); the QuickLook appex only serves com.autodesk.dwg while machines with AutoCAD installed resolve .dwg to its exported com.autodesk.autocad.dwg (BricsCAD etc. likewise win with their own UTIs); and the ad-hoc --deep signature carries no entitlements, but a QuickLook thumbnail extension must be sandboxed (com.apple.security.app-sandbox) before macOS will run it. Import-declare both UTIs, claim and serve the AutoCAD variants too, add the appex sandbox entitlements, and add packaging/build_macos_signed.sh — a local mirror of the CI build-macos job that signs with a Developer ID (hardened runtime, per-component entitlements), notarizes and staples. DEVELOPER_ID=- reproduces the CI ad-hoc build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5bd683cf4e
commit
5488d6c13f
4 changed files with 216 additions and 0 deletions
|
|
@ -33,6 +33,14 @@
|
||||||
<key>QLSupportedContentTypes</key>
|
<key>QLSupportedContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>com.autodesk.dwg</string>
|
<string>com.autodesk.dwg</string>
|
||||||
|
<!-- QuickLook matches these against the UTI the *system*
|
||||||
|
resolves for a .dwg file, which is whatever the
|
||||||
|
highest-precedence installed declaration says. With
|
||||||
|
AutoCAD installed that is its exported UTI below — list
|
||||||
|
it too or thumbnails never render on those machines
|
||||||
|
(#365). Other CAD apps' exported UTIs (e.g. BricsCAD's)
|
||||||
|
can be appended here as they are identified. -->
|
||||||
|
<string>com.autodesk.autocad.dwg</string>
|
||||||
</array>
|
</array>
|
||||||
<key>QLThumbnailMinimumDimension</key>
|
<key>QLThumbnailMinimumDimension</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
|
|
|
||||||
16
crates/dwg-thumbnailer/macos/entitlements.plist
Normal file
16
crates/dwg-thumbnailer/macos/entitlements.plist
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<!-- Entitlements for the DWG QuickLook Thumbnail Extension (.appex).
|
||||||
|
|
||||||
|
App Extensions MUST be sandboxed: without com.apple.security.app-sandbox
|
||||||
|
the extension registers (shows up in System Settings and pluginkit) but
|
||||||
|
secd/QuickLook refuses to launch it, so Finder simply never renders a
|
||||||
|
thumbnail — with no error anywhere (#365). The system hands the appex a
|
||||||
|
read-only security-scoped handle to the file being thumbnailed, so no
|
||||||
|
other entitlement is needed. -->
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -40,6 +40,10 @@
|
||||||
<key>LSItemContentTypes</key>
|
<key>LSItemContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>com.autodesk.dwg</string>
|
<string>com.autodesk.dwg</string>
|
||||||
|
<!-- AutoCAD exports its own DWG UTI; when it is installed,
|
||||||
|
.dwg files bind to this identifier instead, so claim it
|
||||||
|
too or OCS never appears under "Open with". -->
|
||||||
|
<string>com.autodesk.autocad.dwg</string>
|
||||||
</array>
|
</array>
|
||||||
<key>LSHandlerRank</key>
|
<key>LSHandlerRank</key>
|
||||||
<string>Alternate</string>
|
<string>Alternate</string>
|
||||||
|
|
@ -54,10 +58,62 @@
|
||||||
<key>LSItemContentTypes</key>
|
<key>LSItemContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>com.autodesk.dxf</string>
|
<string>com.autodesk.dxf</string>
|
||||||
|
<string>com.autodesk.autocad.dxf</string>
|
||||||
</array>
|
</array>
|
||||||
<key>LSHandlerRank</key>
|
<key>LSHandlerRank</key>
|
||||||
<string>Alternate</string>
|
<string>Alternate</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<!-- Declare (import) the DWG/DXF UTIs we claim above. Without a
|
||||||
|
declaration Launch Services cannot map the .dwg/.dxf filename
|
||||||
|
extensions to these identifiers on systems where no other CAD app
|
||||||
|
declares them, so "Open with", default-handler binding and the
|
||||||
|
QuickLook thumbnail extension all silently fail (#365). An *import*
|
||||||
|
declaration defers to any app that *exports* the same UTI. -->
|
||||||
|
<key>UTImportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.autodesk.dwg</string>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>AutoCAD Drawing</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>dwg</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>image/vnd.dwg</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.autodesk.dxf</string>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>AutoCAD DXF</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>dxf</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>image/vnd.dxf</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
136
packaging/build_macos_signed.sh
Executable file
136
packaging/build_macos_signed.sh
Executable file
|
|
@ -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<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)"
|
||||||
|
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"
|
||||||
Loading…
Reference in a new issue