1 个不稳定版本
0.1.0 | 2020 年 8 月 29 日 |
---|
#754 in 开发工具
在 2 crates 中使用
96KB
2K SLoC
ninja-rs
ninja-rs (像忍者一样,但发音时加上一个“r”声 ;-)) 是 ninja 构建系统的克隆。
动机
ninja-rs 的创建有多个原因
-
教育 - Build Systems a la Carte 是对我影响巨大的论文,因为它正式和精炼了构建系统。这是尝试使用该论文中的想法实现一个真实构建系统的一次尝试。关于设计的更多内容,请参阅下面的设计说明。Ninja 的原始作者 Evan Martin 也 希望 他们有这篇论文。
-
在日常工作之外使用 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中的过度泛型代码可能会很快变得陈旧。在Rust中,如果需要在每个地方放置特质界限或泛型,则编写过多泛型代码可能会很快变得陈旧。首先,解析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
文件,可以用 ninja
迅速运行以确定它们是否遵循“规范”。
依赖项
~300–760KB
~18K SLoC