From eaca05119108b29794b61eed3776df64ebcc8ab7 Mon Sep 17 00:00:00 2001 From: locpham Date: Sat, 8 Aug 2026 13:59:35 +0000 Subject: [PATCH] FIX (lan 2): bundle app assets qua collect_data_files (import-system) + fallback static dir trong main.py + verify TOC trong build_windows.ps1 --- app/main.py | 17 +++++++++++++++++ build_windows.ps1 | 24 ++++++++++++++++++++++++ engine.spec | 24 ++++++++++++------------ wiki.md | 9 +++++++++ 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/app/main.py b/app/main.py index 7996f26..45e2e91 100644 --- a/app/main.py +++ b/app/main.py @@ -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... # nhưng static/templates nằm trong sys._MEIPASS/app (config.py đã xử lý). 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") # Include routers diff --git a/build_windows.ps1 b/build_windows.ps1 index dfff013..5e7398c 100644 --- a/build_windows.ps1 +++ b/build_windows.ps1 @@ -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) ==" 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) ==" 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 diff --git a/engine.spec b/engine.spec index 4e03903..ec6f7ed 100644 --- a/engine.spec +++ b/engine.spec @@ -9,20 +9,20 @@ from PyInstaller.utils.hooks import collect_dynamic_libs, collect_submodules, co binaries = collect_dynamic_libs('pedalboard') binaries += collect_dynamic_libs('soundfile') -# Root tuyet doi cua thu muc chua spec — datas PHẢI absolute: PyInstaller -# resolve duong dan relative theo CWD luc chay lenh (KHONG theo spec file), -# chay tu noi khac -> khong tim thay -> bo qua am tham -> app/static thieu -# trong bundle -> RuntimeError 'app\static does not exist' luc chay (da gap). +# Root tuyet doi cua thu muc chua spec — dung cho cac datas KHONG nam trong +# package 'app' (vd thu muc md/). Cac assets cua app (static/templates/models) +# duoc bundle qua collect_data_files('app') — PyInstaller tu tim qua import +# 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) -# Assets doc (read-only) + thu muc storage (khoi tao rong; khi frozen, -# config.py chuyen storage sang %APPDATA%\SonicForgeDAW\storage) -datas = [ - (os.path.join(_SPEC_ROOT, 'app', 'templates'), 'app/templates'), # index.html, favicon.svg - (os.path.join(_SPEC_ROOT, 'app', 'static'), 'app/static'), # js/css/processors - (os.path.join(_SPEC_ROOT, 'app', 'models'), 'app/models'), # project_schema.json (projects.py) - (os.path.join(_SPEC_ROOT, 'app', 'storage'), 'app/storage'), - (os.path.join(_SPEC_ROOT, 'md'), 'md'), # /ai-prompt-generator +# Assets cua app: bundle QUA IMPORT SYSTEM (collect_data_files) — an toan nhat. +# Loai tru storage (57MB soundfonts/uploads — vo ich trong onefile, config.py +# da chuyen storage sang %APPDATA%\\SonicForgeDAW khi frozen) va __pycache__. +datas = collect_data_files('app', excludes=['**/storage/**', '**/__pycache__/**', '**/*.pyc']) +datas += [ + (os.path.join(_SPEC_ROOT, 'md'), 'md'), # /ai-prompt-generator (doc, ngoai package app) ] # librosa 0.11 dùng lazy_loader.attach_stub -> lúc RUNTIME cần file .pyi diff --git a/wiki.md b/wiki.md index 11bd6f7..99de8ab 100644 --- a/wiki.md +++ b/wiki.md @@ -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) - **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 `` (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)