-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathbuild_local.sh
More file actions
executable file
·280 lines (249 loc) · 11.1 KB
/
build_local.sh
File metadata and controls
executable file
·280 lines (249 loc) · 11.1 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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/bin/bash
# ---------------------------------------------------------------------------
# AceForge - Local Build Script
# Builds the PyInstaller app bundle for local testing.
# Includes the new React UI (ui/) when present; requires Bun (https://bun.sh).
#
# Optional env vars (safe, non-destructive caching for faster rebuilds):
# ACEFORGE_QUICK_BUILD=1 - Reuse PyInstaller cache (omit --clean, keep build/AceForge).
# Use when only code changed; full clean build if things break.
# ACEFORGE_SKIP_UI_BUILD=1 - Skip UI build; use existing ui/dist/. Use when only Python changed.
# ACEFORGE_SKIP_PIP=1 - Skip venv/pip steps. Use when deps unchanged and venv already ready.
# ---------------------------------------------------------------------------
set -e # Exit on error
echo "=========================================="
echo "AceForge - Local Build"
echo "=========================================="
echo ""
# App root = folder this script lives in
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$APP_DIR"
# ---------------------------------------------------------------------------
# Build new UI (React/Vite) with Bun when ui/ exists; skip if ACEFORGE_SKIP_UI_BUILD=1.
# ---------------------------------------------------------------------------
UI_DIR="${APP_DIR}/ui"
if [ -f "$UI_DIR/package.json" ]; then
if [ -n "${ACEFORGE_SKIP_UI_BUILD}" ]; then
if [ ! -f "$UI_DIR/dist/index.html" ]; then
echo "ERROR: ACEFORGE_SKIP_UI_BUILD is set but ui/dist/index.html not found. Run without it once."
exit 1
fi
echo "[Build] Skipping UI build (ACEFORGE_SKIP_UI_BUILD)"
else
if ! command -v bun &> /dev/null; then
echo "ERROR: Bun is required to build the new UI. Install from https://bun.sh"
exit 1
fi
echo "[Build] Building new UI (React SPA) with Bun..."
"${APP_DIR}/scripts/build_ui.sh"
echo "[Build] New UI build OK"
fi
else
echo "ERROR: ui/package.json not found. The new UI source is required for the full app build."
exit 1
fi
echo ""
# Check Python version
PYTHON_CMD=""
if command -v python3.11 &> /dev/null; then
PYTHON_CMD="python3.11"
elif command -v python3 &> /dev/null; then
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}' | cut -d. -f1,2)
if [[ "$PYTHON_VERSION" == "3.11" ]]; then
PYTHON_CMD="python3"
else
echo "WARNING: python3 is version $PYTHON_VERSION, but 3.11 is recommended"
read -p "Continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
PYTHON_CMD="python3"
fi
else
echo "ERROR: Python 3.11 not found. Please install Python 3.11."
exit 1
fi
echo "[Build] Using Python: $($PYTHON_CMD --version)"
echo ""
# Virtual environment
VENV_DIR="${APP_DIR}/venv_build"
VENV_PY="${VENV_DIR}/bin/python"
# Create/activate virtual environment
if [ ! -f "$VENV_PY" ]; then
if [ -n "${ACEFORGE_SKIP_PIP}" ]; then
echo "ERROR: ACEFORGE_SKIP_PIP is set but venv_build not found. Run without it once."
exit 1
fi
echo "[Build] Creating virtual environment..."
$PYTHON_CMD -m venv "$VENV_DIR"
fi
echo "[Build] Activating virtual environment..."
source "${VENV_DIR}/bin/activate"
# Use venv Python for all installs and PyInstaller (ensures TTS and deps are in the bundle)
PY="${VENV_PY}"
if [ -z "${ACEFORGE_SKIP_PIP}" ]; then
# Upgrade pip
echo "[Build] Upgrading pip..."
"$PY" -m pip install --upgrade pip --quiet
# Install dependencies
echo "[Build] Installing dependencies..."
"$PY" -m pip install -r requirements_ace_macos.txt --quiet
# Install additional dependencies
echo "[Build] Installing additional dependencies..."
"$PY" -m pip install "audio-separator==0.40.0" --no-deps --quiet
"$PY" -m pip install "py3langid==0.3.0" --no-deps --quiet
"$PY" -m pip install "git+https://github.com/ace-step/ACE-Step.git" --no-deps --quiet
"$PY" -m pip install "rotary_embedding_torch" --quiet
# ---------------------------------------------------------------------------
# Slimming: remove Japanese (Sudachi) dictionary payload
# SudachiDict-core is ~200MB and not needed for AceForge.
# We explicitly uninstall it (and SudachiPy) so PyInstaller cannot bundle it.
# ---------------------------------------------------------------------------
echo "[Build] Removing Japanese Sudachi packages (if present)..."
"$PY" -m pip uninstall -y SudachiDict-core SudachiPy sudachidict-core sudachipy >/dev/null 2>&1 || true
# Install TTS for voice cloning (required for frozen app; build fails if TTS cannot be imported)
# TTS 0.21.2 needs its full dependency tree (phonemizers etc.); --no-deps breaks "from TTS.api import TTS"
echo "[Build] Installing TTS for voice cloning..."
"$PY" -m pip install "coqpit" "trainer>=0.0.32" "pysbd>=0.3.4" "inflect>=5.6.0" "unidecode>=1.3.2" --quiet
"$PY" -m pip install "TTS==0.21.2" --quiet
if ! "$PY" -c "from TTS.api import TTS" 2>/dev/null; then
echo "[Build] ERROR: TTS installed but 'from TTS.api import TTS' failed. Voice cloning will not work in the app."
echo "[Build] Run: $PY -c \"from TTS.api import TTS\" to see the error."
"$PY" -c "from TTS.api import TTS" || true
exit 1
fi
echo "[Build] TTS verified: from TTS.api import TTS OK"
# Install Demucs for stem splitting (optional component)
echo "[Build] Installing Demucs for stem splitting..."
"$PY" -m pip install "demucs==4.0.1" --quiet
if ! "$PY" -c "import demucs.separate" 2>/dev/null; then
echo "[Build] WARNING: Demucs installed but 'import demucs.separate' failed. Stem splitting will not work in the app."
echo "[Build] Run: $PY -c \"import demucs.separate\" to see the error."
"$PY" -c "import demucs.separate" || true
# Don't exit - stem splitting is optional
else
echo "[Build] Demucs verified: import demucs.separate OK"
fi
# Install basic-pitch for MIDI generation (optional component)
echo "[Build] Installing basic-pitch for MIDI generation..."
"$PY" -m pip install "basic-pitch>=0.4.0" --quiet
if ! "$PY" -c "from basic_pitch.inference import predict" 2>/dev/null; then
echo "[Build] WARNING: basic-pitch installed but 'from basic_pitch.inference import predict' failed. MIDI generation will not work in the app."
echo "[Build] Run: $PY -c \"from basic_pitch.inference import predict\" to see the error."
"$PY" -c "from basic_pitch.inference import predict" || true
# Don't exit - MIDI generation is optional
else
echo "[Build] basic-pitch verified: from basic_pitch.inference import predict OK"
fi
"$PY" -m pip install "pyinstaller>=6.0" --quiet
# One last pass right before bundling, in case anything reintroduced Sudachi.
echo "[Build] Final check: removing Japanese Sudachi packages (if present)..."
"$PY" -m pip uninstall -y SudachiDict-core SudachiPy sudachidict-core sudachipy >/dev/null 2>&1 || true
else
echo "[Build] Skipping pip steps (ACEFORGE_SKIP_PIP)"
fi
# Check for PyInstaller (always run)
if ! "$PY" -m PyInstaller --version &> /dev/null; then
echo "ERROR: PyInstaller not found. Please install it:"
echo " $PY -m pip install pyinstaller"
exit 1
fi
echo "[Build] PyInstaller version: $("$PY" -m PyInstaller --version)"
echo ""
# Clean previous builds (PyInstaller outputs only).
# NEVER delete build/macos/ — it contains AceForge.icns (app icon), codesign.sh, pyinstaller hooks.
# NEVER delete ui/dist/ — may have been produced by the new UI build above.
# ACEFORGE_QUICK_BUILD=1: keep build/AceForge so PyInstaller can reuse cache.
if [ -n "${ACEFORGE_QUICK_BUILD}" ]; then
echo "[Build] Quick build: reusing PyInstaller cache (keeping build/AceForge)"
rm -rf dist/AceForge.app dist/CDMF
else
echo "[Build] Cleaning previous PyInstaller builds..."
rm -rf dist/AceForge.app dist/CDMF build/AceForge
fi
# Safeguard: build/macos must exist for the app icon and code signing
if [ ! -f "build/macos/AceForge.icns" ]; then
echo "ERROR: build/macos/AceForge.icns not found. build/macos/ must never be deleted."
echo " Restore from main: git checkout main -- build/macos/"
exit 1
fi
# Build with PyInstaller (omit --clean when ACEFORGE_QUICK_BUILD=1 to reuse cache)
echo "[Build] Building app bundle with PyInstaller..."
echo "This may take several minutes..."
if [ -n "${ACEFORGE_QUICK_BUILD}" ]; then
"$PY" -m PyInstaller CDMF.spec --noconfirm
else
"$PY" -m PyInstaller CDMF.spec --clean --noconfirm
fi
# Check if build succeeded
BUNDLED_APP="${APP_DIR}/dist/AceForge.app"
BUNDLED_BIN="${BUNDLED_APP}/Contents/MacOS/AceForge_bin"
if [ ! -f "$BUNDLED_BIN" ]; then
echo ""
echo "ERROR: Build failed - binary not found at: $BUNDLED_BIN"
exit 1
fi
# For serverless pywebview app, we don't need launcher scripts
# The binary (AceForge_bin) should be the main executable
# Rename it to AceForge for cleaner app bundle structure
echo ""
echo "[Build] Setting up app bundle executable..."
if [ -f "${BUNDLED_BIN}" ]; then
# Create a symlink or copy so the app can be launched as "AceForge"
# The Info.plist CFBundleExecutable should point to "AceForge"
if [ ! -f "${BUNDLED_APP}/Contents/MacOS/AceForge" ]; then
cp "${BUNDLED_BIN}" "${BUNDLED_APP}/Contents/MacOS/AceForge"
chmod +x "${BUNDLED_APP}/Contents/MacOS/AceForge"
fi
fi
# Code sign the app bundle (critical for macOS - must be LAST step)
echo ""
echo "[Build] Code signing app bundle..."
if [ -f "${APP_DIR}/build/macos/codesign.sh" ]; then
chmod +x "${APP_DIR}/build/macos/codesign.sh"
MACOS_SIGNING_IDENTITY="-" "${APP_DIR}/build/macos/codesign.sh" "$BUNDLED_APP"
if [ $? -eq 0 ]; then
echo "[Build] ✓ Code signing completed"
# Remove quarantine attributes (allows app to run without Gatekeeper blocking)
echo "[Build] Removing quarantine attributes..."
xattr -cr "$BUNDLED_APP" 2>/dev/null || true
# Verify the signature
echo "[Build] Verifying code signature..."
if codesign --verify --deep --strict --verbose=2 "$BUNDLED_APP" &> /dev/null; then
echo "[Build] ✓ Code signature verified"
else
echo "[Build] ⚠ Code signature verification had warnings"
fi
else
echo "[Build] ⚠ Code signing had warnings, but continuing..."
fi
else
echo "[Build] ⚠ WARNING: codesign.sh not found, skipping code signing"
echo "[Build] App may show security warnings when launched"
fi
echo ""
echo "=========================================="
echo "✓ Build successful!"
echo "=========================================="
echo ""
echo "App bundle: $BUNDLED_APP"
echo "Binary: $BUNDLED_BIN"
echo ""
echo "⚠ IMPORTANT: macOS Gatekeeper may block adhoc-signed apps"
echo " If you see 'app is damaged' warning:"
echo " 1. Right-click the app → Open (bypasses Gatekeeper)"
echo " 2. Or run: xattr -cr \"$BUNDLED_APP\""
echo ""
echo "To test the app:"
echo " 1. Check for ACE-Step models:"
echo " python ace_model_setup.py"
echo ""
echo " 2. Run the app (right-click → Open if blocked):"
echo " open \"$BUNDLED_APP\""
echo ""
echo " 3. Or run directly:"
echo " \"$BUNDLED_BIN\""
echo ""
echo " ✓ New React UI is bundled; app will serve it at / when launched."
echo ""