From 773e75713f351acaeec813c51dd8b0afccd6f4dc Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Mon, 2 Nov 2026 20:33:48 +0700 Subject: [PATCH 1/2] build(release): open the DMG to a laid-out Finder window The disk image opened in whatever view the Mac defaulted to, with Applications sorted before TablePro, so the drag ran right to left. diskutil image create from a folder also leaves .DS_Store out, so no layout could ship. dmg.sh builds an HFS+ volume named TablePro with diskutil, fills it while mounted with the app, the /Applications link, the app's icon as the volume icon and a .DS_Store Finder wrote, converts it to a read-only ULFO image, and checks what it built. The layout sets no background picture, so the window follows Light and Dark. package.sh signs the image with an identifier of its own. --- .gitattributes | 1 + apple/CONTRIBUTING.md | 2 + apple/scripts/release/dmg.DS_Store | Bin 0 -> 6148 bytes apple/scripts/release/dmg.sh | 155 ++++++++++++++++++++++++ apple/scripts/release/package.sh | 8 +- scripts/ci/detect-changed-paths.sh | 4 +- scripts/ci/test_detect_changed_paths.py | 2 + 7 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 apple/scripts/release/dmg.DS_Store create mode 100755 apple/scripts/release/dmg.sh diff --git a/.gitattributes b/.gitattributes index dadb6eebce..2573ca7f00 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,6 +14,7 @@ *.gif binary *.ico binary *.icns binary +*.DS_Store binary *.pdf binary # Fonts *.ttf binary diff --git a/apple/CONTRIBUTING.md b/apple/CONTRIBUTING.md index 61edc5ae2d..4d67df0a86 100644 --- a/apple/CONTRIBUTING.md +++ b/apple/CONTRIBUTING.md @@ -72,6 +72,8 @@ scripts/release/build.sh plugins --unsigned && scripts/release/package.sh plugin Signed: drop `--unsigned`, with the Developer ID identity and the provisioning profile `scripts/release/ExportOptions.plist` names. `scripts/release/notarize.sh ...` needs the profile `xcrun notarytool store-credentials TablePro --key --key-id --issuer ` creates. +The disk image opens to the window `scripts/release/dmg.DS_Store` describes, a file Finder wrote. To change it, edit the Finder settings in `scripts/release/dmg.sh`, run `scripts/release/dmg.sh layout build/release/TablePro.app` on a Mac with a logged-in session, and open a disk image built with the new file before committing it. + ### Clean builds | Reach for | When | diff --git a/apple/scripts/release/dmg.DS_Store b/apple/scripts/release/dmg.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e1dad2ac7631a4e36225869d341335d20a50d5d6 GIT binary patch literal 6148 zcmeHKO-~a+7=8zeEMIn|MbU$9(s(fhR}6UIU@aKJ3BooA0W7=iP&X_y&F+?pgrsM= z8oiqM2R!lzJbUw|e?gDl^qr4}O7&`tnYWpFo}GE0*_~(GnGOJuQnPjszy<&di@?k< zR=)|+FG^4J>6QURBF5kWva_HOg-P0zZ5oUN#({s$0rlIR4q8bgh`N8juKS7f^=Aey z4PuN8?s(65%S-*VnUwr^OI(#XkAe>OgY}TMDt3N>v(yh6jin&0QB2u>60#a^m3WiY z5~=l6hlNqGCy$S3TzATub!SedoZ~sc@6Wj>Czdr)xI4dA*^dqwf2ph^xYEcN)7XJi zBm}0z>Y<(qd8Jk&*ZrbrVhs(CjE-H-+E=pU*<3C^arIjMdd1EKes#Ydi>F)Q5synz zlWx`Hu;w-VYRKvvDSe)L;R{-^$1kL=(c07Vrt(n(bMog6a=4|KQYOb++1pEKy3xfq zw-a=7Ii&~7Y>(skhv?!;BMmuA)@hs|Zu)WTvpOvu?L>Y+qZL--Y03{Oc2;VuoUO)G z=r%&+;yDWszbRJ=#mx@6qlgU!MQ}~ecbG;}W}ML>t5Dq4Ef(B{d02u~ppe2Tyn%P{ z9zMWF_zYj+J0WD4n=Z|Q~}NdqY&!MAki#Vq2iF0TMa;Sn_O z6F~@7@bO+DaA1%uN4%=Pm$*fdGt7VeLk2RTD`R$mI*QL2qh=bfaim{$@D_8sbwX=G zzawkJSYaG64j2b6I3U&s2aCX>#oYNfDKl z=pzPEa%^{WUQuIPp^^j9hYzCfO!Ntb$h+hCj!Xx+;V5cqe*~lrrZNuvRR?|o6Rps_ literal 0 HcmV?d00001 diff --git a/apple/scripts/release/dmg.sh b/apple/scripts/release/dmg.sh new file mode 100755 index 0000000000..bee515a23e --- /dev/null +++ b/apple/scripts/release/dmg.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Usage: dmg.sh build | dmg.sh layout +# build: the disk image a release ships. A read-only HFS+ volume named TablePro holds the app, a link to +# /Applications and the app's icon as the volume icon, and opens to the window layout in dmg.DS_Store. +# layout: on a Mac with a logged-in session, Finder lays out the same volume and its .DS_Store replaces +# dmg.DS_Store. macOS asks once to let the terminal control Finder. CI never runs it. + +VOLUME="TablePro" +LAYOUT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/dmg.DS_Store" +# The root folder's Finder flags with kHasCustomIcon (0x0400) set, which makes Finder draw .VolumeIcon.icns. +# Apple DTS names setxattr on com.apple.FinderInfo as the supported way to set it. +CUSTOM_ICON_FINDER_INFO="0000000000000000040000000000000000000000000000000000000000000000" + +usage() { + echo "Usage: dmg.sh build | dmg.sh layout " >&2 + exit 2 +} + +die() { + echo "dmg.sh: $*" >&2 + exit 1 +} + +WORK="$(mktemp -d)" +DEVICE="" +DEVICES=() +cleanup() { + local device + for device in ${DEVICES[@]+"${DEVICES[@]}"}; do + diskutil eject "$device" > /dev/null 2>&1 || true + done + rm -rf "$WORK" +} +trap cleanup EXIT + +# Attaches the image $1 with the diskutil image attach options after it, and sets DEVICE. +attach() { + local image="$1" + shift + DEVICE="$(diskutil image attach --plist "$@" "$image" | plutil -extract 'system-entities.0.dev-entry' raw -o - -)" + DEVICES+=("$DEVICE") +} + +# Writes the read/write image $1 holding the volume for the app $2, with the .DS_Store $3 when given, unmounted. +create_volume() { + local image="$1" app="$2" layout="$3" kilobytes mountpoint="$WORK/volume" + # Room for the app and the HFS+ catalog. Free space compresses to almost nothing in the read-only image. + kilobytes=$(($(du -sk "$app" | cut -f1) * 11 / 10 + 20480)) + diskutil image create blank --fs None --size $((kilobytes * 1024)) "$image" > /dev/null + # Apple DTS recommends HFS+ for a laid-out volume, and diskutil image formats no blank image as HFS+. + attach "$image" --noMount + diskutil eraseDisk HFS+ "$VOLUME" GPT "$DEVICE" > /dev/null + diskutil eject "$DEVICE" > /dev/null + + mkdir "$mountpoint" + attach "$image" --nobrowse --mountPoint "$mountpoint" + ditto "$app" "$mountpoint/TablePro.app" + ln -s /Applications "$mountpoint/Applications" + iconutil -c icns -o "$mountpoint/.VolumeIcon.icns" "$app/Contents/Resources/Assets.car" AppIcon + xattr -wx com.apple.FinderInfo "$CUSTOM_ICON_FINDER_INFO" "$mountpoint" + [ -z "$layout" ] || cp "$layout" "$mountpoint/.DS_Store" + # fseventsd writes .fseventsd onto a writable HFS+ volume, and it would ship as a hidden folder. + rm -rf "$mountpoint/.fseventsd" + diskutil eject "$DEVICE" > /dev/null +} + +check() { + local dmg="$1" app="$2" mountpoint="$WORK/check" items flags + mkdir "$mountpoint" + attach "$dmg" --readOnly --nobrowse --mountPoint "$mountpoint" + diskutil info -plist "$mountpoint" > "$WORK/info.plist" + [ "$(plutil -extract FilesystemType raw -o - "$WORK/info.plist")" = hfs ] || die "$dmg is not HFS+" + [ "$(plutil -extract VolumeName raw -o - "$WORK/info.plist")" = "$VOLUME" ] || die "$dmg is not named $VOLUME" + items="$(cd "$mountpoint" && find . -mindepth 1 -maxdepth 1 | LC_ALL=C sort | tr '\n' ' ')" + [ "$items" = "./.DS_Store ./.VolumeIcon.icns ./Applications ./TablePro.app " ] || die "$dmg holds $items" + [ "$(readlink "$mountpoint/Applications")" = /Applications ] || die "Applications in $dmg is not a link to /Applications" + cmp -s "$mountpoint/.DS_Store" "$LAYOUT" || die "the .DS_Store in $dmg is not $LAYOUT" + flags="$(xattr -px com.apple.FinderInfo "$mountpoint" | tr -d ' \n')" + [ $((16#${flags:16:4} & 16#0400)) -ne 0 ] || die "the volume in $dmg has no custom icon" + # HFS+ stores names decomposed, which would break the seal of a bundle that has a precomposed name. + diff -rq --no-dereference "$app" "$mountpoint/TablePro.app" || die "the app in $dmg differs from $app" + diskutil eject "$DEVICE" > /dev/null +} + +build() { + local app="${1%/}" dmg="$2" image="$WORK/volume.dmg" + [ -s "$LAYOUT" ] || die "no $LAYOUT; run dmg.sh layout first" + create_volume "$image" "$app" "$LAYOUT" + # Converted image to image: diskutil image create from a folder leaves .DS_Store out. + rm -f "$dmg" + diskutil image create from "$image" "$dmg" --format ULFO > /dev/null + check "$dmg" "$app" +} + +has_records() { + local file="$1" record + shift + [ -f "$file" ] || return 1 + for record in "$@"; do + grep -aq "$record" "$file" || return 1 + done +} + +layout() { + local app="${1%/}" image="$WORK/volume.dmg" mountpoint="/Volumes/$VOLUME" waited=0 + [ ! -e "$mountpoint" ] || die "eject $mountpoint first; Finder finds the volume by its name" + create_volume "$image" "$app" "" + attach "$image" + [ -d "$mountpoint" ] || die "the volume did not mount at $mountpoint" + rm -f "$mountpoint/.DS_Store" + + osascript < Date: Mon, 2 Nov 2026 20:34:31 +0700 Subject: [PATCH 2/2] build(release): add the changelog entry for the laid-out DMG --- apple/changelog.d/3990.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 apple/changelog.d/3990.changed.md diff --git a/apple/changelog.d/3990.changed.md b/apple/changelog.d/3990.changed.md new file mode 100644 index 0000000000..c19a2bf3a6 --- /dev/null +++ b/apple/changelog.d/3990.changed.md @@ -0,0 +1 @@ +The disk image opens to a window with TablePro beside the Applications folder, ready to drag.