#!/bin/bash # # Build script for Path Juggler # Creates a standalone macOS .app bundle with ad-hoc code signing # set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" APP_NAME="Path Juggler" BUNDLE_ID="com.pathjuggler.app" VENV_DIR="$SCRIPT_DIR/.build_venv" DIST_DIR="$SCRIPT_DIR/dist" echo "=== Path Juggler - Build Script ===" echo "" # Check for Python 3 if ! command -v python3 &> /dev/null; then echo "Error: python3 is required but not found" exit 1 fi PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') echo "Using Python $PYTHON_VERSION" # Create virtual environment echo "" echo "=== Creating virtual environment ===" if [ -d "$VENV_DIR" ]; then echo "Removing existing build environment..." rm -rf "$VENV_DIR" fi python3 -m venv "$VENV_DIR" source "$VENV_DIR/bin/activate" # Upgrade pip echo "" echo "=== Installing build dependencies ===" pip install --upgrade pip --quiet pip install pyinstaller watchdog --quiet echo "Installed:" pip list | grep -E "(pyinstaller|watchdog)" # Create PyInstaller spec file for more control echo "" echo "=== Creating build configuration ===" cat > "$SCRIPT_DIR/path_juggler.spec" << 'SPEC' # -*- mode: python ; coding: utf-8 -*- import sys from pathlib import Path block_cipher = None a = Analysis( ['path_juggler_gui.py'], pathex=[], binaries=[], datas=[], hiddenimports=[ 'watchdog.observers', 'watchdog.observers.fsevents', 'watchdog.events', ], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='Path Juggler', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=False, # No terminal window disable_windowed_traceback=False, argv_emulation=True, # Important for macOS target_arch=None, codesign_identity=None, entitlements_file=None, ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='Path Juggler', ) app = BUNDLE( coll, name='Path Juggler.app', icon=None, bundle_identifier='com.pathjuggler.app', info_plist={ 'CFBundleName': 'Path Juggler', 'CFBundleDisplayName': 'Path Juggler', 'CFBundleVersion': '1.0.0', 'CFBundleShortVersionString': '1.0.0', 'NSHighResolutionCapable': True, 'LSMinimumSystemVersion': '10.13.0', 'NSAppleEventsUsageDescription': 'Path Juggler needs to access files.', }, ) SPEC # Build the app echo "" echo "=== Building application ===" pyinstaller --clean --noconfirm path_juggler.spec # Sign the app with ad-hoc signature echo "" echo "=== Signing application (ad-hoc) ===" APP_PATH="$DIST_DIR/Path Juggler.app" if [ -d "$APP_PATH" ]; then # Remove any existing signatures codesign --remove-signature "$APP_PATH" 2>/dev/null || true # Sign with ad-hoc signature (dash means ad-hoc) codesign --force --deep --sign - "$APP_PATH" echo "Signature applied" codesign -dv "$APP_PATH" 2>&1 | head -5 else echo "Error: App bundle not found at $APP_PATH" exit 1 fi # Clean up echo "" echo "=== Cleaning up ===" rm -rf "$SCRIPT_DIR/build" rm -f "$SCRIPT_DIR/path_juggler.spec" deactivate rm -rf "$VENV_DIR" echo "" echo "=== Build complete ===" echo "" echo "Application created at:" echo " $APP_PATH" echo "" echo "To install, drag the app to your Applications folder:" echo " open \"$DIST_DIR\"" echo "" echo "Note: On first launch, you may need to right-click and select 'Open'" echo "to bypass Gatekeeper since this is a self-signed app."