build_mac_universal.sh
6.58 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/bin/bash
# macOS Build Script - Universal (Intel + Apple Silicon)
# 自动检测架构,自动安装依赖
set -e # 遇错即停
echo "================================"
echo "Building Gemini Image Generator"
echo "================================"
# 检测架构
ARCH=$(uname -m)
echo "Detected architecture: $ARCH"
# 设置 Homebrew 路径
if [ "$ARCH" = "arm64" ]; then
BREW_PREFIX="/opt/homebrew"
else
BREW_PREFIX="/usr/local"
fi
# 检查 Homebrew 是否安装
if ! command -v brew &> /dev/null; then
echo "Homebrew not found. Installing..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 添加到当前 shell 的 PATH
eval "$($BREW_PREFIX/bin/brew shellenv)"
fi
# 确保 brew 在 PATH 中
if ! command -v brew &> /dev/null; then
eval "$($BREW_PREFIX/bin/brew shellenv)"
fi
# 检查 Python 3.11 是否安装
PYTHON_CMD="$BREW_PREFIX/bin/python3.11"
if [ ! -f "$PYTHON_CMD" ]; then
echo "Python 3.11 not found. Installing via Homebrew..."
brew install python@3.11
fi
# 验证 Python 可用
if [ ! -f "$PYTHON_CMD" ]; then
echo "Error: Failed to install Python 3.11"
exit 1
fi
echo "Using Python: $PYTHON_CMD"
$PYTHON_CMD --version
# 检查虚拟环境是否有效(macOS 用 bin/activate,Windows 用 Scripts/activate)
if [ ! -f ".venv/bin/activate" ]; then
echo "Valid virtual environment not found, creating..."
rm -rf .venv
"$PYTHON_CMD" -m venv .venv
fi
# 激活虚拟环境
echo "Activating virtual environment..."
source .venv/bin/activate
# 安装依赖
echo "Installing dependencies..."
pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller
# 清理旧构建 (保留 spec 文件,它是构建配置的唯一真相源)
echo "Cleaning previous builds..."
rm -rf build dist
# 构建 (所有配置都在 ZB100ImageGenerator.spec 里)
echo "Building executable..."
pyinstaller ZB100ImageGenerator.spec
# 验证 .app 构建结果
if [ ! -d "dist/ZB100ImageGenerator.app" ]; then
echo "================================"
echo "Build failed! (.app not produced)"
echo "================================"
exit 1
fi
# 打包 DMG 作为分发格式 (带 drag-to-install 布局)
# 为什么必须: .app 里有嵌套 symlink (PIL/.dylibs -> __dot__dylibs),
# cp/NAS/SMB/微信 等传输方式可能吞掉内层 symlink 导致目标机启动失败.
# .dmg 是 HFS+ 镜像, symlink 原样保存, 用户挂载拖拽即用.
echo "Creating DMG with drag-to-install layout..."
DMG_PATH="dist/ZB100ImageGenerator.dmg"
TMP_DMG="dist/.tmp_rw.dmg"
STAGE="dist/.dmg_stage"
VOL_NAME="ZB100ImageGenerator"
# 1. 准备 staging: .app + Applications 快捷方式 + 背景图
rm -rf "$STAGE"
mkdir -p "$STAGE/.background"
cp -R "dist/ZB100ImageGenerator.app" "$STAGE/"
ln -s /Applications "$STAGE/Applications"
# 用 PIL 生成带箭头+中文提示的背景图 (600x400, 跟 DMG 窗口尺寸匹配)
DMG_BG_PATH="$STAGE/.background/bg.png" python3 <<'PYEOF'
import os
from PIL import Image, ImageDraw, ImageFont
W, H = 600, 400
img = Image.new('RGB', (W, H), color=(248, 248, 250))
d = ImageDraw.Draw(img)
# 中间箭头 (从 .app 指向 Applications)
arrow_y = 200
arrow_color = (120, 120, 125)
d.line([(260, arrow_y), (425, arrow_y)], fill=arrow_color, width=5)
d.polygon(
[(425, arrow_y - 14), (455, arrow_y), (425, arrow_y + 14)],
fill=arrow_color,
)
# 底部提示文字
def load_font(size):
for path in (
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/Fonts/STHeiti Medium.ttc",
"/System/Library/Fonts/Helvetica.ttc",
):
if os.path.exists(path):
try:
return ImageFont.truetype(path, size)
except Exception:
continue
return ImageFont.load_default()
font = load_font(22)
text = "将左侧应用拖到右侧 Applications 即完成安装"
bbox = d.textbbox((0, 0), text, font=font)
tw = bbox[2] - bbox[0]
d.text(((W - tw) // 2, 320), text, fill=(90, 90, 95), font=font)
img.save(os.environ['DMG_BG_PATH'])
print(f"[dmg] 背景图已生成: {os.environ['DMG_BG_PATH']}")
PYEOF
# 2. 先卸载可能残留的同名卷, 防止上次失败留下的挂载
hdiutil detach "/Volumes/$VOL_NAME" -force >/dev/null 2>&1 || true
# 3. 创建可写 DMG (UDRW), 便于随后调整窗口布局
rm -f "$TMP_DMG" "$DMG_PATH"
hdiutil create \
-volname "$VOL_NAME" \
-srcfolder "$STAGE" \
-ov -format UDRW \
"$TMP_DMG"
# 4. 挂载并用 AppleScript 布置窗口 (.app 在左, Applications 在右)
MOUNT_POINT="/Volumes/$VOL_NAME"
hdiutil attach "$TMP_DMG" -readwrite -noverify -noautoopen >/dev/null
# osascript 失败不致命 (可能因 Finder 自动化权限), DMG 仍然可用, 只是没布局
osascript <<OSASCRIPT_EOF || echo "Warning: Finder 布局失败 (可能需要授予 Finder 自动化权限), DMG 仍可用"
tell application "Finder"
tell disk "$VOL_NAME"
open
set current view of container window to icon view
set toolbar visible of container window to false
set statusbar visible of container window to false
-- 窗口尺寸匹配背景图 600x400, 坐标系从屏幕左上角算
set the bounds of container window to {200, 120, 800, 520}
set theViewOptions to the icon view options of container window
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 128
-- 设置背景图 (HFS path, 冒号分隔)
set background picture of theViewOptions to file ".background:bg.png"
-- 图标位置要跟背景图里的箭头对齐
set position of item "ZB100ImageGenerator.app" of container window to {150, 200}
set position of item "Applications" of container window to {450, 200}
update without registering applications
delay 1
close
end tell
end tell
OSASCRIPT_EOF
sync
hdiutil detach "$MOUNT_POINT" -force >/dev/null
# 5. 压缩成最终只读 DMG
hdiutil convert "$TMP_DMG" -format UDZO -imagekey zlib-level=9 -o "$DMG_PATH" >/dev/null
# 6. 清理临时
rm -f "$TMP_DMG"
rm -rf "$STAGE"
if [ ! -f "$DMG_PATH" ]; then
echo "================================"
echo "Build partially failed: .app OK but DMG creation failed"
echo "================================"
exit 1
fi
echo "================================"
echo "Build successful!"
echo "Architecture: $ARCH"
echo " App: dist/ZB100ImageGenerator.app ($(du -sh dist/ZB100ImageGenerator.app | awk '{print $1}'))"
echo " DMG: $DMG_PATH ($(du -sh "$DMG_PATH" | awk '{print $1}'))"
echo "================================"
echo "分发请用 DMG, 不要直接拷贝 .app 目录 (symlink 易丢)."