# visionA-local — Windows 10/11 x86_64 一鍵 build # # 使用方式(PowerShell 以「系統管理員」開啟): # git clone https://github.com/jim800121/visionA.git # cd visionA\local-tool # powershell -ExecutionPolicy Bypass -File scripts\bootstrap-windows.ps1 # # 環境變數: # $env:VISIONA_TARGET 預設 exe,可設 wails-windows / payload-windows $ErrorActionPreference = 'Stop' $Target = if ($env:VISIONA_TARGET) { $env:VISIONA_TARGET } else { 'exe' } function Log($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } function Fail($msg) { Write-Host "!!! $msg" -ForegroundColor Red; exit 1 } # 必須以系統管理員身份執行(winget 需要) $isAdmin = ([Security.Principal.WindowsPrincipal] ` [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltInRole] 'Administrator') if (-not $isAdmin) { Fail '請以系統管理員身份執行 PowerShell 再跑此腳本' } if (-not (Test-Path 'Makefile') -or -not (Test-Path 'visiona-local')) { Fail '請先 cd 到 local-tool\ 目錄再執行此腳本' } if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { Fail 'winget 未安裝。請先從 Microsoft Store 安裝「App Installer」' } function Ensure-Winget($id) { $installed = winget list --id $id -e 2>$null | Select-String $id if (-not $installed) { Log "安裝 $id" winget install -e --id $id --accept-source-agreements --accept-package-agreements } else { Log "$id 已安裝,跳過" } } Log '[1/4] 安裝系統套件' Ensure-Winget 'Git.Git' Ensure-Winget 'GoLang.Go' Ensure-Winget 'OpenJS.NodeJS.LTS' Ensure-Winget 'Python.Python.3.12' Ensure-Winget 'JRSoftware.InnoSetup' Ensure-Winget 'MSYS2.MSYS2' # 重新載入 PATH(winget 裝完目前 session 拿不到) $env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + ` [System.Environment]::GetEnvironmentVariable('Path','User') $env:Path += ";$HOME\go\bin;C:\msys64\usr\bin;C:\msys64\mingw64\bin" Log '[2/4] 安裝 pnpm' if (-not (Get-Command pnpm -ErrorAction SilentlyContinue)) { npm i -g pnpm } Log '[3/4] 安裝 Wails CLI + 確認 MSYS2 make' if (-not (Get-Command wails -ErrorAction SilentlyContinue)) { go install github.com/wailsapp/wails/v2/cmd/wails@latest } wails doctor if (-not (Test-Path 'C:\msys64\usr\bin\make.exe')) { Log '安裝 MSYS2 make' & 'C:\msys64\usr\bin\bash.exe' -lc 'pacman -Sy --noconfirm make' } Log "[4/4] 開始 build(target=$Target)" Log '⚠️ ffmpeg 使用 GPL build,需設定 VISIONA_ALLOW_GPL_FFMPEG=1' # 讓 MSYS2 bash 繼承 Windows PATH(才找得到 go / pnpm / python / wails) $env:MSYS2_PATH_TYPE = 'inherit' $env:CHERE_INVOKING = '1' # 找真實的 Python(避開 Microsoft Store 的 WindowsApps stub) $realPython = $null $pyCandidates = @( "$env:LOCALAPPDATA\Programs\Python\Python312\python.exe", "$env:LOCALAPPDATA\Programs\Python\Python313\python.exe", "$env:ProgramFiles\Python312\python.exe", "$env:ProgramFiles\Python313\python.exe" ) foreach ($p in $pyCandidates) { if (Test-Path $p) { $realPython = $p; break } } if (-not $realPython) { # 試 py launcher $pyLauncher = Get-Command py -ErrorAction SilentlyContinue if ($pyLauncher) { $realPython = 'py -3' } } if (-not $realPython) { Fail '找不到真實 Python(winget 可能沒裝成功,或 PATH 沒更新)。請重開 PowerShell 再試一次' } Log "偵測到 Python: $realPython" # 把 Windows 路徑轉成 MSYS2 bash 能吃的格式 function Convert-ToMsysPath($winPath) { if ($winPath -match '^[A-Za-z]:\\') { return '/' + $winPath.Substring(0,1).ToLower() + '/' + ($winPath.Substring(3) -replace '\\','/') } return $winPath } $msysPython = Convert-ToMsysPath $realPython # 找 Inno Setup Compiler (ISCC.exe) # 重要:檔名是 ISCC.exe(大寫),winget 裝在 Program Files (x86) 不在 PATH 裡 function Find-Iscc { # 1. 固定路徑(system + user scope) $candidates = @( "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe", "$env:ProgramFiles\Inno Setup 6\ISCC.exe", "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe", "$env:USERPROFILE\AppData\Local\Programs\Inno Setup 6\ISCC.exe", "${env:ProgramFiles(x86)}\Inno Setup 5\ISCC.exe" ) foreach ($p in $candidates) { if ($p -and (Test-Path $p)) { return $p } } # 2. 登錄檔 Uninstall key(HKLM + HKCU 都看) $regPaths = @( 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1', 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1', 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1' ) foreach ($rp in $regPaths) { $item = Get-ItemProperty $rp -ErrorAction SilentlyContinue if ($item) { if ($item.InstallLocation) { $p = Join-Path $item.InstallLocation 'ISCC.exe' if (Test-Path $p) { return $p } } if ($item.'Inno Setup: App Path') { $p = Join-Path $item.'Inno Setup: App Path' 'ISCC.exe' if (Test-Path $p) { return $p } } } } # 3. 遞迴掃 Program Files + user AppData $scanRoots = @( "${env:ProgramFiles(x86)}", "$env:ProgramFiles", "$env:LOCALAPPDATA\Programs" ) | Where-Object { $_ -and (Test-Path $_) } foreach ($root in $scanRoots) { $found = Get-ChildItem -Path $root -Filter 'ISCC.exe' -Recurse ` -ErrorAction SilentlyContinue -Force | Select-Object -First 1 if ($found) { return $found.FullName } } return $null } $isccPath = Find-Iscc if (-not $isccPath) { Log 'Inno Setup 偵測不到,用 winget 重裝一次' winget install -e --id JRSoftware.InnoSetup --accept-source-agreements --accept-package-agreements --force $isccPath = Find-Iscc } if ($isccPath) { Log "偵測到 Inno Setup: $isccPath" $msysIsccDir = Convert-ToMsysPath (Split-Path $isccPath -Parent) $msysIsccExe = Convert-ToMsysPath $isccPath } else { Log 'WARN: 仍然找不到 Inno Setup(iscc.exe),make exe 步驟會失敗' $msysIsccDir = $null $msysIsccExe = $null } # Makefile 需要 bash + make,透過 MSYS2 bash 執行 # 將 Windows 路徑 C:\foo\bar 轉成 MSYS2 路徑 /c/foo/bar $projectPath = (Get-Location).Path $msysPath = '/' + $projectPath.Substring(0,1).ToLower() + '/' + ` ($projectPath.Substring(3) -replace '\\','/') $bashParts = @( "cd '$msysPath'", 'export VISIONA_ALLOW_GPL_FFMPEG=1', "export VISIONA_PYTHON='$msysPython'" ) if ($msysIsccDir) { $bashParts += "export PATH=`"$msysIsccDir`":`$PATH" } if ($msysIsccExe) { $bashParts += "export ISCC=`"$msysIsccExe`"" } # 偵測前置產物是否已齊全 —— 齊全就走 fast path(只重跑 iscc),省 wails rebuild 幾分鐘 $fastPath = (Test-Path 'visiona-local\build\bin\visiona-local.exe') -and ` (Test-Path 'payload\windows\bin\visiona-local-server.exe') -and ` (Test-Path 'payload\windows\bin\ffmpeg.exe') -and ` (Test-Path 'payload\windows\python\python.tar.gz') if ($fastPath -and ($Target -eq 'exe' -or -not $env:VISIONA_TARGET)) { Log 'FAST PATH:前置產物齊全,跳過 vendor/payload/wails,只重跑 iscc 打包' $bashParts += 'make exe-only' } else { $bashParts += 'make vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows vendor-ytdlp-windows' $bashParts += 'make payload-windows' switch ($Target) { 'payload-windows' { } 'wails-windows' { $bashParts += 'make wails-windows' } default { $bashParts += 'make wails-windows'; $bashParts += 'make exe' } } } # 把 bash 指令寫成 script 檔再執行,避開 PS → bash 多層 quoting 地獄 # (尤其是含空格的路徑如 "C:\Program Files (x86)\Inno Setup 6\") $bashScriptLines = @('#!/usr/bin/env bash', 'set -e', '') foreach ($line in $bashParts) { $bashScriptLines += $line } $bashScriptContent = ($bashScriptLines -join "`n") + "`n" $tmpScript = Join-Path (Get-Location) '.visiona-build.sh' # 用 UTF8 無 BOM + LF 換行,bash 才吃得下 [System.IO.File]::WriteAllText($tmpScript, $bashScriptContent, (New-Object System.Text.UTF8Encoding $false)) Log "bash 執行腳本內容:" Get-Content $tmpScript | ForEach-Object { Write-Host " $_" } $msysScript = Convert-ToMsysPath $tmpScript & 'C:\msys64\usr\bin\bash.exe' -l $msysScript $buildRc = $LASTEXITCODE Remove-Item $tmpScript -ErrorAction SilentlyContinue if ($buildRc -ne 0) { Fail "build 失敗,exit code=$buildRc" } # 驗證 dist 真的有 .exe 產出(exe target 是 phony,make 成功 != 產物存在) if ($Target -eq 'exe' -or $Target -eq 'default' -or -not $Target) { $exeFiles = Get-ChildItem dist -Filter 'visiona-local-*-windows-x64.exe' -ErrorAction SilentlyContinue if (-not $exeFiles) { Fail "make exe 沒報錯,但 dist\ 下沒有 visiona-local-*-windows-x64.exe。往上捲看 iscc 輸出找原因,或直接跑: & `"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe`" installer\windows\visiona-local.iss" } } Log '完成 ✅' Log "產出位置:$(Join-Path (Get-Location) 'dist')" Get-ChildItem dist -ErrorAction SilentlyContinue | Format-Table Name, Length