Windows 啟動卡在階段 2,pip 在 2 秒內失敗:
ERROR: Cannot install certifi 2026.2.25 and certifi 2026.6.17
because these package versions have conflicting dependencies.
ERROR: ResolutionImpossible
根因:Makefile 的 vendor-wheels* 用 pip download --dest 但從不清空目錄,
payload-* 複製時也只疊上去不清。upstream 每發一次新版就多留一顆,於是
`pip install *.whl` 同時收到同一套件的多個版本,相依解析器立刻放棄。
vendor/wheels/darwin 目前 16 顆 = 9 個套件 + 7 顆重複舊版,與 log 的
「正在安裝 16 個 Python 套件」完全吻合。
三層修正:
1. ensurePythonRuntime 的 Auto 分支不再丟棄 bundled/system 的失敗原因。
這是使用者連續三輪拿不到線索的原因 —— 它不是根因,是放大器。
pip 的完整輸出本來就在 error 裡,只是被這裡擋掉。
2. selectLatestWheelPerPackage:送 pip 前每個套件只留最新版。這層才救得了
已經出貨的安裝包,只修 Makefile 對使用者手上那包無效。套件名依 PEP 503
正規化,版本用整數逐段比(避免 2.9 > 2.10 的字典序錯誤)。
3. Makefile 新增 clean_wheels_dir(沿用 copy_bundled_data 的路徑防護),
vendor-wheels* 先清再下載、payload-* 複製前先清,從源頭杜絕。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
828 lines
42 KiB
Makefile
828 lines
42 KiB
Makefile
# 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
|
||
|
||
# ── 打包用 .nef 白名單(M5-a)────────────────────────────────────────
|
||
#
|
||
# server/data/ 底下有 8 個 .nef(KL520 五個、KL720 三個),但安裝包只帶
|
||
# 白名單內的這幾個,其餘不進 payload,藉此縮小安裝檔體積。
|
||
#
|
||
# models.json 不做任何過濾,7 個 model 定義全部照原樣複製。執行期由
|
||
# server/internal/model/repository.go 的 NewRepository() 檢查每個 model 的
|
||
# filePath 是否實際存在,不存在的直接不載入(見 M5-c)。因此使用者在 UI
|
||
# 只會看到白名單內的 model,未打包的不會出現、也不會選到後拿到莫名錯誤。
|
||
#
|
||
# 未來要把某個 model 加回安裝包:把對應的 .nef 相對路徑加進下面這個變數即可,
|
||
# models.json 不用動。
|
||
#
|
||
# 路徑相對於 server/data/。
|
||
BUNDLED_NEFS := \
|
||
nef/kl520/kl520_20004_fcos-drk53s_w512h512.nef \
|
||
nef/kl520/kl520_tiny_yolo_v3.nef
|
||
|
||
# copy_bundled_data:把 server/data/ 複製到 $(1),但 nef/ 只帶 BUNDLED_NEFS 白名單。
|
||
# $(1) = 目標 data 目錄(例:payload/darwin/data)
|
||
#
|
||
# 步驟:(a) 先清空 $(1),確保這個 helper 是冪等的(見下方「為什麼要清空」)
|
||
# (b) 複製 server/data/ 下除了 nef/ 以外的所有東西(models.json 等)
|
||
# (c) 再逐一複製白名單內的 .nef
|
||
# (d) 白名單檔案不存在就直接 fail,避免安靜地產出缺 model 的安裝包
|
||
# (e) 最後數 .nef 個數,與白名單不符就 fail
|
||
#
|
||
# 為什麼要清空 $(1)((a) 步驟):
|
||
# payload-windows / payload-linux 刻意不 rm -rf 整個 payload/<os>/(因為
|
||
# build-server-* 已先把 binary 放進 bin/),所以先前 build 留下的檔案會殘留。
|
||
# 白名單機制(M5-a)之前的舊 build 會把 8 個 .nef 全部複製進去,升級到白名單
|
||
# 版本後再 build,就變成「新複製 2 個 + 殘留 6 個 = 8 個」,被下方的後置檢查
|
||
# 擋下來,build 直接失敗。清空目標目錄讓這個 helper 重複執行結果一致,不受
|
||
# 目標目錄既有內容影響。
|
||
# (payload-macos 沒踩到只是因為它有 rm -rf payload/darwin。)
|
||
#
|
||
# 清整個 $(1) 而不是只清 nef/:$(1) 的內容 100% 由本 helper 產生(Makefile 中
|
||
# 只有這裡寫入 payload/<os>/data/),沒有其他來源的檔案需要保留。只清 nef/ 的話,
|
||
# models.json 以外的殘留(例如未來從 server/data/ 移除的檔案)仍會被 installer
|
||
# 打包進去 —— installer/windows/visiona-local.iss 是用 data\* + recursesubdirs
|
||
# 整包收,殘留什麼就出貨什麼。清整包才是真正的冪等。
|
||
#
|
||
# rm -rf 安全防護($(1) 來自 make 變數,打錯會刪掉不該刪的東西):
|
||
# 1. 空值檢查:$(1) 展開為空就 fail,避免變成 `rm -rf /`(rm -rf "" 在部分
|
||
# shell 下是 no-op、但空值代表呼叫端寫錯,直接擋掉比較安全)
|
||
# 2. 絕對路徑 / 跳脫檢查:只接受相對路徑、且不得含 `..`,避免 `/`、`$$HOME`、
|
||
# `../..` 這類目標
|
||
# 3. 白名單前綴:路徑必須以 payload/ 開頭並以 /data 結尾,鎖死在 build 產物區
|
||
# 4. 刪的是「已存在且確定是目錄」的路徑,且刪除後立刻重建
|
||
#
|
||
# 只用 POSIX find / cp,不用 rsync —— Windows CI 跑在 Git Bash(windows-2022 +
|
||
# shell: bash),該環境沒有 rsync。
|
||
define copy_bundled_data
|
||
@set -e; \
|
||
echo "==> 複製 server/data → $(1)(.nef 白名單:$(words $(BUNDLED_NEFS)) 個)"; \
|
||
if [ ! -d server/data ]; then echo "!! ERROR: server/data 不存在 !!"; exit 1; fi; \
|
||
target='$(strip $(1))'; \
|
||
if [ -z "$$target" ]; then \
|
||
echo "!! ERROR: copy_bundled_data 的目標目錄為空,拒絕執行 !!"; exit 1; \
|
||
fi; \
|
||
case "$$target" in \
|
||
/*|~*) echo "!! ERROR: 目標目錄必須是相對路徑,收到 '$$target' !!"; exit 1 ;; \
|
||
*..*) echo "!! ERROR: 目標目錄不得含 '..',收到 '$$target' !!"; exit 1 ;; \
|
||
esac; \
|
||
case "$$target" in \
|
||
payload/*/data) : ;; \
|
||
*) echo "!! ERROR: 目標目錄必須符合 payload/<os>/data,收到 '$$target' !!"; exit 1 ;; \
|
||
esac; \
|
||
if [ -e "$$target" ] && [ ! -d "$$target" ]; then \
|
||
echo "!! ERROR: '$$target' 存在但不是目錄,拒絕刪除 !!"; exit 1; \
|
||
fi; \
|
||
if [ -d "$$target" ]; then \
|
||
echo " 清空既有的 $${target}(確保冪等,不留前次 build 的殘留)"; \
|
||
rm -rf -- "$$target"; \
|
||
fi; \
|
||
mkdir -p "$$target"; \
|
||
dest="$$(cd "$$target" && pwd)"; \
|
||
( cd server/data && \
|
||
find . -path ./nef -prune -o -type d -print | while read -r d; do mkdir -p "$$dest/$$d"; done && \
|
||
find . -path ./nef -prune -o -type f -print | while read -r f; do cp "$$f" "$$dest/$$f"; done ); \
|
||
if [ ! -f "$$dest/models.json" ]; then \
|
||
echo "!! ERROR: models.json 沒有被複製到 $$dest !!"; exit 1; \
|
||
fi; \
|
||
for nef in $(BUNDLED_NEFS); do \
|
||
if [ ! -f "server/data/$$nef" ]; then \
|
||
echo "!! ERROR: BUNDLED_NEFS 列出的 server/data/$$nef 不存在 !!"; \
|
||
exit 1; \
|
||
fi; \
|
||
mkdir -p "$$dest/$$(dirname $$nef)"; \
|
||
cp "server/data/$$nef" "$$dest/$$nef"; \
|
||
echo " + $$nef"; \
|
||
done; \
|
||
copied=$$(find "$$dest" -name '*.nef' | wc -l | tr -d ' '); \
|
||
if [ "$$copied" != "$(words $(BUNDLED_NEFS))" ]; then \
|
||
echo "!! ERROR: 預期 $(words $(BUNDLED_NEFS)) 個 .nef,實際 $$copied 個 !!"; exit 1; \
|
||
fi; \
|
||
echo " models.json + $$copied 個 .nef 已就位"
|
||
endef
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# clean_wheels_dir — 清空並重建 vendor/wheels/<os>/,確保 vendor-wheels* 冪等
|
||
#
|
||
# $(1) = 目標 wheels 目錄(例:vendor/wheels/windows)
|
||
#
|
||
# 為什麼必須清空(事故迴歸):
|
||
# vendor-wheels* 用 `pip download --dest $(1)`,而 pip download 只「補下載缺的」,
|
||
# 不會移除舊版。upstream 每發一次新版,這個目錄就多留一顆 whl —— 實機上
|
||
# vendor/wheels/darwin 累積成 certifi ×3、numpy ×2、idna ×2 … 共 16 顆
|
||
# (正常應為 9 顆)。
|
||
#
|
||
# 這些多版本會原封不動被 payload-* 複製進安裝包,最後 app 啟動時
|
||
# `pip install a.whl b.whl ...` 同時收到 certifi 2026.2.25 與 2026.6.17,
|
||
# 直接 ResolutionImpossible 並在 ~2 秒內失敗,使用者完全無法啟動。
|
||
#
|
||
# (app 端 selectLatestWheelPerPackage 已有防護,能救「已出貨」的舊安裝包;
|
||
# 這裡則是從源頭杜絕,讓新產出的安裝包一開始就乾淨。兩層都要有。)
|
||
#
|
||
# rm -rf 安全防護(與 copy_bundled_data 同一套,$(1) 打錯不能傷到別的目錄):
|
||
# 1. 空值檢查
|
||
# 2. 只接受相對路徑、不得含 '..'
|
||
# 3. 白名單前綴:必須符合 vendor/wheels/<os>
|
||
# 4. 存在但不是目錄 → 拒絕刪除
|
||
define clean_wheels_dir
|
||
@set -e; \
|
||
target='$(strip $(1))'; \
|
||
if [ -z "$$target" ]; then \
|
||
echo "!! ERROR: clean_wheels_dir 的目標目錄為空,拒絕執行 !!"; exit 1; \
|
||
fi; \
|
||
case "$$target" in \
|
||
/*|~*) echo "!! ERROR: 目標目錄必須是相對路徑,收到 '$$target' !!"; exit 1 ;; \
|
||
*..*) echo "!! ERROR: 目標目錄不得含 '..',收到 '$$target' !!"; exit 1 ;; \
|
||
esac; \
|
||
case "$$target" in \
|
||
vendor/wheels/*) : ;; \
|
||
*) echo "!! ERROR: 目標目錄必須符合 vendor/wheels/<os>,收到 '$$target' !!"; exit 1 ;; \
|
||
esac; \
|
||
if [ -e "$$target" ] && [ ! -d "$$target" ]; then \
|
||
echo "!! ERROR: '$$target' 存在但不是目錄,拒絕刪除 !!"; exit 1; \
|
||
fi; \
|
||
if [ -d "$$target" ]; then \
|
||
stale=$$(ls -1 "$$target"/*.whl 2>/dev/null | wc -l | tr -d ' '); \
|
||
echo "==> 清空既有的 $${target}($${stale} 個舊 whl,避免同套件多版本累積)"; \
|
||
rm -rf -- "$$target"; \
|
||
fi; \
|
||
mkdir -p "$$target"
|
||
endef
|
||
|
||
.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)
|
||
$(call clean_wheels_dir,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
|
||
$(call copy_bundled_data,payload/darwin/data)
|
||
cp -R server/scripts/* payload/darwin/scripts/
|
||
cp vendor/python/darwin/python.tar.gz payload/darwin/python/
|
||
@# payload-macos 雖有 rm -rf payload/darwin,仍顯式清空以與其他平台一致
|
||
@# (日後若移除該 rm -rf,這裡不會默默退化成累積多版本)。
|
||
@rm -f payload/darwin/wheels/*.whl
|
||
@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/
|
||
$(call clean_wheels_dir,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
|
||
$(call copy_bundled_data,payload/windows/data)
|
||
cp -R server/scripts/. payload/windows/scripts/
|
||
cp vendor/python/windows/python.tar.gz payload/windows/python/
|
||
@# 先清空再複製:payload-windows 刻意不 rm -rf payload/windows(server.exe 已先放進去),
|
||
@# 所以 wheels/ 會殘留前次 build 的舊版本,造成安裝包內同套件多版本 → pip
|
||
@# ResolutionImpossible 秒退。這裡確保 staging 精確等於 vendor 的內容。
|
||
@rm -f payload/windows/wheels/*.whl
|
||
@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/
|
||
$(call clean_wheels_dir,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
|
||
$(call copy_bundled_data,payload/linux/data)
|
||
@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/
|
||
@# 同 payload-windows:先清空,避免前次 build 的舊版 whl 殘留成多版本。
|
||
@rm -f payload/linux/wheels/*.whl
|
||
@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 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 ## 美化 DMG(create-dmg 有裝)或 plain DMG(fallback)→ 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 DMG(hdiutil 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 "==> 執行 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
|