local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
Wails IPC raise endpoint, stale process cleanup
.autoflow/: full PRD / Design Spec / Architecture / Testing docs
(4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
414 lines
16 KiB
Markdown
414 lines
16 KiB
Markdown
# Build Pipeline — visionA-local
|
||
|
||
> Makefile、build 腳本、CI(選配)、跨平台建置策略。**無簽章。**
|
||
|
||
---
|
||
|
||
## 1. Makefile 骨架
|
||
|
||
```makefile
|
||
# visionA-local - Makefile
|
||
|
||
.PHONY: help dev dev-mock build build-server build-frontend build-embed \
|
||
payload payload-macos payload-windows payload-linux \
|
||
installer installer-macos installer-windows installer-linux \
|
||
clean test lint fmt
|
||
|
||
VERSION ?= v0.1.0
|
||
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
OS := $(shell uname -s | tr A-Z a-z)
|
||
DIST := dist
|
||
|
||
# ── 幫助 ───────────────────────────────────────────────────
|
||
help:
|
||
@echo "visionA-local - Available targets:"
|
||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
|
||
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}'
|
||
|
||
# ── 開發 ───────────────────────────────────────────────────
|
||
dev: ## 開發模式(真實硬體 + 前後端 hot reload)
|
||
@$(MAKE) -j2 dev-server dev-frontend
|
||
|
||
dev-server:
|
||
cd server && go run main.go --dev
|
||
|
||
dev-mock: ## 開發模式(Mock)
|
||
@$(MAKE) -j2 dev-mock-server dev-frontend
|
||
|
||
dev-mock-server:
|
||
cd server && go run main.go --dev --mock --mock-devices=3 --mock-camera
|
||
|
||
dev-frontend:
|
||
cd frontend && pnpm dev
|
||
|
||
# ── Build(單元) ───────────────────────────────────────────
|
||
build: build-frontend build-embed build-server ## Build Go server binary (含 embedded Next.js)
|
||
|
||
build-frontend: ## 編譯 Next.js 靜態檔
|
||
cd frontend && pnpm build
|
||
|
||
build-embed: build-frontend
|
||
@rm -rf server/web/out
|
||
@mkdir -p server/web/out
|
||
cp -r frontend/out/. server/web/out/
|
||
|
||
build-server:
|
||
@mkdir -p $(DIST)
|
||
cd server && go build \
|
||
-ldflags="-X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)" \
|
||
-o ../$(DIST)/visiona-local-server main.go
|
||
|
||
# ── Payload 準備 ────────────────────────────────────────────
|
||
payload: payload-$(OS) ## 依當前 OS 準備 payload
|
||
|
||
payload-macos: build ## 準備 macOS 的 Wails payload
|
||
@echo "Staging macOS payload..."
|
||
@rm -rf visiona-local/payload
|
||
@mkdir -p visiona-local/payload/{bin,data/nef/kl520,data/nef/kl720,scripts/wheels,python}
|
||
cp $(DIST)/visiona-local-server visiona-local/payload/bin/
|
||
cp server/data/models.json visiona-local/payload/data/
|
||
cp server/data/nef/kl520/*.nef visiona-local/payload/data/nef/kl520/
|
||
cp server/data/nef/kl720/*.nef visiona-local/payload/data/nef/kl720/
|
||
cp server/scripts/kneron_bridge.py visiona-local/payload/scripts/
|
||
cp server/scripts/requirements.txt visiona-local/payload/scripts/
|
||
# Python runtime(python-build-standalone)
|
||
cp vendor/python/cpython-3.12-macos-x64.tar.gz visiona-local/payload/python/
|
||
# Wheels
|
||
cp vendor/wheels/macos-x64/*.whl visiona-local/payload/scripts/wheels/
|
||
# ffmpeg (LGPL static)
|
||
cp vendor/ffmpeg/macos-x64/ffmpeg visiona-local/payload/bin/
|
||
chmod +x visiona-local/payload/bin/ffmpeg
|
||
# yt-dlp
|
||
cp vendor/yt-dlp/yt-dlp_macos visiona-local/payload/bin/yt-dlp
|
||
chmod +x visiona-local/payload/bin/yt-dlp
|
||
|
||
payload-windows: build
|
||
@echo "Staging Windows payload..."
|
||
@rm -rf visiona-local/payload
|
||
@mkdir -p visiona-local/payload/{bin,data/nef/kl520,data/nef/kl720,scripts/wheels,python,drivers/amd64}
|
||
cp $(DIST)/visiona-local-server.exe visiona-local/payload/bin/
|
||
cp server/data/models.json visiona-local/payload/data/
|
||
cp -r server/data/nef/* visiona-local/payload/data/nef/
|
||
cp server/scripts/kneron_bridge.py visiona-local/payload/scripts/
|
||
cp server/scripts/requirements.txt visiona-local/payload/scripts/
|
||
cp vendor/python/cpython-3.12-windows-x64.tar.gz visiona-local/payload/python/
|
||
cp vendor/wheels/windows-x64/*.whl visiona-local/payload/scripts/wheels/
|
||
cp vendor/ffmpeg/windows-x64/*.{exe,dll} visiona-local/payload/bin/
|
||
cp vendor/yt-dlp/yt-dlp.exe visiona-local/payload/bin/
|
||
# WinUSB driver
|
||
cp vendor/drivers/kneron_winusb.inf visiona-local/payload/drivers/
|
||
cp vendor/drivers/amd64/*.dll visiona-local/payload/drivers/amd64/
|
||
cp vendor/drivers/libusb-1.0.dll visiona-local/payload/bin/
|
||
|
||
payload-linux: build
|
||
@echo "Staging Linux payload..."
|
||
@rm -rf visiona-local/payload
|
||
@mkdir -p visiona-local/payload/{bin,data/nef/kl520,data/nef/kl720,scripts/wheels,python}
|
||
cp $(DIST)/visiona-local-server visiona-local/payload/bin/
|
||
cp server/data/models.json visiona-local/payload/data/
|
||
cp -r server/data/nef/* visiona-local/payload/data/nef/
|
||
cp server/scripts/kneron_bridge.py visiona-local/payload/scripts/
|
||
cp server/scripts/requirements.txt visiona-local/payload/scripts/
|
||
cp vendor/python/cpython-3.12-linux-x64.tar.gz visiona-local/payload/python/
|
||
cp vendor/wheels/linux-x64/*.whl visiona-local/payload/scripts/wheels/
|
||
cp vendor/ffmpeg/linux-x64/ffmpeg visiona-local/payload/bin/
|
||
chmod +x visiona-local/payload/bin/ffmpeg
|
||
cp vendor/yt-dlp/yt-dlp visiona-local/payload/bin/
|
||
chmod +x visiona-local/payload/bin/yt-dlp
|
||
# 內帶 libusb 供 AppImage 使用
|
||
cp /usr/lib/x86_64-linux-gnu/libusb-1.0.so.0 visiona-local/payload/bin/
|
||
|
||
# ── Installer 建置 ──────────────────────────────────────────
|
||
installer: installer-$(OS) ## 依當前 OS 建 installer
|
||
|
||
installer-macos: payload-macos
|
||
cd visiona-local && wails build -platform darwin/amd64 -clean
|
||
codesign --force --deep --sign - visiona-local/build/bin/visiona-local.app
|
||
mkdir -p $(DIST)
|
||
dmgbuild -s dmg-config.py "visionA-local" \
|
||
$(DIST)/visiona-local-$(VERSION)-macos-x64.dmg
|
||
|
||
installer-windows: payload-windows
|
||
cd visiona-local && wails build -platform windows/amd64 -clean -webview2 embed
|
||
mkdir -p $(DIST)
|
||
iscc /DVERSION=$(VERSION) visiona-local-installer.iss
|
||
|
||
installer-linux: payload-linux
|
||
cd visiona-local && wails build -platform linux/amd64 -clean
|
||
bash scripts/build-appimage.sh $(VERSION)
|
||
|
||
# ── 測試 / Lint ─────────────────────────────────────────────
|
||
test: test-server test-frontend
|
||
|
||
test-server:
|
||
cd server && go test -v ./...
|
||
|
||
test-frontend:
|
||
cd frontend && pnpm test
|
||
|
||
lint:
|
||
cd server && go vet ./...
|
||
cd frontend && pnpm lint
|
||
|
||
fmt:
|
||
cd server && go fmt ./...
|
||
|
||
# ── 清理 ───────────────────────────────────────────────────
|
||
clean:
|
||
rm -rf $(DIST)
|
||
rm -rf frontend/.next frontend/out
|
||
rm -rf server/web/out
|
||
@mkdir -p server/web/out && touch server/web/out/.gitkeep
|
||
rm -rf visiona-local/build visiona-local/payload
|
||
@mkdir -p visiona-local/payload && touch visiona-local/payload/.gitkeep
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Vendor 目錄結構(離線打包依賴)
|
||
|
||
**關鍵:所有第三方二進位都放進 `vendor/`,由建置腳本首次執行時透過 `make vendor-sync` 下載。一旦下載就 cache,之後 offline build。**
|
||
|
||
```
|
||
/Users/jimchen/visionA/local-tool/vendor/
|
||
├── python/ ← python-build-standalone tarballs
|
||
│ ├── cpython-3.12-macos-x64.tar.gz
|
||
│ ├── cpython-3.12-windows-x64.tar.gz
|
||
│ └── cpython-3.12-linux-x64.tar.gz
|
||
├── wheels/ ← 預先下載的 Python wheels
|
||
│ ├── macos-x64/
|
||
│ │ ├── numpy-*.whl
|
||
│ │ ├── opencv_python_headless-*.whl
|
||
│ │ ├── pyusb-*.whl
|
||
│ │ └── KneronPLUS-*.whl
|
||
│ ├── windows-x64/
|
||
│ └── linux-x64/
|
||
├── ffmpeg/ ← LGPL static builds
|
||
│ ├── macos-x64/ffmpeg
|
||
│ ├── windows-x64/{ffmpeg.exe, *.dll}
|
||
│ └── linux-x64/ffmpeg
|
||
├── yt-dlp/
|
||
│ ├── yt-dlp_macos
|
||
│ ├── yt-dlp.exe
|
||
│ └── yt-dlp
|
||
└── drivers/ ← Windows WinUSB driver
|
||
├── kneron_winusb.inf
|
||
├── libusb-1.0.dll
|
||
└── amd64/
|
||
```
|
||
|
||
### 2.1 `make vendor-sync` 腳本(首次 setup 用)
|
||
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
# scripts/vendor-sync.sh - 下載所有第三方依賴到 vendor/
|
||
set -e
|
||
|
||
VENDOR=vendor
|
||
mkdir -p $VENDOR/{python,wheels,ffmpeg,yt-dlp,drivers}
|
||
|
||
# 1. python-build-standalone
|
||
PBS_VERSION="20250317"
|
||
PBS_PY="3.12.9"
|
||
PBS_BASE="https://github.com/astral-sh/python-build-standalone/releases/download/$PBS_VERSION"
|
||
|
||
curl -L -o $VENDOR/python/cpython-3.12-macos-x64.tar.gz \
|
||
"$PBS_BASE/cpython-$PBS_PY+$PBS_VERSION-x86_64-apple-darwin-install_only.tar.gz"
|
||
curl -L -o $VENDOR/python/cpython-3.12-windows-x64.tar.gz \
|
||
"$PBS_BASE/cpython-$PBS_PY+$PBS_VERSION-x86_64-pc-windows-msvc-install_only.tar.gz"
|
||
curl -L -o $VENDOR/python/cpython-3.12-linux-x64.tar.gz \
|
||
"$PBS_BASE/cpython-$PBS_PY+$PBS_VERSION-x86_64-unknown-linux-gnu-install_only.tar.gz"
|
||
|
||
# 2. Python wheels(用每個平台的 Python 預先下載)
|
||
# 注意:必須在對應平台上跑 pip download 才能拿到正確的 wheel tag
|
||
# 這裡假設使用者已在 macOS 機器上跑過:
|
||
# pip download -d vendor/wheels/macos-x64 \
|
||
# --platform macosx_11_0_x86_64 --python-version 3.12 --only-binary=:all: \
|
||
# numpy opencv-python-headless pyusb
|
||
# KneronPLUS wheel 另外手動放進去(從 edge-ai-platform/installer/wheels/ 複製)
|
||
|
||
cp -n ../edge-ai-platform/installer/wheels/macos/KneronPLUS*.whl $VENDOR/wheels/macos-x64/ 2>/dev/null || true
|
||
cp -n ../edge-ai-platform/installer/wheels/linux/KneronPLUS*.whl $VENDOR/wheels/linux-x64/ 2>/dev/null || true
|
||
cp -n ../edge-ai-platform/installer/wheels/windows/KneronPLUS*.whl $VENDOR/wheels/windows-x64/ 2>/dev/null || true
|
||
|
||
# 3. ffmpeg LGPL
|
||
FFMPEG_VER="7.1"
|
||
# macOS
|
||
curl -L -o /tmp/ffmpeg-mac.zip "https://evermeet.cx/ffmpeg/ffmpeg-$FFMPEG_VER.zip"
|
||
unzip -o /tmp/ffmpeg-mac.zip -d $VENDOR/ffmpeg/macos-x64/
|
||
# Windows(BtbN LGPL build)
|
||
curl -L -o /tmp/ffmpeg-win.zip \
|
||
"https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-n$FFMPEG_VER-latest-win64-lgpl-shared-$FFMPEG_VER.zip"
|
||
# 解壓 ffmpeg.exe 與需要的 DLLs 到 vendor/ffmpeg/windows-x64/
|
||
# Linux
|
||
curl -L -o /tmp/ffmpeg-linux.tar.xz \
|
||
"https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz"
|
||
tar -xf /tmp/ffmpeg-linux.tar.xz -C /tmp/
|
||
cp /tmp/ffmpeg-*-amd64-static/ffmpeg $VENDOR/ffmpeg/linux-x64/
|
||
|
||
# 4. yt-dlp
|
||
YTDLP_BASE="https://github.com/yt-dlp/yt-dlp/releases/latest/download"
|
||
curl -L -o $VENDOR/yt-dlp/yt-dlp_macos "$YTDLP_BASE/yt-dlp_macos"
|
||
curl -L -o $VENDOR/yt-dlp/yt-dlp.exe "$YTDLP_BASE/yt-dlp.exe"
|
||
curl -L -o $VENDOR/yt-dlp/yt-dlp "$YTDLP_BASE/yt-dlp"
|
||
chmod +x $VENDOR/yt-dlp/yt-dlp_macos $VENDOR/yt-dlp/yt-dlp
|
||
|
||
# 5. WinUSB driver(從 edge-ai-platform 複製)
|
||
cp -r ../edge-ai-platform/installer/drivers/* $VENDOR/drivers/
|
||
|
||
echo "✅ Vendor sync complete."
|
||
```
|
||
|
||
**vendor/ 是否進 git?(第三輪使用者決策 Q-D=D2:不進 git)**
|
||
|
||
- **整個 `vendor/` 目錄加入 `.gitignore`**,不進版本控制
|
||
- 所有第三方二進位(Python tarball、ffmpeg、yt-dlp、KneronPLUS wheel、WinUSB driver 等)都由 `make vendor-sync` 從來源下載
|
||
- 開發者第一次 clone 後執行 `make vendor-sync` 即可取得完整 vendor tree
|
||
- CI 亦在每次 build 前跑 `make vendor-sync`(可加 cache 加速)
|
||
- 保留 `vendor/.gitkeep`(或 `vendor/README.md`)讓空目錄存在並說明取得方式
|
||
- 優點:repo 保持小;缺點:依賴外部下載來源可用性(由 R8 風險緩解處理 — pbs 下載 URL pin 版本 + 備援 mirror)
|
||
|
||
**`.gitignore` 片段:**
|
||
|
||
```gitignore
|
||
# 第三方依賴(由 make vendor-sync 下載,不進 git)
|
||
/vendor/**
|
||
!/vendor/.gitkeep
|
||
!/vendor/README.md
|
||
|
||
# 建置產出
|
||
/dist/
|
||
/visiona-local/build/
|
||
/visiona-local/payload/
|
||
```
|
||
|
||
---
|
||
|
||
## 3. CI 策略(選配,第一版可純手動)
|
||
|
||
### 3.1 建議:GitHub Actions matrix build
|
||
|
||
```yaml
|
||
# .github/workflows/release.yml
|
||
name: Release
|
||
|
||
on:
|
||
push:
|
||
tags:
|
||
- 'v*'
|
||
|
||
jobs:
|
||
build-macos:
|
||
runs-on: macos-13 # Intel runner (x86_64)
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-go@v5
|
||
with:
|
||
go-version: '1.22'
|
||
- uses: pnpm/action-setup@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: '20'
|
||
cache: 'pnpm'
|
||
cache-dependency-path: 'frontend/pnpm-lock.yaml'
|
||
- name: Install Wails
|
||
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||
- name: Install dmgbuild
|
||
run: pip install dmgbuild
|
||
- name: Vendor sync
|
||
run: make vendor-sync
|
||
- name: Build installer
|
||
run: make installer-macos VERSION=${{ github.ref_name }}
|
||
- uses: actions/upload-artifact@v4
|
||
with:
|
||
name: macos-installer
|
||
path: dist/*.dmg
|
||
|
||
build-windows:
|
||
runs-on: windows-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-go@v5
|
||
- uses: pnpm/action-setup@v4
|
||
- uses: actions/setup-node@v4
|
||
- name: Install Wails
|
||
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||
- name: Install Inno Setup
|
||
run: choco install innosetup -y
|
||
- run: make vendor-sync
|
||
- run: make installer-windows VERSION=${{ github.ref_name }}
|
||
- uses: actions/upload-artifact@v4
|
||
with:
|
||
name: windows-installer
|
||
path: dist/*.exe
|
||
|
||
build-linux:
|
||
runs-on: ubuntu-22.04
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-go@v5
|
||
- uses: pnpm/action-setup@v4
|
||
- uses: actions/setup-node@v4
|
||
- name: Install Wails deps
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev \
|
||
libusb-1.0-0-dev
|
||
- name: Install Wails
|
||
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||
- name: Install appimagetool
|
||
run: |
|
||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||
chmod +x appimagetool-x86_64.AppImage
|
||
sudo mv appimagetool-x86_64.AppImage /usr/local/bin/appimagetool
|
||
- run: make vendor-sync
|
||
- run: make installer-linux VERSION=${{ github.ref_name }}
|
||
- uses: actions/upload-artifact@v4
|
||
with:
|
||
name: linux-installer
|
||
path: dist/*.AppImage
|
||
|
||
release:
|
||
needs: [build-macos, build-windows, build-linux]
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/download-artifact@v4
|
||
- uses: softprops/action-gh-release@v2
|
||
with:
|
||
files: |
|
||
macos-installer/*.dmg
|
||
windows-installer/*.exe
|
||
linux-installer/*.AppImage
|
||
```
|
||
|
||
### 3.2 第一版可以不用 CI
|
||
|
||
MVP 階段可以**純手動**:
|
||
- 在自己的 Mac 上 `make installer-macos`
|
||
- 用 UTM / Parallels VM 跑 Windows 11 `make installer-windows`
|
||
- 用 Docker / multipass VM 跑 Ubuntu 22.04 `make installer-linux`
|
||
|
||
CI 可以等 M5 之後再做。
|
||
|
||
---
|
||
|
||
## 4. 版本號管理
|
||
|
||
單一來源:Git tag `v1.0.0` → 透過 Makefile `VERSION` 變數傳入:
|
||
- Go binary:`-ldflags "-X main.Version=$(VERSION)"`
|
||
- Wails: 讀取 `wails.json` 的 `info.productVersion`
|
||
- Installer 檔名:`visiona-local-v1.0.0-*`
|
||
|
||
**建議版本策略:**
|
||
- `v0.x.x`:MVP / alpha
|
||
- `v1.0.0`:第一個對內部釋出的穩定版
|
||
- 每個 milestone 對應一個 minor(M1→v0.1.0、M2→v0.2.0、M6→v1.0.0)
|
||
|
||
---
|
||
|
||
## 5. 開發迭代速度優化
|
||
|
||
- **dev 模式不用 payload**:`make dev` 直接 `go run` server + `pnpm dev` 前端,不過 Wails app
|
||
- **dev 模式用系統 Python**:`--python-mode=system` 避免每次都要解壓內嵌 runtime
|
||
- **前端改動不用重 build server**:`go run main.go --dev` 時 `web/out` 不啟用,前端直接連 3000 port
|