feat(local-tool): clean build 為預設 + 藏 server 小黑窗 + 預設真實模式
Makefile:
- 新增 clean-all target:clean + wails build/ + frontend build/ + server embed
- 新增 clean-build-exe / clean-build-dmg / clean-build-appimage
bootstrap-windows.ps1:
- 預設改為 clean build(每次重做 wails + server binary + frontend embed)
- 保留 vendor/ 快取避免重下 200MB+ 第三方相依
- 需要 fast path(只重跑 iscc)時設 VISIONA_FAST=1
app.go:
- configureSysProcAttr() 注入子行程,Windows 下 CREATE_NO_WINDOW 藏掉 server 小黑窗
- 覆蓋 server spawn / tar 解壓 / venv 建立 / pip install 四個關鍵點
- mockMode 預設改 false(依使用者決策 Q8,預設走真實硬體模式)
需要強制 mock 時設環境變數 VISIONA_MOCK=1
platform_{windows,darwin,linux}.go:
- 新增 configureSysProcAttr(cmd):Windows 設 HideWindow + CREATE_NO_WINDOW,
macOS/Linux 空實作
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eb52f8c690
commit
8e7b6ae435
@ -28,7 +28,8 @@ PAYLOAD := visiona-local/payload
|
|||||||
stage-macos stage-windows \
|
stage-macos stage-windows \
|
||||||
wails-macos wails-windows wails-linux \
|
wails-macos wails-windows wails-linux \
|
||||||
dmg exe exe-only _run-iscc appimage \
|
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
|
help: ## 列出所有 make targets
|
||||||
@ -551,3 +552,19 @@ clean: ## 清除 dist/ 與 payload/ 產物
|
|||||||
@mkdir -p $(DIST) $(PAYLOAD)
|
@mkdir -p $(DIST) $(PAYLOAD)
|
||||||
@touch $(DIST)/.gitkeep $(PAYLOAD)/.gitkeep
|
@touch $(DIST)/.gitkeep $(PAYLOAD)/.gitkeep
|
||||||
@echo "Done."
|
@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
|
||||||
|
|||||||
@ -185,16 +185,25 @@ if ($msysIsccDir) {
|
|||||||
if ($msysIsccExe) {
|
if ($msysIsccExe) {
|
||||||
$bashParts += "export ISCC=`"$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 `
|
$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\visiona-local-server.exe') -and `
|
||||||
(Test-Path 'payload\windows\bin\ffmpeg.exe') -and `
|
(Test-Path 'payload\windows\bin\ffmpeg.exe') -and `
|
||||||
(Test-Path 'payload\windows\python\python.tar.gz')
|
(Test-Path 'payload\windows\python\python.tar.gz')
|
||||||
|
|
||||||
if ($fastPath -and ($Target -eq 'exe' -or -not $env:VISIONA_TARGET)) {
|
if ($env:VISIONA_FAST -eq '1' -and $fastPath -and ($Target -eq 'exe' -or -not $env:VISIONA_TARGET)) {
|
||||||
Log 'FAST PATH:前置產物齊全,跳過 vendor/payload/wails,只重跑 iscc 打包'
|
Log 'FAST PATH:VISIONA_FAST=1 + 前置產物齊全,跳過 vendor/payload/wails,只重跑 iscc'
|
||||||
$bashParts += 'make exe-only'
|
$bashParts += 'make exe-only'
|
||||||
} else {
|
} 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 vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows vendor-ytdlp-windows'
|
||||||
$bashParts += 'make payload-windows'
|
$bashParts += 'make payload-windows'
|
||||||
switch ($Target) {
|
switch ($Target) {
|
||||||
|
|||||||
@ -112,9 +112,12 @@ func NewApp() *App {
|
|||||||
mode = PythonModeAuto
|
mode = PythonModeAuto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// M7:預設真實硬體模式(使用者決策 Q8)
|
||||||
|
// 若要強制 mock 模式(無 Kneron 裝置環境下 debug),設環境變數 VISIONA_MOCK=1
|
||||||
|
mock := os.Getenv("VISIONA_MOCK") == "1"
|
||||||
return &App{
|
return &App{
|
||||||
pythonMode: mode,
|
pythonMode: mode,
|
||||||
mockMode: true, // M1 預設 mock(沒有真的 python/hardware)
|
mockMode: mock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,6 +362,7 @@ func (a *App) startServer() error {
|
|||||||
// 6. spawn
|
// 6. spawn
|
||||||
cmd := exec.Command(binPath, args...)
|
cmd := exec.Command(binPath, args...)
|
||||||
cmd.Dir = filepath.Dir(binPath)
|
cmd.Dir = filepath.Dir(binPath)
|
||||||
|
configureSysProcAttr(cmd) // Windows: CREATE_NO_WINDOW 藏掉 server 小黑窗
|
||||||
|
|
||||||
// 注入 bundle bin dir 給 server 偵測 ffmpeg / yt-dlp(M6)
|
// 注入 bundle bin dir 給 server 偵測 ffmpeg / yt-dlp(M6)
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
@ -633,6 +637,7 @@ func (a *App) ensureBundledPython() (string, error) {
|
|||||||
// 解壓 tarball(strip-components=1 剝掉 "python/" 前綴)
|
// 解壓 tarball(strip-components=1 剝掉 "python/" 前綴)
|
||||||
fmt.Fprintln(os.Stderr, "[visiona-local] extracting bundled python runtime...")
|
fmt.Fprintln(os.Stderr, "[visiona-local] extracting bundled python runtime...")
|
||||||
extract := exec.Command("tar", "-xzf", pyTarball, "-C", pyHome, "--strip-components=1")
|
extract := exec.Command("tar", "-xzf", pyTarball, "-C", pyHome, "--strip-components=1")
|
||||||
|
configureSysProcAttr(extract)
|
||||||
if out, err := extract.CombinedOutput(); err != nil {
|
if out, err := extract.CombinedOutput(); err != nil {
|
||||||
return "", fmt.Errorf("extract python tarball: %w (%s)", err, string(out))
|
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)
|
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))
|
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 := []string{"-m", "pip", "install", "--no-index", "--find-links", wheelsDir, "--prefer-binary"}
|
||||||
args = append(args, wheels...)
|
args = append(args, wheels...)
|
||||||
pipCmd := exec.Command(pythonBin, args...)
|
pipCmd := exec.Command(pythonBin, args...)
|
||||||
|
configureSysProcAttr(pipCmd)
|
||||||
if out, err := pipCmd.CombinedOutput(); err != nil {
|
if out, err := pipCmd.CombinedOutput(); err != nil {
|
||||||
return "", fmt.Errorf("pip install wheels: %w\n%s", err, string(out))
|
return "", fmt.Errorf("pip install wheels: %w\n%s", err, string(out))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,9 +4,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// configureSysProcAttr 在 macOS 上不需要特殊設定。
|
||||||
|
func configureSysProcAttr(_ *exec.Cmd) {}
|
||||||
|
|
||||||
// platformDataDir 回傳 macOS 的應用程式資料目錄。
|
// platformDataDir 回傳 macOS 的應用程式資料目錄。
|
||||||
// ~/Library/Application Support/visiona-local
|
// ~/Library/Application Support/visiona-local
|
||||||
func platformDataDir() string {
|
func platformDataDir() string {
|
||||||
|
|||||||
@ -4,9 +4,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// configureSysProcAttr 在 Linux 上不需要特殊設定。
|
||||||
|
func configureSysProcAttr(_ *exec.Cmd) {}
|
||||||
|
|
||||||
// platformDataDir 回傳 Linux 的應用程式資料目錄(遵循 XDG)。
|
// platformDataDir 回傳 Linux 的應用程式資料目錄(遵循 XDG)。
|
||||||
// $XDG_DATA_HOME/visiona-local 或 ~/.local/share/visiona-local
|
// $XDG_DATA_HOME/visiona-local 或 ~/.local/share/visiona-local
|
||||||
func platformDataDir() string {
|
func platformDataDir() string {
|
||||||
|
|||||||
@ -4,9 +4,22 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"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 的應用程式資料目錄。
|
// platformDataDir 回傳 Windows 的應用程式資料目錄。
|
||||||
// %APPDATA%\visiona-local
|
// %APPDATA%\visiona-local
|
||||||
func platformDataDir() string {
|
func platformDataDir() string {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user