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

16 KiB
Raw Permalink Blame History

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 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 用)

#!/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 片段:

# 第三方依賴(由 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.jsoninfo.productVersion
  • Installer 檔名:visiona-local-v1.0.0-*

建議版本策略:

  • v0.x.xMVP / alpha
  • v1.0.0:第一個對內部釋出的穩定版
  • 每個 milestone 對應一個 minorM1→v0.1.0、M2→v0.2.0、M6→v1.0.0

5. 開發迭代速度優化

  • dev 模式不用 payloadmake dev 直接 go run server + pnpm dev 前端,不過 Wails app
  • dev 模式用系統 Python--python-mode=system 避免每次都要解壓內嵌 runtime
  • 前端改動不用重 build servergo run main.go --devweb/out 不啟用,前端直接連 3000 port