From f657a4ba2f7fc83919aa5d36acabd26b94bbf813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E5=B0=8F=E8=88=9F?= Date: Sat, 30 May 2026 17:23:11 +0800 Subject: [PATCH] fix: bundle missing runtime deps (pytz + LLM SDKs); guard against drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The installed app opened but the project list was stuck forever on "正在加载项目列表...". Root cause: GET /api/v1/projects/ returned HTTP 500 with `ModuleNotFoundError: No module named 'pytz'`. The portable build only installs requirements.txt, but the backend imported packages the dev venv had that were never listed there. A static scan of backend imports against the portable runtime found 4 missing: - pytz (broke the project list — hot path) - openai } LLM provider SDKs — the AI clipping pipeline. These - google-generativeai } lived only in install_llm_dependencies.py, so the - dashscope } bundle shipped without them and any provider failed. Add all 4 to requirements.txt. Verified the rebuilt app returns 200 for /api/v1/projects/ on a clean PATH. Also add a build-time dependency-completeness check to build_macos_arm.sh: after installing deps + copying the backend, it AST-scans every third-party import and fails the build if any can't be resolved in the portable runtime. This turns "works in dev, 500s in the bundle" into a hard build error. Co-Authored-By: Claude Opus 4.8 --- requirements.txt | 9 ++++++++ scripts/build_macos_arm.sh | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/requirements.txt b/requirements.txt index 292e66cf..eff34cd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,3 +22,12 @@ qrcode[pil] yt-dlp>=2024.12.13 pysrt psutil +pytz + +# LLM provider SDKs — required for the AI clipping pipeline. These used to live +# only in install_llm_dependencies.py (installed separately), so the portable +# desktop build shipped without them and any provider call failed. Bundle them +# so the desktop app works out of the box. +openai>=1.0.0 +google-generativeai>=0.3.0 +dashscope>=1.10.0 diff --git a/scripts/build_macos_arm.sh b/scripts/build_macos_arm.sh index 763d9e70..1f4771cc 100755 --- a/scripts/build_macos_arm.sh +++ b/scripts/build_macos_arm.sh @@ -116,6 +116,51 @@ rsync -a \ backend/ "$BACKEND_DEST/" echo "OK" +# ---- dependency completeness check ---- +# Guard against the classic "works in dev, broken in the bundle" trap: the dev +# venv accumulates packages that requirements.txt never listed, so the portable +# runtime ships without them and the backend 500s at runtime (e.g. pytz, the +# LLM SDKs). Statically scan the copied backend for third-party imports and +# assert every one resolves in the portable runtime. Fail the build if not. +echo "==> Verifying backend dependencies are present in portable runtime" +"$PORTABLE_PY" - "$BACKEND_DEST" <<'PY' +import ast, os, sys, importlib.util +backend_dir = sys.argv[1] +# Make the bundle's own packages resolvable (both `import backend.x` and `from core import x` styles). +sys.path.insert(0, os.path.dirname(backend_dir)) # parent → resolves `backend` +sys.path.insert(0, backend_dir) # backend → resolves `core`, `app`, ... +stdlib = set(sys.stdlib_module_names) +mods = set() +for root, _, files in os.walk(backend_dir): + if '__pycache__' in root: + continue + for f in files: + if not f.endswith('.py'): + continue + try: + tree = ast.parse(open(os.path.join(root, f), encoding='utf-8').read()) + except Exception: + continue + for n in ast.walk(tree): + if isinstance(n, ast.Import): + for a in n.names: + mods.add(a.name.split('.')[0]) + elif isinstance(n, ast.ImportFrom) and n.level == 0 and n.module: + mods.add(n.module.split('.')[0]) +missing = sorted( + m for m in mods + if m and not m.startswith('_') and m not in stdlib + and importlib.util.find_spec(m) is None +) +if missing: + print("ERROR: backend imports modules missing from the portable runtime:") + for m in missing: + print(" -", m) + print("Add them to requirements.txt so the bundle installs them.") + sys.exit(1) +print("OK (all backend imports resolve)") +PY + # ---- ffmpeg ---- # We bundle STATIC, self-contained ffmpeg + ffprobe (arm64). Do NOT copy the # homebrew binary from PATH: it is dynamically linked against ~57 dylibs under