# 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 .dmg(dmgbuild)" @echo " exe Windows .exe(Inno 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 # ── ffmpeg(LGPL v3,方案 B 混合)── # v2 TDD §2.2:macOS 自 build decoder-only(~20 MB,commit 到 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 "==> 同步內部 wheels(KneronPLUS 等)..." @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: ## macOS:LGPL 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 版本時才跑此 target;binary 產出後 commit 到 git(v2 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-config(brew install pkg-config)"; exit 1; } @command -v nasm >/dev/null 2>&1 || command -v yasm >/dev/null 2>&1 || { echo "❌ 需要 nasm 或 yasm(brew 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 "==> configure(decoder-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 # ── M4:Windows 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 build(BtbN 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: 找不到真實 python(WindowsApps 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 來解壓 zip(WindowsApps 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.txt;COPYING.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 # ── M5:Linux 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 build(BtbN 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 @cp vendor/python/linux/python.tar.gz payload/linux/python/ 2>/dev/null || echo "!! WARN: python tarball 缺失" @cp vendor/wheels/linux/*.whl payload/linux/wheels/ 2>/dev/null || true @echo "==> Linux payload 完成:" @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 runner(GitHub 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_41),4.0 則 # 不加 tag(Wails 預設)。 @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 ## hdiutil UDZO → dist/visiona-local.dmg 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 @du -sh $(DIST)/visiona-local.dmg @file $(DIST)/visiona-local.dmg 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 "==> 執行 iscc(cwd: $$(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