Anki 的 Ninja 构建系统实战指南:从 Bazel 迁移到 `./run` 与 `./ninja` 的完整工作流 Anki 的 Ninja 构建系统实战指南从 Bazel 迁移到./run与./ninja的完整工作流【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki本文基于仓库 docs/ninja.md其源文件为 docs-site/developers/ninja.mdx展开面向熟悉旧 Bazel 构建体系的开发者系统讲解 Anki 当前的 Ninja 驱动构建系统如何准备环境、用./run一键构建启动、用./ninja运行分层测试与格式检查并结合仓库源码剖析runner入口、目标分层机制与常见问题排查。读完本文你将能独立完成 Anki 源码从环境准备、构建运行到测试、格式化、静态检查的完整开发闭环并理解其构建系统的内部工作原理。一、背景Anki 为什么从 Bazel 走向 NinjaAnki 是一个以 Rust 为核心rslib/、Python 为胶水层pylib/、qt/aqt/、TypeScript/Svelte 为前端ts/的三层大型项目。历史上其构建体系基于 Bazel而当前仓库已经切换到Ninja及其高性能替代 n2驱动的构建方案。docs/ninja.md开篇即点明其定位Brief notes for people used to the existing Bazel build system.也就是说这份文档是写给已熟悉 Bazel 构建系统的开发者的迁移速记。与其对应docs/development.md 中的Building from source一节给出了更完整的依赖要求除了 Rustup 之外需要N2 或 Ninja1.10其中 n2 的构建状态输出更好可用仓库自带的tools/install-n2一键安装just命令运行器则作为实验性的官方命令入口被引入见 justfile。整套构建的产物统一落入仓库根目录下的out/文件夹Windows 上另有node_modulesCargo、yarn、pip 的依赖缓存则复用系统共享缓存。理解了这套布局再往下看具体的命令就水到渠成了。二、环境准备三步从零进入 Ninja 世界按照docs/ninja.md的说明从一个 Bazel 时代的旧 checkout 切换到 Ninja 构建体系需要完成三步把 ninja 二进制加入 PATH文档明确指向 ninja v1.11.1 的发布版本。特别提醒在 Windows 上如果你同时在 msys 中安装了 ninja必须确保原生native二进制在 PATH 中排在更前面否则 msys 版本可能破坏构建行为。这一点在源码中有直接印证——build/runner/src/build.rs 构造子进程 PATH 时在 Windows 上显式拼接了out\bin;out\extracted\node;node_modules\.bin;...;\msys64\usr\bin将构建所需工具链置于 msys 之前。通过 rustup 安装 Rust仓库根目录的 rust-toolchain.toml 固定了工具链版本1.97.1并声明了rust-analyzer组件rustup 会在首次构建时自动下载该版本。注释明确说明过旧的 Rust 版本可能根本无法编译而过新的版本可能无法通过 clippy 测试因此不要随意改动这个文件。清理旧 checkout 残留删除已有的.bazel和node_modules文件夹。当前构建体系已将 Node 运行时、依赖安装全部托管进out/例如out/extracted/node、:node_modules构建组仓库根目录不再需要 Bazel 时代的这些遗留物。另外虽然文档推荐标准 ninja但仓库实际上优先使用 n2build/runner/src/build.rs中的get_ninja_command()build.rs会先探测 PATH 上是否存在n2存在则用它否则回退到ninja。安装 n2 只需执行仓库自带的 tools/install-n2其内部通过 cargo 从 n2 上游安装并固定到指定 revision。docs/development.md还提示在 Windows 上若 WSL 与 MSYS2 bash 冲突导致安装报错可改用C:\msys64\usr\bin\bash.exe tools/install-n2。三、一键构建并启动./run环境就绪后开发期启动 Anki 的方式非常简单./run # Windows 上为 .\rundocs/ninja.md对这一命令的描述只有一句话但它背后的链路值得拆解。根目录的 run 脚本bash做了这几件事预设开发环境变量ANKIDEV1打印额外日志、禁用自动备份切勿在正式 profile 上使用、QTWEBENGINE_REMOTE_DEBUGGING8080、ANKI_API_PORT40000可通过http://localhost:40000/_anki/pages/xxx.html直接访问前端页面配合tools/web-watch可实现自动重建热调试调用./ninja pylib qt把 Python 库与 Qt 界面编译到out/最后用out/pyenv/bin/python tools/run.py启动 Anki。第一次构建会较慢需要下载并编译大量 Rust 依赖、提取 Node、建立 Python 虚拟环境之后即为增量构建。若需优化构建运行更快、编译更慢按 docs/development.md 的说明使用./tools/runopt # 等价于 RELEASE1 ./run # 或 RELEASE1 ./runRELEASE2会做进一步优化但构建显著变慢。tools/runopt的实现也正是RELEASE1 $(dirname $0)/../run见 tools/runopt。注意RELEASE、CI、MAC_X86、LIN_ARM64、SOURCEMAP、HMR等环境变量被写入RECONFIGURE_KEY见 run 与 ninja一旦这些值发生变化构建系统会自动触发重新配置reconfigure。四、./ninja入口runner 与 build.ninja 的生成./ninja是整个构建体系的核心入口Windows 上对应tools\ninja。先看它的实现ninja 脚本export CARGO_TARGET_DIR$out/rust cargo build -p runner --profile $runner_profile # 编译构建器本身 exec $out/rust/$runner_profile/runner build -- $* # 把参数转交给 runnerrunner是一个位于 build/runner/src/main.rs 的 Rust 小工具通过 clap 提供pyenv、yarn、rsync、run、build、archive六个子命令负责跨平台地调用各种构建动作并静默成功输出。Windows 版本 tools/ninja.bat 逻辑一致并特意将构建 runner与运行 runner拆成两步避免构建环境变量泄漏到子进程。build子命令的核心行为build/runner/src/build.rs非常值得一提自动引导若out/build.ninja不存在会先执行cargo run -p configure对应build/configurecrate生成它build.rs快速失败重试如果构建在 3 秒内失败大概率是build.ninja引用了被改名/删除的文件系统会重新生成build.ninja并重试一次build.rs冒号转换Ninja 目标名无法包含冒号runner 会把foo:bar自动改写成foo_barbuild.rs——这正是一切分层目标的底层前提友好输出默认设置NINJA_STATUS如[%f/%t; %r active; %es]强制开启颜色成功时以绿色加粗打印Build succeeded in x.xx s.失败则红字退出build.rs。所以文档中./ninja check之类的命令实际流程是编译 runner → 生成/校验out/build.ninja→ 调用ninja或n2执行对应目标。五、核心目标check / format / fixdocs/ninja.md给出了三个最高频的目标这里结合 docs/development.md 与源码把它们的覆盖范围讲透。5.1./ninja check—— 跑全部测试与检查./ninja check # Linux/macOS tools\ninja check # Windows它会一次性执行仓库所有检查目标。当前通过build/configure各模块注册的检查目标可在对应源码中逐一核对包括目标覆盖内容源码位置check:pytest:pylibpylib 的 pytest 测试build/configure/src/pylib.rscheck:pytest:aqtqt/aqt 的 pytest 测试build/configure/src/aqt.rscheck:pytest:toolstools 的 pytest 测试build/configure/src/python.rscheck:mypyPython 类型检查build/configure/src/python.rscheck:ruffPython lintbuild/configure/src/python.rscheck:clippyRust lintbuild/configure/src/rust.rscheck:rust_testRust 单元测试build/configure/src/rust.rscheck:vitestTypeScript/Svelte 单元测试build/configure/src/web.rscheck:svelteSvelte 类型检查svelte-checkbuild/configure/src/web.rscheck:eslint前端 lint--max-warnings0零警告容忍build/configure/src/web.rscheck:typescript:aqtqt 遗留 JS 的类型检查build/configure/src/aqt.rscheck:minilints版权头、贡献者、许可证等小检查build/configure/src/rust.rs此外还有格式类检查目标check:format:rustrustfmt、check:format:dprint、check:format:prettier、check:format:sql、check:format:python:{group}、check:format:proto、check:format:cog:{group}等它们分布在 build/configure/src/rust.rs、build/configure/src/web.rs、build/ninja_gen/src/python.rs 等处。5.2./ninja format—— 自动修正格式./ninja format当check报告格式问题后执行该命令即可自动修复。它对应上述各format:*目标如format:rust、format:dprint、format:prettier、format:sql、format:python:{group}、format:proto、format:cog:{group}。以 Python 为例build/ninja_gen/src/python.rs中PythonFormat的实际命令是$ruff format $mode $in $ruff check --select I --fix $in即先用 ruff 格式化再自动修复 import 排序。5.3./ninja fix—— 修复 eslint 与版权问题./ninja fixfix面向两类问题一是fix:eslint对ts与qt/aqt/data/web/js两个目录运行带--fix的 eslint见 build/configure/src/web.rs二是fix:minilints同步版权、贡献者、许可证声明见 build/configure/src/rust.rs。对应地docs/development.md还建议 Rust 侧的 clippy 问题可用cargo clippy --fix处理。5.4 只跑单个检查docs/development.md给出了精细化复跑的范例如果check输出中check:svelte:editor失败可以只跑./ninja check:svelte:editor或退一级用./ninja check:svelte重跑全部 Svelte 检查——这正是分层目标下一节的直接应用。justfile中的just test、just test-rust、just test-py、just test-ts、just lint、just fmt等命令最终也都映射到这些./ninja目标上见 justfile二者等价。六、层次化目标Hierarchical Targets的实现原理docs/ninja.md用两个例子说明了分层目标的用法./ninja check:jest:deck-options # 只跑 ts/deck-options 的 Jest 测试 ./ninja check:jest # 跑全部 Jest 测试也就是说目标名用冒号分隔越靠前越宽泛越靠后越具体。这套机制有两层实现支撑第一层分组树。build/ninja_gen/src/build.rs中的split_groups()build.rs把形如foo:bar:baz的目标逐级拆分为[foo:bar:baz, foo:bar, foo]注册到以组为键的哈希表中其单元测试build.rs明确断言了split_groups(foo:bar:baz) [foo:bar:baz, foo:bar, foo]。因此运行最具体的叶子目标时会自动覆盖其全部祖先组而运行宽泛组时则聚合其下所有子组产物。第二层冒号转义。由于 Ninja 自身无法在目标名中表达冒号runner 在执行前将foo:bar统一改写为foo_bar见上文 build.rs从而把这套分层语法映射为 Ninja 可识别的扁平目标。一个需要留意的历史差异docs/ninja.md中示例写的是check:jest:deck-options但当前仓库的前端测试框架已从 Jest 迁移到Vitest——package.json 中定义的是vitest:once: cd ts vitest runbuild/configure/src/web.rs 注册的也是check:vitest目标测试文件位于ts/routes/deck-options等目录。也就是说分层目标的使用原则完全不变只是把示例中的jest换成当前的vitest或直接使用你关心的具体子组名。这一差异也解释了为何docs/ninja.md文件头部明确标注 DO NOT MANUALLY EDIT THIS FILE——它是从 docs-site/developers/ninja.mdx 自动同步的更新应改源文件。七、常见问题与排查路径结合docs/ninja.md与 docs/development.md开发中最常遇到的几类问题及对策如下n2 and ninja missing/failed. did you forget bash tools/install-n2?这是 runner 找不到任何 ninja 实现时的直接报错build.rs。按提示执行bash tools/install-n2Windows 上为bash tools\install-n2或把 ninja v1.11.1 加入 PATH 即可。构建刚启动就失败大概率是build.ninja引用了已被改名/删除的文件runner 会自动重新生成并重试一次若持续失败可删除out/build.ninja强制重新引导。Windows 上 ninja 行为异常检查 PATH 中是否混入了 msys/WSL 的版本确保原生二进制排在前面对应文档中的特别提醒WSL 与 MSYS2 bash 冲突时用完整路径调用 msys 的 bash 执行安装脚本。误改工具链版本rust-toolchain.toml固定了 1.97.1删除它使用发行版 Rust 时较新版本通常能编译但可能挂测试较旧版本可能根本无法编译见 docs/development.md。想要干净重来大部分构建产物都在out/目录Windows 另含node_modules删除它即可完全重建、释放空间Cargo/yarn/pip 的依赖缓存在系统共享位置~/.cargo、~/.cache/yarn等不受影响。自定义目录加入构建而免于格式检查把个人文件放进extra/文件夹会自动被版本跟踪与各类检查忽略见 docs/development.md。开发与日常使用混用开发时建议用-p [profile名]加载独立 profile因为ANKIDEV1会禁用自动备份见 run 与 docs/development.md。八、小结一条命令打通整个开发循环docs/ninja.md虽短却勾勒出了 Anki 现代开发工作流的全部关键动作场景命令构建并运行./runWindows.\run优化模式运行RELEASE1 ./run或./tools/runopt全部测试与检查./ninja check单个/子组检查./ninja check:svelte:editor、./ninja check:vitest等修正格式./ninja format修复 eslint/版权问题./ninja fix修复 clippycargo clippy --fix其背后是Rust runner 生成的 build.ninja 分层目标三位一体的设计runner 负责跨平台调用与冒号转义build/configure与build/ninja_gen两个 crate 负责声明目标和依赖图Ninja/n2 负责增量执行。对于从 Bazel 迁移过来的开发者只需要记住先装 ninja/n2 与 rustup、清掉旧构建残留然后一切操作都围绕./run与./ninja展开即可。更深入的构建体系细节可继续阅读 docs/development.md 与 justfile 中的命令清单。【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考