#ninja-build #build-system #ninja #tooling

构建 ninja-metrics

为 ninja-rs 创建的一个简单的基于 RAII 的指标收集 crate,但也可以独立使用

2 个不稳定版本

0.2.0 2020 年 8 月 29 日
0.1.0 2020 年 8 月 29 日

#266构建工具


3 个 crate 使用

Apache-2.0 协议

6KB
126

ninja-rs

ninja-rs(就像忍者一样,但是发音时加入了“r”声;-)是 ninja 构建系统的克隆。

动机

ninja-rs 被创建出于几个不同的原因

  1. 教育 - 按需构建系统 是对我影响巨大的论文,因为它正式化了构建系统。这是尝试使用该论文中的想法实现真正的构建系统。有关更多设计方面的信息,请参阅下面的笔记。

  2. 在真实项目中使用 Rust 的实践,这是我日常工作之外的项目。

  3. 我以前从未编写过正确的解析器。

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.mdparse/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 处理 includesubninja 规则之间的作用域的方式,使得这变得难以处理。具体来说,包含文件中的变量会在当前环境中立即评估,因此这不能并行化,并且一旦发生包含,每个文件的 AST 就没有意义。

主要的 ninja 库只是将这些组件组合在一起。理论上,可以使用 tasksbuild 库来创建另一个构建系统。目前存在一些不干净的 API 边界,但这些都可以被清理。

启用性能优化

我开始非常专注于尝试避免分配和复制,但随着 Rust 强大的所有权要求的加入,这很快变得难以处理。因此,我目前走向了另一个极端,大量使用 clone()。词法分析器仍然是“零拷贝”,提供单个 &[u8] 的引用。规范化字符串归一化可以处理路径,只要愿意传播更多的生命周期,就可以在词法分析器和任务描述之间进行,而不需要复制。当然,变量评估始终需要分配新的字节。

还不清楚这能否在保持可读性的同时达到 ninja 的性能水平。

可读性/文档化

这通常是通过坚持原语来实现的,但我还针对一些更明显的问题进行了明确说明,比如重构建器对伪规则和污点的处理。类似地,调度器进行非常有意向的深度优先拓扑排序,与 ninja 不同,在 ninja 中,虽然也进行了类似操作,但图构建与解析和动态依赖的处理耦合在一起。

可测试性

Ninja 实际上没有规范。它有一个解释行为的手册,但没有原始实现是唯一的事实来源。这意味着许多规范行为只能通过在 ninja 中运行特定的构建文件来确定。我已经尝试尽可能地为代码库添加测试。特别是,解析器有一系列验收测试,以确保符合 ninja 规范,并作为回归测试。这些验收测试只是 .ninja 文件,可以用 ninja 快速运行,以确定它们是否遵循“规范”。

无运行时依赖