199 lines
5.8 KiB
Bash
199 lines
5.8 KiB
Bash
#!/usr/bin/env bash
|
|
# Build QwenPaw with Tauri for macOS (PyInstaller backend)
|
|
# Creates a self-contained desktop app with bundled Python backend
|
|
#
|
|
# Usage:
|
|
# ./scripts/pack-tauri/build_macos_pyinstaller.sh
|
|
|
|
set -e
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
|
|
VERSION=$(sed -n 's/^__version__[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' src/pineagents/__version__.py)
|
|
|
|
echo "========================================="
|
|
echo "QwenPaw Tauri Build - macOS (PyInstaller)"
|
|
echo "========================================="
|
|
echo "Version: ${VERSION}"
|
|
echo ""
|
|
|
|
SIGN_MACOS_BUNDLE="${REPO_ROOT}/scripts/pack-tauri/sign_macos_bundle.sh"
|
|
|
|
# Step 0: Prerequisites
|
|
echo "== Step 0: Checking Prerequisites =="
|
|
missing=()
|
|
|
|
if command -v npm &>/dev/null; then
|
|
echo " [OK] npm ($(npm --version))"
|
|
else
|
|
echo " [MISSING] npm"
|
|
echo " Install Node.js: https://nodejs.org/"
|
|
missing+=("npm")
|
|
fi
|
|
|
|
if command -v rustc &>/dev/null; then
|
|
echo " [OK] rustc ($(rustc --version))"
|
|
else
|
|
echo " [MISSING] rustc (Rust)"
|
|
echo " Install: https://rustup.rs"
|
|
missing+=("rustc")
|
|
fi
|
|
|
|
if command -v uv &>/dev/null; then
|
|
echo " [OK] uv ($(uv --version))"
|
|
else
|
|
echo " [MISSING] uv"
|
|
echo " Install: https://docs.astral.sh/uv/getting-started/installation/"
|
|
missing+=("uv")
|
|
fi
|
|
|
|
if [ ${#missing[@]} -gt 0 ]; then
|
|
echo ""
|
|
echo "Missing prerequisites: ${missing[*]}"
|
|
echo "Install the missing tools and re-run this script."
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
if [ ! -f "${SIGN_MACOS_BUNDLE}" ]; then
|
|
echo "ERROR: macOS signing helper not found at ${SIGN_MACOS_BUNDLE}"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${APPLE_SIGNING_IDENTITY:-}" ] && [ -z "${APPLE_CERTIFICATE:-}" ]; then
|
|
# The Tauri app and PyInstaller sidecar are native Mach-O executables.
|
|
# Keep their signature state consistent with ad-hoc signatures when no
|
|
# Developer ID certificate is configured. This matches the legacy desktop
|
|
# package behavior: signed enough for local loading, not notarized.
|
|
export APPLE_SIGNING_IDENTITY="-"
|
|
echo "Using ad-hoc macOS code signing"
|
|
fi
|
|
if [ -z "${PYINSTALLER_CODESIGN_IDENTITY:-}" ]; then
|
|
# PyInstaller uses the same identity as the final app for bundled Mach-O
|
|
# files; "-" means ad-hoc signing on macOS.
|
|
export PYINSTALLER_CODESIGN_IDENTITY="${APPLE_SIGNING_IDENTITY:-}"
|
|
fi
|
|
echo ""
|
|
|
|
# Step 1: Build console static assets
|
|
echo "== Step 1: Building Console Static Assets =="
|
|
cd console
|
|
yarn install --immutable
|
|
echo "Generating Tauri icons..."
|
|
npm exec -- tauri icon ../scripts/pack/assets/icon.svg
|
|
echo "Syncing Tauri version..."
|
|
node ../scripts/pack-tauri/sync_tauri_version.mjs
|
|
echo "Building console frontend..."
|
|
npm run build:prod
|
|
cd ..
|
|
echo "Console static assets built"
|
|
echo ""
|
|
|
|
# Step 2: Build PyInstaller backend
|
|
echo "== Step 2: Building PyInstaller Backend =="
|
|
bash scripts/pack-tauri/build_pyinstaller.sh
|
|
echo "PyInstaller backend built"
|
|
echo ""
|
|
|
|
echo "== Step 2b: Signing PyInstaller Backend =="
|
|
bash "${SIGN_MACOS_BUNDLE}" \
|
|
"${REPO_ROOT}/console/src-tauri/binaries/qwenpaw-backend" \
|
|
"${APPLE_SIGNING_IDENTITY}"
|
|
echo "PyInstaller backend signed"
|
|
echo ""
|
|
|
|
# Step 3: Build Tauri app
|
|
echo "== Step 3: Building Tauri App =="
|
|
BUNDLE_DIR="${REPO_ROOT}/console/src-tauri/target/release/bundle"
|
|
rm -rf "${BUNDLE_DIR}/dmg" "${BUNDLE_DIR}/macos"
|
|
cd console
|
|
echo "Building for macOS..."
|
|
npm exec -- tauri build \
|
|
--config src-tauri/tauri.version.conf.json \
|
|
--bundles app,dmg
|
|
cd ..
|
|
echo "Tauri app built"
|
|
echo ""
|
|
|
|
APP_PATH="${BUNDLE_DIR}/macos/QwenPaw Desktop.app"
|
|
if [ ! -d "${APP_PATH}" ]; then
|
|
echo "ERROR: No Tauri macOS app found at ${APP_PATH}"
|
|
exit 1
|
|
fi
|
|
HELPER_PATH="${APP_PATH}/Contents/MacOS/qwenpaw-computer-use-helper"
|
|
if [ ! -x "${HELPER_PATH}" ]; then
|
|
echo "ERROR: Computer Use helper was not bundled at ${HELPER_PATH}"
|
|
exit 1
|
|
fi
|
|
|
|
echo "== Step 3b: Signing Final macOS App =="
|
|
bash "${SIGN_MACOS_BUNDLE}" \
|
|
"${APP_PATH}" \
|
|
"${APPLE_SIGNING_IDENTITY}"
|
|
echo "Final macOS app signed and verified"
|
|
echo ""
|
|
|
|
# Step 4: Collect distribution artifacts
|
|
echo "== Step 4: Collecting Distribution Artifacts =="
|
|
DIST="${DIST:-dist}"
|
|
if [[ "${DIST}" = /* ]]; then
|
|
DIST_ROOT="${DIST}"
|
|
else
|
|
DIST_ROOT="${REPO_ROOT}/${DIST}"
|
|
fi
|
|
DIST_DIR="${DIST_ROOT}/tauri-macos"
|
|
rm -rf "${DIST_DIR}"
|
|
mkdir -p "${DIST_DIR}"
|
|
|
|
# Match the legacy macOS package shape: one zip containing one .app bundle.
|
|
cp -R "${APP_PATH}" "${DIST_DIR}/"
|
|
STAGED_APP_PATH="${DIST_DIR}/$(basename "${APP_PATH}")"
|
|
echo ".app copied to ${STAGED_APP_PATH}"
|
|
|
|
# Create ZIP archive
|
|
ZIP_NAME="${DIST_ROOT}/QwenPaw-Tauri-${VERSION}-macOS.zip"
|
|
if [ -f "${ZIP_NAME}" ]; then
|
|
rm -f "${ZIP_NAME}"
|
|
fi
|
|
if command -v ditto &>/dev/null; then
|
|
ditto -c -k --sequesterRsrc --keepParent "${STAGED_APP_PATH}" "${ZIP_NAME}"
|
|
else
|
|
cd "${DIST_DIR}"
|
|
zip -r "${ZIP_NAME}" "$(basename "${STAGED_APP_PATH}")"
|
|
cd "${REPO_ROOT}"
|
|
fi
|
|
|
|
if [ -f "${ZIP_NAME}" ]; then
|
|
SIZE=$(du -sh "${ZIP_NAME}" | cut -f1)
|
|
echo "Created ${ZIP_NAME} (${SIZE})"
|
|
else
|
|
echo "ERROR: Failed to create ZIP archive"
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
UPDATER_NAME="${DIST_ROOT}/QwenPaw-Tauri-${VERSION}-macOS.app.tar.gz"
|
|
case "$(uname -m)" in
|
|
arm64 | aarch64) UPDATER_TARGET="darwin-aarch64" ;;
|
|
*) UPDATER_TARGET="darwin-x86_64" ;;
|
|
esac
|
|
python "${REPO_ROOT}/scripts/pack-tauri/generate_update_manifest.py" stage \
|
|
--bundle-dir "${BUNDLE_DIR}/macos" \
|
|
--pattern '*.app.tar.gz' \
|
|
--target "${UPDATER_TARGET}" \
|
|
--output "${UPDATER_NAME}" \
|
|
--pubkey-config "${REPO_ROOT}/console/src-tauri/tauri.version.conf.json"
|
|
|
|
echo ""
|
|
echo "========================================="
|
|
echo "Build Complete!"
|
|
echo "========================================="
|
|
echo "App: ${APP_PATH}"
|
|
echo "Distribution: ${DIST_DIR}"
|
|
echo "Archive: ${ZIP_NAME}"
|
|
echo "Updater: ${UPDATER_NAME}"
|
|
echo ""
|
|
echo "Test: open \"${STAGED_APP_PATH}\""
|
|
echo ""
|