diff --git a/local-tool/Makefile b/local-tool/Makefile index 5270df5..6b9f68e 100644 --- a/local-tool/Makefile +++ b/local-tool/Makefile @@ -28,7 +28,8 @@ PAYLOAD := visiona-local/payload stage-macos stage-windows \ wails-macos wails-windows wails-linux \ dmg exe exe-only _run-iscc appimage \ - dev dev-mock test lint fmt clean + dev dev-mock test lint fmt \ + clean clean-all clean-build-exe clean-build-dmg clean-build-appimage # ── 幫助 ─────────────────────────────────────────────────────────── help: ## 列出所有 make targets @@ -551,3 +552,19 @@ clean: ## 清除 dist/ 與 payload/ 產物 @mkdir -p $(DIST) $(PAYLOAD) @touch $(DIST)/.gitkeep $(PAYLOAD)/.gitkeep @echo "Done." + +clean-all: clean ## 完整清除:dist/ payload/ wails build/ frontend build/ server embed + @echo "==> 清除 Wails build 產物..." + rm -rf visiona-local/build/bin + rm -rf visiona-local/build/darwin/Resources + rm -rf visiona-local/build/windows/Resources + @echo "==> 清除 frontend build 產物..." + rm -rf frontend/out + rm -rf frontend/.next + @echo "==> 清除 server 內嵌前端..." + rm -rf server/web/out + @echo "==> clean-all 完成" + +clean-build-exe: clean-all exe ## Windows:完整 clean + 從頭 build installer .exe(最乾淨的 build) +clean-build-dmg: clean-all dmg ## macOS:完整 clean + 從頭 build installer .dmg +clean-build-appimage: clean-all appimage ## Linux:完整 clean + 從頭 build installer .AppImage diff --git a/local-tool/scripts/bootstrap-windows.ps1 b/local-tool/scripts/bootstrap-windows.ps1 index d135c5f..f178831 100644 --- a/local-tool/scripts/bootstrap-windows.ps1 +++ b/local-tool/scripts/bootstrap-windows.ps1 @@ -185,16 +185,25 @@ if ($msysIsccDir) { if ($msysIsccExe) { $bashParts += "export ISCC=`"$msysIsccExe`"" } -# 偵測前置產物是否已齊全 —— 齊全就走 fast path(只重跑 iscc),省 wails rebuild 幾分鐘 +# Build 模式: +# VISIONA_FAST=1 → 前置產物齊全時跳過 vendor/payload/wails,只重跑 iscc(debug iteration 用) +# 預設 → 每次 clean build(wails build / server binary / frontend embed 全重做) +# 保留 vendor/ 快取(Python runtime / wheels / ffmpeg / yt-dlp)以免重下 200MB $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 打包' +if ($env:VISIONA_FAST -eq '1' -and $fastPath -and ($Target -eq 'exe' -or -not $env:VISIONA_TARGET)) { + Log 'FAST PATH:VISIONA_FAST=1 + 前置產物齊全,跳過 vendor/payload/wails,只重跑 iscc' $bashParts += 'make exe-only' } else { + Log '預設 clean build:每次重做 wails + server binary + frontend embed(vendor cache 保留)' + # 清 wails / frontend / server/web/out,但不清 vendor/(避免重下 Python / wheels / ffmpeg / yt-dlp 200MB+) + $bashParts += 'rm -rf visiona-local/build/bin visiona-local/build/windows/Resources' + $bashParts += 'rm -rf frontend/out frontend/.next server/web/out' + $bashParts += 'rm -rf payload/windows/bin/visiona-local-server.exe' + $bashParts += 'rm -rf dist/visiona-local-*-windows-x64.exe' $bashParts += 'make vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows vendor-ytdlp-windows' $bashParts += 'make payload-windows' switch ($Target) { diff --git a/local-tool/visiona-local/app.go b/local-tool/visiona-local/app.go index b31f16e..87921d5 100644 --- a/local-tool/visiona-local/app.go +++ b/local-tool/visiona-local/app.go @@ -112,9 +112,12 @@ func NewApp() *App { mode = PythonModeAuto } } + // M7:預設真實硬體模式(使用者決策 Q8) + // 若要強制 mock 模式(無 Kneron 裝置環境下 debug),設環境變數 VISIONA_MOCK=1 + mock := os.Getenv("VISIONA_MOCK") == "1" return &App{ pythonMode: mode, - mockMode: true, // M1 預設 mock(沒有真的 python/hardware) + mockMode: mock, } } @@ -359,6 +362,7 @@ func (a *App) startServer() error { // 6. spawn cmd := exec.Command(binPath, args...) cmd.Dir = filepath.Dir(binPath) + configureSysProcAttr(cmd) // Windows: CREATE_NO_WINDOW 藏掉 server 小黑窗 // 注入 bundle bin dir 給 server 偵測 ffmpeg / yt-dlp(M6) env := os.Environ() @@ -633,6 +637,7 @@ func (a *App) ensureBundledPython() (string, error) { // 解壓 tarball(strip-components=1 剝掉 "python/" 前綴) fmt.Fprintln(os.Stderr, "[visiona-local] extracting bundled python runtime...") extract := exec.Command("tar", "-xzf", pyTarball, "-C", pyHome, "--strip-components=1") + configureSysProcAttr(extract) if out, err := extract.CombinedOutput(); err != nil { return "", fmt.Errorf("extract python tarball: %w (%s)", err, string(out)) } @@ -646,7 +651,9 @@ func (a *App) ensureBundledPython() (string, error) { } fmt.Fprintln(os.Stderr, "[visiona-local] creating venv at", venvPath) - if out, err := exec.Command(embeddedPython, "-m", "venv", venvPath).CombinedOutput(); err != nil { + venvCmd := exec.Command(embeddedPython, "-m", "venv", venvPath) + configureSysProcAttr(venvCmd) + if out, err := venvCmd.CombinedOutput(); err != nil { return "", fmt.Errorf("create venv: %w (%s)", err, string(out)) } @@ -669,6 +676,7 @@ func (a *App) ensureBundledPython() (string, error) { args := []string{"-m", "pip", "install", "--no-index", "--find-links", wheelsDir, "--prefer-binary"} args = append(args, wheels...) pipCmd := exec.Command(pythonBin, args...) + configureSysProcAttr(pipCmd) if out, err := pipCmd.CombinedOutput(); err != nil { return "", fmt.Errorf("pip install wheels: %w\n%s", err, string(out)) } diff --git a/local-tool/visiona-local/platform_darwin.go b/local-tool/visiona-local/platform_darwin.go index 58bc608..c1bb4f8 100644 --- a/local-tool/visiona-local/platform_darwin.go +++ b/local-tool/visiona-local/platform_darwin.go @@ -4,9 +4,13 @@ package main import ( "os" + "os/exec" "path/filepath" ) +// configureSysProcAttr 在 macOS 上不需要特殊設定。 +func configureSysProcAttr(_ *exec.Cmd) {} + // platformDataDir 回傳 macOS 的應用程式資料目錄。 // ~/Library/Application Support/visiona-local func platformDataDir() string { diff --git a/local-tool/visiona-local/platform_linux.go b/local-tool/visiona-local/platform_linux.go index 0ee68ed..064b195 100644 --- a/local-tool/visiona-local/platform_linux.go +++ b/local-tool/visiona-local/platform_linux.go @@ -4,9 +4,13 @@ package main import ( "os" + "os/exec" "path/filepath" ) +// configureSysProcAttr 在 Linux 上不需要特殊設定。 +func configureSysProcAttr(_ *exec.Cmd) {} + // platformDataDir 回傳 Linux 的應用程式資料目錄(遵循 XDG)。 // $XDG_DATA_HOME/visiona-local 或 ~/.local/share/visiona-local func platformDataDir() string { diff --git a/local-tool/visiona-local/platform_windows.go b/local-tool/visiona-local/platform_windows.go index 9d85885..0a32974 100644 --- a/local-tool/visiona-local/platform_windows.go +++ b/local-tool/visiona-local/platform_windows.go @@ -4,9 +4,22 @@ package main import ( "os" + "os/exec" "path/filepath" + "syscall" ) +// configureSysProcAttr 設定子行程的 Windows 特有屬性。 +// CREATE_NO_WINDOW (0x08000000) 讓 server 子行程不彈 console 視窗(小黑窗)。 +// HideWindow 對某些情境也一起加保險。 +func configureSysProcAttr(cmd *exec.Cmd) { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.HideWindow = true + cmd.SysProcAttr.CreationFlags |= 0x08000000 // CREATE_NO_WINDOW +} + // platformDataDir 回傳 Windows 的應用程式資料目錄。 // %APPDATA%\visiona-local func platformDataDir() string {