1 个不稳定版本
0.1.0 | 2020 年 8 月 29 日 |
---|
#1422 in 开发工具
用于 ninjars
160KB
3.5K SLoC
ninja-rs
ninja-rs (像忍者一样,但发音时加上一个突兀的 "r" ;-)) 是 ninja 构建系统的克隆。
动机
ninja-rs 是出于几个不同的原因创建的
-
教育 - Build Systems a la Carte 是对我影响极大的论文,因为它正式化和提炼了构建系统。这是尝试使用该论文中的思想实现一个真正的构建系统的尝试。有关更多设计信息,请参阅下面的设计笔记。
-
在我日常工作之外使用 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
,您可以将它复制到您需要的地方。
贡献
欢迎贡献。请理解我查看pull请求的时间有限,因此可能无法及时回复。对于错误修复,只需提交一个PR,最好附上对该错误的回归测试。
如果您正在考虑实现一个主要功能,请提交一个issue,讨论该功能以及您打算如何实现它。可能存在多种解决方案和权衡。如果一个包含主要功能的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语言的编译器管道,但最终阶段是动作图的解释器而不是代码生成器。
尽量有可用的独立crates
实现是一个crates集合。解析器可以单独使用。词法分析器保留有关标记的相对完整信息(与ninja不同),这可能允许像ninja-fmt
这样的功能。最初,我设想解析器会生成每个文件的AST,但ninja处理跨include
和subninja
规则的作用域的方式,使得这变得复杂。具体来说,包含文件中的变量会在当前环境中立即评估,因此无法并行化,并且一旦发生包含,每个文件的AST就变得没有意义。
主要的ninja
crate将这些组件组合在一起。理论上,可以使用tasks
和build
crates创建另一个构建系统。目前存在一些不干净的API边界,但它们适合清理。
启用优化性能
我最初非常专注于尝试避免分配和复制,但由于Rust对所有权有严格的要求,这很快就变得难以处理。因此,我目前采取了另一种极端做法,即大量使用clone()
。词法分析器仍然是“零复制”,提供单个&[u8]
的引用。规范化(Canonicalization)和字符串国际化可以处理路径,只要愿意传播更多生命周期,就可以从词法分析器直接到任务描述而不需要复制。当然,变量评估始终需要分配新的字节。
目前还不清楚这样做是否可以在保持可读性的同时达到ninja的性能水平。
可读性/文档化
这通常源于坚持原语,但在不清楚如何将“ninja如何做”转化为“如何用论文中的原语表达”时,我也指出了更多片段。一个特定的例子是重建器对伪规则和脏度的处理。同样,调度器执行了非常有意向的深度优先拓扑排序,而ninja则不同,其中也执行了类似操作,但图构建与解析和处理动态依赖耦合在一起。
可测试性
Ninja实际上没有规范。它有一个手册,广泛解释了行为,但原始实现是唯一可信的来源。这意味着许多规范行为只能通过运行特定的构建文件通过ninja来确定。我已经尽可能为代码库添加了测试。特别是,解析器有一系列验收测试,以确保与ninja的兼容性,同时也作为回归测试。这些验收测试只是.ninja
文件,可以用ninja
快速运行,以确定它们是否遵循“规范”。
依赖项
~8MB
~124K SLoC