FIX (lan 2): bundle app assets qua collect_data_files (import-system) + fallback static dir trong main.py + verify TOC trong build_windows.ps1
This commit is contained in:
+17
@@ -62,6 +62,23 @@ app.mount("/static/audio", StaticFiles(directory=settings.STORAGE_DIR), name="au
|
|||||||
# khi PyInstaller onefile, __file__ trỏ vào thư mục giải nén tạm _MEI...
|
# khi PyInstaller onefile, __file__ trỏ vào thư mục giải nén tạm _MEI...
|
||||||
# nhưng static/templates nằm trong sys._MEIPASS/app (config.py đã xử lý).
|
# nhưng static/templates nằm trong sys._MEIPASS/app (config.py đã xử lý).
|
||||||
STATIC_DIR = os.path.join(settings.APP_DIR, "static")
|
STATIC_DIR = os.path.join(settings.APP_DIR, "static")
|
||||||
|
# ⚠️ Fallback an toàn: nếu vì lý do nào đó static không nằm đúng chỗ (vd
|
||||||
|
# bundle thiếu file, chạy từ nơi khác), thử các vị trí khác; nếu vẫn không
|
||||||
|
# có → TỰ TẠO thư mục rỗng để app KHÔNG crash khi khởi động (lỗi "Directory
|
||||||
|
# does not exist" từ StaticFiles làm engine chết ngay lúc import — đã gặp).
|
||||||
|
if not os.path.isdir(STATIC_DIR):
|
||||||
|
for cand in [
|
||||||
|
os.path.join(getattr(sys, "_MEIPASS", ""), "app", "static"),
|
||||||
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "static"),
|
||||||
|
]:
|
||||||
|
if cand and os.path.isdir(cand):
|
||||||
|
STATIC_DIR = cand
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.makedirs(STATIC_DIR, exist_ok=True)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
||||||
|
|
||||||
# Include routers
|
# Include routers
|
||||||
|
|||||||
@@ -14,6 +14,30 @@ node build.mjs # @babel/standalone; thay cho 'npm run build' (Babel 8 ESM-only
|
|||||||
Write-Host "== [3/6] Build daw_engine.exe (PyInstaller) =="
|
Write-Host "== [3/6] Build daw_engine.exe (PyInstaller) =="
|
||||||
pyinstaller engine.spec --clean --noconfirm
|
pyinstaller engine.spec --clean --noconfirm
|
||||||
|
|
||||||
|
Write-Host "== [3.5/6] Verify bundle contents (app/static, app/templates phải có) =="
|
||||||
|
# Bắt lỗi bundle NGAY tại build (đã gặp 2 lần: chạy pyinstaller từ nơi khác
|
||||||
|
# -> static/templates thiếu -> exe crash 'Directory ...\app\static does not exist').
|
||||||
|
$missing = @()
|
||||||
|
if (-not (Test-Path "build\engine\Analysis-00.toc")) {
|
||||||
|
# PyInstaller 6.x: đọc TOC trực tiếp từ Analysis
|
||||||
|
$tocFiles = Get-ChildItem "build\engine" -Filter "*.toc" -ErrorAction SilentlyContinue
|
||||||
|
if ($tocFiles) {
|
||||||
|
$tocContent = Get-Content $tocFiles[0].FullName -Raw
|
||||||
|
if ($tocContent -notmatch "app/static") { $missing += "app/static" }
|
||||||
|
if ($tocContent -notmatch "app/templates") { $missing += "app/templates" }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$tocContent = Get-Content "build\engine\Analysis-00.toc" -Raw
|
||||||
|
if ($tocContent -notmatch "app/static") { $missing += "app/static" }
|
||||||
|
if ($tocContent -notmatch "app/templates") { $missing += "app/templates" }
|
||||||
|
}
|
||||||
|
if ($missing.Count -gt 0) {
|
||||||
|
Write-Host "ERROR: Bundle thiếu: $($missing -join ', '). Dừng build — kiểm tra engine.spec datas!" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
Write-Host "OK: app/static + app/templates có trong bundle." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
Write-Host "== [4/6] Sidecar binary -> src-tauri/binaries (tauri triple naming) =="
|
Write-Host "== [4/6] Sidecar binary -> src-tauri/binaries (tauri triple naming) =="
|
||||||
New-Item -ItemType Directory -Force src-tauri\binaries | Out-Null
|
New-Item -ItemType Directory -Force src-tauri\binaries | Out-Null
|
||||||
Copy-Item dist\daw_engine.exe src-tauri\binaries\daw_engine-x86_64-pc-windows-msvc.exe -Force
|
Copy-Item dist\daw_engine.exe src-tauri\binaries\daw_engine-x86_64-pc-windows-msvc.exe -Force
|
||||||
|
|||||||
+12
-12
@@ -9,20 +9,20 @@ from PyInstaller.utils.hooks import collect_dynamic_libs, collect_submodules, co
|
|||||||
binaries = collect_dynamic_libs('pedalboard')
|
binaries = collect_dynamic_libs('pedalboard')
|
||||||
binaries += collect_dynamic_libs('soundfile')
|
binaries += collect_dynamic_libs('soundfile')
|
||||||
|
|
||||||
# Root tuyet doi cua thu muc chua spec — datas PHẢI absolute: PyInstaller
|
# Root tuyet doi cua thu muc chua spec — dung cho cac datas KHONG nam trong
|
||||||
# resolve duong dan relative theo CWD luc chay lenh (KHONG theo spec file),
|
# package 'app' (vd thu muc md/). Cac assets cua app (static/templates/models)
|
||||||
# chay tu noi khac -> khong tim thay -> bo qua am tham -> app/static thieu
|
# duoc bundle qua collect_data_files('app') — PyInstaller tu tim qua import
|
||||||
# trong bundle -> RuntimeError 'app\static does not exist' luc chay (da gap).
|
# system, KHONG phu thuoc CWD/SPECPATH khi chay lenh (da gap 2 lan: chay
|
||||||
|
# pyinstaller tu noi khac -> relative path khong resolve -> app/static thieu
|
||||||
|
# trong bundle -> RuntimeError 'app\\static does not exist' luc chay).
|
||||||
_SPEC_ROOT = os.path.abspath(SPECPATH)
|
_SPEC_ROOT = os.path.abspath(SPECPATH)
|
||||||
|
|
||||||
# Assets doc (read-only) + thu muc storage (khoi tao rong; khi frozen,
|
# Assets cua app: bundle QUA IMPORT SYSTEM (collect_data_files) — an toan nhat.
|
||||||
# config.py chuyen storage sang %APPDATA%\SonicForgeDAW\storage)
|
# Loai tru storage (57MB soundfonts/uploads — vo ich trong onefile, config.py
|
||||||
datas = [
|
# da chuyen storage sang %APPDATA%\\SonicForgeDAW khi frozen) va __pycache__.
|
||||||
(os.path.join(_SPEC_ROOT, 'app', 'templates'), 'app/templates'), # index.html, favicon.svg
|
datas = collect_data_files('app', excludes=['**/storage/**', '**/__pycache__/**', '**/*.pyc'])
|
||||||
(os.path.join(_SPEC_ROOT, 'app', 'static'), 'app/static'), # js/css/processors
|
datas += [
|
||||||
(os.path.join(_SPEC_ROOT, 'app', 'models'), 'app/models'), # project_schema.json (projects.py)
|
(os.path.join(_SPEC_ROOT, 'md'), 'md'), # /ai-prompt-generator (doc, ngoai package app)
|
||||||
(os.path.join(_SPEC_ROOT, 'app', 'storage'), 'app/storage'),
|
|
||||||
(os.path.join(_SPEC_ROOT, 'md'), 'md'), # /ai-prompt-generator
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# librosa 0.11 dùng lazy_loader.attach_stub -> lúc RUNTIME cần file .pyi
|
# librosa 0.11 dùng lazy_loader.attach_stub -> lúc RUNTIME cần file .pyi
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
### [2026-08-08] FIX (LẦN 2): Windows exe vẫn crash 'app\static does not exist' + stuck 'Đang khởi động Engine' — bundle qua collect_data_files + fallback + verify build
|
||||||
|
- **Tóm tắt thay đổi:** User build bản Windows vẫn gặp RuntimeError `Directory '..._MEIxxxx\app\static' does not exist` (lần 2 — fix trước dùng datas ABSOLUTE theo SPECPATH nhưng trên máy user vẫn thiếu static trong bundle) + UI stuck "Đang khởi động SonicForge Engine". 3 lớp phòng thủ:
|
||||||
|
1. **engine.spec — bundle qua IMPORT SYSTEM**: bỏ datas đường dẫn tĩnh, thay bằng `collect_data_files('app', excludes=['**/storage/**','**/__pycache__/**','**/*.pyc'])` — PyInstaller tự tìm assets (static/templates/models) qua package import, KHÔNG phụ thuộc CWD/SPECPATH lúc chạy lệnh (nguyên nhân gốc: chạy pyinstaller từ thư mục khác → relative path không resolve → bỏ qua âm thầm). Loại luôn `app/storage` (57MB soundfonts — vô ích trong onefile, config.py đã redirect %APPDATA%). Giữ `md/` (ngoài package) + librosa .pyi.
|
||||||
|
2. **app/main.py — fallback an toàn**: nếu STATIC_DIR không tồn tại → thử `sys._MEIPASS/app/static` và `dirname(__file__)/static`; vẫn thiếu → **tự os.makedirs** → StaticFiles không còn crash lúc import (engine không chết, app hiện lỗi rõ thay vì 6 hộp thoại + stuck loader).
|
||||||
|
3. **build_windows.ps1 — verify post-build [3.5/6]**: sau pyinstaller, đọc Analysis-00.toc kiểm tra `app/static` + `app/templates` có trong bundle — thiếu → in ERROR đỏ + `exit 1` (bắt lỗi NGAY lúc build, không đợi chạy app mới vỡ).
|
||||||
|
- **Các file ảnh hưởng:** `engine.spec` (collect_data_files('app') + bỏ storage), `app/main.py` (fallback static dir), `build_windows.ps1` (verify TOC post-build)
|
||||||
|
- **Ghi chú/Test (nếu có):** VERIFY THẬT trên Linux (PyInstaller 6.21, cùng spec): Analysis-00.toc chứa app/static (124), app/templates (12), app/models (5); exe 155MB (giảm 57MB); chạy binary frozen → /health OK, index 200, static js 200, favicon 200, không crash. pytest 86 passed. User cần chạy lại `build_windows.ps1` (bước 3.5 sẽ tự kiểm tra bundle).
|
||||||
|
|
||||||
|
---
|
||||||
### [2026-08-08] IMPROVE: About modal — logo dùng favicon của app (app/templates/favicon.svg, serve /favicon.svg)
|
### [2026-08-08] IMPROVE: About modal — logo dùng favicon của app (app/templates/favicon.svg, serve /favicon.svg)
|
||||||
- **Tóm tắt thay đổi:** User yêu cầu logo trong About modal phải là logo của web/app — favicon lưu trong hệ thống. Trước đây AboutModal dùng div gradient chữ "SF". Fix: thay bằng `<img src="/favicon.svg">` (route đã có sẵn trong app/main.py — serve từ TEMPLATES_DIR; file app/templates/favicon.svg, SVG 1254x1254). Hiển thị 48x48 object-contain, nền tối + border cho nổi trên modal.
|
- **Tóm tắt thay đổi:** User yêu cầu logo trong About modal phải là logo của web/app — favicon lưu trong hệ thống. Trước đây AboutModal dùng div gradient chữ "SF". Fix: thay bằng `<img src="/favicon.svg">` (route đã có sẵn trong app/main.py — serve từ TEMPLATES_DIR; file app/templates/favicon.svg, SVG 1254x1254). Hiển thị 48x48 object-contain, nền tối + border cho nổi trên modal.
|
||||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx` (AboutModal img), `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html` (bump v=202608081600)
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx` (AboutModal img), `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html` (bump v=202608081600)
|
||||||
|
|||||||
Reference in New Issue
Block a user