15 KiB
开发环境设置指南
本文档旨在帮助开发者快速设置开发环境,并说明主程序、开发测试、构建工具和插件依赖的管理边界。
环境准备
在开始之前,请确保您的系统已安装以下软件:
- Python 3.14+
- uv 0.12.5+(Python 版本、虚拟环境和依赖锁定工具,推荐使用最新稳定版)
- Git (用于版本控制)
- RAR 解压工具:本地开发如需测试或使用
.rar字幕包解压,请安装unar、unrar、7z或bsdtar之一;Docker 镜像会内置unar。
Rust 加速扩展通过 moviepilot-rust PyPI 包安装,主项目本地开发不再需要 Rust toolchain。需要修改或发布 Rust 扩展时,请在 MoviePilot-Rust 仓库中构建。
1. 创建锁定环境
仓库通过 pyproject.toml 声明直接依赖,并提交统一的 uv.lock。在项目根目录执行:
uv sync --locked
uv 会创建或更新 .venv,并安装运行时与默认 dev 依赖组。命令中的
--locked 会在 pyproject.toml 与 uv.lock 不一致时直接失败,避免开发环境静默解析出一套
未提交的依赖结果。只需要生产运行依赖时使用:
uv sync --locked --no-dev --no-install-project
以上命令以独立 checkout 的仓内 .venv 为默认。多仓工作区若已有共享运行环境 .venv 和
隔离测试环境 .venv-test,按工作区说明选择,不创建另一套仓内环境。以下变量指向实际工作区
根目录,路径使用绝对路径,避免 --directory 改变相对环境路径的含义:
MOVIEPILOT_WORKSPACE="${MOVIEPILOT_WORKSPACE:?set absolute workspace root}"
UV_PROJECT_ENVIRONMENT="${MOVIEPILOT_WORKSPACE}/.venv" \
uv sync --locked --directory "${MOVIEPILOT_WORKSPACE}/MoviePilot"
UV_PROJECT_ENVIRONMENT="${MOVIEPILOT_WORKSPACE}/.venv-test" \
uv run --directory "${MOVIEPILOT_WORKSPACE}/MoviePilot" --locked --no-sync \
python -c 'import sys; print(sys.executable)'
测试与静态检查均在目标后端 checkout 工作目录执行:把公共命令中的
uv run --locked --no-sync 映射到上述 .venv-test,或直接用该环境的 Python 执行
-m pytest、-m pylint 及检查脚本。先确认解释器、锁文件和所需依赖组相符;--no-sync
只避免同步,不证明已安装依赖匹配。环境创建/重建按工作区指令执行,不在普通验证中重建或
同步共享环境。测试不加载运行用 app.env,由测试引导隔离临时 CONFIG_DIR;本地服务启动
则遵循工作区的子进程环境加载约定。
2. 依赖分层与事实源
主程序只维护以下依赖事实源:
| 位置 | 用途 | 维护方式 |
|---|---|---|
pyproject.toml 的 [project].dependencies |
两套 Python 运行时共享的主程序生产依赖。 | 开发者按直接依赖的兼容范围维护。 |
pyproject.toml 的 [dependency-groups].dev |
pytest、覆盖率、Pylint 和源码构建等开发工具。 | 不进入 Docker 生产运行环境。 |
pyproject.toml 的 [dependency-groups].runtime-* |
标准与 free-threaded 解释器互斥的 ABI 敏感运行依赖。 | 只放两套运行时确实不同的直接依赖。 |
uv.lock |
Python 3.14+、两套运行时 profile 和受支持平台共享的完整解析结果。 | 修改 pyproject.toml 后由 uv lock 更新并提交。 |
主程序不再维护 requirements.in、requirements-dev.in 或 requirements.txt,也不生成
平台专属的 requirements 锁文件。Docker、CLI 和 CI 都以提交的 uv.lock 为安装输入。
2.1 本地启动脚本
不需要打开 IDE 时,可以直接使用仓库内的启动脚本。脚本会自动定位项目根目录和虚拟环境,并以模块方式启动后端,避免 ModuleNotFoundError: No module named 'app'。
# 默认启动后端开发服务,前台运行,按 Ctrl+C 停止
./scripts/start-local.sh
./scripts/start-local.sh backend
# 如果已经安装前端发布包,可启动完整的前后端服务
./scripts/start-local.sh service start
# 管理完整服务
./scripts/start-local.sh stop
./scripts/start-local.sh restart
./scripts/start-local.sh status
./scripts/start-local.sh logs --follow
默认会使用 DEBUG=true 和 DEV=true,与 IDE 开发启动保持一致。开发热重载通过
app.factory:create_app 的 import string/factory 入口运行,文件变化后由 Uvicorn 重新创建
应用结构;不会尝试在 reload 进程间传递已经实例化的 FastAPI 对象。如果不需要热重载,
可以这样启动以降低资源占用:
DEV=false ./scripts/start-local.sh
脚本会优先使用 CONFIG_DIR,其次使用 MOVIEPILOT_CONFIG_DIR,再检测 ~/Documents/moviepilot,最后回退到仓库内的 config 目录。需要使用其他配置目录时,可以这样运行:
MOVIEPILOT_CONFIG_DIR=/path/to/moviepilot-config ./scripts/start-local.sh
首次使用前如果脚本没有执行权限,运行:
chmod +x scripts/start-local.sh
3. 修改主程序依赖
新增或升级依赖时,先确认依赖属于哪个层级:
- 共享运行时依赖:被
app/生产代码直接导入,或是生产功能、后台任务、插件框架启动必需,写入[project].dependencies。 - ABI 敏感运行依赖:标准与 free-threaded 解释器必须选择不同制品或版本时,分别写入
runtime-standard和runtime-free-threaded;两组保持互斥并由运行时统一选择。 - 开发 / 测试 / 静态检查 / 构建依赖:只用于单测、覆盖率、lint 辅助、源码构建等,写入
[dependency-groups].dev。 - 工具依赖:仓库要求使用
uv 0.12.5+,推荐使用最新稳定版;不应为了安装工具而把它加入主程序运行依赖。 - 插件依赖:由插件清单声明并在插件安装阶段处理,不直接并入主程序依赖。
修改后更新并校验锁文件:
uv lock
uv lock --check
uv sync --locked
uv sync --locked --offline --inexact --no-dev --check
uv pip check 可用于查看第三方包元数据诊断,但不作为项目依赖合同:oss2 已停止维护,其元数据仍
声明旧 crcmod,而主程序统一使用保持相同导入接口的 crcmod-plus。项目一致性以锁文件和上述
uv sync --check 结果为准。
uv.lock 同时覆盖 Linux x86_64/arm64、macOS x86_64/arm64 和 Windows x64。统一锁文件只
固定解析结果,不能替代这些平台的真实安装门禁;平台条件依赖变更必须通过对应 CI 环境验证。
3.1 插件依赖清单
新插件可以在插件根目录使用 pyproject.toml,宿主只读取 [project].dependencies 作为运行依赖:
[project]
name = "example-plugin"
version = "1.0.0"
dependencies = ["example-package>=1,<2"]
插件依赖遵循以下合同:
pyproject.toml优先于历史requirements.txt;两者同时存在时只读取前者;[dependency-groups]属于插件自身的开发、测试或构建环境,宿主不安装其中内容;pyproject.toml存在但格式或依赖声明无效时直接报错,不回退到requirements.txt;- 仅有
requirements.txt的历史插件继续按原方式安装; - 宿主不消费插件自己的
uv.lock,因为多个插件共享同一主程序环境,不能分别同步独立锁文件。
3.2 异步 HTTP 客户端边界
主程序自建的 AsyncRequestUtils 使用 HTTPX2,app.sdk.network.AsyncRequestUtils 与旧插件
入口 app.utils.http.AsyncRequestUtils 共享同一实现。未显式传入客户端时,返回的响应与抛出的
请求异常均来自 httpx2;直接依赖响应类型或异常类型的 V3 代码应导入 httpx2。
OpenAI、Anthropic、Google GenAI、LangChain、CloakBrowser 等第三方 SDK 继续使用它们声明的
HTTPX 版本。不得调用 httpx2.alias_httpx() 在进程内替换 httpx,否则会同时改变第三方 SDK、
测试工具和插件的导入结果。确需复用调用方自管客户端时,向 AsyncRequestUtils 传入
httpx2.AsyncClient。
4. 准备资源与插件目录
本地源码开发时,主程序需要读取资源文件和插件源码。相关文件需要放到主程序实际加载的目录下:
- 资源文件:将 MoviePilot-Resources 仓库中
resources.v3/下的文件同步到本仓库的app/application/site/目录下。CLI 安装和 Docker 构建流程只读取 V3 资源。 - 插件源码:需要开发或调试的插件放到本仓库的
app/plugins/目录下,例如app/plugins/<插件目录>/。主程序运行时从该目录加载插件,独立插件仓库只是源码来源。
如果资源文件没有放到 app/application/site/,站点索引、规则和内置资源相关能力可能无法按本地开发预期工作;如果插件没有放到 app/plugins/,主程序也不会在本地运行时发现该插件。
4.1 GitHub 发版时生成插件市场默认值
源码分支中的 ConfigModel.PLUGIN_MARKET 只保留官方插件仓库作为离线兜底。GitHub 的 V3 正式版与 Beta 镜像构建会检出 MoviePilot-Wiki 的 main 分支,并由 scripts/generate_plugin_market_default.py 读取 plugin.md 中 plugin-market-repos:start/end 标记区域,将规范化、去重后的公开仓库清单写入构建工作区。
生成过程遵循以下约束:
- 标记必须唯一、顺序正确,清单不能为空且必须包含
jxxghp/MoviePilot-Plugins;不满足时直接终止构建。 - 生成脚本只替换
ConfigModel中的PLUGIN_MARKET默认值,不写入运行时环境变量,因此用户仍可通过系统环境变量或/config/app.env覆盖。 - 正式版工作流会创建仅由 Release Tag 引用的本地快照提交,Docker 镜像和 Tag 源码归档均来自该快照;Actions 不会将生成结果回写到
v3分支。 - Release Tag 快照提交信息和镜像标签会记录本次使用的 MoviePilot Wiki Commit,便于追溯清单来源。
本地验证生成结果时,先激活项目虚拟环境,再执行:
python -m scripts.generate_plugin_market_default \
--wiki-file /path/to/MoviePilot-Wiki/plugin.md \
--config-file app/runtime/config.py
5. 运行依赖漏洞检查
正式发布会使用最新稳定版 pip-audit 检查 uv.lock 锁定的运行时依赖。依赖变更后也可以在
本地执行同一检查:
uv export --quiet --locked --no-dev --no-emit-project \
--output-file /tmp/moviepilot-audit-requirements.txt
uvx --from pip-audit pip-audit \
--require-hashes --disable-pip --strict --progress-spinner off \
--requirement /tmp/moviepilot-audit-requirements.txt
导出文件由 uv.lock 生成且保留哈希,不作为项目依赖清单提交。
Docker 镜像发布前还会使用 Trivy 扫描 OS 与语言包;根目录 .trivyignore.yaml 只允许记录按路径或 PURL
限定、写明原因并设置到期时间的临时例外,修复或重新评估后应移除。
CVE-2026-84445 临时例外的核查记录(2026-09-09,2026-10-09 到期):
- 上游公告 将触发条件限定为
xds.NewGRPCServer()安装的 xDS 路由拦截器;普通 gRPC 依赖的存在不代表包含该路径。 - 当前固定的
rclone/rclone:beta@sha256:d6f5448594ecefefcf09cfeaf85cb7a21a866328032576ce2c1813e7b59c66dc对应v1.76.0-beta.10267.220fe7619。从该摘要提取两个架构的/usr/local/bin/rclone, 使用go version -m确认内嵌 gRPC 为v1.84.0-dev.0.20260723093437-b6eac429d7b6。 - 使用 Go 标准库
debug/elf和debug/gosym解析二进制.gopclntab:amd64 共 91,641 个函数, arm64 共 91,150 个函数;各有 1,643 个 gRPC 函数,均无google.golang.org/grpc/xds或google.golang.org/grpc/internal/xds函数,未链接受影响的服务端拦截器。 - 核查时官方稳定版
v1.75.1和主分支仍引用同一 gRPC 版本。例外只匹配镜像内usr/bin/rclone和上述精确依赖 PURL;更新 rclone 摘要时必须重新核查两个架构,包含修复后应移除例外。
6. Contributor 提交准备
公共提交准备按 AGENTS.md 的 contributor default 执行;已确认的维护者可以 依据有效证据明确调整适用检查范围、时机和交付顺序,包括先保存本地 anchor 再补验证。记录 决定、证据、未验证项和后续安排,已有同范围授权不重问;这不取消架构、兼容、正确性和真实报告。
- 依赖变化:确认依赖分层、
uv.lock、锁定环境一致性和pip-audit;平台条件依赖还需对应平台安装证据。 - 行为变化:运行受影响测试;依赖/锁文件、共享脚手架、数据库、启动、跨模块生命周期、兼容或大范围行为变化运行
python tests/run.py全量。纯文档及其契约测试使用对应文本、结构、链接和 focused 检查。 - 架构与静态检查:按受影响合同选择架构策略、snapshot 和 ratchet;策略测试先于 snapshot。Pylint 候选范围分别取 PR base 到 HEAD、暂存 diff、未暂存 diff 及本次新文件的并集;以实际待提交的索引 tree 过滤路径并检查内容。仅暂存本次选择的路径/hunks;代码、项目配置与仓内 lint 依赖必须来自同一 tree。只有全部 tracked 工作树与索引一致(
git diff --quiet)且没有会影响 lint 的额外配置或导入输入时才使用工作树快捷路径,否则(含部分暂存)按AGENTS.md导出索引验证。这是现有 lint 的输入选择,不增加门禁;大范围 Python 变化在同一待提交 tree 中检查app/。 - 最终核对:复用仍有效的证据,只重跑被后续变化失效的检查;检查最终 diff、
git diff --check和工作树。CI 失败归属及维护者预授权按 协作规则 处理。
主仓架构检查不依赖独立插件仓;官方插件兼容观察通过每周或手工工作流单独运行,仅上传语义差异
报告,不自动更新基线。普通主仓改动不要求额外检出插件仓或运行观察任务。宿主架构门禁和 changed-file
Pylint 由 v3 PR/push 的 GitHub Actions 执行,app/ 全量 Pylint 是建议性报告。
Ruff/Mypy 基线只允许收紧,不接受新增诊断或类型错误增长;受治零错误文件由 mypy.ini 的
files= 维护。Mypy 完整 ratchet 固定按 Linux/Python 3.14 分析。Coverage job 在 v3 PR/push
运行,按 tests/run.py 的分片合同执行并合并报告,Application 与 Domain 固定不低于 80%。
每个 Coverage 分片预算为 15 分钟(测试 step 为 10 分钟),报告与 ratchet 为 10 分钟;不得靠
跳过测试或产物规避超时。Coverage 只接受 GitHub Actions 的 Ubuntu/Python 3.14、locked
依赖和全量测试工件,本机 macOS 报告仅供诊断,不得写入并提交 canonical baseline。