211 lines
6.2 KiB
Bash
Executable File
211 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
BUILD_DIR="$ROOT_DIR/build"
|
||
ARTIFACTS_DIR="$ROOT_DIR/dist"
|
||
SCHEME="iKeyMon"
|
||
PROJECT="iKeyMon.xcodeproj"
|
||
CREDENTIALS_FILE="$ROOT_DIR/.signing.env"
|
||
VERSION_FILE="$ROOT_DIR/version.json"
|
||
DERIVED_DATA_ROOT="${DERIVED_DATA_ROOT:-$HOME/Library/Developer/Xcode/DerivedData}"
|
||
|
||
find_generate_appcast() {
|
||
if [[ -n "${SPARKLE_GENERATE_APPCAST:-}" && -x "${SPARKLE_GENERATE_APPCAST}" ]]; then
|
||
echo "$SPARKLE_GENERATE_APPCAST"
|
||
return
|
||
fi
|
||
|
||
if [[ -d "$DERIVED_DATA_ROOT" ]]; then
|
||
local candidate
|
||
candidate="$(find "$DERIVED_DATA_ROOT" -path "*/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_appcast" -type f 2>/dev/null | head -n 1 || true)"
|
||
if [[ -n "$candidate" ]]; then
|
||
echo "$candidate"
|
||
return
|
||
fi
|
||
fi
|
||
}
|
||
|
||
generate_appcast() {
|
||
local generator
|
||
generator="$(find_generate_appcast)"
|
||
local download_prefix=""
|
||
if [[ -n "${SPARKLE_DOWNLOAD_BASE_TEMPLATE:-}" ]]; then
|
||
download_prefix="${SPARKLE_DOWNLOAD_BASE_TEMPLATE//\{\{VERSION\}\}/$VERSION}"
|
||
else
|
||
download_prefix="${SPARKLE_DOWNLOAD_BASE_URL:-}"
|
||
fi
|
||
|
||
if [[ -z "$generator" || -z "${SPARKLE_EDDSA_KEY_FILE:-}" || -z "$download_prefix" ]]; then
|
||
echo "ℹ️ Skipping Sparkle appcast generation (generator/key/download prefix not configured)."
|
||
return
|
||
fi
|
||
|
||
local output="$SPARKLE_APPCAST_OUTPUT"
|
||
mkdir -p "$(dirname "$output")"
|
||
local staging_dir
|
||
staging_dir="$(mktemp -d)"
|
||
cp "$ARTIFACTS_DIR"/*.zip "$staging_dir"/ 2>/dev/null || true
|
||
|
||
if ! ls "$staging_dir"/*.zip >/dev/null 2>&1; then
|
||
echo "ℹ️ Skipping Sparkle appcast generation (no ZIP archives found)."
|
||
rm -rf "$staging_dir"
|
||
return
|
||
fi
|
||
|
||
echo "🧾 Generating Sparkle appcast at $output"
|
||
if ! "$generator" \
|
||
--download-url-prefix "$download_prefix" \
|
||
--ed-key-file "$SPARKLE_EDDSA_KEY_FILE" \
|
||
-o "$output" \
|
||
"$staging_dir"; then
|
||
echo "⚠️ Sparkle appcast generation failed."
|
||
fi
|
||
rm -rf "$staging_dir"
|
||
}
|
||
|
||
sign_update_artifacts() {
|
||
local signer
|
||
signer="$(find "$DERIVED_DATA_ROOT" -path "*/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update" -type f 2>/dev/null | head -n 1 || true)"
|
||
if [[ -z "$signer" || -z "${SPARKLE_EDDSA_KEY_FILE:-}" ]]; then
|
||
echo "ℹ️ Skipping Sparkle signing (sign_update or SPARKLE_EDDSA_KEY_FILE missing)."
|
||
return
|
||
fi
|
||
echo "🔑 Signing ${ZIP_NAME} for Sparkle feed"
|
||
if ! "$signer" "${ARTIFACTS_DIR}/${ZIP_NAME}" --ed-key-file "${SPARKLE_EDDSA_KEY_FILE}"; then
|
||
echo "⚠️ sign_update failed (continuing without signature)"
|
||
fi
|
||
}
|
||
|
||
rewrite_appcast_urls() {
|
||
: # no-op (old helper removed)
|
||
}
|
||
|
||
submit_for_notarization() {
|
||
local target="$1"
|
||
local label="$2"
|
||
echo "📝 Submitting ${label} for notarization..."
|
||
xcrun notarytool submit "$target" \
|
||
--apple-id "$NOTARY_APPLE_ID" \
|
||
--team-id "$NOTARY_TEAM_ID" \
|
||
--password "$NOTARY_PASSWORD" \
|
||
--wait
|
||
}
|
||
|
||
notarize_app_bundle() {
|
||
local bundle="$1"
|
||
local label="$2"
|
||
if [[ -z "${NOTARY_APPLE_ID:-}" || -z "${NOTARY_TEAM_ID:-}" || -z "${NOTARY_PASSWORD:-}" ]]; then
|
||
echo "ℹ️ Skipping notarization for ${label} (NOTARY_* variables not set)."
|
||
return 1
|
||
fi
|
||
|
||
local tmp_dir
|
||
tmp_dir="$(mktemp -d)"
|
||
local archive="$tmp_dir/$(basename "$bundle").zip"
|
||
ditto -c -k --keepParent "$bundle" "$archive"
|
||
|
||
submit_for_notarization "$archive" "$label"
|
||
xcrun stapler staple "$bundle"
|
||
rm -rf "$tmp_dir"
|
||
}
|
||
|
||
notarize_artifact() {
|
||
local artifact="$1"
|
||
local label="$2"
|
||
if [[ -z "${NOTARY_APPLE_ID:-}" || -z "${NOTARY_TEAM_ID:-}" || -z "${NOTARY_PASSWORD:-}" ]]; then
|
||
echo "ℹ️ Skipping notarization for ${label} (NOTARY_* variables not set)."
|
||
return 1
|
||
fi
|
||
submit_for_notarization "$artifact" "$label"
|
||
xcrun stapler staple "$artifact"
|
||
}
|
||
|
||
if [[ -f "$CREDENTIALS_FILE" ]]; then
|
||
set -a
|
||
# shellcheck disable=SC1090
|
||
source "$CREDENTIALS_FILE"
|
||
set +a
|
||
fi
|
||
|
||
: "${SPARKLE_APPCAST_OUTPUT:=$ROOT_DIR/Sparkle/appcast.xml}"
|
||
export SPARKLE_APPCAST_OUTPUT
|
||
|
||
"$ROOT_DIR/scripts/sync_version.sh"
|
||
|
||
rm -rf "$BUILD_DIR" "$ARTIFACTS_DIR"
|
||
mkdir -p "$ARTIFACTS_DIR"
|
||
|
||
xcodebuild \
|
||
-project "$ROOT_DIR/$PROJECT" \
|
||
-scheme "$SCHEME" \
|
||
-configuration Release \
|
||
-derivedDataPath "$BUILD_DIR" \
|
||
CODE_SIGNING_ALLOWED=NO \
|
||
clean build
|
||
|
||
APP_PATH="$BUILD_DIR/Build/Products/Release/iKeyMon.app"
|
||
if [[ ! -d "$APP_PATH" ]]; then
|
||
echo "❌ Failed to find built app at $APP_PATH"
|
||
exit 1
|
||
fi
|
||
|
||
if [[ -n "${CODESIGN_IDENTITY:-}" ]]; then
|
||
echo "🔏 Codesigning app with identity: $CODESIGN_IDENTITY"
|
||
codesign \
|
||
--deep \
|
||
--force \
|
||
--options runtime \
|
||
--timestamp \
|
||
--entitlements "$ROOT_DIR/iKeyMon.entitlements" \
|
||
--sign "$CODESIGN_IDENTITY" \
|
||
"$APP_PATH"
|
||
else
|
||
echo "⚠️ Skipping codesign (CODESIGN_IDENTITY not set)."
|
||
fi
|
||
|
||
notarize_app_bundle "$APP_PATH" "iKeyMon.app"
|
||
|
||
STAGING_DIR=$(mktemp -d)
|
||
mkdir -p "$STAGING_DIR"
|
||
cp -R "$APP_PATH" "$STAGING_DIR/"
|
||
ln -s /Applications "$STAGING_DIR/Applications"
|
||
mkdir -p "$STAGING_DIR/.background"
|
||
cp "$ROOT_DIR/Assets/dmg_background.png" "$STAGING_DIR/.background/background.png"
|
||
|
||
VERSION="$(python3 - <<'PY' "$VERSION_FILE"
|
||
import json, sys
|
||
with open(sys.argv[1], "r", encoding="utf-8") as handle:
|
||
data = json.load(handle)
|
||
print(data.get("marketing_version", "dev"))
|
||
PY
|
||
)"
|
||
ZIP_NAME="iKeyMon-${VERSION}.zip"
|
||
pushd "$(dirname "$APP_PATH")" >/dev/null
|
||
zip -r "$ARTIFACTS_DIR/$ZIP_NAME" "$(basename "$APP_PATH")"
|
||
popd >/dev/null
|
||
|
||
DMG_NAME="iKeyMon-${VERSION}.dmg"
|
||
hdiutil create -volname "iKeyMon" -srcfolder "$STAGING_DIR" -ov -format UDZO "$ARTIFACTS_DIR/$DMG_NAME"
|
||
|
||
sign_update_artifacts
|
||
|
||
if [[ -n "${NOTARY_APPLE_ID:-}" && -n "${NOTARY_TEAM_ID:-}" && -n "${NOTARY_PASSWORD:-}" ]]; then
|
||
notarize_artifact "$ARTIFACTS_DIR/$DMG_NAME" "$DMG_NAME"
|
||
else
|
||
echo "⚠️ Skipping DMG notarization (NOTARY_* variables not set)."
|
||
fi
|
||
rm -rf "$STAGING_DIR"
|
||
|
||
generate_appcast
|
||
|
||
if [[ -n "${GITEA_TOKEN:-}" && -n "${GITEA_OWNER:-}" && -n "${GITEA_REPO:-}" ]]; then
|
||
"$ROOT_DIR/scripts/publish_release.sh" "$VERSION" "$ARTIFACTS_DIR/$ZIP_NAME" "$ARTIFACTS_DIR/$DMG_NAME"
|
||
else
|
||
echo "ℹ️ Skipping Gitea release publishing (GITEA_* variables not fully set)."
|
||
fi
|
||
|
||
echo "✅ Build complete. Artifacts:"
|
||
echo " - $ARTIFACTS_DIR/$ZIP_NAME"
|
||
echo " - $ARTIFACTS_DIR/$DMG_NAME"
|