1 个不稳定版本
0.1.0 | 2020 年 8 月 29 日 |
---|
#11 在 #ninja 分类中
175KB
4K SLoC
ninja-rs
ninja-rs (就像忍者一样,但发音时加上一个“r”声 ;-)) 是 ninja 构建系统的克隆。
动机
ninja-rs 的创建有几个不同的原因
-
教育 - 《按需构建系统》对我影响很大,因为它正式化和精炼了构建系统。这是尝试使用该论文中的想法实现一个真正的构建系统的尝试。有关更多设计方面的信息,请参阅下面的注释。ninja 的原始作者 Evan Martin 也 希望 他们有这篇论文。
-
在真实项目中使用 Rust 的一个练习,而不仅仅是在我的日常工作之外。
-
我以前从未写过合适的解析器。
ninja 是一个相当简单的构建系统(它被明确设计为构建系统的“汇编语言”),所以我认为这是一个好的起点。它相对较小,原始代码相当易于阅读。它不涉及任何网络或包装。正如 Evan Martin 所说
Ninja 的实现相当简单,乐趣的 20% 就是这样,剩下的 80% 只是一些繁琐的细节。
这是一个正在进行中的项目。随着我对 ninja 和 Rust 的理解不断深化,各种事情都在变化,并且仍在变化。
功能完整性
目前,解析器功能相当完整,ninjars 能够构建这个仓库中的简单 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,最好附上该错误的回归测试。
如果您正在考虑实现一个主要功能,请提出一个问题讨论该功能和您打算如何实现它。可能存在多种解决方案和权衡。如果您不先讨论,那么包含主要功能的1000行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那样的完整泛型/高阶类型支持。如果必须在每个地方放置trait界限或泛型,那么在Rust中过度编写通用代码会很快变得过时。有一个阶段解析ninja文件并将这些转换为构建描述。然后,将其转换为论文中提到的键和任务。然后,有一个调度器(拓扑)和重构器(基于mtime)的实现,实现了ninja语义。
在某种意义上,这几乎是一个ninja语言的编译器管道,但最终阶段是动作图的解释器而不是代码生成器。
尽可能有可用的独立crate
实现是一个crate集合。解析器可以单独使用。词法分析器保留了关于标记的相对完整信息(与ninja不同),这可能允许像ninja-fmt
这样的东西。我最初设想解析器会生成每个文件的AST,但ninja处理跨越include
和subninja
规则的作用域的方式使得这变得复杂。具体来说,包含文件中的变量会立即在当前环境中评估,因此无法并行化,并且一旦发生包含,每个文件的AST就毫无意义。
主要的ninja
crate将这些组件组合在一起。理论上,可以使用tasks
和build
crates来创建另一个构建系统。目前存在一些不干净的API边界,但这些是可以清理的。
启用性能优化
我一开始非常注重尝试避免分配和复制,但随着Rust强大的所有权要求的限制,这很快就变得难以处理。所以我目前采取了另一个极端,大量使用 clone()
。词法分析器仍然是“零复制”,提供对单个 &[u8]
的引用。规范化(Canonicalization)和字符串内联(string-interning)可以处理路径,只要愿意传播更多的生命周期,就可以从词法分析器直接到任务描述,而不需要复制。当然,变量评估始终需要分配新的字节。
目前还不清楚,这种做法是否能够在保持可读性的同时达到ninjas级别的性能。
易于阅读/文档化
这通常是通过坚持原始数据类型来实现的,但在某些情况下,当不清楚如何将“ninjas如何实现”转换为“如何用论文中的原始数据类型表达”时,我也会明确指出这些零散的部分。一个特别的例子是重建器对伪规则和脏污的处理。同样,调度器执行了一个非常有意向的深度优先拓扑排序,这与ninjas不同,ninjas虽然也执行类似的操作,但图构建与解析和动态依赖的处理是耦合的。
可测试性
Ninja实际上没有一个规范。它有一个手册,广泛地解释了行为,但原始实现是唯一的真实来源。这意味着很多规范行为只能通过运行特定的构建文件通过ninja来确定。我已经尽可能地为代码库添加了测试。特别是,解析器有一系列接受测试,以确保与ninja的一致性,并作为回归测试。这些接受测试就是 .ninja
文件,可以用 ninja
快速运行以确定它们是否遵循“规范”。
依赖关系
~8MB
~128K SLoC