diff --git a/.github/workflows/deploy-launcher.yml b/.github/workflows/deploy-launcher.yml index 88d4f1ea..a22bbc4b 100644 --- a/.github/workflows/deploy-launcher.yml +++ b/.github/workflows/deploy-launcher.yml @@ -2,8 +2,8 @@ name: Deploy Launcher # Builds the small native launcher for each OS, signs/notarizes it, packages it # (.dmg / setup.exe / .AppImage), and uploads the result as a build artifact. -# Replaces deploy-pyinstaller.yml: the launcher does NOT bundle torch, so there -# is no cpu/cu129 matrix and no disk-space juggling — builds are small and fast. +# The launcher does NOT bundle torch (uv fetches it on first run), so builds are +# small and fast — no cpu/cu129 matrix and no disk-space juggling. # # Required repository secrets (signing is skipped gracefully if absent, so # workflow_dispatch still produces unsigned artifacts for testing): diff --git a/.github/workflows/deploy-pyinstaller.yml b/.github/workflows/deploy-pyinstaller.yml deleted file mode 100644 index 2086eeee..00000000 --- a/.github/workflows/deploy-pyinstaller.yml +++ /dev/null @@ -1,203 +0,0 @@ -name: Deploy PyInstaller Executables - -# DEPRECATED: superseded by deploy-launcher.yml (the small signed uv-bootstrap -# launcher). No longer wired into deploy.yml; kept one release cycle as a -# fallback and for manual dispatch. Remove this workflow, photomap.spec, and -# INSTALL/pyinstaller/ once the signed launcher has shipped on all platforms. - -on: - workflow_call: - workflow_dispatch: - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - build: - strategy: - matrix: - os: [ubuntu-22.04, macos-latest, windows-latest] - torch_variant: [cpu, cu129] - include: - - os: ubuntu-22.04 - platform: linux-x64 - build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.sh - executable_ext: "" - macos_app_flag: "" - archive_ext: ".zip" - - os: macos-latest - platform: macos-x64 - build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.sh - executable_ext: "" - macos_app_flag: "--macos-app" - archive_ext: ".zip" - - os: windows-latest - platform: windows-x64 - build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.ps1 - executable_ext: ".exe" - macos_app_flag: "" - archive_ext: ".zip" - exclude: - - os: macos-latest - torch_variant: cu129 - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - - name: Free up disk space (Linux/macOS) - if: matrix.torch_variant != 'cpu' && runner.os != 'Windows' - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/share/boost - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - df -h - shell: bash - - - name: Free up disk space (Windows) - if: matrix.torch_variant != 'cpu' && runner.os == 'Windows' - run: | - Get-PSDrive C - Remove-Item -Recurse -Force $env:TEMP\* -ErrorAction SilentlyContinue - pip cache purge - Get-PSDrive C - shell: pwsh - - - name: Install tomli for version extraction (Linux/macOS) - if: runner.os != 'Windows' - run: python -m pip install tomli - shell: bash - - - name: Install tomli for version extraction (Windows) - if: runner.os == 'Windows' - run: python -m pip install tomli - shell: pwsh - - - name: Extract version from pyproject.toml (Linux/macOS) - if: runner.os != 'Windows' - id: get_version_unix - run: | - VERSION=$(python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])") - echo "version=$VERSION" >> $GITHUB_OUTPUT - shell: bash - - - name: Extract version from pyproject.toml (Windows) - if: runner.os == 'Windows' - id: get_version_win - run: | - $VERSION = python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])" - echo "version=$VERSION" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - shell: pwsh - - - name: Create virtual environment (Linux/macOS) - if: runner.os != 'Windows' - run: python -m venv .venv - shell: bash - - - name: Create virtual environment (Windows) - if: runner.os == 'Windows' - run: python -m venv .venv - shell: pwsh - - - name: Install dependencies and build (Linux/macOS) - if: runner.os != 'Windows' - run: | - source .venv/bin/activate - chmod +x ${{ matrix.build_script }} - ${{ matrix.build_script }} ${{ matrix.torch_variant }} ${{ matrix.macos_app_flag }} - shell: bash - - - name: Install dependencies and build (Windows) - if: runner.os == 'Windows' - run: | - .\.venv\Scripts\Activate.ps1 - & ${{ matrix.build_script }} ${{ matrix.torch_variant }} - shell: pwsh - - - name: Rename executable or directory (Linux/macOS) - if: runner.os != 'Windows' - run: | - cd dist - ls -la - BASE="photomap-${{ matrix.torch_variant }}" - EXT="${{ matrix.executable_ext }}" - VERSION="${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}" - ARCHIVE_NAME="photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v$VERSION" - if [ -f "photomap$EXT" ]; then - mv "photomap$EXT" "$ARCHIVE_NAME$EXT" - elif [ -d "photomap.app" ]; then - mv "photomap.app" "$ARCHIVE_NAME.app" - elif [ -d "photomap" ]; then - mv "photomap" "$ARCHIVE_NAME" - else - echo "Neither photomap$EXT nor photomap directory found!" - exit 1 - fi - ls -la - shell: bash - - - name: Rename executable or directory (Windows) - if: runner.os == 'Windows' - run: | - cd dist - $base = "photomap-${{ matrix.torch_variant }}" - $ext = "${{ matrix.executable_ext }}" - $version = "${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}" - $archiveName = "photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v$version" - if (Test-Path "photomap$ext") { - Rename-Item -Path "photomap$ext" -NewName "$archiveName$ext" - } elseif (Test-Path "photomap") { - Rename-Item -Path "photomap" -NewName "$archiveName" - } else { - Write-Error "Neither photomap$ext nor photomap directory found!" - exit 1 - } - Get-ChildItem * - shell: pwsh - - - name: Create archive (Linux/macOS) - if: runner.os != 'Windows' - run: | - VERSION="${{ steps.get_version_unix.outputs.version }}" - ARCHIVE_NAME="photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v$VERSION" - cd dist - if [ -d "${ARCHIVE_NAME}.app" ]; then - zip -r "${ARCHIVE_NAME}.zip" "${ARCHIVE_NAME}.app" - else - zip -r "${ARCHIVE_NAME}.zip" "${ARCHIVE_NAME}" - fi - shell: bash - - - name: Create archive (Windows) - if: runner.os == 'Windows' - # Use 7z instead of Compress-Archive: the latter throws - # "Stream was too long" on archives above ~2 GB, which the cu129 build hits. - run: | - $version = "${{ steps.get_version_win.outputs.version }}" - $archiveName = "photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v$version" - 7z a -tzip "dist\$archiveName.zip" "dist\$archiveName" - shell: pwsh - - - name: Debug - List dist contents - run: ls -la dist/ - shell: bash - if: runner.os != 'Windows' - - - name: Debug - List dist contents (Windows) - run: Get-ChildItem dist/ - shell: pwsh - if: runner.os == 'Windows' - - - name: Upload artifact - uses: actions/upload-artifact@v5 - with: - name: photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }} - path: dist/photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}${{ matrix.archive_ext }} - retention-days: 30 diff --git a/.gitignore b/.gitignore index 85385450..83c16f4f 100644 --- a/.gitignore +++ b/.gitignore @@ -110,9 +110,6 @@ demo_images/*.jpg demo_images/*.png demo_images/photomap_index -# pyinstaller spec files -*.spec - # Node.js node_modules/ diff --git a/INSTALL/pyinstaller/make_pyinstaller_image.ps1 b/INSTALL/pyinstaller/make_pyinstaller_image.ps1 deleted file mode 100644 index afa387dc..00000000 --- a/INSTALL/pyinstaller/make_pyinstaller_image.ps1 +++ /dev/null @@ -1,107 +0,0 @@ -<# -.SYNOPSIS - Build a PyInstaller executable for PhotoMap on Windows. -.DESCRIPTION - Usage: .\make_pyinstaller_image.ps1 [cpu|cu121|cu118|cu124|cu129|...] - - cpu : Install CPU-only PyTorch (default) - - cuXXX : Install CUDA-enabled PyTorch (e.g., cu121 for CUDA 12.1) -#> - -param( - [string]$TorchVariant = "cpu" -) - -function Show-Usage { - Write-Host "Usage: .\make_pyinstaller_image.ps1 [cpu|cu121|cu118|cu124|cu129|...]" - Write-Host " cpu - Install CPU-only PyTorch (default)" - Write-Host " cuXXX - Install CUDA-enabled PyTorch (e.g., cu121 for CUDA 12.1)" - exit 1 -} - -Write-Host "Requested PyTorch variant: $TorchVariant" - -switch ($TorchVariant) { - "cpu" { - pip install torch torchvision -U --index-url https://download.pytorch.org/whl/cpu - } - { $_ -match "^cu\d+$" } { - pip install torch torchvision -U --index-url "https://download.pytorch.org/whl/$TorchVariant" - } - Default { Show-Usage } -} - -# Set PyInstaller mode based on torch variant -# if ($TorchVariant -eq "cpu") { -# $pyinstallerMode = "--onefile" -# } else { -# $pyinstallerMode = "--onedir" -# } - -# --onefile has too many problems with large packages like torch! -$pyinstallerMode = "--onedir" - -# Upgrade build tools and hooks -python -m pip install -U pip wheel setuptools -python -m pip install -U pyinstaller pyinstaller-hooks-contrib - -# Install runtime dependencies -python -m pip install -U numpy pillow scikit-learn - -# Install CLIP and your package -pip install clip-anytorch -pip install . - -Write-Host "Installing CLIP model..." -python -c "import clip; clip.load('ViT-B/32')" - -# to permit testing on non-windows platforms -if ($IsWindows) { - $sep = ";" -} else { - $sep = ":" -} - -# After installing PyTorch -pip cache purge - -# Before running PyInstaller -Write-Host "Disk space before PyInstaller:" -Get-PSDrive C - -# Run PyInstaller -pyinstaller ` - --hidden-import clip ` - --hidden-import numpy ` - --hidden-import torch ` - --hidden-import torchvision ` - --hidden-import photomap ` - --hidden-import photomap.backend ` - --hidden-import photomap.backend.photomap_server ` - --hidden-import photomap.backend.main_wrapper ` - --hidden-import photomap.backend.routers ` - --hidden-import photomap.backend.routers.album ` - --hidden-import photomap.backend.routers.search ` - --hidden-import photomap.backend.embeddings ` - --hidden-import photomap.backend.config ` - --hidden-import uvicorn ` - --hidden-import fastapi ` - --collect-all torch ` - --collect-all torchvision ` - --collect-all clip ` - --collect-all numpy ` - --collect-all sklearn ` - --collect-all PIL ` - --collect-all photomap ` - --add-data "$(python -c "import clip; print(clip.__path__[0])"):clip" ` - --add-data "$env:USERPROFILE/.cache/clip${sep}clip_models" ` - --add-data "photomap/frontend/static${sep}photomap/frontend/static" ` - --add-data "photomap/frontend/templates${sep}photomap/frontend/templates" ` - --add-data "THIRD_PARTY_LICENSES.txt${sep}.THIRD_PARTY_LICENSES" ` - --paths . ` - $pyinstallerMode ` - --name photomap ` - -y ` - photomap/backend/photomap_server.py - -# After PyInstaller -Remove-Item -Recurse -Force build/ -ErrorAction SilentlyContinue \ No newline at end of file diff --git a/INSTALL/pyinstaller/make_pyinstaller_image.sh b/INSTALL/pyinstaller/make_pyinstaller_image.sh deleted file mode 100755 index c8e3a60a..00000000 --- a/INSTALL/pyinstaller/make_pyinstaller_image.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/bash -# filepath: /home/lstein/Projects/PhotoMap/INSTALL/pyinstaller/make_pyinstaller_image.sh - -set -e - -# Usage info -usage() { - echo "Usage: $0 [cpu|cu121|cu118|cu124|cu129|...] [--macos-app]" - echo " cpu - Install CPU-only PyTorch (default)" - echo " cuXXX - Install CUDA-enabled PyTorch (e.g., cu121 for CUDA 12.1)" - echo " --macos-app - Create macOS .app bundle (macOS only)" - exit 1 -} - -# Parse arguments -TORCH_VARIANT="${1:-cpu}" -MACOS_APP=false - -# Check for --macos-app flag -for arg in "$@"; do - case $arg in - --macos-app) - MACOS_APP=true - shift - ;; - esac -done - -# Validate macOS app option -if [[ "$MACOS_APP" == true && "$(uname)" != "Darwin" ]]; then - echo "Error: --macos-app option can only be used on macOS" - exit 1 -fi - -# Set PyInstaller mode based on torch variant and platform -if [[ "$MACOS_APP" == true ]]; then - PYINSTALLER_MODE="--windowed" -# always use --onedir for CPU builds to avoid startup issues -# elif [[ "$TORCH_VARIANT" == cpu ]]; then -# PYINSTALLER_MODE="--onefile" -else - PYINSTALLER_MODE="--onedir" -fi - -# Install appropriate PyTorch -echo "Requested PyTorch variant: $TORCH_VARIANT" -case "$TORCH_VARIANT" in - cpu) - pip install torch torchvision -U --index-url https://download.pytorch.org/whl/cpu - ;; - cu129|cu128|cu126|cu125|cu124|cu121|cu118) - pip install torch torchvision -U --index-url https://download.pytorch.org/whl/$TORCH_VARIANT - ;; - *) - echo "Unknown or unsupported variant: $TORCH_VARIANT" - usage - ;; -esac - -# After installing PyTorch -pip cache purge -python -c "import torch; print(f'PyTorch cache cleared')" - -# Make sure build tools and hooks are up to date -python -m pip install -U pip wheel setuptools -python -m pip install -U pyinstaller pyinstaller-hooks-contrib - -# Ensure runtime deps are installed in this venv before bundling -python -m pip install -U numpy pillow scikit-learn - -# Load CLIP model in its cache (and your package) -pip install clip-anytorch -pip install . -echo "Installing CLIP model..." -python -c "import clip; clip.load('ViT-B/32')" - -# Prepare PyInstaller arguments -PYINSTALLER_ARGS=( - --hidden-import clip - --hidden-import numpy - --hidden-import torch - --hidden-import torchvision - --hidden-import photomap - --hidden-import photomap.backend - --hidden-import photomap.backend.photomap_server - --hidden-import photomap.backend.main_wrapper - --hidden-import photomap.backend.routers - --hidden-import photomap.backend.routers.album - --hidden-import photomap.backend.routers.search - --hidden-import photomap.backend.embeddings - --hidden-import photomap.backend.config - --hidden-import uvicorn - --hidden-import fastapi - --collect-all torch - --collect-all torchvision - --collect-all clip - --collect-all numpy - --collect-all sklearn - --collect-all PIL - --collect-all photomap - --add-data "$(python -c "import clip; print(clip.__path__[0])"):clip" - --add-data "$HOME/.cache/clip:clip_models" - --add-data "photomap/frontend/static:photomap/frontend/static" - --add-data "photomap/frontend/templates:photomap/frontend/templates" - --add-data "THIRD_PARTY_LICENSES.txt:THIRD_PARTY_LICENSES" - --paths . - $PYINSTALLER_MODE - --argv-emulation - --name photomap - -y -) - -# Add macOS-specific options if building app bundle -if [[ "$MACOS_APP" == true ]]; then - PYINSTALLER_ARGS+=( - --osx-bundle-identifier org.4crabs.photomap - --icon photomap/frontend/static/icons/icon.icns - ) - echo "Building macOS .app bundle..." -else - echo "Building standard executable..." -fi - -# Run PyInstaller -pyinstaller "${PYINSTALLER_ARGS[@]}" photomap/backend/photomap_server.py - -# Before running PyInstaller -echo "Disk space before PyInstaller:" -df -h - -# After PyInstaller -rm -rf build/ # Remove PyInstaller temp files - -# Add a Linux launcher script to run in terminal -if [[ "$(uname)" == "Linux" && "$MACOS_APP" == false ]]; then - LAUNCHER="dist/run_photomap" - cat > "$LAUNCHER" < /dev/null; then - TERMINAL_CMD="gnome-terminal --" -elif command -v konsole &> /dev/null; then - TERMINAL_CMD="konsole -e" -elif command -v xterm &> /dev/null; then - TERMINAL_CMD="xterm -e" -fi -exec $TERMINAL_CMD \$(dirname "\$0")/photomap/photomap -EOF - chmod +x "$LAUNCHER" - echo "✅ Linux launcher script created: dist/photomap" -fi - -# Post-process macOS .app bundle to launch in Terminal -if [[ "$MACOS_APP" == true ]]; then - APP_BUNDLE="dist/photomap.app" - MACOS_DIR="$APP_BUNDLE/Contents/MacOS" - BIN_NAME="photomap" - - # Create a launcher script - LAUNCHER="$MACOS_DIR/run_in_terminal.sh" - cat > "$LAUNCHER" < str | None: - """ - Determine the root directory for CLIP model caching. - This is important for PyInstaller compatibility. - """ - if getattr(sys, "frozen", False): - # If running in a PyInstaller bundle, use the bundled cache directory - bundle_dir = sys._MEIPASS - return os.path.join(bundle_dir, "clip_models") - else: - # Otherwise, use the default cache directory - return None + """Root directory for CLIP model caching (None = use the default cache).""" + return None def _load_image( self, image_path: Path diff --git a/pyproject.toml b/pyproject.toml index 425958e8..ebb90b3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,6 @@ development = [ "mkdocs-material", "pymdown-extensions", "twine", - "pyinstaller", "ruff", ] [tool.setuptools.packages.find]