feat(#32): Packaging, signing, and CI #65
@@ -0,0 +1,57 @@
|
|||||||
|
name: macOS CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'milestone/m6-gamut-stage0-cgats-release'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Fetch Argyll sidecars
|
||||||
|
run: scripts/fetch-argyll.sh
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate Xcode project
|
||||||
|
run: xcodegen generate --project .
|
||||||
|
|
||||||
|
- name: Build and test (universal)
|
||||||
|
run: |
|
||||||
|
xcodebuild test \
|
||||||
|
-scheme ICCery \
|
||||||
|
-destination 'platform=macOS' \
|
||||||
|
ARCHS='arm64 x86_64' \
|
||||||
|
ONLY_ACTIVE_ARCH=NO \
|
||||||
|
CODE_SIGNING_ALLOWED=NO
|
||||||
|
|
||||||
|
package:
|
||||||
|
needs: build-and-test
|
||||||
|
runs-on: self-hosted
|
||||||
|
if: github.ref == 'refs/heads/milestone/m6-gamut-stage0-cgats-release' || startsWith(github.ref, 'refs/tags/v')
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Package release
|
||||||
|
run: scripts/package-release.sh
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
|
||||||
|
DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
|
||||||
|
NOTARIZE_APPLE_ID: ${{ secrets.NOTARIZE_APPLE_ID }}
|
||||||
|
NOTARIZE_PASSWORD: ${{ secrets.NOTARIZE_PASSWORD }}
|
||||||
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
|
||||||
|
- name: Upload DMG artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: iccery-dmg
|
||||||
|
path: ICCery-*.dmg
|
||||||
@@ -20,3 +20,9 @@ ICCery.xcodeproj/
|
|||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Release artefacts (not git blobs)
|
||||||
|
*.dmg
|
||||||
|
*.zip
|
||||||
|
Release/
|
||||||
|
notarization/
|
||||||
|
|||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/dmgbuild-settings.py
|
||||||
|
#
|
||||||
|
# dmgbuild settings for ICCery. Set DMG_FILENAME and DMG_VOLUME_NAME in the
|
||||||
|
# environment, or accept the defaults. Background art can be supplied later by
|
||||||
|
# placing a PNG at Resources/dmg-background.png and setting DMG_BACKGROUND.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
filename = os.environ.get('DMG_FILENAME', 'ICCery.dmg')
|
||||||
|
volume_name = os.environ.get('DMG_VOLUME_NAME', 'ICCery')
|
||||||
|
|
||||||
|
# Background art is optional. If the referenced PNG does not exist, fall back
|
||||||
|
# to a plain window. See docs/23-assets.md for the DMG background spec.
|
||||||
|
background = os.environ.get('DMG_BACKGROUND', 'Resources/dmg-background.png')
|
||||||
|
if background and not os.path.exists(background):
|
||||||
|
background = None
|
||||||
|
|
||||||
|
icon = None
|
||||||
|
|
||||||
|
# Window size is enough for the app icon and the Applications alias.
|
||||||
|
window_rect = ((100, 100), (640, 480))
|
||||||
|
|
||||||
|
# Use icon view without extra chrome.
|
||||||
|
default_view = 'icon-view'
|
||||||
|
show_status_bar = False
|
||||||
|
show_tab_view = False
|
||||||
|
show_toolbar = False
|
||||||
|
show_pathbar = False
|
||||||
|
show_sidebar = False
|
||||||
|
sidebar_width = 180
|
||||||
|
|
||||||
|
# Position the .app on the left and the Applications alias on the right.
|
||||||
|
icon_locations = {
|
||||||
|
'ICCery.app': (140, 240),
|
||||||
|
'Applications': (500, 240),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Symlink to /Applications for drag-and-drop install.
|
||||||
|
symlinks = {'Applications': '/Applications'}
|
||||||
Executable
+120
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# scripts/package-release.sh
|
||||||
|
#
|
||||||
|
# Release packaging pipeline for ICCery v2 macOS.
|
||||||
|
#
|
||||||
|
# Steps:
|
||||||
|
# 1. Fetch and ad-hoc sign Argyll sidecars (scripts/fetch-argyll.sh).
|
||||||
|
# 2. Generate the Xcode project from project.yml.
|
||||||
|
# 3. Build a universal Release ICCery.app.
|
||||||
|
# 4. Sign the .app (Developer ID if CODESIGN_IDENTITY is set, else ad-hoc).
|
||||||
|
# 5. Hard-fail verify every bundled Mach-O sidecar with codesign -dvv.
|
||||||
|
# 6. Build a DMG with dmgbuild.
|
||||||
|
# 7. Optionally notarize and staple the DMG when notarization secrets exist.
|
||||||
|
#
|
||||||
|
# Required secrets (optional):
|
||||||
|
# CODESIGN_IDENTITY Developer ID Application identity name
|
||||||
|
# DEVELOPMENT_TEAM Apple development team ID (for xcodebuild signing)
|
||||||
|
# NOTARIZE_APPLE_ID Apple ID for notarytool
|
||||||
|
# NOTARIZE_PASSWORD App-specific password for notarytool
|
||||||
|
# APPLE_TEAM_ID Team ID for notarytool
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
echo "==> Fetching Argyll sidecars"
|
||||||
|
scripts/fetch-argyll.sh
|
||||||
|
|
||||||
|
echo "==> Generating Xcode project"
|
||||||
|
xcodegen generate --project .
|
||||||
|
|
||||||
|
CONFIG="Release"
|
||||||
|
DEST="platform=macOS"
|
||||||
|
|
||||||
|
# Default to ad-hoc signing. A real Developer ID can be injected via env.
|
||||||
|
IDENTITY="${CODESIGN_IDENTITY:--}"
|
||||||
|
DEVELOPMENT_TEAM="${DEVELOPMENT_TEAM:-}"
|
||||||
|
|
||||||
|
echo "==> Building universal Release app"
|
||||||
|
BUILD_EXTRA=""
|
||||||
|
if [ -n "$DEVELOPMENT_TEAM" ]; then
|
||||||
|
BUILD_EXTRA="DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
xcodebuild \
|
||||||
|
-scheme ICCery \
|
||||||
|
-destination "$DEST" \
|
||||||
|
-configuration "$CONFIG" \
|
||||||
|
ARCHS='arm64 x86_64' \
|
||||||
|
ONLY_ACTIVE_ARCH=NO \
|
||||||
|
CODE_SIGNING_ALLOWED=YES \
|
||||||
|
CODE_SIGN_IDENTITY="$IDENTITY" \
|
||||||
|
$BUILD_EXTRA \
|
||||||
|
build
|
||||||
|
|
||||||
|
echo "==> Locating built app"
|
||||||
|
BUILT_PRODUCTS_DIR="$(xcodebuild \
|
||||||
|
-scheme ICCery \
|
||||||
|
-destination "$DEST" \
|
||||||
|
-configuration "$CONFIG" \
|
||||||
|
-showBuildSettings \
|
||||||
|
| sed -n 's/^ *BUILT_PRODUCTS_DIR = //p' \
|
||||||
|
| head -n 1)"
|
||||||
|
|
||||||
|
APP="$BUILT_PRODUCTS_DIR/ICCery.app"
|
||||||
|
if [ ! -d "$APP" ]; then
|
||||||
|
echo "error: built app not found at $APP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "App: $APP"
|
||||||
|
|
||||||
|
# If a Developer ID identity was supplied, re-sign the .app bundle. Sidecars
|
||||||
|
# live in Resources/Argyll and remain ad-hoc signed by fetch-argyll.sh.
|
||||||
|
if [ -n "${CODESIGN_IDENTITY:-}" ] && [ "$CODESIGN_IDENTITY" != "-" ]; then
|
||||||
|
echo "==> Signing $APP with '$CODESIGN_IDENTITY'"
|
||||||
|
codesign --force --sign "$CODESIGN_IDENTITY" \
|
||||||
|
--entitlements Resources/ICCery.entitlements \
|
||||||
|
--options runtime \
|
||||||
|
"$APP"
|
||||||
|
else
|
||||||
|
echo "==> App ad-hoc signed by xcodebuild; not re-signing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Verifying sidecar signatures"
|
||||||
|
scripts/verify-sidecar-signatures.sh "$APP"
|
||||||
|
|
||||||
|
echo "==> Building DMG"
|
||||||
|
VERSION="$(plutil -extract CFBundleShortVersionString raw "$APP/Contents/Info.plist" 2>/dev/null || echo '2.0.0')"
|
||||||
|
BUILD_NUM="$(plutil -extract CFBundleVersion raw "$APP/Contents/Info.plist" 2>/dev/null || echo '1')"
|
||||||
|
DMG="ICCery-${VERSION}-${BUILD_NUM}.dmg"
|
||||||
|
VOLUME_NAME="ICCery ${VERSION}"
|
||||||
|
|
||||||
|
if ! command -v dmgbuild >/dev/null 2>&1; then
|
||||||
|
echo "==> Installing dmgbuild"
|
||||||
|
pip3 install dmgbuild
|
||||||
|
fi
|
||||||
|
|
||||||
|
DMG_FILENAME="$DMG" \
|
||||||
|
DMG_VOLUME_NAME="$VOLUME_NAME" \
|
||||||
|
dmgbuild -s scripts/dmgbuild-settings.py "$VOLUME_NAME" "$DMG"
|
||||||
|
|
||||||
|
echo "DMG: $PWD/$DMG"
|
||||||
|
|
||||||
|
# Optional notarization/stapling when credentials are present.
|
||||||
|
if [ -n "${NOTARIZE_APPLE_ID:-}" ] && \
|
||||||
|
[ -n "${NOTARIZE_PASSWORD:-}" ] && \
|
||||||
|
[ -n "${APPLE_TEAM_ID:-}" ]; then
|
||||||
|
echo "==> Submitting $DMG for notarization"
|
||||||
|
xcrun notarytool submit "$DMG" \
|
||||||
|
--apple-id "$NOTARIZE_APPLE_ID" \
|
||||||
|
--password "$NOTARIZE_PASSWORD" \
|
||||||
|
--team-id "$APPLE_TEAM_ID" \
|
||||||
|
--wait
|
||||||
|
xcrun stapler staple "$DMG"
|
||||||
|
echo "==> Stapled $DMG"
|
||||||
|
else
|
||||||
|
echo "==> Notarization credentials not set; skipping"
|
||||||
|
fi
|
||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# scripts/verify-sidecar-signatures.sh
|
||||||
|
#
|
||||||
|
# Hard-fail check that every Mach-O Argyll sidecar shipped inside the built
|
||||||
|
# ICCery.app bundle is signed (ad-hoc or Developer ID). Run this in CI after
|
||||||
|
# xcodebuild and before packaging.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-sidecar-signatures.sh <path/to/ICCery.app>
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
APP="${1:-}"
|
||||||
|
if [ -z "$APP" ]; then
|
||||||
|
echo "usage: $0 <path/to/ICCery.app>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$APP" ]; then
|
||||||
|
echo "error: app bundle not found: $APP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SIDECAR_DIR="$APP/Contents/Resources/Argyll"
|
||||||
|
if [ ! -d "$SIDECAR_DIR" ]; then
|
||||||
|
echo "error: Argyll sidecar directory not found: $SIDECAR_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
UNSIGNED=""
|
||||||
|
for f in "$SIDECAR_DIR"/*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
if file -b "$f" | grep -q 'Mach-O'; then
|
||||||
|
if ! codesign -dvv "$f" >/dev/null 2>&1; then
|
||||||
|
echo "error: unsigned Mach-O sidecar: $f" >&2
|
||||||
|
UNSIGNED="$UNSIGNED $f"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$UNSIGNED" ]; then
|
||||||
|
echo "error: unsigned Argyll sidecars remain:$UNSIGNED" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK: all Mach-O sidecars in $SIDECAR_DIR are signed"
|
||||||
Reference in New Issue
Block a user