#!/bin/zsh # Build ProdDash Viewer.app — a window onto ProdDash installations, for Apple # silicon Macs. Native Swift, no dependencies. # # ./build.sh build/ProdDash Viewer.app # ./build.sh --dmg …and build/ProdDash-Viewer--arm64.dmg # ./build.sh --install …and copy it to /Applications # ./build.sh --run …and launch it # ./build.sh --notarize with --dmg: notarise and staple (needs a # Developer ID and a notarytool keychain profile) # # Signing works as ProdDash's own app does (launcher/macos/README.md): a # "Developer ID Application" certificate when one is installed (override with # CODESIGN_IDENTITY), ad-hoc when none is — fine on your own Macs, but another # Mac asks before it opens an ad-hoc app. # # Needs Xcode (xcode-select pointing at it): with the command line tools # alone, the macOS 27 SDK can't expand SwiftUI's @State macro. set -e -u SCRIPT="${0:A}" cd "${SCRIPT:h}" APP_NAME="ProdDash Viewer" EXECUTABLE="ProdDashViewer" BUNDLE_ID="org.waterschurch.proddash.viewer" VERSION="1.0.0" ARCH="arm64" # Apple silicon DEPLOYMENT_TARGET="13.0" OUT_DIR="build" APP="$OUT_DIR/$APP_NAME.app" CONTENTS="$APP/Contents" INSTALL=0 RUN=0 MAKE_DMG=0 NOTARIZE=0 while [[ $# -gt 0 ]]; do case "$1" in --install) INSTALL=1 ;; --run) RUN=1 ;; --dmg) MAKE_DMG=1 ;; --notarize) NOTARIZE=1; MAKE_DMG=1 ;; -h|--help) sed -n '2,18p' "$SCRIPT"; exit 0 ;; *) echo "build.sh: unknown option $1" >&2; exit 2 ;; esac shift done command -v swiftc >/dev/null || { echo "build.sh: swiftc not found — install Xcode, then: sudo xcode-select -s /Applications/Xcode.app" >&2 exit 1 } echo "$APP_NAME $VERSION — building for $ARCH" rm -rf "$OUT_DIR" mkdir -p "$CONTENTS/MacOS" "$CONTENTS/Resources" echo " compiling" swiftc -O -whole-module-optimization \ -target "$ARCH-apple-macos$DEPLOYMENT_TARGET" \ -module-name ProdDashViewer \ -o "$CONTENTS/MacOS/$EXECUTABLE" \ Sources/*.swift cp Resources/Info.plist "$CONTENTS/Info.plist" /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$CONTENTS/Info.plist" >/dev/null /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$CONTENTS/Info.plist" >/dev/null printf 'APPL????' > "$CONTENTS/PkgInfo" cp -R Resources/Help "$CONTENTS/Resources/Help" # Resources/AppIcon.png is drawn by Resources/make-icon.swift — ProdDash's # tile mark on a display, so the Viewer and ProdDash itself tell apart. echo " building the icon" ICONSET="$OUT_DIR/AppIcon.iconset" mkdir -p "$ICONSET" for size in 16 32 128 256 512; do sips -z $size $size Resources/AppIcon.png --out "$ICONSET/icon_${size}x${size}.png" >/dev/null double=$((size * 2)) sips -z $double $double Resources/AppIcon.png --out "$ICONSET/icon_${size}x${size}@2x.png" >/dev/null done iconutil -c icns "$ICONSET" -o "$CONTENTS/Resources/AppIcon.icns" rm -rf "$ICONSET" find "$APP" -name '.DS_Store' -delete # ── signing ────────────────────────────────────────────────────────────── IDENTITY="${CODESIGN_IDENTITY:-}" if [[ -z "$IDENTITY" ]]; then IDENTITY=$(security find-identity -v -p codesigning 2>/dev/null \ | awk -F'"' '/Developer ID Application/ {print $2; exit}') fi if [[ -n "$IDENTITY" ]]; then echo " signing as $IDENTITY" SIGN=(--force --sign "$IDENTITY" --options runtime --timestamp) else echo " signing ad-hoc (no Developer ID installed — other Macs will ask before opening it)" IDENTITY="-" SIGN=(--force --sign -) fi codesign "${SIGN[@]}" --identifier "$BUNDLE_ID" "$APP" codesign --verify --deep --strict "$APP" # stops the build if it doesn't echo " signature verifies" echo "built $APP ($(du -sh "$APP" | awk '{print $1}'))" # ── disk image ─────────────────────────────────────────────────────────── if [[ $MAKE_DMG -eq 1 ]]; then DMG="$OUT_DIR/ProdDash-Viewer-$VERSION-$ARCH.dmg" STAGE="$OUT_DIR/dmg" rm -rf "$STAGE" mkdir -p "$STAGE" cp -R "$APP" "$STAGE/" ln -s /Applications "$STAGE/Applications" # drag-to-install, the usual way hdiutil create -volname "$APP_NAME $VERSION" -srcfolder "$STAGE" \ -ov -format UDZO -quiet "$DMG" rm -rf "$STAGE" [[ "$IDENTITY" == "-" ]] || codesign --force --sign "$IDENTITY" --timestamp "$DMG" echo "built $DMG ($(du -sh "$DMG" | awk '{print $1}'))" if [[ $NOTARIZE -eq 1 ]]; then if [[ "$IDENTITY" == "-" ]]; then echo "build.sh: can't notarise an ad-hoc signed app — a Developer ID is required" >&2 exit 1 fi PROFILE="${NOTARY_PROFILE:-proddash}" echo " notarising as keychain profile \"$PROFILE\" (a few minutes)" xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait xcrun stapler staple "$DMG" echo " notarised and stapled — this opens anywhere with no warning" fi fi if [[ $INSTALL -eq 1 ]]; then DEST="/Applications/$APP_NAME.app" if pgrep -x "$EXECUTABLE" >/dev/null; then osascript -e "tell application id \"$BUNDLE_ID\" to quit" >/dev/null 2>&1 || true sleep 1 fi rm -rf "$DEST" cp -R "$APP" "$DEST" echo "installed $DEST" APP="$DEST" fi [[ $RUN -eq 1 ]] && open "$APP" exit 0