# build_windows.ps1 - ONE-COMMAND build: frontend + native bridge + engine + Tauri (NSIS + MSI) # # powershell -ExecutionPolicy Bypass -File build_windows.ps1 # FULL (tat ca) # powershell -ExecutionPolicy Bypass -File build_windows.ps1 -Frontend # chi bundle JS + sync static # powershell -ExecutionPolicy Bypass -File build_windows.ps1 -Bridge # chi build native bridge + sync binaries # powershell -ExecutionPolicy Bypass -File build_windows.ps1 -Engine # chi build engine + sync resources # powershell -ExecutionPolicy Bypass -File build_windows.ps1 -Tauri # chi build installer Tauri # ... -SkipPip -SkipNpm # bo qua cai dependency # # Moi mode TU DONG copy output vao dung vi tri (dist/, src-tauri/resources/, # src-tauri/binaries/) va TU DONG bump cache-buster (?v=) -> khong the miss # buoc copy nao du build theo cach nao. Bridge chi rebuild khi source moi hon. # LUU Y: file nay chi dung ky tu ASCII (khong dau) - PowerShell 5.1 doc an toan. param( [switch]$Frontend, [switch]$Bridge, [switch]$Engine, [switch]$Tauri, [switch]$SkipPip, [switch]$SkipNpm ) # "Stop" + npm/pip warn tren stderr -> NativeCommandError -> chet oai (da gap o # build_install.ps1). Dung "Continue" + kiem tra $LASTEXITCODE sau tung lenh native. $ErrorActionPreference = "Continue" Set-Location $PSScriptRoot $any = $Frontend -or $Bridge -or $Engine -or $Tauri $doFrontend = $Frontend -or $Engine -or (-not $any) # Engine can bundle frontend moi truoc $doBridge = $Bridge -or (-not $any) $doEngine = $Engine -or (-not $any) $doTauri = $Tauri -or (-not $any) $Stamp = Get-Date -Format "yyyyMMddHHmm" function Fail([string]$msg) { Write-Host "ERROR: $msg" -ForegroundColor Red; exit 1 } if ($doEngine -or $doTauri) { Write-Host "== [0] Kill engine cu (chi install/dist/resources, khong dong production) ==" Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "$PSScriptRoot\install\*" -or $_.Path -like "$PSScriptRoot\dist\daw_engine\*" -or $_.Path -like "$PSScriptRoot\src-tauri\resources\daw_engine\*" } | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 500 } # ---- [1] Frontend bundle + sync static ------------------------------------- if ($doFrontend) { Write-Host "== [1] Frontend bundle (app.jsx -> app.precompiled.js, cache-buster v=$Stamp) ==" if (-not $SkipNpm) { npm install; if ($LASTEXITCODE -ne 0) { Fail "npm install that bai" } } if (-not (Test-Path "node_modules\@babel\standalone")) { Write-Host "Thieu @babel/standalone - dang cai them..." npm install @babel/standalone --no-audit --no-fund if ($LASTEXITCODE -ne 0) { Fail "npm install @babel/standalone that bai" } } if (-not (Test-Path "node_modules\@babel\standalone")) { Fail "Khong cai duoc @babel/standalone. Kiem tra ket noi npm!" } node build.mjs $Stamp if ($LASTEXITCODE -ne 0) { Fail "build.mjs that bai" } # Stamp cache-buster vao index.html source (no-cache HTML, bundle ?v= phai moi) $idx = "app\templates\index.html" $utf8 = New-Object System.Text.UTF8Encoding($false) $html = [IO.File]::ReadAllText($idx, $utf8) [IO.File]::WriteAllText($idx, [regex]::Replace($html, "app\.precompiled\.js\?v=\d+", "app.precompiled.js?v=$Stamp"), $utf8) # Sync 3 file static -> dist + src-tauri/resources (ban packaged engine doc tu day) $pairs = @( @("static\js\app.precompiled.js", "_internal\app\static\js\app.precompiled.js"), @("static\js\sf-fx-realtime.js", "_internal\app\static\js\sf-fx-realtime.js"), @("templates\index.html", "_internal\app\templates\index.html") ) $roots = @("dist\daw_engine", "src-tauri\resources\daw_engine") foreach ($p in $pairs) { $src = Join-Path "app" $p[0] foreach ($r in $roots) { $dst = Join-Path $r $p[1] New-Item -ItemType Directory -Force (Split-Path $dst) | Out-Null Copy-Item $src $dst -Force } } # Verify hash source == copy (bat loi "copy sai / thieu") foreach ($p in $pairs) { $srcHash = (Get-FileHash (Join-Path "app" $p[0]) -Algorithm SHA256).Hash foreach ($r in $roots) { $dst = Join-Path $r $p[1] if (-not (Test-Path $dst)) { Fail "Thieu file sau sync: $dst" } if ((Get-FileHash $dst -Algorithm SHA256).Hash -ne $srcHash) { Fail "Hash khac nhau sau sync: $dst" } } } Write-Host "Sync static OK (dist + src-tauri/resources)" } # ---- [2] Native bridge (MSBuild Release x64) -------------------------------- if ($doBridge) { Write-Host "== [2] Native bridge (MSBuild Release x64 -> src-tauri/binaries) ==" $msbuildCandidates = @( "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\amd64\MSBuild.exe", "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\amd64\MSBuild.exe", "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\amd64\MSBuild.exe", "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe" ) $msbuild = $msbuildCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 if (-not $msbuild) { Fail "Khong tim thay MSBuild.exe. Cai VS Build Tools 2019/2022 (workload C++) va chay CMake native_bridge truoc." } foreach ($name in @("daw_vst_bridge", "fx_vst_bridge", "plugin_host")) { $proj = "native_bridge\build\bridge-win\$name.vcxproj" $exe = "native_bridge\build\bridge-win\Release\$name.exe" if (-not (Test-Path $proj)) { Fail "Thieu project: $proj (can CMake native_bridge truoc - xem native_bridge/build/bridge-win)" } $newest = Get-ChildItem -Path "native_bridge\src","native_bridge\include" -Recurse -Include *.cpp,*.h,*.hpp -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 $needBuild = $true if ((Test-Path $exe) -and $newest) { $needBuild = $newest.LastWriteTime -gt (Get-Item $exe).LastWriteTime } if ($needBuild) { Write-Host "Build $name ..." & $msbuild $proj "/p:Configuration=Release" "/p:Platform=x64" "/m" "/nologo" "/v:m" if ($LASTEXITCODE -ne 0) { Fail "MSBuild $name that bai" } } else { Write-Host "Skip $name (exe moi nhat)" } if (-not (Test-Path $exe)) { Fail "Thieu output: $exe" } Copy-Item $exe "src-tauri\binaries\$name.exe" -Force Copy-Item $exe "src-tauri\binaries\$name-x86_64-pc-windows-msvc.exe" -Force } foreach ($dll in @("libfluidsynth-3.dll", "jpeg62.dll")) { if (Test-Path "native_bridge\build\Release\$dll") { Copy-Item "native_bridge\build\Release\$dll" "src-tauri\binaries\$dll" -Force } } Write-Host "Bridge OK -> src-tauri/binaries" } # ---- [3] Engine (PyInstaller) + sync resources ------------------------------ if ($doEngine) { Write-Host "== [3] Engine (PyInstaller onedir -> src-tauri/resources) ==" $specContent = Get-Content "engine.spec" -Raw -ErrorAction SilentlyContinue if ($specContent -notmatch "collect_data_files\('app'") { Fail "engine.spec CU (thieu collect_data_files('app')). Pull code moi truoc khi build!" } if (-not $SkipPip) { python -m pip install --upgrade pip python -m pip install -r requirements.txt pyinstaller pywin32 } if (Test-Path "dist\daw_engine") { Remove-Item -Recurse -Force "dist\daw_engine" } pyinstaller engine.spec --clean --noconfirm if ($LASTEXITCODE -ne 0) { Fail "pyinstaller that bai" } python tools\verify_bundle.py if ($LASTEXITCODE -ne 0) { Fail "Bundle thieu asset - dung build, kiem tra engine.spec datas" } # Ban cai phai SACH: khong duoc mang theo DB/password/secret/scan-state cua dev. $devFiles = Get-ChildItem "dist\daw_engine" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @(".secret_key", "plugin_dirs.json", "sf_scan_state.json", "sf_scan_state.json.bak-root") -or $_.Extension -eq ".db" } if ($devFiles) { $devFiles | ForEach-Object { Write-Host " DEV-CONFIG: $($_.FullName)" -ForegroundColor Red } Fail "Bundle chua config dev - DUNG TAO BAN CAI" } if (-not (Test-Path "dist\daw_engine\daw_engine.exe")) { Fail "dist\daw_engine\daw_engine.exe khong ton tai (onedir build loi?)" } # Robocopy /MIR (khong Copy-Item wildcard): giu dung cau truc onedir exe+_internal. robocopy "dist\daw_engine" "src-tauri\resources\daw_engine" /MIR /NFL /NDL /NJH /NP /R:1 /W:1 | Out-Null if ($LASTEXITCODE -ge 8) { Fail "robocopy resources that bai (exit $LASTEXITCODE)" } if (-not (Test-Path "src-tauri\resources\daw_engine\_internal")) { Fail "resources/daw_engine/_internal thieu - onedir layout sai" } Write-Host "Engine OK -> src-tauri/resources/daw_engine" } # ---- [4] Tauri installer ----------------------------------------------------- if ($doTauri) { Write-Host "== [4] VC++ Redistributable (cho hooks.nsh) ==" if (-not (Test-Path src-tauri\vc_redist.x64.exe)) { Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vc_redist.x64.exe" -OutFile src-tauri\vc_redist.x64.exe } Write-Host "== [5] Tauri build (NSIS + MSI) ==" if (-not (Test-Path "src-tauri\resources\daw_engine\daw_engine.exe")) { Fail "resources/daw_engine thieu - chay -Engine hoac full truoc" } if (-not (Test-Path "src-tauri\resources\daw_engine\_internal")) { Fail "resources/daw_engine/_internal thieu (onedir khong day du)" } foreach ($name in @("daw_vst_bridge", "fx_vst_bridge", "plugin_host")) { if (-not (Test-Path "src-tauri\binaries\$name-x86_64-pc-windows-msvc.exe")) { Fail "Thieu src-tauri\binaries\$name-x86_64-pc-windows-msvc.exe - chay -Bridge truoc" } } if (-not $SkipNpm) { npm install -D @tauri-apps/cli; if ($LASTEXITCODE -ne 0) { Fail "npm install @tauri-apps/cli that bai" } } npx tauri build if ($LASTEXITCODE -ne 0) { Fail "tauri build that bai" } } Write-Host "" Write-Host "== DONE ==" Write-Host " NSIS: src-tauri\target\release\bundle\nsis\ScoreFXStudio_1.0.0_x64-setup.exe" Write-Host " MSI : src-tauri\target\release\bundle\msi\ScoreFXStudio_1.0.0_x64_en-US.msi" Write-Host "" Write-Host "== XAC MINH SAU KHI CAI DAT ==" Write-Host " Mo %APPDATA%\SonicForgeDAW\logs\spawn.log - phai thay [resource_dir/daw_engine (map layout)] ...exists=True"