-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_app.py
More file actions
131 lines (108 loc) · 4.09 KB
/
build_app.py
File metadata and controls
131 lines (108 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/env python3
"""
Build FinMail as a standalone macOS .app bundle + .dmg installer.
Usage:
pip install pyinstaller
python build_app.py
Output:
dist/FinMail.app — the app bundle
dist/FinMail.dmg — drag-and-drop installer
"""
import subprocess
import sys
import os
import shutil
def build():
app_name = "FinMail"
icon_path = os.path.join("assets", "icon.icns")
cmd = [
sys.executable, "-m", "PyInstaller",
"--name", app_name,
"--windowed", # .app bundle, no terminal window
"--onedir", # faster startup than --onefile
"--noconfirm", # overwrite previous build
# Collect customtkinter theme data files (required or app crashes)
"--collect-data", "customtkinter",
# Hidden imports PyInstaller can't auto-detect
"--hidden-import", "customtkinter",
"--hidden-import", "anthropic",
"--hidden-import", "cryptography",
"--hidden-import", "cryptography.fernet",
"--hidden-import", "cffi",
"--hidden-import", "fitz", # PyMuPDF
"--hidden-import", "pymupdf",
"--hidden-import", "docx",
"--hidden-import", "openpyxl",
"--hidden-import", "PIL",
"--hidden-import", "bs4",
"--hidden-import", "certifi",
"--hidden-import", "httpx",
"--hidden-import", "httpcore",
"--hidden-import", "h11",
"--hidden-import", "sniffio",
"--hidden-import", "anyio",
"--hidden-import", "idna",
# Bundle the assets directory
"--add-data", "assets:assets",
# macOS bundle identifier
"--osx-bundle-identifier", "com.finmail.app",
]
# Use app icon if it exists
if os.path.exists(icon_path):
cmd.extend(["--icon", icon_path])
cmd.append("main.py")
print(f"Building {app_name}.app ...")
print(f"Command: {' '.join(cmd)}\n")
subprocess.run(cmd, check=True)
app_path = os.path.join("dist", f"{app_name}.app")
if not os.path.exists(app_path):
print("ERROR: .app bundle not found at", app_path)
sys.exit(1)
print(f"\n.app bundle ready: {app_path}")
# --- Build DMG ---
dmg_path = os.path.join("dist", f"{app_name}.dmg")
_build_dmg(app_name, app_path, dmg_path)
print(f"\n{'=' * 50}")
print(" Build complete!")
print(f" App bundle : {app_path}")
print(f" DMG installer: {dmg_path}")
print(f" Double-click the .dmg and drag FinMail to Applications.")
print("=" * 50)
def _build_dmg(app_name, app_path, dmg_path):
"""Create a DMG disk image with a drag-to-Applications layout."""
# Remove old DMG if it exists
if os.path.exists(dmg_path):
os.remove(dmg_path)
# Create a temporary directory for DMG contents
dmg_staging = os.path.join("dist", "dmg_staging")
if os.path.exists(dmg_staging):
shutil.rmtree(dmg_staging)
os.makedirs(dmg_staging)
# Copy .app into staging
staged_app = os.path.join(dmg_staging, f"{app_name}.app")
shutil.copytree(app_path, staged_app, symlinks=True)
# Create Applications symlink for drag-and-drop
os.symlink("/Applications", os.path.join(dmg_staging, "Applications"))
print(f"\nCreating DMG: {dmg_path} ...")
# Use hdiutil to create DMG (macOS built-in)
try:
subprocess.run([
"hdiutil", "create",
"-volname", app_name,
"-srcfolder", dmg_staging,
"-ov", # overwrite
"-format", "UDZO", # compressed
dmg_path,
], check=True, capture_output=True, text=True)
print(f"DMG created: {dmg_path}")
except subprocess.CalledProcessError as e:
print(f"WARNING: DMG creation failed: {e.stderr}")
print("The .app bundle is still available in dist/")
except FileNotFoundError:
print("WARNING: hdiutil not found (not on macOS?). Skipping DMG creation.")
print("The .app bundle is still available in dist/")
finally:
# Clean up staging
shutil.rmtree(dmg_staging, ignore_errors=True)
if __name__ == "__main__":
build()