Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-dmg.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build macOS .dmg
name: Deploy macOS .dmg
on:
workflow_call:
workflow_dispatch:
Expand Down
93 changes: 69 additions & 24 deletions .github/workflows/deploy-pyinstaller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,33 @@ name: Deploy PyInstaller Executables
on:
workflow_call:
workflow_dispatch:
pull_request:

jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
torch_variant: [cpu, cu128]
torch_variant: [cpu, cu129]
include:
- os: ubuntu-latest
platform: linux-x64
build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.sh
executable_ext: ""
macos_app_flag: ""
- os: macos-latest
platform: macos-x64
build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.sh
executable_ext: ""
macos_app_flag: "--macos-app"
- os: windows-latest
platform: windows-x64
build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.ps1
executable_ext: ".exe"
macos_app_flag: ""
exclude:
- os: macos-latest
torch_variant: cu128
torch_variant: cu129
runs-on: ${{ matrix.os }}

steps:
Expand All @@ -37,6 +41,25 @@ jobs:
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
Expand Down Expand Up @@ -77,7 +100,8 @@ jobs:
if: runner.os != 'Windows'
run: |
source .venv/bin/activate
${{ matrix.build_script }} ${{ matrix.torch_variant }}
chmod +x ${{ matrix.build_script }}
${{ matrix.build_script }} ${{ matrix.torch_variant }} ${{ matrix.macos_app_flag }}
shell: bash

- name: Install dependencies and build (Windows)
Expand All @@ -87,39 +111,60 @@ jobs:
& ${{ matrix.build_script }} ${{ matrix.torch_variant }}
shell: pwsh

- name: Rename executable
run: |
mv dist/photomap${{ matrix.executable_ext }} dist/photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}
shell: bash
if: runner.os != 'Windows'

- name: Rename executable (Windows)
run: |
Rename-Item -Path dist\photomap${{ matrix.executable_ext }} -NewName photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}
shell: pwsh
if: runner.os == 'Windows'

- name: Create zip archive with version (Linux/macOS)
- name: Rename executable or directory (Linux/macOS)
if: runner.os != 'Windows'
run: |
cd dist
ARCHIVE_NAME="photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}.zip"
zip -j "$ARCHIVE_NAME" "photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}"
ls -la *.zip
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: Create zip archive with version (Windows)
- name: Rename executable or directory (Windows)
if: runner.os == 'Windows'
run: |
cd dist
$archiveName = "photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}.zip"
Compress-Archive -Path "photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}" -DestinationPath $archiveName
Get-ChildItem *.zip
$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: 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@v4
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 }}.zip
path: dist/photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}*
retention-days: 30
2 changes: 0 additions & 2 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Run Pytest

on:
push:
branches: [ master, main ]
pull_request:
branches: [ master, main ]

Expand Down
12 changes: 11 additions & 1 deletion INSTALL/pyinstaller/make_pyinstaller_image.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ if ($IsWindows) {
$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 `
Expand Down Expand Up @@ -90,4 +97,7 @@ pyinstaller `
$pyinstallerMode `
--name photomap `
-y `
photomap/backend/photomap_server.py
photomap/backend/photomap_server.py

# After PyInstaller
Remove-Item -Recurse -Force build/ -ErrorAction SilentlyContinue
143 changes: 106 additions & 37 deletions INSTALL/pyinstaller/make_pyinstaller_image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,37 @@ set -e

# Usage info
usage() {
echo "Usage: $0 [cpu|cu121|cu118|cu124|cu129|...]"
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 argument, default to "cpu" if not provided
# Parse arguments
TORCH_VARIANT="${1:-cpu}"
MACOS_APP=false

# Set PyInstaller mode based on torch variant
if [[ "$TORCH_VARIANT" == cpu ]]; then
# 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"
elif [[ "$TORCH_VARIANT" == cpu ]]; then
PYINSTALLER_MODE="--onefile"
else
PYINSTALLER_MODE="--onedir"
Expand All @@ -36,6 +56,10 @@ case "$TORCH_VARIANT" in
;;
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
Expand All @@ -49,37 +73,82 @@ 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"
--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 \
--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" \
--paths . \
$PYINSTALLER_MODE \
--argv-emulation \
--name photomap \
-y \
photomap/backend/photomap_server.py
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

# 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" <<EOF
#!/bin/bash
exec osascript -e 'tell application "Terminal" to do script "'"$MACOS_DIR/$BIN_NAME"'"'
EOF
chmod +x "$LAUNCHER"

# Update Info.plist to use the launcher script
PLIST="$APP_BUNDLE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable run_in_terminal.sh" "$PLIST"

echo "✅ macOS app bundle created: dist/photomap.app"
echo "Users can double-click photomap.app to launch PhotoMap in Terminal"
else
echo "✅ Executable created in dist/ directory"
fi
Loading