Files
MoviePilot/docs/development-setup.md

284 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 开发环境设置指南
本文档旨在帮助开发者快速设置开发环境,并说明主程序、开发测试、构建工具和插件依赖的管理边界。
### 环境准备
在开始之前,请确保您的系统已安装以下软件:
- **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`。在项目根目录执行:
```bash
uv sync --locked
```
`uv` 会创建或更新 `.venv`,并安装运行时与默认 `dev` 依赖组。命令中的
`--locked` 会在 `pyproject.toml` 与 `uv.lock` 不一致时直接失败,避免开发环境静默解析出一套
未提交的依赖结果。只需要生产运行依赖时使用:
```bash
uv sync --locked --no-dev --no-install-project
```
以上命令以独立 checkout 的仓内 `.venv` 为默认。多仓工作区若已有共享运行环境 `.venv` 和
隔离测试环境 `.venv-test`,按工作区说明选择,不创建另一套仓内环境。以下变量指向实际工作区
根目录,路径使用绝对路径,避免 `--directory` 改变相对环境路径的含义:
```bash
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'`。
```bash
# 默认启动后端开发服务,前台运行,按 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 对象。如果不需要热重载,
可以这样启动以降低资源占用:
```bash
DEV=false ./scripts/start-local.sh
```
脚本会优先使用 `CONFIG_DIR`,其次使用 `MOVIEPILOT_CONFIG_DIR`,再检测 `~/Documents/moviepilot`,最后回退到仓库内的 `config` 目录。需要使用其他配置目录时,可以这样运行:
```bash
MOVIEPILOT_CONFIG_DIR=/path/to/moviepilot-config ./scripts/start-local.sh
```
首次使用前如果脚本没有执行权限,运行:
```bash
chmod +x scripts/start-local.sh
```
### 3. 修改主程序依赖
新增或升级依赖时,先确认依赖属于哪个层级:
1. **共享运行时依赖**:被 `app/` 生产代码直接导入,或是生产功能、后台任务、插件框架启动必需,写入 `[project].dependencies`。
2. **ABI 敏感运行依赖**:标准与 free-threaded 解释器必须选择不同制品或版本时,分别写入 `runtime-standard` 和 `runtime-free-threaded`;两组保持互斥并由运行时统一选择。
3. **开发 / 测试 / 静态检查 / 构建依赖**:只用于单测、覆盖率、lint 辅助、源码构建等,写入 `[dependency-groups].dev`。
4. **工具依赖**:仓库要求使用 `uv 0.12.5+`,推荐使用最新稳定版;不应为了安装工具而把它加入主程序运行依赖。
5. **插件依赖**:由插件清单声明并在插件安装阶段处理,不直接并入主程序依赖。
修改后更新并校验锁文件:
如果只需要修复某个传递依赖(例如安全审计发现的问题),可先用
`uv lock --upgrade-package <package>` 定向更新,避免无关依赖随之升级。
```bash
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` 作为运行依赖:
```toml
[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`,因为多个插件共享同一主程序环境,不能分别同步独立锁文件。
启动恢复依赖时,只收集已安装且存在启用本体或分身的插件清单;全部实例都停用的插件
不会参与依赖解析,也不会提供本地 wheels。先尝试统一安装,失败后逐个恢复插件,并在
后续请求中保留已就绪和已恢复插件的原始清单约束,避免互斥版本轮流覆盖共享环境。
仍不可满足的插件保持 `dependency_pending`,其它就绪插件继续激活。两个互斥版本无法
在同一环境中同时满足,应停用所有冲突实例、卸载冲突插件或由插件作者统一依赖版本。
安装结束后宿主重新分类并激活就绪插件;容器外手工修改依赖后,需重载插件或重启以更新运行状态。
### 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](https://github.com/jxxghp/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,便于追溯清单来源。
本地验证生成结果时,先激活项目虚拟环境,再执行:
```bash
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` 锁定的运行时依赖。依赖变更后也可以在
本地执行同一检查:
```bash
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 镜像直接基于官方基础镜像构建,只安装应用运行所需的系统依赖;不运行镜像漏洞扫描,
也不为扫描结果刷新系统包、删除基础镜像自带工具或维护漏洞忽略清单。正式版和 Beta 均直接构建
并发布标准版与 free-threaded 版的双架构镜像。应用锁定依赖仍按上述流程审计。
### 6. Contributor 提交准备
公共提交准备按 [AGENTS.md](../AGENTS.md) 的 contributor default 执行;已确认的维护者可以
依据有效证据明确调整适用检查范围、时机和交付顺序,包括先保存本地 anchor 再补验证。记录
决定、证据、未验证项和后续安排,已有同范围授权不重问;这不取消架构、兼容、正确性和真实报告。
按改动选择检查,命令入口统一见 [命令参考](rules/03-commands.md) 与
[测试规范](testing.md):
1. **依赖变化**:确认依赖分层、`uv.lock`、锁定环境一致性和 `pip-audit`;平台条件依赖还需对应平台安装证据。
2. **行为变化**:运行受影响测试;依赖/锁文件、共享脚手架、数据库、启动、跨模块生命周期、兼容或大范围行为变化运行 `python tests/run.py` 全量。纯文档及其契约测试使用对应文本、结构、链接和 focused 检查。
3. **架构与静态检查**:按受影响合同选择架构策略、snapshot 和 ratchet;策略测试先于 snapshot。Pylint 候选范围分别取 PR base 到 HEAD、暂存 diff、未暂存 diff 及本次新文件的并集;以实际待提交的索引 tree 过滤路径并检查内容。仅暂存本次选择的路径/hunks;代码、项目配置与仓内 lint 依赖必须来自同一 tree。只有全部 tracked 工作树与索引一致(`git diff --quiet`)且没有会影响 lint 的额外配置或导入输入时才使用工作树快捷路径,否则(含部分暂存)按 `AGENTS.md` 导出索引验证。这是现有 lint 的输入选择,不增加门禁;大范围 Python 变化在同一待提交 tree 中检查 `app/`。
4. **最终核对**:复用仍有效的证据,只重跑被后续变化失效的检查;检查最终 diff、`git diff --check` 和工作树。CI 失败归属及维护者预授权按 [协作规则](rules/12-collaboration-and-distribution.md) 处理。
主仓架构检查不依赖独立插件仓;官方插件兼容观察通过每周或手工工作流单独运行,仅上传语义差异
报告,不自动更新基线。普通主仓改动不要求额外检出插件仓或运行观察任务。宿主架构门禁和 changed-file
Pylint 由 `v3` PR/push 的 GitHub Actions 执行,`app/` 全量 Pylint 是建议性报告。
站点采集器使用独立的 `site-adapter-collector-v1.0.x` Release,仅在采集器发布输入变化时递增版本并构建,不跟随后端 Release。下载入口为 [采集器独立 Release](https://github.com/jxxghp/MoviePilot/releases?q=site-adapter-collector-v&expanded=true),自动版本和手动测试构建规则见 [采集器发布说明](site-adapter-collector-release.md#维护者发布流程)。
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。
### 6.1 结构复杂度门禁
复杂度按全宿主函数的 Ruff C901(上限 15)与 PLR1702(嵌套上限 5)检查,排除
`app/plugins/**`。方法、类和文件行数只生成观察报告,注释、空行和格式换行不再消耗硬预算。
原来的 `--v2` 行数检查已退役。阈值以内允许正常开发;存量超限函数不能新增或继续增长。
```bash
uv run --locked --no-sync python scripts/architecture/complexity.py --report /tmp/moviepilot-complexity-report.json
# 仅在消除全部回退、确认真实下降后固化低水位
uv run --locked --no-sync python scripts/architecture/complexity.py --write
uv run --locked --no-sync python scripts/architecture/complexity.py
```
`--write` 拒绝接受已有基线上的增长;减少或删除热点后必须固化低水位。
CI 上传 `complexity-report` 工件,门禁失败时也保留已生成的指标。指标含义、限制与后续扩围见
[代码门禁说明](code-quality.md)。
### 7. 参考资源
- [uv 官方文档](https://docs.astral.sh/uv/)
- [pip-audit](https://github.com/pypa/pip-audit)
- [MoviePilot-Resources](https://github.com/jxxghp/MoviePilot-Resources)
- [MoviePilot-Plugins](https://github.com/jxxghp/MoviePilot-Plugins)
### Agent 历史消息索引验证
独立消息库位于 Agent 运行目录,不参与主库 Alembic。使用临时运行目录运行 `tests/test_agent_recall_persistence.py`,不得用真实用户的 `state.db` 做索引破坏或重建测试。CJK tokenizer 保留 Hermes 原版源码及许可证于 `native/fts5_cjk/`;Docker 构建与 CLI 依赖安装使用该目录的 `install.py`,按本机架构编译并用目标解释器实际验证两字中文查询,安装到 `<sys.prefix>/lib/moviepilot/libfts5_cjk.so`。旧配置目录内的手工扩展作为加载回退;编译器要求和补装/检查命令见该目录 README。测试有本地 C 编译器时在临时目录构建,无编译器时只跳过原生扩展用例,不能宣称该分词路径已验证。常规 FTS5 与 trigram、短中文回退无需该扩展,扩展缺失或失效时索引状态会明确降级。独立 schema 版本不等于产品版本;后续新增主库升级脚本从 3.1.0 命名。
### Agent 学习验证
`tests/test_agent_learning_skills.py`、`tests/test_agent_learning_memory.py` 和
`tests/test_agent_learning_review.py` 用临时目录与确定性模型核对 Hermes 学习契约,
不访问实际 LLM 或个人记忆。匹配器、威胁模式和提示词的上游来源及 MIT 许可在
`app/agent/learning/LICENSE.hermes`;不得以这些离线测试替代固定真实模型的效果评测。
`tests/test_agent_guardrail_controller.py` 保留 Hermes 原始循环规则的纯行为回归,
`tests/test_agent_guardrails.py` 使用真实 LangGraph 验证并行顺序、逐运行隔离、正常轮询、
未知结果、尾部行动继续提示和流式停止交付。派生代码许可在 `app/agent/guardrails/LICENSE.hermes`。