2 个不稳定版本
0.2.0 | 2020 年 8 月 29 日 |
---|---|
0.1.0 | 2020 年 8 月 29 日 |
#266 在 构建工具
被 3 个 crate 使用
6KB
126 行
ninja-rs
ninja-rs(就像忍者一样,但是发音时加入了“r”声;-)是 ninja 构建系统的克隆。
动机
ninja-rs 被创建出于几个不同的原因
-
教育 - 按需构建系统 是对我影响巨大的论文,因为它正式化了构建系统。这是尝试使用该论文中的想法实现真正的构建系统。有关更多设计方面的信息,请参阅下面的笔记。
-
在真实项目中使用 Rust 的实践,这是我日常工作之外的项目。
-
我以前从未编写过正确的解析器。
ninja 是一个相当简单的构建系统(它被明确设计成构建系统的“汇编语言”),因此我认为这是一个很好的起点。它相对较小,原始代码相当易于阅读。它不涉及任何网络或打包。正如 Evan Martin 所说
Ninja 的实现相当容易,有趣的部分占 20%,而剩下的 80% 只是一些繁琐的细节。
它仍在进行中。随着我对 Ninja 和 Rust 理解的不断发展,各种事情都在不断变化。
特性完整性
目前,解析器已相当完整,ninja-rs 能够构建此存储库中的简单 CMake hello-world。要完全支持 ninja 所支持的所有功能,还有很长的路要走。
- 基于解析器和拓扑排序的构建器
- 基于 mtime 的重建
- 与 Ninja 的基本命令行兼容性
- 隐式和有序依赖关系
- 变量和作用域
- 正确处理失败的命令
- 池
- 路径规范化
- Windows 支持(没有故意阻止它,但也没有进行测试。)
- Ninja 日志
- 构建文件再生
- C 编译器包含解析(GCC/clang 中的
-M,MSVC 中的
/showIncludes
)和依赖日志 - 动态依赖关系
- 更好的格式化输出
- 额外工具
- 提供版本
- 将
ninjars
作为可用的cargo install
命令提供。
用法
请确保您至少有 Rust 1.46-nightly(这是我测试过的唯一版本)。克隆此存储库,然后
cargo build --release -p ninjars --bin ninja
这将生成一个二进制文件 ./target/release/ninja
,它是静态链接的,可以复制到您需要的任何位置。
贡献
欢迎贡献。请理解我查看拉取请求的时间有限,因此我可能不会立即回复。对于错误修复,请简单地提交一个 PR,最好包括对该错误的回归测试。
如果您正在考虑实现一个主要功能,请 提交一个问题 讨论该功能以及您打算如何实现它。可能会有多种解决方案和权衡。如果您不先讨论,那么一个包含主要功能的千行 PR 很可能被拒绝。
测试
在工作区目录中运行此命令将运行所有测试。提交 PR 时,请确保所有测试都通过。请为任何引入的新代码添加一个测试。带有测试的 PR 更容易审查和接受。
RUST_BACKTRACE=full cargo test
格式化
在提交之前,在代码上运行 cargo fmt
。
设计
在思考各种事情的同时,我编写了 DESIGN.md 和 parse/DESIGN.md 文档,它们并不旨在对任何人有意义。本节总结了实际的设计。
此设计符合以下目标
尽量遵循《Build Systems a la Carte》论文中的原语
大多数生产构建系统在论文之前就已经存在,并且大多数系统在各个阶段之间没有严格的界限。我尽量在可能的情况下使原语发光。例如,重建器和调度器非常明确地使用图,用键和任务进行讨论,并尽可能避免直接 I/O(或思考现实世界中的事物,如 Unix 时间)。当然,由于 Rust 没有 Haskell 那样的完整泛型/高阶类型支持,因此在这方面存在限制。在 Rust 中过度编写通用代码会很快变得陈旧,因为您可能需要开始到处放置 trait 约束或泛型。这个第一阶段解析 ninja 文件,并将这些转换为构建描述。然后将其转换为论文中提到的键和任务。然后,有一个调度器(拓扑)和重建器(基于 mtime)的实现,实现了 ninja 语义。
在某种程度上,这几乎是一个 ninja 语言的编译器管道,但最终阶段是动作图的解释器,而不是代码生成器。
尽量提供可用的独立 crate
实现是一个 crate 的集合。解析器可以单独使用。词法分析器保留有关标记的相对完整信息(与 ninja 不同),这可能允许像 ninja-fmt
这样的功能。最初,我设想解析器会生成每个文件的 AST,但 ninja 处理 include
和 subninja
规则之间的作用域的方式,使得这变得难以处理。具体来说,包含文件中的变量会在当前环境中立即评估,因此这不能并行化,并且一旦发生包含,每个文件的 AST 就没有意义。
主要的 ninja
库只是将这些组件组合在一起。理论上,可以使用 tasks
和 build
库来创建另一个构建系统。目前存在一些不干净的 API 边界,但这些都可以被清理。
启用性能优化
我开始非常专注于尝试避免分配和复制,但随着 Rust 强大的所有权要求的加入,这很快变得难以处理。因此,我目前走向了另一个极端,大量使用 clone()
。词法分析器仍然是“零拷贝”,提供单个 &[u8]
的引用。规范化字符串归一化可以处理路径,只要愿意传播更多的生命周期,就可以在词法分析器和任务描述之间进行,而不需要复制。当然,变量评估始终需要分配新的字节。
还不清楚这能否在保持可读性的同时达到 ninja 的性能水平。
可读性/文档化
这通常是通过坚持原语来实现的,但我还针对一些更明显的问题进行了明确说明,比如重构建器对伪规则和污点的处理。类似地,调度器进行非常有意向的深度优先拓扑排序,与 ninja 不同,在 ninja 中,虽然也进行了类似操作,但图构建与解析和动态依赖的处理耦合在一起。
可测试性
Ninja 实际上没有规范。它有一个解释行为的手册,但没有原始实现是唯一的事实来源。这意味着许多规范行为只能通过在 ninja 中运行特定的构建文件来确定。我已经尝试尽可能地为代码库添加测试。特别是,解析器有一系列验收测试,以确保符合 ninja 规范,并作为回归测试。这些验收测试只是 .ninja
文件,可以用 ninja
快速运行,以确定它们是否遵循“规范”。