mirror of
https://github.com/harry7557558/spirula-studio.git
synced 2026-10-02 02:44:54 +08:00
167 lines
6.7 KiB
Bash
167 lines
6.7 KiB
Bash
#!/bin/bash
|
|
# Assemble "Spirula Studio.app" from an already-built `spirula`, and
|
|
# optionally a .dmg holding it. macOS only; see docs/build.md#packaging.
|
|
#
|
|
# bash tools/package_macos.sh [--build-dir DIR] [--out DIR]
|
|
# [--sign IDENTITY] [--dmg]
|
|
#
|
|
# The bundle carries one binary and its icon, which only works because MoltenVK
|
|
# and libomp are linked statically (cmake/SsVulkan.cmake,
|
|
# cmake/SsMacBundle.cmake) -- verified below, not assumed.
|
|
#
|
|
# --sign defaults to ad-hoc ("-"): enough for a Mac the app is copied to
|
|
# directly, not enough to survive the quarantine flag a download attaches.
|
|
|
|
set -eu
|
|
|
|
if [ "$(uname)" != "Darwin" ]; then
|
|
echo "package_macos.sh: macOS only (this is $(uname))" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
BUILD_DIR="$ROOT/build"
|
|
OUT_DIR=""
|
|
IDENTITY="-"
|
|
MAKE_DMG=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--build-dir) BUILD_DIR=$2; shift 2 ;;
|
|
--out) OUT_DIR=$2; shift 2 ;;
|
|
--sign) IDENTITY=$2; shift 2 ;;
|
|
--dmg) MAKE_DMG=1; shift ;;
|
|
-h|--help) sed -n '2,13p' "$0" | sed 's/^#\{1\} \{0,1\}//'; exit 0 ;;
|
|
*) echo "package_macos.sh: unknown argument $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
[ -n "$OUT_DIR" ] || OUT_DIR=$BUILD_DIR
|
|
|
|
EXE="$BUILD_DIR/spirula"
|
|
if [ ! -x "$EXE" ]; then
|
|
echo "package_macos.sh: no $EXE -- build with -DSS_BUILD_GUI=ON first" >&2
|
|
exit 1
|
|
fi
|
|
# The dispatcher lists only the tools this build has, and `gui --help` is not
|
|
# the way to ask: that argument reaches spirula_gui_main, which opens a window.
|
|
if ! "$EXE" --help 2>&1 | grep -q '^ *gui '; then
|
|
echo "package_macos.sh: $EXE has no GUI; rebuild with -DSS_BUILD_GUI=ON" >&2
|
|
exit 1
|
|
fi
|
|
|
|
VERSION=$("$EXE" --version 2>/dev/null | tr -d '[:space:]')
|
|
[ -n "$VERSION" ] || VERSION=dev
|
|
# CFBundleVersion must be dotted digits, and "dev" is what an untagged tree
|
|
# reports; the real string still goes in CFBundleGetInfoString below.
|
|
case "$VERSION" in
|
|
[0-9]*) NUMERIC=$(echo "$VERSION" | sed 's/[^0-9.].*$//; s/\.$//') ;;
|
|
*) NUMERIC=0.0.0 ;;
|
|
esac
|
|
[ -n "$NUMERIC" ] || NUMERIC=0.0.0
|
|
|
|
APP="$OUT_DIR/Spirula Studio.app"
|
|
echo "==> $APP (version $VERSION)"
|
|
rm -rf "$APP"
|
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
|
|
|
cp "$EXE" "$APP/Contents/MacOS/spirula"
|
|
|
|
# A regional build (SS_FONT_CJK=sc|tc|jp|kr|all) ships its face beside the
|
|
# executable, and inside the bundle that is Contents/MacOS.
|
|
if [ -d "$BUILD_DIR/fonts" ]; then
|
|
cp -R "$BUILD_DIR/fonts" "$APP/Contents/MacOS/fonts"
|
|
echo " bundled $(ls "$BUILD_DIR/fonts" | wc -l | tr -d ' ') font file(s)"
|
|
fi
|
|
|
|
# ---- icon ----------------------------------------------------------------
|
|
# sips and iconutil are in the base system, so this needs no Xcode.
|
|
ICONSET=$(mktemp -d)/AppIcon.iconset
|
|
mkdir -p "$ICONSET"
|
|
for spec in 16:16x16 32:16x16@2x 32:32x32 64:32x32@2x 128:128x128 \
|
|
256:128x128@2x 256:256x256 512:256x256@2x 512:512x512 1024:512x512@2x; do
|
|
px=${spec%%:*}
|
|
name=${spec#*:}
|
|
sips -z "$px" "$px" "$ROOT/assets/icon.png" \
|
|
--out "$ICONSET/icon_$name.png" >/dev/null
|
|
done
|
|
iconutil -c icns "$ICONSET" -o "$APP/Contents/Resources/AppIcon.icns"
|
|
rm -rf "$(dirname "$ICONSET")"
|
|
|
|
# ---- Info.plist ----------------------------------------------------------
|
|
# No CFBundleDocumentTypes on purpose. GLFW does not forward the Apple Event
|
|
# that carries a double-clicked file, so claiming .ply would open the app with
|
|
# nothing in it -- worse than not appearing in "Open With" at all.
|
|
cat > "$APP/Contents/Info.plist" <<PLIST
|
|
<?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">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>CFBundleName</key> <string>Spirula Studio</string>
|
|
<key>CFBundleExecutable</key> <string>spirula</string>
|
|
<key>CFBundleIdentifier</key> <string>io.github.harry7557558.spirula-studio</string>
|
|
<key>CFBundleVersion</key> <string>$NUMERIC</string>
|
|
<key>CFBundleShortVersionString</key><string>$NUMERIC</string>
|
|
<key>CFBundleGetInfoString</key> <string>Spirula Studio $VERSION</string>
|
|
<key>CFBundleIconFile</key> <string>AppIcon</string>
|
|
<key>CFBundlePackageType</key> <string>APPL</string>
|
|
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
|
<key>LSMinimumSystemVersion</key> <string>11.0</string>
|
|
<key>LSApplicationCategoryType</key> <string>public.app-category.graphics-design</string>
|
|
<key>NSHighResolutionCapable</key> <true/>
|
|
<key>NSHumanReadableCopyright</key> <string>GPLv3. See LICENSE.</string>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
plutil -lint "$APP/Contents/Info.plist" >/dev/null
|
|
printf 'APPL????' > "$APP/Contents/PkgInfo"
|
|
|
|
# ---- self-containment ----------------------------------------------------
|
|
STRAY=$(otool -L "$APP/Contents/MacOS/spirula" | tail -n +2 | awk '{print $1}' \
|
|
| grep -v '^/usr/lib/' | grep -v '^/System/Library/' || true)
|
|
if [ -n "$STRAY" ]; then
|
|
echo "package_macos.sh: the binary links libraries this bundle does not carry:" >&2
|
|
echo "$STRAY" | sed 's/^/ /' >&2
|
|
echo " such a bundle runs only on this machine. libvulkan: rebuild with" >&2
|
|
echo " -DSS_MACOS_VULKAN=static (the default). Anything else: install its" >&2
|
|
echo " .a so cmake/SsMacBundle.cmake links that (brew install libomp)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---- signature -----------------------------------------------------------
|
|
# Not --deep: it is deprecated, and there is one binary to sign anyway. Ad-hoc
|
|
# is also mandatory rather than cosmetic on Apple silicon -- an unsigned arm64
|
|
# binary is killed on exec, and copying the executable into place invalidates
|
|
# whatever signature it had.
|
|
if [ "$IDENTITY" = "-" ]; then
|
|
codesign --force --sign - --timestamp=none "$APP"
|
|
else
|
|
# A real identity is only worth having if the result can be notarized, and
|
|
# that wants both of these -- a timestamp, and the hardened runtime.
|
|
codesign --force --sign "$IDENTITY" --options runtime --timestamp "$APP"
|
|
fi
|
|
codesign --verify --strict "$APP"
|
|
echo " signed ($IDENTITY) and verified"
|
|
|
|
if [ "$MAKE_DMG" = 1 ]; then
|
|
DMG="$OUT_DIR/Spirula Studio.dmg"
|
|
STAGE=$(mktemp -d)
|
|
trap "rm -rf '$STAGE'" EXIT
|
|
|
|
cp -R "$APP" "$STAGE/"
|
|
ln -s /Applications "$STAGE/Applications"
|
|
rm -f "$DMG"
|
|
|
|
if ! hdiutil create -volname "Spirula Studio" -srcfolder "$STAGE" \
|
|
-ov -format UDZO "$DMG"; then
|
|
echo "package_macos.sh: hdiutil create failed" >&2
|
|
echo " Check disk space: $(df -h "$OUT_DIR" | tail -1)" >&2
|
|
echo " Staging dir: $(du -sh "$STAGE")" >&2
|
|
exit 1
|
|
fi
|
|
|
|
rm -rf "$STAGE"
|
|
echo "==> $DMG ($(du -h "$DMG" | cut -f1 | tr -d ' '))"
|
|
fi
|
|
|
|
echo "==> done ($(du -sh "$APP" | cut -f1 | tr -d ' '))"
|