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>
16 KiB
16 KiB
Build Pipeline — visionA-local
Makefile、build 腳本、CI(選配)、跨平台建置策略。無簽章。
1. 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 用)
#!/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 片段:
# 第三方依賴(由 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
# .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 / alphav1.0.0:第一個對內部釋出的穩定版- 每個 milestone 對應一個 minor(M1→v0.1.0、M2→v0.2.0、M6→v1.0.0)
5. 開發迭代速度優化
- dev 模式不用 payload:
make dev直接go runserver +pnpm dev前端,不過 Wails app - dev 模式用系統 Python:
--python-mode=system避免每次都要解壓內嵌 runtime - 前端改動不用重 build server:
go run main.go --dev時web/out不啟用,前端直接連 3000 port