jim800121chen c1d2e2ddaa feat(local-tool): macOS DMG 美化(create-dmg 背景圖 + Applications 捷徑)
需求:Mac 端 installer 體驗類比 Windows .exe — 進 DMG 就看到漂亮的視窗
背景 + 拖到 Applications 的視覺引導。

實作:
- installer/macos/ 新資料夾
  - make-dmg-background.py:動態生成 640×400 深色背景,配色對齊 Wails
    控制台 splash(#111827→#0B0F19 漸層 + #38BDF8 accent)
  - background.png + background@2x.png(1x + 2x Retina)
- Makefile dmg 拆三 target:
  - dmg:auto-detect,有 create-dmg 走 fancy,沒有 fallback plain(CI 無痛)
  - dmg-fancy:強制美化版(需 `brew install create-dmg`)
  - dmg-plain:原 hdiutil UDZO(保留為 fallback)
- Windows / Linux 流程零改動

驗證:
- `make dmg-fancy` 產出 157MB DMG,mount 後內容:app + Applications 捷徑
  + .background/background.png + .DS_Store(視窗樣式)
- `hdiutil verify` 通過

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 01:11:22 +08:00

672 lines
34 KiB
Makefile
Raw Permalink 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.

# visionA-local — Makefile骨架M1-1
#
# 這份 Makefile 目前所有 targets 都只是 placeholder實際內容會在後續任務逐步填入
# - M1-2 複製 server core
# - M1-4 複製 frontend
# - M1-8 build-server / build-frontend 實作
# - M1-9 visiona-local (Wails) shell
# - M1-11 payload-macos
# - M1-12 installer-macos (dmg)
# - M2+ Windows / Linux / CI
#
# 詳見 .autoflow/04-architecture/build-pipeline.md
SHELL := /bin/bash
VERSION ?= v0.1.0-dev
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
OS := $(shell uname -s | tr A-Z a-z)
DIST := dist
PAYLOAD := visiona-local/payload
.PHONY: help \
vendor-sync vendor-python vendor-wheels vendor-ffmpeg vendor-ffmpeg-macos-build \
vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows \
vendor-python-linux vendor-wheels-linux vendor-ffmpeg-linux \
server build-server \
frontend build-frontend build-embed \
payload payload-macos payload-windows payload-linux \
stage-macos stage-windows \
wails-macos wails-windows wails-linux \
dmg exe exe-only _run-iscc appimage \
dev test lint fmt \
clean clean-all clean-build-exe clean-build-dmg clean-build-appimage
# ── 幫助 ───────────────────────────────────────────────────────────
help: ## 列出所有 make targets
@echo "visionA-local — available targets (M1-1 skeleton)"
@echo ""
@echo " 依賴同步:"
@echo " vendor-sync 下載 python-build-standalone / wheels / ffmpeg → vendor/"
@echo ""
@echo " Build單元"
@echo " server build Go server binary (→ dist/visiona-local-server)"
@echo " frontend pnpm build Next.js 靜態產物 (→ frontend/out)"
@echo ""
@echo " Payload 準備:"
@echo " payload-macos stage macOS payload → visiona-local/payload/"
@echo " payload-windows stage Windows payload"
@echo " payload-linux stage Linux payload"
@echo ""
@echo " Wails 應用 build"
@echo " wails-macos wails build darwin/amd64"
@echo " wails-windows wails build windows/amd64"
@echo " wails-linux wails build linux/amd64"
@echo ""
@echo " 安裝檔打包:"
@echo " dmg macOS .dmgdmgbuild"
@echo " exe Windows .exeInno Setup"
@echo " appimage Linux .AppImage"
@echo ""
@echo " 工具:"
@echo " clean 清除 dist/ payload/"
@echo ""
@echo "Note: 目前所有 target 都是 placeholder只印 TODO尚未實作。"
# ── 依賴同步 ───────────────────────────────────────────────────────
PYTHON_VERSION := 3.12.9
PBS_RELEASE := 20250317
PBS_URL_DARWIN := https://github.com/astral-sh/python-build-standalone/releases/download/$(PBS_RELEASE)/cpython-$(PYTHON_VERSION)+$(PBS_RELEASE)-x86_64-apple-darwin-install_only.tar.gz
# ── ffmpegLGPL v3方案 B 混合)──
# v2 TDD §2.2macOS 自 build decoder-only~20 MBcommit 到 vendor/ffmpeg/macos/
# Windows / Linux 用 BtbN 的 n7.1 LGPL build。
FFMPEG_VERSION := n7.1
FFMPEG_SRC_URL := https://github.com/FFmpeg/FFmpeg/archive/refs/tags/$(FFMPEG_VERSION).tar.gz
# sha256 於第一次 build 時由 `make vendor-ffmpeg-macos-build` 計算後填入 vendor/ffmpeg/macos/BUILD.md
# 之後每次 build 使用此值做 integrity check下方 Makefile 變數亦同步更新)。
FFMPEG_SRC_SHA256 := 7ddad2d992bd250a6c56053c26029f7e728bebf0f37f80cf3f8a0e6ec706431a
vendor-sync: vendor-python vendor-wheels vendor-ffmpeg ## 下載所有第三方依賴到 vendor/(不進 git第三輪決策 Q-D=D2
@echo "==> vendor-sync 完成"
vendor-python: ## 下載 python-build-standalone tarball → vendor/python/darwin/
@mkdir -p vendor/python/darwin
@if [ ! -f vendor/python/darwin/python.tar.gz ]; then \
echo "==> 下載 python-build-standalone $(PYTHON_VERSION)+$(PBS_RELEASE) (macOS x86_64 install_only)..."; \
curl -fL -o vendor/python/darwin/python.tar.gz "$(PBS_URL_DARWIN)"; \
echo "==> tarball 大小:$$(du -sh vendor/python/darwin/python.tar.gz | cut -f1)"; \
else \
echo "==> python tarball 已存在,跳過下載 ($$(du -sh vendor/python/darwin/python.tar.gz | cut -f1))"; \
fi
vendor-wheels: ## 同步 wheels → vendor/wheels/darwin/(內部 wheel 從 visiona-local/wheels 複製,公開相依用 pip download
@mkdir -p vendor/wheels/darwin
@echo "==> 同步內部 wheelsKneronPLUS 等)..."
@if [ -d visiona-local/wheels/macos ]; then \
cp visiona-local/wheels/macos/*.whl vendor/wheels/darwin/ 2>/dev/null || true; \
fi
@echo "==> 從 PyPI 下載公開相依 wheels (cp312, macosx_x86_64)..."
@pip3 download \
--only-binary=:all: \
--platform macosx_10_9_x86_64 \
--platform macosx_11_0_x86_64 \
--platform macosx_12_0_x86_64 \
--python-version 3.12 \
--implementation cp \
--dest vendor/wheels/darwin \
numpy opencv-python-headless pyusb requests || echo "WARN: pip download 部分失敗(詳見上方訊息)"
@echo "==> wheels 總覽:"
@ls -1 vendor/wheels/darwin/*.whl 2>/dev/null | wc -l | xargs -I{} echo " 共 {} 個 wheel"
@du -sh vendor/wheels/darwin
vendor-ffmpeg: ## macOSLGPL v3 ffmpeg + ffprobe 已 commit 到 vendor/ffmpeg/macos/,本 target 只驗證存在 + LGPL 合規
@if [ ! -f vendor/ffmpeg/macos/ffmpeg ]; then \
echo "❌ vendor/ffmpeg/macos/ffmpeg 不存在。"; \
echo " 第一次 build 請執行make vendor-ffmpeg-macos-build"; \
echo " (只需要在升級 ffmpeg 版本時跑一次;平常 clone repo 後 binary 已在 git 內)"; \
exit 1; \
fi
@if [ ! -f vendor/ffmpeg/macos/ffprobe ]; then \
echo "❌ vendor/ffmpeg/macos/ffprobe 不存在。"; \
echo " 請執行make vendor-ffmpeg-macos-build"; \
exit 1; \
fi
@echo "==> vendor/ffmpeg/macos/ffmpeg 存在:$$(du -h vendor/ffmpeg/macos/ffmpeg | cut -f1)"
@echo "==> vendor/ffmpeg/macos/ffprobe 存在:$$(du -h vendor/ffmpeg/macos/ffprobe | cut -f1)"
@if vendor/ffmpeg/macos/ffmpeg -version 2>&1 | grep -qE -- '--enable-gpl|libx264|libx265'; then \
echo "❌ LGPL 驗證失敗binary 含 --enable-gpl / libx264 / libx265"; \
exit 1; \
fi
@echo "==> LGPL 驗證通過(無 --enable-gpl / libx264 / libx265"
# 只有升級 ffmpeg 版本時才跑此 targetbinary 產出後 commit 到 gitv2 TDD R5-6b
# 需要的系統依賴macOS
# brew install pkg-config nasm # 或 yasm
vendor-ffmpeg-macos-build: ## macOS從源碼 build LGPL v3 decoder-only ffmpeg + ffprobe升級時才跑~15 分鐘)
@if [ "$$(uname -s)" != "Darwin" ]; then \
echo "❌ vendor-ffmpeg-macos-build 只能在 macOS 上跑"; exit 1; \
fi
@command -v pkg-config >/dev/null 2>&1 || { echo "❌ 需要 pkg-configbrew install pkg-config"; exit 1; }
@command -v nasm >/dev/null 2>&1 || command -v yasm >/dev/null 2>&1 || { echo "❌ 需要 nasm 或 yasmbrew install nasm"; exit 1; }
@mkdir -p vendor/ffmpeg/macos build/ffmpeg-macos
@echo "==> 下載 ffmpeg source $(FFMPEG_VERSION)..."
curl -fL -o build/ffmpeg-macos/source.tar.gz "$(FFMPEG_SRC_URL)"
@echo "==> 驗證 source tarball sha256..."
@echo "$(FFMPEG_SRC_SHA256) build/ffmpeg-macos/source.tar.gz" | shasum -a 256 -c || { \
echo "❌ sha256 不符,請更新 Makefile 中的 FFMPEG_SRC_SHA256 或檢查來源"; \
echo " 實際 sha256$$(shasum -a 256 build/ffmpeg-macos/source.tar.gz | awk '{print $$1}')"; \
exit 1; }
@rm -rf build/ffmpeg-macos/src build/ffmpeg-macos/install
@mkdir -p build/ffmpeg-macos/src
tar xzf build/ffmpeg-macos/source.tar.gz -C build/ffmpeg-macos/src --strip-components=1
@echo "==> configuredecoder-only LGPL v3..."
cd build/ffmpeg-macos/src && ./configure \
--prefix="$$(pwd)/../install" \
--enable-version3 \
--disable-debug \
--disable-doc \
--disable-ffplay \
--disable-network \
--disable-autodetect \
--disable-shared \
--enable-static \
--disable-everything \
--enable-small \
--enable-protocol=file,pipe \
--enable-demuxer=mov,avi,mpegps,mpegts,matroska,image2 \
--enable-decoder=h264,hevc,mpeg1video,mpeg2video,mpeg4,mjpeg,prores,vp8,vp9,aac,mp2,mp3,pcm_s16le,pcm_s16be \
--enable-parser=h264,hevc,mpeg4video,mpegaudio,aac \
--enable-filter=scale,format,fps,null,anull \
--enable-muxer=image2pipe,image2,null \
--enable-encoder=mjpeg \
--enable-swscale \
--enable-swresample \
--extra-cflags="-arch x86_64 -mmacosx-version-min=10.15" \
--extra-ldflags="-arch x86_64 -mmacosx-version-min=10.15 -Wl,-search_paths_first" \
--arch=x86_64 \
--target-os=darwin \
--cc="clang -arch x86_64"
cd build/ffmpeg-macos/src && make -j$$(sysctl -n hw.ncpu)
cd build/ffmpeg-macos/src && make install
@echo "==> 複製 ffmpeg + ffprobe 到 vendor/ffmpeg/macos/..."
cp build/ffmpeg-macos/install/bin/ffmpeg vendor/ffmpeg/macos/ffmpeg
cp build/ffmpeg-macos/install/bin/ffprobe vendor/ffmpeg/macos/ffprobe
@strip -S -x vendor/ffmpeg/macos/ffmpeg
@strip -S -x vendor/ffmpeg/macos/ffprobe
@chmod +x vendor/ffmpeg/macos/ffmpeg vendor/ffmpeg/macos/ffprobe
@echo "==> ad-hoc 簽章..."
codesign --force --sign - vendor/ffmpeg/macos/ffmpeg
codesign --force --sign - vendor/ffmpeg/macos/ffprobe
@echo "==> 驗證授權configuration line 不含 --enable-gpl / libx264 / libx265..."
@if vendor/ffmpeg/macos/ffmpeg -version 2>&1 | grep -E -- '--enable-gpl|libx264|libx265'; then \
echo "❌ LGPL 驗證失敗build 不該出現 gpl / x264 / x265"; exit 1; \
fi
@echo "==> 複製 COPYING.LGPLv3 到 vendor/ffmpeg/macos/..."
cp build/ffmpeg-macos/src/COPYING.LGPLv3 vendor/ffmpeg/macos/COPYING.LGPLv3
@echo "==> ffmpeg 大小:$$(du -h vendor/ffmpeg/macos/ffmpeg | cut -f1)"
@echo "==> ffprobe 大小:$$(du -h vendor/ffmpeg/macos/ffprobe | cut -f1)"
@echo ""
@echo "✅ macOS LGPL ffmpeg build 完成。接下來請:"
@echo " 1) 更新 vendor/ffmpeg/macos/BUILD.md 的 Binary sha256 區塊"
@echo " 2) git add vendor/ffmpeg/macos/{ffmpeg,ffprobe,COPYING.LGPLv3,BUILD.md}"
# ── Build單元 ──────────────────────────────────────────────────
server: build-server ## alias for build-server
build-server: build-embed ## build Go server binary → dist/visiona-local-server會先 build frontend + embed
@mkdir -p $(DIST)
cd server && go build -o ../$(DIST)/visiona-local-server .
@echo "built: $(DIST)/visiona-local-server"
build-server-windows: build-embed ## 交叉/原生 build Windows server → payload/windows/bin/visiona-local-server.exe
@mkdir -p payload/windows/bin
cd server && GOOS=windows GOARCH=amd64 go build -o ../payload/windows/bin/visiona-local-server.exe .
@echo "built: payload/windows/bin/visiona-local-server.exe"
build-server-linux: build-embed ## 交叉/原生 build Linux server → payload/linux/bin/visiona-local-server
@mkdir -p payload/linux/bin
cd server && GOOS=linux GOARCH=amd64 go build -o ../payload/linux/bin/visiona-local-server .
@echo "built: payload/linux/bin/visiona-local-server"
frontend: build-frontend ## alias for build-frontend
build-frontend: ## pnpm build → frontend/out/
@echo "==> pnpm build frontend..."
cd frontend && pnpm install --frozen-lockfile && pnpm build
@echo "==> frontend/out 完成:"
@du -sh frontend/out
build-embed: build-frontend ## 同步 frontend/out → server/web/out 供 go:embed
@echo "==> 同步 frontend/out → server/web/out..."
rm -rf server/web/out
mkdir -p server/web/out
cp -R frontend/out/. server/web/out/
@du -sh server/web/out
# ── Payload 準備 ───────────────────────────────────────────────────
payload: payload-$(OS) ## 依當前 OS 準備 payload
payload-macos: build-server vendor-python vendor-wheels vendor-ffmpeg ## 準備 macOS payload → payload/darwin/(含 python runtime + wheels + ffmpeg
@echo "==> 建立 macOS payload (binary + models + scripts + python + wheels + ffmpeg + ffprobe)..."
rm -rf payload/darwin
mkdir -p payload/darwin/bin payload/darwin/data payload/darwin/scripts payload/darwin/python payload/darwin/wheels
cp dist/visiona-local-server payload/darwin/bin/
cp vendor/ffmpeg/macos/ffmpeg payload/darwin/bin/
cp vendor/ffmpeg/macos/ffprobe payload/darwin/bin/
cp vendor/ffmpeg/macos/COPYING.LGPLv3 payload/darwin/bin/ffmpeg-COPYING.LGPLv3
chmod +x payload/darwin/bin/ffmpeg payload/darwin/bin/ffprobe
cp -R server/data/* payload/darwin/data/
cp -R server/scripts/* payload/darwin/scripts/
cp vendor/python/darwin/python.tar.gz payload/darwin/python/
@cp vendor/wheels/darwin/*.whl payload/darwin/wheels/ 2>/dev/null || true
@echo "==> macOS payload 完成:"
@du -sh payload/darwin
@echo ""
@echo "payload/darwin 結構:"
@find payload/darwin -maxdepth 2 -type d | sed 's|^| |'
@echo ""
@echo "payload/darwin/python"
@ls -lh payload/darwin/python
@echo "payload/darwin/wheels"
@ls -1 payload/darwin/wheels | head -20
stage-macos: payload-macos ## 將 payload/darwin/ 放到 Wails build/darwin/Resources/
@echo "==> 放置 payload 到 Wails build/darwin/Resources..."
rm -rf visiona-local/build/darwin/Resources
mkdir -p visiona-local/build/darwin/Resources
cp -R payload/darwin/. visiona-local/build/darwin/Resources/
@echo "==> stage 完成:"
@du -sh visiona-local/build/darwin/Resources
# ── M4Windows vendor + payload + wails + exe ─────────────────────
# 注意wails-windows 與 exe 必須在 Windows runner 上跑;在 macOS 上會明確 fail。
# payload-windows / vendor-*-windows 是 curl 下載跨平台可跑server.exe 步驟除外)。
PBS_URL_WINDOWS := https://github.com/astral-sh/python-build-standalone/releases/download/$(PBS_RELEASE)/cpython-$(PYTHON_VERSION)+$(PBS_RELEASE)-x86_64-pc-windows-msvc-install_only.tar.gz
# LGPL v3 buildBtbN n7.1 穩定分支,含 LGPL-safe extra libs— v2 TDD §3
FFMPEG_URL_WINDOWS := https://github.com/BtbN/FFmpeg-Builds/releases/latest/download/ffmpeg-n7.1-latest-win64-lgpl-7.1.zip
vendor-python-windows: ## 下載 python-build-standalone Windows x86_64 → vendor/python/windows/
@mkdir -p vendor/python/windows
@if [ ! -f vendor/python/windows/python.tar.gz ]; then \
echo "==> 下載 python-build-standalone $(PYTHON_VERSION)+$(PBS_RELEASE) (Windows x86_64 install_only)..."; \
curl -fL -o vendor/python/windows/python.tar.gz "$(PBS_URL_WINDOWS)"; \
echo "==> tarball 大小:$$(du -sh vendor/python/windows/python.tar.gz | cut -f1)"; \
else \
echo "==> python (Windows) tarball 已存在,跳過 ($$(du -sh vendor/python/windows/python.tar.gz | cut -f1))"; \
fi
vendor-wheels-windows: ## 同步 Windows wheels → vendor/wheels/windows/
@mkdir -p vendor/wheels/windows
@echo "==> 同步內部 wheels (Windows, KneronPLUS 等)..."
@if [ -d visiona-local/wheels/windows ]; then \
cp visiona-local/wheels/windows/*.whl vendor/wheels/windows/ 2>/dev/null || true; \
fi
@echo "==> 從 PyPI 下載公開相依 wheels (cp312, win_amd64)..."
@PY=""; \
for candidate in "$$VISIONA_PYTHON" "py -3" python3 python; do \
[ -z "$$candidate" ] && continue; \
resolved=$$(command -v $${candidate%% *} 2>/dev/null || true); \
case "$$resolved" in *WindowsApps*) continue ;; esac; \
if $$candidate --version >/dev/null 2>&1; then PY="$$candidate"; break; fi; \
done; \
if [ -z "$$PY" ]; then \
echo "WARN: 找不到真實 pythonWindowsApps stub 不算),跳過 PyPI 下載(僅使用內部 wheels"; \
else \
echo "==> 使用 $$PY -m pip"; \
$$PY -m pip download \
--only-binary=:all: \
--platform win_amd64 \
--python-version 3.12 \
--implementation cp \
--dest vendor/wheels/windows \
numpy opencv-python-headless pyusb requests || echo "WARN: pip download 部分失敗(詳見上方訊息)"; \
fi
@echo "==> Windows wheels 總覽:"
@ls -1 vendor/wheels/windows/*.whl 2>/dev/null | wc -l | xargs -I{} echo " 共 {} 個 wheel"
@du -sh vendor/wheels/windows 2>/dev/null || true
vendor-ffmpeg-windows: ## 下載 ffmpeg Windows LGPL v3 build (n7.1) → vendor/ffmpeg/windows/
@mkdir -p vendor/ffmpeg/windows
@if [ -f vendor/ffmpeg/windows/ffmpeg.exe ] && [ -f vendor/ffmpeg/windows/ffprobe.exe ]; then \
echo "==> ffmpeg.exe + ffprobe.exe 已存在,跳過"; \
else \
echo "==> 下載 BtbN LGPL ffmpeg (Windows, n7.1)..."; \
curl -fL -o vendor/ffmpeg/windows/ffmpeg-win.zip "$(FFMPEG_URL_WINDOWS)"; \
PY=""; \
for candidate in "$$VISIONA_PYTHON" "py -3" python3 python; do \
[ -z "$$candidate" ] && continue; \
resolved=$$(command -v $${candidate%% *} 2>/dev/null || true); \
case "$$resolved" in *WindowsApps*) continue ;; esac; \
if $$candidate --version >/dev/null 2>&1; then PY="$$candidate"; break; fi; \
done; \
if [ -z "$$PY" ]; then echo "ERROR: 需要真實 python 來解壓 zipWindowsApps stub 無法使用)"; exit 1; fi; \
echo "==> 使用 $$PY 解壓 ffmpeg zip取出 ffmpeg.exe / ffprobe.exe / LICENSE.txt"; \
$$PY -c "import zipfile, os; z=zipfile.ZipFile('vendor/ffmpeg/windows/ffmpeg-win.zip'); \
wanted=['/bin/ffmpeg.exe','/bin/ffprobe.exe','/LICENSE.txt','/COPYING.LGPLv3']; \
members=[n for n in z.namelist() if any(n.endswith(w) for w in wanted)]; \
assert any(n.endswith('/bin/ffmpeg.exe') for n in members), 'ffmpeg.exe not found in zip'; \
assert any(n.endswith('/bin/ffprobe.exe') for n in members), 'ffprobe.exe not found in zip'; \
os.makedirs('vendor/ffmpeg/windows', exist_ok=True); \
[open('vendor/ffmpeg/windows/'+m.rsplit('/',1)[1],'wb').write(z.read(m)) for m in members]; \
print('extracted:', [m.rsplit('/',1)[1] for m in members])" || { echo "ERROR: python 解壓失敗"; exit 1; }; \
rm -f vendor/ffmpeg/windows/ffmpeg-win.zip; \
[ -f vendor/ffmpeg/windows/ffmpeg.exe ] || { echo "ERROR: ffmpeg.exe 沒被寫出"; exit 1; }; \
[ -f vendor/ffmpeg/windows/ffprobe.exe ] || { echo "ERROR: ffprobe.exe 沒被寫出"; exit 1; }; \
echo "==> ffmpeg.exe 大小:$$(du -h vendor/ffmpeg/windows/ffmpeg.exe | cut -f1)"; \
echo "==> ffprobe.exe 大小:$$(du -h vendor/ffmpeg/windows/ffprobe.exe | cut -f1)"; \
fi
payload-windows: build-server-windows vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows ## 準備 Windows payload → payload/windows/
@echo "==> 建立 Windows payload (binary + models + scripts + python + wheels + ffmpeg)..."
@# 注意:不 rm -rf payload/windows因為 build-server-windows 已先把 .exe 放進去
mkdir -p payload/windows/bin payload/windows/data payload/windows/scripts payload/windows/python payload/windows/wheels
@if [ ! -f payload/windows/bin/visiona-local-server.exe ]; then \
echo "!! ERROR: payload/windows/bin/visiona-local-server.exe 不存在build-server-windows 可能失敗 !!"; \
exit 1; \
fi
cp vendor/ffmpeg/windows/ffmpeg.exe payload/windows/bin/
cp vendor/ffmpeg/windows/ffprobe.exe payload/windows/bin/
@# LGPL 授權條款BtbN build 自帶 LICENSE.txtCOPYING.LGPLv3 不一定在壓縮檔內,失敗不致命)
@cp vendor/ffmpeg/windows/LICENSE.txt payload/windows/bin/ffmpeg-LICENSE.txt 2>/dev/null || true
@cp vendor/ffmpeg/windows/COPYING.LGPLv3 payload/windows/bin/ffmpeg-COPYING.LGPLv3 2>/dev/null || true
cp -R server/data/. payload/windows/data/
cp -R server/scripts/. payload/windows/scripts/
cp vendor/python/windows/python.tar.gz payload/windows/python/
@cp vendor/wheels/windows/*.whl payload/windows/wheels/ 2>/dev/null || true
@echo "==> Windows payload 完成:"
@du -sh payload/windows
@echo ""
@echo "payload/windows 結構:"
@find payload/windows -maxdepth 2 -type d | sed 's|^| |'
stage-windows: payload-windows ## 將 payload/windows/ 放到 Wails build/windows/Resources/
@echo "==> 放置 payload 到 Wails build/windows/Resources..."
rm -rf visiona-local/build/windows/Resources
mkdir -p visiona-local/build/windows/Resources
cp -R payload/windows/. visiona-local/build/windows/Resources/
@echo "==> stage 完成:"
@du -sh visiona-local/build/windows/Resources
# ── M5Linux vendor + payload + wails + AppImage ──────────────────
# 注意wails-linux 與 appimage 必須在 Linux runner 上跑;在 macOS 上會明確 fail。
# payload-linux / vendor-*-linux 是 curl 下載跨平台可跑server 步驟除外)。
PBS_URL_LINUX := https://github.com/astral-sh/python-build-standalone/releases/download/$(PBS_RELEASE)/cpython-$(PYTHON_VERSION)+$(PBS_RELEASE)-x86_64-unknown-linux-gnu-install_only.tar.gz
# LGPL v3 buildBtbN n7.1 穩定分支)— v2 TDD §4
FFMPEG_URL_LINUX := https://github.com/BtbN/FFmpeg-Builds/releases/latest/download/ffmpeg-n7.1-latest-linux64-lgpl-7.1.tar.xz
vendor-python-linux: ## 下載 python-build-standalone Linux x86_64 → vendor/python/linux/
@mkdir -p vendor/python/linux
@if [ ! -f vendor/python/linux/python.tar.gz ]; then \
echo "==> 下載 python-build-standalone $(PYTHON_VERSION)+$(PBS_RELEASE) (Linux x86_64 install_only)..."; \
curl -fL -o vendor/python/linux/python.tar.gz "$(PBS_URL_LINUX)"; \
echo "==> tarball 大小:$$(du -sh vendor/python/linux/python.tar.gz | cut -f1)"; \
else \
echo "==> python (Linux) tarball 已存在,跳過 ($$(du -sh vendor/python/linux/python.tar.gz | cut -f1))"; \
fi
vendor-wheels-linux: ## 同步 Linux wheels → vendor/wheels/linux/
@mkdir -p vendor/wheels/linux
@echo "==> 同步內部 wheels (Linux, KneronPLUS 等)..."
@if [ -d visiona-local/wheels/linux ]; then \
cp visiona-local/wheels/linux/*.whl vendor/wheels/linux/ 2>/dev/null || true; \
fi
@echo "==> 從 PyPI 下載公開相依 wheels (cp312, manylinux2014_x86_64)..."
@pip3 download \
--only-binary=:all: \
--platform manylinux2014_x86_64 \
--python-version 3.12 \
--implementation cp \
--dest vendor/wheels/linux \
numpy opencv-python-headless pyusb requests || echo "WARN: pip download 部分失敗(詳見上方訊息)"
@echo "==> Linux wheels 總覽:"
@ls -1 vendor/wheels/linux/*.whl 2>/dev/null | wc -l | xargs -I{} echo " 共 {} 個 wheel"
@du -sh vendor/wheels/linux 2>/dev/null || true
vendor-ffmpeg-linux: ## 下載 ffmpeg Linux LGPL v3 build (n7.1) → vendor/ffmpeg/linux/
@mkdir -p vendor/ffmpeg/linux
@if [ -f vendor/ffmpeg/linux/ffmpeg ] && [ -f vendor/ffmpeg/linux/ffprobe ]; then \
echo "==> ffmpeg + ffprobe (Linux) 已存在,跳過"; \
else \
echo "==> 下載 BtbN LGPL ffmpeg (Linux, n7.1)..."; \
curl -fL -o /tmp/ffmpeg-linux.tar.xz "$(FFMPEG_URL_LINUX)"; \
rm -rf /tmp/ffmpeg-linux-extract; \
mkdir -p /tmp/ffmpeg-linux-extract; \
tar xf /tmp/ffmpeg-linux.tar.xz -C /tmp/ffmpeg-linux-extract --strip-components=1; \
cp /tmp/ffmpeg-linux-extract/bin/ffmpeg vendor/ffmpeg/linux/; \
cp /tmp/ffmpeg-linux-extract/bin/ffprobe vendor/ffmpeg/linux/; \
cp /tmp/ffmpeg-linux-extract/LICENSE.txt vendor/ffmpeg/linux/LICENSE.txt 2>/dev/null || true; \
chmod +x vendor/ffmpeg/linux/ffmpeg vendor/ffmpeg/linux/ffprobe; \
rm -rf /tmp/ffmpeg-linux* ; \
echo "==> ffmpeg (Linux) 大小:$$(du -h vendor/ffmpeg/linux/ffmpeg | cut -f1)"; \
echo "==> ffprobe (Linux) 大小:$$(du -h vendor/ffmpeg/linux/ffprobe | cut -f1)"; \
fi
payload-linux: build-server-linux vendor-python-linux vendor-wheels-linux vendor-ffmpeg-linux ## 準備 Linux payload → payload/linux/
@echo "==> 建立 Linux payload (binary + models + scripts + python + wheels + ffmpeg)..."
mkdir -p payload/linux/bin payload/linux/data payload/linux/scripts payload/linux/python payload/linux/wheels
@if [ ! -f payload/linux/bin/visiona-local-server ]; then \
echo "!! ERROR: payload/linux/bin/visiona-local-server 不存在build-server-linux 可能失敗 !!"; \
exit 1; \
fi
@chmod +x payload/linux/bin/visiona-local-server
@cp vendor/ffmpeg/linux/ffmpeg payload/linux/bin/ 2>/dev/null && chmod +x payload/linux/bin/ffmpeg || echo "!! WARN: ffmpeg 缺失"
@cp vendor/ffmpeg/linux/ffprobe payload/linux/bin/ 2>/dev/null && chmod +x payload/linux/bin/ffprobe || echo "!! WARN: ffprobe 缺失"
@cp vendor/ffmpeg/linux/LICENSE.txt payload/linux/bin/ffmpeg-LICENSE.txt 2>/dev/null || true
@if [ -d server/data ]; then cp -R server/data/. payload/linux/data/; fi
@if [ -d server/scripts ]; then cp -R server/scripts/. payload/linux/scripts/; fi
@if [ ! -f vendor/python/linux/python.tar.gz ]; then \
echo "!! ERROR: vendor/python/linux/python.tar.gz 不存在vendor-python-linux 應該已先跑過 !!"; \
exit 1; \
fi
@cp vendor/python/linux/python.tar.gz payload/linux/python/
@cp vendor/wheels/linux/*.whl payload/linux/wheels/ 2>/dev/null || true
@wheel_count=$$(ls -1 payload/linux/wheels/*.whl 2>/dev/null | wc -l); \
if [ "$$wheel_count" -lt 4 ]; then \
echo "!! ERROR: payload/linux/wheels 只找到 $$wheel_count 個 wheel預期至少 4 個numpy / opencv / pyusb / kp"; \
echo " 請檢查 vendor/wheels/linux/ 並重跑 make vendor-wheels-linux"; \
exit 1; \
fi
@echo "==> Linux payload 完成:包含 $$(ls payload/linux/wheels/*.whl 2>/dev/null | wc -l) 個 wheel + python tarball"
@du -sh payload/linux 2>/dev/null || true
@echo ""
@echo "payload/linux 結構:"
@find payload/linux -maxdepth 2 -type d 2>/dev/null | sed 's|^| |'
# ── Wails build ────────────────────────────────────────────────────
wails-macos: stage-macos ## wails build darwin/amd64 → visiona-local/build/bin/visiona-local.app
cd visiona-local && wails build -platform darwin/amd64 -clean
cp -R visiona-local/build/darwin/Resources/bin \
visiona-local/build/darwin/Resources/data \
visiona-local/build/darwin/Resources/scripts \
visiona-local/build/darwin/Resources/python \
visiona-local/build/darwin/Resources/wheels \
visiona-local/build/bin/visiona-local.app/Contents/Resources/
codesign --force --deep --sign - visiona-local/build/bin/visiona-local.app
codesign --verify --verbose visiona-local/build/bin/visiona-local.app
@du -sh visiona-local/build/bin/visiona-local.app
wails-windows: stage-windows ## ⚠️ 必須在 Windows runner 上執行wails build -platform windows/amd64
@case "$$(uname -s 2>/dev/null)" in \
MINGW*|CYGWIN*|MSYS*) : ;; \
*) \
echo ""; \
echo "❌ wails-windows 只能在 Windows runner 上 build偵測到 $$(uname -s)"; \
echo " 請在 Windows 機器上執行此 target或用 CI 的 Windows runner"; \
echo " 若只是要檢查前置步驟,可單獨跑 make stage-windows"; \
echo ""; \
exit 1 ;; \
esac
cd visiona-local && wails build -platform windows/amd64 -clean
@du -sh visiona-local/build/bin/visiona-local.exe
wails-linux: payload-linux ## ⚠️ 必須在 Linux runner 上執行wails build -platform linux/amd64
@if [ "$$(uname -s)" != "Linux" ]; then \
echo ""; \
echo "❌ wails-linux 只能在 Linux 上 build偵測到 $$(uname -s)"; \
echo " 請在 Linux x86_64 runnerGitHub Actions ubuntu-latest 即可)執行"; \
echo " 若只是要檢查前置步驟,可單獨跑 make payload-linux"; \
echo ""; \
exit 1; \
fi
# webkit2gtk-4.0 從 Ubuntu 22.10+ / Debian 12+ 起被 webkit2gtk-4.1 取代。
# 偵測 pkg-config 哪個存在:優先用 4.1(加 -tags webkit2_414.0 則
# 不加 tagWails 預設)。
@if pkg-config --exists webkit2gtk-4.1 2>/dev/null; then \
echo "==> webkit2gtk-4.1 detected → wails build with -tags webkit2_41"; \
cd visiona-local && wails build -platform linux/amd64 -clean -tags webkit2_41; \
elif pkg-config --exists webkit2gtk-4.0 2>/dev/null; then \
echo "==> webkit2gtk-4.0 detected → wails build (default)"; \
cd visiona-local && wails build -platform linux/amd64 -clean; \
else \
echo ""; \
echo "❌ 找不到 webkit2gtk-4.0 或 webkit2gtk-4.1 dev header"; \
echo " 請執行sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev"; \
echo " (舊版 Ubuntu 20.04 可改用 libwebkit2gtk-4.0-dev"; \
echo ""; \
exit 1; \
fi
@du -sh visiona-local/build/bin/visiona-local
# ── 安裝檔打包 ─────────────────────────────────────────────────────
dmg: wails-macos ## 美化 DMGcreate-dmg 有裝)或 plain DMGfallback→ dist/visiona-local.dmg
@mkdir -p $(DIST)
@rm -f $(DIST)/visiona-local.dmg
@if command -v create-dmg > /dev/null 2>&1; then \
$(MAKE) --no-print-directory dmg-fancy; \
else \
echo "⚠️ create-dmg 未安裝,使用 plain DMGhdiutil UDZO"; \
echo " 想要美化版本請執行brew install create-dmg"; \
$(MAKE) --no-print-directory dmg-plain; \
fi
@du -sh $(DIST)/visiona-local.dmg
@file $(DIST)/visiona-local.dmg
dmg-plain: ## hdiutil UDZO → dist/visiona-local.dmg無背景圖CI / fallback 用)
@mkdir -p $(DIST)
rm -f $(DIST)/visiona-local.dmg
hdiutil create -volname "visionA-local" \
-srcfolder visiona-local/build/bin/visiona-local.app \
-ov -format UDZO \
$(DIST)/visiona-local.dmg
dmg-fancy: ## create-dmg 美化版 → dist/visiona-local.dmg需 brew install create-dmg
@if [ ! -d visiona-local/build/bin/visiona-local.app ]; then \
echo "❌ visiona-local/build/bin/visiona-local.app 不存在,請先跑 make wails-macos"; exit 1; \
fi
@if ! command -v create-dmg > /dev/null 2>&1; then \
echo "❌ create-dmg 未安裝請執行brew install create-dmg"; exit 1; \
fi
@mkdir -p $(DIST)
rm -f $(DIST)/visiona-local.dmg
create-dmg \
--volname "visionA-local" \
--background installer/macos/background.png \
--window-pos 200 120 \
--window-size 640 400 \
--icon-size 128 \
--icon "visiona-local.app" 180 200 \
--app-drop-link 460 200 \
--hide-extension "visiona-local.app" \
--no-internet-enable \
$(DIST)/visiona-local.dmg \
visiona-local/build/bin/visiona-local.app
exe-only: ## 只跑 iscc 打包 installer前置產物必須已存在不重 build wails/payload
@if [ ! -f visiona-local/build/bin/visiona-local.exe ]; then \
echo "❌ visiona-local/build/bin/visiona-local.exe 不存在,請先跑 make wails-windows"; exit 1; \
fi
@if [ ! -f payload/windows/bin/visiona-local-server.exe ]; then \
echo "❌ payload/windows/bin/visiona-local-server.exe 不存在,請先跑 make payload-windows"; exit 1; \
fi
@$(MAKE) --no-print-directory _run-iscc
exe: wails-windows _run-iscc ## ⚠️ 必須在 Windows 上跑Inno Setup → dist/visiona-local-*-windows-x64.exe
_run-iscc:
@echo "DEBUG _run-iscc: ISCC='$$ISCC'"
@echo "DEBUG _run-iscc: PATH first entries: $$(echo $$PATH | tr ':' '\n' | head -5)"
@ISCC_BIN="$$ISCC"; \
if [ -z "$$ISCC_BIN" ]; then \
if command -v iscc > /dev/null 2>&1; then ISCC_BIN=iscc; \
elif command -v iscc.exe > /dev/null 2>&1; then ISCC_BIN=iscc.exe; \
else \
for p in "/c/Program Files (x86)/Inno Setup 6/ISCC.exe" \
"/c/Program Files/Inno Setup 6/ISCC.exe" \
"$$LOCALAPPDATA/Programs/Inno Setup 6/ISCC.exe" \
"$$USERPROFILE/AppData/Local/Programs/Inno Setup 6/ISCC.exe" \
"/c/Program Files (x86)/Inno Setup 5/ISCC.exe"; do \
if [ -f "$$p" ]; then ISCC_BIN="$$p"; break; fi; \
done; \
fi; \
fi; \
ISCC_OK=0; \
if [ -n "$$ISCC_BIN" ]; then \
if [ -e "$$ISCC_BIN" ] || command -v "$$ISCC_BIN" > /dev/null 2>&1; then ISCC_OK=1; fi; \
fi; \
if [ "$$ISCC_OK" = "0" ]; then \
echo ""; \
echo "❌ Inno Setup Compiler (iscc) 未安裝 / 找不到"; \
echo " 已嘗試偵測的路徑:"; \
echo " - \$$ISCC 環境變數"; \
echo " - PATH 上的 iscc / iscc.exe"; \
echo " - /c/Program Files (x86)/Inno Setup 6/ISCC.exe"; \
echo " - /c/Program Files/Inno Setup 6/ISCC.exe"; \
echo " 請從 https://jrsoftware.org/isdl.php 下載並安裝 Inno Setup 6"; \
echo " 或設定 ISCC 環境變數指向 ISCC.exe 絕對路徑"; \
echo ""; \
exit 1; \
fi; \
echo "==> 使用 ISCC: $$ISCC_BIN"; \
mkdir -p $(DIST); \
echo "==> 執行 iscccwd: $$(pwd)..."; \
"$$ISCC_BIN" installer/windows/visiona-local.iss; \
ISCC_RC=$$?; \
echo "==> iscc exit code: $$ISCC_RC"; \
if [ $$ISCC_RC -ne 0 ]; then exit $$ISCC_RC; fi
@echo "==> 產出:"
@ls -lh $(DIST)/ 2>/dev/null || echo "dist 目錄不存在"
@ls -lh $(DIST)/visiona-local-*-windows-x64.exe 2>/dev/null || echo "未找到 .exe 產出檔"
appimage: wails-linux ## ⚠️ 必須在 Linux 上跑build-appimage.sh → dist/visiona-local-*-linux-x64.AppImage
@if [ "$$(uname -s)" != "Linux" ]; then \
echo ""; \
echo "❌ appimage 只能在 Linux 上 build偵測到 $$(uname -s)"; \
echo " 需要 appimagetool請在 Linux x86_64 runner 執行"; \
echo ""; \
exit 1; \
fi
@mkdir -p $(DIST)
VERSION=$(VERSION) bash installer/linux/build-appimage.sh
@echo "==> 產出:"
@ls -lh $(DIST)/visiona-local-*-linux-x64.AppImage 2>/dev/null || echo "未找到產出檔"
# ── 開發placeholder ────────────────────────────────────────────
dev:
@echo "TODO: make -j2 dev-server dev-frontend開發模式"
test:
@echo "TODO: go test + pnpm test"
lint:
@echo "TODO: go vet + pnpm lint"
fmt:
@echo "TODO: go fmt"
# ── 清理 ───────────────────────────────────────────────────────────
clean: ## 清除 dist/ 與 payload/ 產物
@echo "Cleaning dist/ and payload artifacts..."
rm -rf $(DIST)
rm -rf $(PAYLOAD)
@mkdir -p $(DIST) $(PAYLOAD)
@touch $(DIST)/.gitkeep $(PAYLOAD)/.gitkeep
@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