Skip to content

Commit 20fac1e

Browse files
lsteinLincoln Stein
andauthored
[Bugfix] Pull request for testing pyapplication deployment script (#82)
* bump version number; fix deploy macos .dmg name * only run tests on pull requests * reorganize pyinstaller * make space for CUDA builds * do not use sudo on windows! * switch to using 7zip for windows compression step * install cuda 12.9 torch libraries * add a --macos-app flag to build .apps on macOS * prevent double-zip of zip archives * get rid of zip step in pyinstaller deploy script completely * remove reference to zip file * remove extraneous photomap directory from macOS build * remove --windowed option from macOS app build * add back --windowed to macOS build; post-process to launch terminal * make glob more permissive for identifying artifacts to zip --------- Co-authored-by: Lincoln Stein <lstein@gmail.com>
1 parent 89f21a0 commit 20fac1e

8 files changed

Lines changed: 189 additions & 139 deletions

File tree

.github/workflows/deploy-dmg.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Build macOS .dmg
1+
name: Deploy macOS .dmg
22
on:
33
workflow_call:
44
workflow_dispatch:

.github/workflows/deploy-pyinstaller.yml

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,33 @@ name: Deploy PyInstaller Executables
33
on:
44
workflow_call:
55
workflow_dispatch:
6+
pull_request:
67

78
jobs:
89
build:
910
strategy:
1011
matrix:
1112
os: [ubuntu-latest, macos-latest, windows-latest]
12-
torch_variant: [cpu, cu128]
13+
torch_variant: [cpu, cu129]
1314
include:
1415
- os: ubuntu-latest
1516
platform: linux-x64
1617
build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.sh
1718
executable_ext: ""
19+
macos_app_flag: ""
1820
- os: macos-latest
1921
platform: macos-x64
2022
build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.sh
2123
executable_ext: ""
24+
macos_app_flag: "--macos-app"
2225
- os: windows-latest
2326
platform: windows-x64
2427
build_script: ./INSTALL/pyinstaller/make_pyinstaller_image.ps1
2528
executable_ext: ".exe"
29+
macos_app_flag: ""
2630
exclude:
2731
- os: macos-latest
28-
torch_variant: cu128
32+
torch_variant: cu129
2933
runs-on: ${{ matrix.os }}
3034

3135
steps:
@@ -37,6 +41,25 @@ jobs:
3741
with:
3842
python-version: '3.12'
3943

44+
- name: Free up disk space (Linux/macOS)
45+
if: matrix.torch_variant != 'cpu' && runner.os != 'Windows'
46+
run: |
47+
sudo rm -rf /usr/share/dotnet
48+
sudo rm -rf /opt/ghc
49+
sudo rm -rf /usr/local/share/boost
50+
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
51+
df -h
52+
shell: bash
53+
54+
- name: Free up disk space (Windows)
55+
if: matrix.torch_variant != 'cpu' && runner.os == 'Windows'
56+
run: |
57+
Get-PSDrive C
58+
Remove-Item -Recurse -Force $env:TEMP\* -ErrorAction SilentlyContinue
59+
pip cache purge
60+
Get-PSDrive C
61+
shell: pwsh
62+
4063
- name: Install tomli for version extraction (Linux/macOS)
4164
if: runner.os != 'Windows'
4265
run: python -m pip install tomli
@@ -77,7 +100,8 @@ jobs:
77100
if: runner.os != 'Windows'
78101
run: |
79102
source .venv/bin/activate
80-
${{ matrix.build_script }} ${{ matrix.torch_variant }}
103+
chmod +x ${{ matrix.build_script }}
104+
${{ matrix.build_script }} ${{ matrix.torch_variant }} ${{ matrix.macos_app_flag }}
81105
shell: bash
82106

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

90-
- name: Rename executable
91-
run: |
92-
mv dist/photomap${{ matrix.executable_ext }} dist/photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}
93-
shell: bash
94-
if: runner.os != 'Windows'
95-
96-
- name: Rename executable (Windows)
97-
run: |
98-
Rename-Item -Path dist\photomap${{ matrix.executable_ext }} -NewName photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}
99-
shell: pwsh
100-
if: runner.os == 'Windows'
101-
102-
- name: Create zip archive with version (Linux/macOS)
114+
- name: Rename executable or directory (Linux/macOS)
103115
if: runner.os != 'Windows'
104116
run: |
105117
cd dist
106-
ARCHIVE_NAME="photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}.zip"
107-
zip -j "$ARCHIVE_NAME" "photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}"
108-
ls -la *.zip
118+
ls -la
119+
BASE="photomap-${{ matrix.torch_variant }}"
120+
EXT="${{ matrix.executable_ext }}"
121+
VERSION="${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}"
122+
ARCHIVE_NAME="photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v$VERSION"
123+
if [ -f "photomap$EXT" ]; then
124+
mv "photomap$EXT" "$ARCHIVE_NAME$EXT"
125+
elif [ -d "photomap.app" ]; then
126+
mv "photomap.app" "$ARCHIVE_NAME.app"
127+
elif [ -d "photomap" ]; then
128+
mv "photomap" "$ARCHIVE_NAME"
129+
else
130+
echo "Neither photomap$EXT nor photomap directory found!"
131+
exit 1
132+
fi
133+
ls -la
109134
shell: bash
110135

