fix: bundle missing runtime deps (pytz + LLM SDKs); guard against drift

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 <noreply@anthropic.com>
This commit is contained in:
周小舟
2026-05-30 17:23:11 +08:00
parent 0735332948
commit f657a4ba2f
2 changed files with 54 additions and 0 deletions

View File

@@ -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

View File

@@ -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