mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
174 lines
6.6 KiB
Bash
Executable File
174 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# Builds the direct-download macOS artifact: a Developer ID–signed, notarized
|
||
# .dmg of the VniDropDirect target (the Sparkle-enabled build). Produces:
|
||
# - apple/dist/VniDrop-<version>.dmg (signed + stapled when notarizing)
|
||
#
|
||
# This is the direct-distribution counterpart to the App Store archive flow; it
|
||
# never touches the App Store `VniDrop` target. The Rust crate is not modified.
|
||
#
|
||
# Usage: apple/scripts/build-dmg.sh
|
||
#
|
||
# Environment:
|
||
# DEVELOPER_ID_APP Codesign identity, e.g. "Developer ID Application: … (TEAMID)".
|
||
# Auto-detected from the keychain when unset.
|
||
# DEVELOPMENT_TEAM Apple team ID (10 chars). Auto-derived from the identity.
|
||
# NOTARY_PROFILE Name of a `xcrun notarytool store-credentials` keychain
|
||
# profile. When set, the DMG is notarized and stapled; when
|
||
# unset the build still produces a signed DMG and prints the
|
||
# pending notarization step (useful before creds exist).
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
APPLE_DIR="$REPO_ROOT/apple"
|
||
DIST_DIR="$APPLE_DIR/dist"
|
||
BUILD_DIR="$APPLE_DIR/.build-dmg"
|
||
PROJECT="$APPLE_DIR/VniDrop.xcodeproj"
|
||
SCHEME="VniDropDirect"
|
||
CONFIG="Release-Direct"
|
||
APP_NAME="VniDrop"
|
||
VERSION_RESOLVER="$REPO_ROOT/packaging/version/resolve-version.sh"
|
||
VERSION_CONFIG_GENERATOR="$REPO_ROOT/packaging/version/generate-apple-xcconfig.sh"
|
||
|
||
VERSION="$("$VERSION_RESOLVER" product)"
|
||
export VNIDROP_BUILD_TIME_UTC="${VNIDROP_BUILD_TIME_UTC:-$(date -u +%Y%m%d%H%M%S)}"
|
||
BUILD_NUMBER="$("$VERSION_RESOLVER" apple-direct-build)"
|
||
"$VERSION_RESOLVER" verify >/dev/null
|
||
BUILD_METADATA="$DIST_DIR/$APP_NAME-$VERSION.build-info.json"
|
||
|
||
# --- Resolve signing identity ------------------------------------------------
|
||
if [ -z "${DEVELOPER_ID_APP:-}" ]; then
|
||
DEVELOPER_ID_APP="$(security find-identity -v -p codesigning 2>/dev/null \
|
||
| sed -nE 's/.*"(Developer ID Application: [^"]+)".*/\1/p' | head -1)"
|
||
fi
|
||
if [ -z "${DEVELOPER_ID_APP:-}" ]; then
|
||
echo "error: no 'Developer ID Application' identity found in the keychain." >&2
|
||
echo " Create one in Xcode ▸ Settings ▸ Accounts, or set DEVELOPER_ID_APP." >&2
|
||
exit 1
|
||
fi
|
||
if [ -z "${DEVELOPMENT_TEAM:-}" ]; then
|
||
# The team ID is the 10-char code in the trailing parenthesis of the identity.
|
||
DEVELOPMENT_TEAM="$(printf '%s' "$DEVELOPER_ID_APP" | sed -nE 's/.*\(([A-Z0-9]{10})\)$/\1/p')"
|
||
fi
|
||
echo "==> Direct build v$VERSION (CFBundleVersion $BUILD_NUMBER)"
|
||
echo " identity: $DEVELOPER_ID_APP"
|
||
echo " team: ${DEVELOPMENT_TEAM:-<unknown>}"
|
||
|
||
# --- Build core + regenerate project ----------------------------------------
|
||
# Release core needs LTO disabled (workspace thin-LTO miscompiles proc-macros).
|
||
echo "==> Building Rust core (release)"
|
||
CARGO_PROFILE_RELEASE_LTO=false "$SCRIPT_DIR/build-core.sh" release
|
||
echo "==> Regenerating Xcode project"
|
||
"$VERSION_CONFIG_GENERATOR" all
|
||
( cd "$APPLE_DIR" && xcodegen generate >/dev/null )
|
||
|
||
rm -rf "$BUILD_DIR" && mkdir -p "$BUILD_DIR" "$DIST_DIR"
|
||
ARCHIVE="$BUILD_DIR/$APP_NAME.xcarchive"
|
||
EXPORT_DIR="$BUILD_DIR/export"
|
||
|
||
# --- Archive + export (Developer ID) ----------------------------------------
|
||
echo "==> Archiving $SCHEME ($CONFIG)"
|
||
xcodebuild archive \
|
||
-project "$PROJECT" \
|
||
-scheme "$SCHEME" \
|
||
-configuration "$CONFIG" \
|
||
-destination 'generic/platform=macOS' \
|
||
-archivePath "$ARCHIVE" \
|
||
MARKETING_VERSION="$VERSION" \
|
||
DEVELOPMENT_TEAM="$DEVELOPMENT_TEAM" \
|
||
CODE_SIGN_STYLE=Manual \
|
||
CODE_SIGN_IDENTITY="$DEVELOPER_ID_APP" \
|
||
| xcbeautify 2>/dev/null || true
|
||
[ -d "$ARCHIVE" ] || { echo "error: archive failed" >&2; exit 1; }
|
||
|
||
echo "==> Exporting Developer ID app"
|
||
EXPORT_OPTS="$BUILD_DIR/ExportOptions.plist"
|
||
sed "s/\${DEVELOPMENT_TEAM}/$DEVELOPMENT_TEAM/" \
|
||
"$SCRIPT_DIR/ExportOptions-DeveloperID.plist" > "$EXPORT_OPTS"
|
||
xcodebuild -exportArchive \
|
||
-archivePath "$ARCHIVE" \
|
||
-exportPath "$EXPORT_DIR" \
|
||
-exportOptionsPlist "$EXPORT_OPTS"
|
||
APP="$EXPORT_DIR/$APP_NAME.app"
|
||
[ -d "$APP" ] || { echo "error: export failed" >&2; exit 1; }
|
||
ACTUAL_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \
|
||
"$APP/Contents/Info.plist")"
|
||
ACTUAL_BUILD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' \
|
||
"$APP/Contents/Info.plist")"
|
||
[ "$ACTUAL_VERSION" = "$VERSION" ] || {
|
||
echo "error: exported app version $ACTUAL_VERSION does not match $VERSION" >&2
|
||
exit 1
|
||
}
|
||
[ "$ACTUAL_BUILD" = "$BUILD_NUMBER" ] || {
|
||
echo "error: exported app build $ACTUAL_BUILD does not match $BUILD_NUMBER" >&2
|
||
exit 1
|
||
}
|
||
|
||
echo "==> Enforcing hardened-runtime signature"
|
||
"$SCRIPT_DIR/sign-exported-app.sh" \
|
||
"$APP" \
|
||
"$DEVELOPER_ID_APP" \
|
||
"$APPLE_DIR/VniDrop/Resources/VniDropDirect.entitlements"
|
||
|
||
# --- Build the DMG -----------------------------------------------------------
|
||
DMG="$DIST_DIR/$APP_NAME-$VERSION.dmg"
|
||
rm -f "$DMG"
|
||
STAGING="$BUILD_DIR/dmg-staging"
|
||
rm -rf "$STAGING" && mkdir -p "$STAGING"
|
||
cp -R "$APP" "$STAGING/"
|
||
ln -s /Applications "$STAGING/Applications"
|
||
|
||
echo "==> Building DMG"
|
||
if command -v create-dmg >/dev/null 2>&1; then
|
||
create-dmg \
|
||
--volname "$APP_NAME" \
|
||
--app-drop-link 380 205 \
|
||
--icon "$APP_NAME.app" 130 205 \
|
||
--window-size 540 380 \
|
||
--no-internet-enable \
|
||
"$DMG" "$STAGING" >/dev/null || {
|
||
# create-dmg exits non-zero if it can't set the fancy layout; fall back.
|
||
[ -f "$DMG" ] || hdiutil create -volname "$APP_NAME" -srcfolder "$STAGING" \
|
||
-ov -format UDZO "$DMG" >/dev/null
|
||
}
|
||
else
|
||
hdiutil create -volname "$APP_NAME" -srcfolder "$STAGING" \
|
||
-ov -format UDZO "$DMG" >/dev/null
|
||
fi
|
||
|
||
echo "==> Signing DMG"
|
||
codesign --force --sign "$DEVELOPER_ID_APP" --timestamp "$DMG"
|
||
|
||
# --- Notarize + staple -------------------------------------------------------
|
||
if [ -n "${NOTARY_PROFILE:-}" ]; then
|
||
echo "==> Notarizing (profile: $NOTARY_PROFILE)"
|
||
NOTARY_LOG="$DIST_DIR/$APP_NAME-$VERSION.notary-log.json"
|
||
"$SCRIPT_DIR/notarize.sh" "$DMG" "$NOTARY_PROFILE" "$NOTARY_LOG"
|
||
echo "==> Stapling"
|
||
xcrun stapler staple "$DMG"
|
||
xcrun stapler validate "$DMG"
|
||
spctl -a -vvv --type install "$DMG" || true
|
||
else
|
||
echo "==> NOTARY_PROFILE unset — skipping notarization."
|
||
echo " The DMG is signed but NOT notarized; Gatekeeper will block it until"
|
||
echo " you run 'xcrun notarytool store-credentials' and re-run with NOTARY_PROFILE set."
|
||
fi
|
||
|
||
SIZE="$(stat -f%z "$DMG")"
|
||
jq -n \
|
||
--arg productVersion "$VERSION" \
|
||
--arg directBuildNumber "$BUILD_NUMBER" \
|
||
--arg artifact "$(basename "$DMG")" \
|
||
'{
|
||
productVersion: $productVersion,
|
||
directBuildNumber: $directBuildNumber,
|
||
distribution: "direct",
|
||
artifact: $artifact
|
||
}' > "$BUILD_METADATA"
|
||
echo "==> Done."
|
||
echo " dmg: $DMG"
|
||
echo " version: $VERSION"
|
||
echo " build: $BUILD_NUMBER"
|
||
echo " size: $SIZE bytes"
|