Anki 仓库的 cargo 目录解析:Rust 许可证清单与 nightly rustfmt 工具链
Anki 仓库的 cargo 目录解析Rust 许可证清单与 nightly rustfmt 工具链【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki导读在 AnkiGitHub 推荐项目精选 / an / anki这个大型 Rust Python TypeScript 混合仓库中cargo/目录承担着两项容易被忽视却至关重要的构建基础设施职责维护一份全量 Rust 依赖许可证清单并为代码格式化锁定一个专用的 nightly rustfmt 工具链。本文基于 cargo/README.md 及其背后的实现源码深入讲解这两个文件的生成、校验与修复机制帮助你理解 Anki 的自动化构建系统Ninja justfile如何保证依赖合规与代码风格统一并掌握./ninja [check|fix]:minilints等命令的实际用法。一、cargo/ 目录在整个仓库中的定位cargo/目录只包含三个条目结构非常精简cargo/ ├── README.md # 目录说明本文的主题文档 ├── format/ │ └── rust-toolchain.toml # nightly 格式化工具链定义 └── licenses.json # Rust 依赖许可证清单约 3700 行正如 cargo/README.md 所述这个目录承载两件事一份 Rust crate 许可证清单licenses.json通过./ninja [check|fix]:minilints检查或更新一份用于代码格式化的 nightly 工具链定义format/rust-toolchain.toml。把这两项基础设施单独收拢在一个目录中是为了与仓库根目录下的稳定版工具链rust-toolchain.toml 中channel 1.97.1相互隔离日常编译使用稳定版只有运行rustfmt时才会切换到 nightly避免 nightly 编译器的行为漂移影响正常的构建与测试。二、cargo/format/nightly rustfmt 格式化工具链2.1 工具链定义文件详解cargo/format/rust-toolchain.toml 的完整内容如下[toolchain] channel nightly-2025-03-20 profile minimal components [rustfmt]三个字段各有讲究channel nightly-2025-03-20把 nightly 工具链精确锁定到具体日期。这是为了保证rustfmt的格式化结果在不同时间、不同开发者机器上完全一致。nightly 版本的rustfmt会持续引入新的格式化规则若不锁定日期同一份代码在不同日期检出后可能被格式化成不同结果导致 CI 与本地检查不一致。profile minimal只安装运行所需的最小组件集合避免把 clippy、rust-docs 等无关组件一并拉取节省磁盘与安装时间。nightly 工具链在本仓库中只用于rustfmtminimal profile 足够。components [rustfmt]显式声明需要 rustfmt 组件。rustfmt 在 nightly 工具链中的默认行为会随版本变动这里把它列为强制组件确保工具链可用。2.2 子目录隔离机制Rust 的 toolchain 文件采用“从当前目录向上查找”的解析规则执行cargo相关命令时rustup 会在当前目录及各级父目录中查找最近的rust-toolchain.toml并采用其定义的通道。Anki 的构建系统正是利用了这一点。在 build/configure/src/rust.rs 中格式化动作check:format:rust与format:rust都指定了working_dir: Some(cargo/format)也就是在cargo/format/子目录下执行 cargo 命令从而使 rustup 命中 nightly 工具链build.add_action( check:format:rust, CargoFormat { inputs: inputs.clone(), check_only: true, working_dir: Some(cargo/format), }, )?; build.add_action( format:rust, CargoFormat { inputs: inputs.clone(), check_only: false, working_dir: Some(cargo/format), }, )?;而同一目录树下的 rust-toolchain.toml稳定版 1.97.1附带rust-analyzer组件则被check_rust列入rslib等编译动作的输入依赖保证常规编译始终使用稳定版。两个 toolchain 文件各司其职、互不干扰。2.3 如何在日常开发中使用格式化动作挂接在 Ninja 构建系统与 justfile 上# 只检查格式不修改文件CI 中常用 ./ninja check:format:rust # 实际执行格式化 ./ninja format:rustjustfile 提供了更短的入口justfile# Check formatting (fast, no build needed) check-format: {{ ninja }} check:format # Fix formatting format: {{ ninja }} format注意 justfile 中的check:format/format是聚合目标除了 Rust 格式化外通常还包含 Pythonruff/black 等与 TypeScript/Svelteprettier 等的格式化检查而check:format:rust/format:rust才是专门针对 Rust 的子目标。三、cargo/licenses.json全量 Rust 依赖许可证清单3.1 文件结构与内容cargo/licenses.json 是一个由cargo-license工具生成的 JSON 数组记录了仓库 Rust workspace 的所有依赖 crate 的许可证信息。以文件开头的两条记录为例[ { authors: null, description: A cross-platform symbolication library written in Rust, using gimli, license: Apache-2.0 OR MIT, license_file: null, name: addr2line, repository: https://github.com/gimli-rs/addr2line }, { authors: Jonas Schievink jonasschievinkgmail.com|oyvindln oyvindlnusers.noreply.github.com, description: A simple clean-room implementation of the Adler-32 checksum, license: 0BSD OR Apache-2.0 OR MIT, license_file: null, name: adler2, repository: https://github.com/oyvindln/adler2 } ]每条记录包含六个字段namecrate 名、licenseSPDX 许可证表达式、authors作者列表、description、repository与license_file。值得注意的是仓库自身的核心 crateanki即rslib在列表中标注为AGPL-3.0-or-later许可证来源正是 rslib 与整个仓库采用的 GNU AGPL v3 协议见根目录 LICENSE。3.2 生成逻辑从源码到清单许可证清单并非手工维护而是由 minilints 工具调用cargo-license自动生成。在 tools/minilints/src/main.rs 中可以看到完整的生成流程fn generate_licences() - ResultString { Command::run(cargo install cargo-license0.7.0)?; let output Command::run_with_output([ cargo-license, --features, rustls, --features, native-tls, --json, --manifest-path, rslib/Cargo.toml, ])?; let licenses: VecBTreeMapString, serde_json::Value serde_json::from_str(output.stdout)?; let filtered: VecBTreeMapString, serde_json::Value licenses .into_iter() .map(|mut entry| { entry.remove(version); entry }) .collect(); Ok(serde_json::to_string_pretty(filtered)?) }关键细节以rslib/Cargo.toml为清单入口许可证收集以 Anki 的 Rust 核心库为根覆盖其全部传递依赖同时启用rustls与native-tls两个 feature确保 TLS 相关依赖在两种特性组合下都被纳入收集范围避免因 feature 裁剪导致清单缺项移除version字段输出前会剔除 crate 版本号降低依赖升级时清单的 diff 噪音——毕竟许可证合规关注的是 crate 与许可证本身而非精确版本输出为 pretty JSON最终以serde_json::to_string_pretty格式化写入便于 diff 与人工审阅。四、minilints驱动 check 与 fix 的利器4.1 minilints 是什么minilints 是 Anki 仓库中一个独立的 Rust 二进制工具workspace 成员见 Cargo.toml负责多项仓库卫生检查。虽然它因许可证检查而被 cargo/README.md 提及但其实际职责更广。tools/minilints/src/main.rs 的入口函数依次执行三类检查fn main() - Result() { let mut args env::args(); let want_fix args.nth(1) Some(fix.to_string()); let stamp args.next().unwrap(); let mut ctx LintContext::new(want_fix); ctx.check_contributors()?; // 校验 CONTRIBUTORS 文件 ctx.check_rust_licenses()?; // 校验 cargo/licenses.json ctx.walk_folders(Path::new(.))?; // 遍历源码目录检查版权头与 /// 注释 ... }以fix作为第一个参数运行时进入修复模式否则为只读检查模式。4.2 许可证检查check_rust_licenses这是与cargo/licenses.json直接相关的检查逻辑tools/minilints/src/main.rscheck 模式按上述流程重新生成许可证清单与现有cargo/licenses.json逐字节比对若不一致则输出提示cargo/licenses.json is out of date; run ./ninja fix:minilints并返回失败状态fix 模式先执行cargo install cargo-deny0.20.2并运行cargo deny check做一次更严格的许可证策略校验通过后才把重新生成的清单写回cargo/licenses.json。也就是说fix:minilints在更新清单之前还会用 cargo-deny 对全部依赖做一次许可证明文校验双重保障依赖的许可证符合项目策略。4.3 版权头与其他检查除了许可证minilints 的walk_folders还会遍历整个仓库对py、ts、rs、svelte、mjs五类源码文件检查标准版权头文件开头必须包含Ankitects Pty Ltd and contributors字样check_copyright缺失时在 fix 模式会自动按扩展名插入对应注释格式的 AGPL 头///注释检查ts与svelte文件中的///会被视为非文档注释而报错/// reference指令除外避免与三斜线指令语义冲突check_triple_slashCONTRIBUTORS 校验最新一次提交的作者必须出现在 CONTRIBUTORS 文件中否则给出提示并退出check_contributors同时支持通过CONTRIBUTORS_BYPASS_EMAILS环境变量放行特定邮箱例如 GitHub 隐藏邮箱的12345userusers.noreply.github.com与userusers.noreply.github.com两种形式会被normalize_email归一化后匹配。此外工具内置了两张排除名单NONSTANDARD_HEADER允许不套用标准版权头的例外文件如 pylib/anki/_vendor/stringcase.py和IGNORED_FOLDERS跳过out、node_modules、qt/aqt/forms、.venv等生成目录或第三方代码目录。4.4 构建系统接线minilints 被接入 Anki 的 Ninja 构建图接线代码位于 build/configure/src/rust.rs 的check_minilintsfn command(self) - str { $minilints_bin $fix $stamp }其中check:minilints与fix:minilints两个 Ninja 目标共用同一个二进制只是传入的fix参数不同check/fix输入依赖覆盖仓库内所有py/rs/ts/svelte/mjs/md文件并排除target、extra、.mypy_cache、node_modules、ts/.svelte-kit等目录以及Cargo.lock任何相关文件变更都会触发重跑minilints 二进制本身通过build:minilints动作以-p minilints编译并以构建工具 profilerelease构建输出是一个 stamp 文件tests/minilints.check或tests/minilints.fix用于增量构建的依赖追踪。4.5 常用命令速查# 只读检查copyright 头、/// 注释、CONTRIBUTORS、许可证清单 ./ninja check:minilints # 修复模式自动补版权头并重新生成 cargo/licenses.json含 cargo-deny 校验 ./ninja fix:minilints # justfile 等价别名 just minilints # 等价于 ./ninja check:minilints just fix-minilints # 等价于 ./ninja fix:minilints结合 justfile 中的定义# Run minilints (copyright, contributors, licenses) minilints: {{ ninja }} check:minilints # Fix minilints (update licenses.json) fix-minilints: {{ ninja }} fix:minilints五、实际工作流建议在 Anki 仓库中贡献代码时建议按以下顺序处理 Rust 相关卫生检查提交前执行./ninja check:minilints与./ninja check:format:rust分别确认许可证清单/版权头与 rustfmt 格式没有漂移若check:minilints报出cargo/licenses.json is out of date说明新增/升级了 Rust 依赖运行./ninja fix:minilints自动更新清单注意 fix 模式会先要求当前工作区没有未暂存的改动并调用cargo deny check做合规校验若格式化检查失败运行./ninja format:rust实际在cargo/format子目录下用 nightly rustfmt 重排代码。这套机制的核心价值在于许可证清单与格式化工具链都不是靠开发者手工记忆维护的而是由构建系统根据实际依赖树与固定 nightly 版本自动生成、自动校验从而让许可证合规检查和代码风格检查可以在 CI 中零人工干预地持续运行。总结cargo/目录虽然只有三个文件却是 Anki 仓库 Rust 工具链治理的缩影format/rust-toolchain.toml通过子目录隔离 日期锁定的 nightly rustfmt为全仓库提供确定性的格式化结果licenses.json则由 minilints 工具基于cargo-license对rslib依赖树自动生成并通过./ninja [check|fix]:minilints与 cargo-deny 双重校验持续保鲜。理解这两个文件及其背后的构建接线不仅能帮助你顺畅地在 Anki 仓库中提交 Rust 代码也为自建大型混合语言仓库时设计自动化卫生检查提供了可借鉴的范式。延伸阅读cargo/README.md本目录的官方说明cargo/format/rust-toolchain.tomlnightly 格式化工具链定义cargo/licenses.json全量 Rust 依赖许可证清单tools/minilints/src/main.rsminilints 检查/修复实现build/configure/src/rust.rscheck:minilints/fix:minilints构建接线justfileformat与minilints相关命令别名rust-toolchain.toml仓库根目录的稳定版1.97.1编译工具链Cargo.tomlworkspace 成员列表含tools/minilintsCONTRIBUTORS贡献者名单minilints 会校验提交作者是否在列【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考