jim800121chen c54f16fca0 Initial commit: visionA monorepo with local-tool subproject
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>
2026-04-11 22:10:38 +08:00

414 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 runtimepython-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/
# WindowsBtbN 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 對應一個 minorM1→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