111-
- name: Create zip archive with version (Windows)
136+
- name: Rename executable or directory (Windows)
112137
if: runner.os == 'Windows'
113138
run: |
114139
cd dist
115-
$archiveName = "photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}.zip"
116-
Compress-Archive -Path "photomap-${{ matrix.torch_variant }}${{ matrix.executable_ext }}" -DestinationPath $archiveName
117-
Get-ChildItem *.zip
140+
$base = "photomap-${{ matrix.torch_variant }}"
141+
$ext = "${{ matrix.executable_ext }}"
142+
$version = "${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}"
143+
$archiveName = "photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v$version"
144+
if (Test-Path "photomap$ext") {
145+
Rename-Item -Path "photomap$ext" -NewName "$archiveName$ext"
146+
} elseif (Test-Path "photomap") {
147+
Rename-Item -Path "photomap" -NewName "$archiveName"
148+
} else {
149+
Write-Error "Neither photomap$ext nor photomap directory found!"
150+
exit 1
151+
}
152+
Get-ChildItem *
153+
shell: pwsh
154+
155+
- name: Debug - List dist contents
156+
run: ls -la dist/
157+
shell: bash
158+
if: runner.os != 'Windows'
159+
160+
- name: Debug - List dist contents (Windows)
161+
run: Get-ChildItem dist/
118162
shell: pwsh
163+
if: runner.os == 'Windows'
119164

120165
- name: Upload artifact
121166
uses: actions/upload-artifact@v4
122167
with:
123168
name: photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}
124-
path: dist/photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}.zip
169+
path: dist/photomap-${{ matrix.platform }}-${{ matrix.torch_variant }}-v${{ steps.get_version_unix.outputs.version || steps.get_version_win.outputs.version }}*
125170
retention-days: 30

.github/workflows/run_tests.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
name: Run Pytest
22

33
on:
4-
push:
5-
branches: [ master, main ]
64
pull_request:
75
branches: [ master, main ]
86

INSTALL/pyinstaller/make_pyinstaller_image.ps1

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ if ($IsWindows) {
5858
$sep = ":"
5959
}
6060

61+
# After installing PyTorch
62+
pip cache purge
63+
64+
# Before running PyInstaller
65+
Write-Host "Disk space before PyInstaller:"
66+
Get-PSDrive C
67+
6168
# Run PyInstaller
6269
pyinstaller `
6370
--hidden-import clip `
@@ -90,4 +97,7 @@ pyinstaller `
9097
$pyinstallerMode `
9198
--name photomap `
9299
-y `
93-
photomap/backend/photomap_server.py
100+
photomap/backend/photomap_server.py
101+
102+
# After PyInstaller
103+
Remove-Item -Recurse -Force build/ -ErrorAction SilentlyContinue

INSTALL/pyinstaller/make_pyinstaller_image.sh

Lines changed: 106 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,37 @@ set -e
55

66
# Usage info
77
usage() {
8-
echo "Usage: $0 [cpu|cu121|cu118|cu124|cu129|...]"
8+
echo "Usage: $0 [cpu|cu121|cu118|cu124|cu129|...] [--macos-app]"
99
echo " cpu - Install CPU-only PyTorch (default)"
1010
echo " cuXXX - Install CUDA-enabled PyTorch (e.g., cu121 for CUDA 12.1)"
11+
echo " --macos-app - Create macOS .app bundle (macOS only)"
1112
exit 1
1213
}
1314

14-
# Parse argument, default to "cpu" if not provided
15+
# Parse arguments
1516
TORCH_VARIANT="${1:-cpu}"
17+
MACOS_APP=false
1618

17-
# Set PyInstaller mode based on torch variant
18-
if [[ "$TORCH_VARIANT" == cpu ]]; then
19+
# Check for --macos-app flag
20+
for arg in "$@"; do
21+
case $arg in
22+
--macos-app)
23+
MACOS_APP=true
24+
shift
25+
;;
26+
esac
27+
done
28+
29+
# Validate macOS app option
30+
if [[ "$MACOS_APP" == true && "$(uname)" != "Darwin" ]]; then
31+
echo "Error: --macos-app option can only be used on macOS"
32+
exit 1
33+
fi
34+
35+
# Set PyInstaller mode based on torch variant and platform
36+
if [[ "$MACOS_APP" == true ]]; then
37+
PYINSTALLER_MODE="--windowed"
38+
elif [[ "$TORCH_VARIANT" == cpu ]]; then
1939
PYINSTALLER_MODE="--onefile"
2040
else
2141
PYINSTALLER_MODE="--onedir"
@@ -36,6 +56,10 @@ case "$TORCH_VARIANT" in
3656
;;
3757
esac
3858

