news 2026/9/10 6:49:06

Anki 的 Ninja 构建系统实战指南:从 Bazel 迁移到 `./run` 与 `./ninja` 的完整工作流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 走向 Ninja

Anki 是一个以 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 或 Ninja(1.10+),其中 n2 的构建状态输出更好,可用仓库自带的tools/install-n2一键安装;just命令运行器则作为实验性的官方命令入口被引入(见 justfile)。

整套构建的产物统一落入仓库根目录下的out/文件夹(Windows 上另有node_modules),Cargo、yarn、pip 的依赖缓存则复用系统共享缓存。理解了这套布局,再往下看具体的命令就水到渠成了。

二、环境准备:三步从零进入 Ninja 世界

按照docs/ninja.md的说明,从一个 Bazel 时代的旧 checkout 切换到 Ninja 构建体系需要完成三步:

  1. 把 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 之前。
  2. 通过 rustup 安装 Rust:仓库根目录的 rust-toolchain.toml 固定了工具链版本1.97.1,并声明了rust-analyzer组件;rustup 会在首次构建时自动下载该版本。注释明确说明:过旧的 Rust 版本可能根本无法编译,而过新的版本可能无法通过 clippy 测试,因此不要随意改动这个文件。
  3. 清理旧 checkout 残留:删除已有的.bazelnode_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 上为 .\run

docs/ninja.md对这一命令的描述只有一句话,但它背后的链路值得拆解。根目录的 run 脚本(bash)做了这几件事:

  1. 预设开发环境变量:ANKIDEV=1(打印额外日志、禁用自动备份,切勿在正式 profile 上使用)、QTWEBENGINE_REMOTE_DEBUGGING=8080ANKI_API_PORT=40000(可通过http://localhost:40000/_anki/pages/xxx.html直接访问前端页面,配合tools/web-watch可实现自动重建热调试);
  2. 调用./ninja pylib qt,把 Python 库与 Qt 界面编译到out/
  3. 最后用out/pyenv/bin/python tools/run.py启动 Anki。

第一次构建会较慢(需要下载并编译大量 Rust 依赖、提取 Node、建立 Python 虚拟环境),之后即为增量构建。若需优化构建(运行更快、编译更慢),按 docs/development.md 的说明使用:

./tools/runopt # 等价于 RELEASE=1 ./run # 或 RELEASE=1 ./run

RELEASE=2会做进一步优化但构建显著变慢。tools/runopt的实现也正是RELEASE=1 $(dirname $0)/../run(见 tools/runopt)。注意RELEASECIMAC_X86LIN_ARM64SOURCEMAPHMR等环境变量被写入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 -- $* # 把参数转交给 runner

runner是一个位于 build/runner/src/main.rs 的 Rust 小工具,通过 clap 提供pyenvyarnrsyncrunbuildarchive六个子命令,负责跨平台地调用各种构建动作并静默成功输出。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_bar(build.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 / fix

docs/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.rs
check:pytest:aqtqt/aqt 的 pytest 测试build/configure/src/aqt.rs
check:pytest:toolstools 的 pytest 测试build/configure/src/python.rs
check:mypyPython 类型检查build/configure/src/python.rs
check:ruffPython lintbuild/configure/src/python.rs
check:clippyRust lintbuild/configure/src/rust.rs
check:rust_testRust 单元测试build/configure/src/rust.rs
check:vitestTypeScript/Svelte 单元测试build/configure/src/web.rs
check:svelteSvelte 类型检查(svelte-checkbuild/configure/src/web.rs
check:eslint前端 lint(--max-warnings=0,零警告容忍)build/configure/src/web.rs
check:typescript:aqtqt 遗留 JS 的类型检查build/configure/src/aqt.rs
check:minilints版权头、贡献者、许可证等小检查build/configure/src/rust.rs

此外还有格式类检查目标:check:format:rust(rustfmt)、check:format:dprintcheck:format:prettiercheck:format:sqlcheck:format:python:{group}check:format:protocheck: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:rustformat:dprintformat:prettierformat:sqlformat:python:{group}format:protoformat:cog:{group})。以 Python 为例,build/ninja_gen/src/python.rsPythonFormat的实际命令是$ruff format $mode $in && $ruff check --select I --fix $in,即先用 ruff 格式化,再自动修复 import 排序。

5.3./ninja fix—— 修复 eslint 与版权问题

./ninja fix

fix面向两类问题:一是fix:eslint(对tsqt/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 testjust test-rustjust test-pyjust test-tsjust lintjust 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 run",build/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-n2(Windows 上为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,因为ANKIDEV=1会禁用自动备份(见 run 与 docs/development.md)。

八、小结:一条命令打通整个开发循环

docs/ninja.md虽短,却勾勒出了 Anki 现代开发工作流的全部关键动作:

场景命令
构建并运行./run(Windows:.\run
优化模式运行RELEASE=1 ./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/configurebuild/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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 6:45:49

从Oracle到openGauss:比亚迪MES系统数据库国产化迁移实践

1. 从Oracle到openGauss:MES系统为什么要换数据库这几年做制造业信息化的朋友应该都有同感:MES(制造执行系统)已经从锦上添花的“车间看板工具”,变成了工厂真正离不开的生产大脑。尤其在新能源汽车这类高度自动化、节…

作者头像 李华
网站建设 2026/9/10 6:45:29

RK3588 AI视觉推理帧率优化:从模型转换到NPU调度全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 6:44:37

CANN/ge 模型缓存特性

GE 模型缓存(Model Cache)特性 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存…

作者头像 李华
网站建设 2026/9/10 6:44:03

深入理解Java数组:从JVM内存到高频算法与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 6:39:05

基于Hadoop+Spark+Hive的租房推荐系统设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华