59+
# After installing PyTorch
60+
pip cache purge
61+
python -c "import torch; print(f'PyTorch cache cleared')"
62+
3963
# Make sure build tools and hooks are up to date
4064
python -m pip install -U pip wheel setuptools
4165
python -m pip install -U pyinstaller pyinstaller-hooks-contrib
@@ -49,37 +73,82 @@ pip install .
4973
echo "Installing CLIP model..."
5074
python -c "import clip; clip.load('ViT-B/32')"
5175

76+
# Prepare PyInstaller arguments
77+
PYINSTALLER_ARGS=(
78+
--hidden-import clip
79+
--hidden-import numpy
80+
--hidden-import torch
81+
--hidden-import torchvision
82+
--hidden-import photomap
83+
--hidden-import photomap.backend
84+
--hidden-import photomap.backend.photomap_server
85+
--hidden-import photomap.backend.main_wrapper
86+
--hidden-import photomap.backend.routers
87+
--hidden-import photomap.backend.routers.album
88+
--hidden-import photomap.backend.routers.search
89+
--hidden-import photomap.backend.embeddings
90+
--hidden-import photomap.backend.config
91+
--hidden-import uvicorn
92+
--hidden-import fastapi
93+
--collect-all torch
94+
--collect-all torchvision
95+
--collect-all clip
96+
--collect-all numpy
97+
--collect-all sklearn
98+
--collect-all PIL
99+
--collect-all photomap
100+
--add-data "$(python -c "import clip; print(clip.__path__[0])"):clip"
101+
--add-data "$HOME/.cache/clip:clip_models"
102+
--add-data "photomap/frontend/static:photomap/frontend/static"
103+
--add-data "photomap/frontend/templates:photomap/frontend/templates"
104+
--paths .
105+
$PYINSTALLER_MODE
106+
--argv-emulation
107+
--name photomap
108+
-y
109+
)
110+
111+
# Add macOS-specific options if building app bundle
112+
if [[ "$MACOS_APP" == true ]]; then
113+
PYINSTALLER_ARGS+=(
114+
--osx-bundle-identifier org.4crabs.photomap
115+
--icon photomap/frontend/static/icons/icon.icns
116+
)
117+
echo "Building macOS .app bundle..."
118+
else
119+
echo "Building standard executable..."
120+
fi
121+
52122
# Run PyInstaller
53-
pyinstaller \
54-
--hidden-import clip \
55-
--hidden-import numpy \
56-
--hidden-import torch \
57-
--hidden-import torchvision \
58-
--hidden-import photomap \
59-
--hidden-import photomap.backend \
60-
--hidden-import photomap.backend.photomap_server \
61-
--hidden-import photomap.backend.main_wrapper \
62-
--hidden-import photomap.backend.routers \
63-
--hidden-import photomap.backend.routers.album \
64-
--hidden-import photomap.backend.routers.search \
65-
--hidden-import photomap.backend.embeddings \
66-
--hidden-import photomap.backend.config \
67-
--hidden-import uvicorn \
68-
--hidden-import fastapi \
69-
--collect-all torch \
70-
--collect-all torchvision \
71-
--collect-all clip \
72-
--collect-all numpy \
73-
--collect-all sklearn \
74-
--collect-all PIL \
75-
--collect-all photomap \
76-
--add-data "$(python -c "import clip; print(clip.__path__[0])"):clip" \
77-
--add-data "$HOME/.cache/clip:clip_models" \
78-
--add-data "photomap/frontend/static:photomap/frontend/static" \
79-
--add-data "photomap/frontend/templates:photomap/frontend/templates" \
80-
--paths . \
81-
$PYINSTALLER_MODE \
82-
--argv-emulation \
83-
--name photomap \
84-
-y \
85-
photomap/backend/photomap_server.py
123+
pyinstaller "${PYINSTALLER_ARGS[@]}" photomap/backend/photomap_server.py
124+
125+
# Before running PyInstaller
126+
echo "Disk space before PyInstaller:"
127+
df -h
128+
129+
# After PyInstaller
130+
rm -rf build/ # Remove PyInstaller temp files
131+
132+
# Post-process macOS .app bundle to launch in Terminal
133+
if [[ "$MACOS_APP" == true ]]; then
134+
APP_BUNDLE="dist/photomap.app"
135+
MACOS_DIR="$APP_BUNDLE/Contents/MacOS"
136+
BIN_NAME="photomap"
137+
138+
# Create a launcher script
139+
LAUNCHER="$MACOS_DIR/run_in_terminal.sh"
140+
cat > "$LAUNCHER" <<EOF
141+
#!/bin/bash
142+
exec osascript -e 'tell application "Terminal" to do script "'"$MACOS_DIR/$BIN_NAME"'"'
143+
EOF
144+
chmod +x "$LAUNCHER"
145+
146+
# Update Info.plist to use the launcher script
147+
PLIST="$APP_BUNDLE/Contents/Info.plist"
148+
/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable run_in_terminal.sh" "$PLIST"
149+
150+
echo "✅ macOS app bundle created: dist/photomap.app"
151+
echo "Users can double-click photomap.app to launch PhotoMap in Terminal"
152+
else
153+
echo "✅ Executable created in dist/ directory"
154+
fi

0 commit comments

Comments
 (0)