重做:递归的通用构建系统

2020-07-19 11:17:46

Redo是长寿的Make程序的竞争对手,但遗憾的是,Make程序并不完美。与其他同类竞争对手不同,Redo抓住了make的简单性和灵活性,同时避免了它的缺陷。它设法做到了这一点,同时比make更简单,比make更灵活,比make更强大,并且不会牺牲性能-ARARE特性的组合。

Redo的原始设计来自Daniel J.Bernstein(qmail和djbdns等许多有用功能的创建者)。他一度(没有日期)在他的网站上发布了一些简短的笔记,标题并不张扬:当源文件发生变化时重建目标文件。这些笔记足以理解系统应该如何工作;不幸的是,没有与之配套的代码。基于这个设计,我从头开始编写了这个重做实现。

在我发现djb redo之后,我在互联网上搜索其他人已经发现我所拥有的东西的任何迹象:一个隐藏的、未实现的、出色代码设计的宝石。我当时只发现了一个有趣的链接:AlanGrosskurth,他在滑铁卢大学的硕士论文是关于自上而下的软件重建,即DJB重做。他用大约250行shell脚本编写了自己的实现(诚然很慢),这让人对这个系统有多简单有个概念。从那时起,出现了几个其他实现(参见下面的列表)。

我的重做实现称为重做,原因与有75个不同版本的make都称为make的原因相同。那样的话就容易多了。

我还在该存储库的minimum/目录中提供了一个非常小的纯POSIX-sh实现,称为do。

重做背后的理论听起来好得令人难以置信:它可以做任何Make能做的事情,但实现要简单得多,语法更清晰,而且您无需求助于丑陋的黑客就可以获得更大的灵活性。此外,您可以获得非递归make的所有速度(每次运行只检查依赖项一次)和递归make的所有清晰度(您不会让一个模块的代码践踏另一个模块的代码)。

(免责声明:我目前的实现没有Make的速度快,因为它是用python编写的。最终,我会用C语言重写它,而且速度会非常非常快。)。

展示它的最简单的方法是跳到一个例子中。下面是编译C++程序的方法。

当然,您还必须创建A.c和B.c,这是您想要构建以创建应用程序的C语言资源文件。

您在default.o.do中捕捉到生成自依赖项的shell咒语了吗?文件名default.o.do表示运行此脚本以生成.o文件,除非有适用的更具体的.o.do脚本。";

关于重做,要理解的关键是声明依赖项是直接的其他shell命令。Redo-ifchange命令意味着构建每个my参数。如果它们中的任何一个或它们的依赖关系发生变化,那么我需要重新运行当前脚本。";

在持久的.redo数据库中跟踪依赖项,以便重做以后可以检查它们。如果需要重新构建文件,它将重新执行Whatever.do脚本并重新生成依赖项。如果文件不需要重新构建,只需使用其持久的.redo数据库,而无需重新运行脚本,redo就可以解决这一问题。而且它可以在项目构建开始时只做一次检查,这真的很快。

最棒的是,正如您在default.o.do中看到的那样,您可以在构建程序之后声明依赖项。在C语言中,您可以通过尝试实际构建来获得最好的依赖信息,因为这是找出您需要的头文件的方法。重做基于这样一个简单的见解:在构建目标之前,您实际上并不关心依赖项是什么。如果目标不存在,您显然需要构建它。

一旦您无论如何都要构建它,构建脚本本身就可以随心所欲地计算依赖关系信息;与make不同,您根本不需要特殊的依赖关系语法。您甚至可以在构建之后声明一些依赖项,这使得C风格的自依赖项变得简单得多。

因此,重做是命令性编程和声明性编程的独特组合。初始构建几乎完全是必需的(运行一系列脚本)。作为其中的一部分,脚本一次声明几个依赖项,然后重做将它们组装到一个更大的数据结构中。然后,在将来,它使用预先声明的数据结构来决定需要重做哪些工作。

(GNU make支持将您的一些依赖项放入包含文件中,并在这些包含文件发生更改时自动重新加载它们。但这非常令人困惑-通过Makefile的程序流已经很难跟踪了,当它从头重新启动时就更难了,因为包含文件在运行时会发生变化。使用重做,您可以从上到下读取每个构建脚本。Redo-ifchange调用类似于调用函数,您也可以从上到下读取该函数。)

一些较大的专有项目正在使用它,但不幸的是,它们可以很容易地从本文档中链接起来。以下是几个开放源码示例:

解放赛道是一个用重做编译的C++二进制(游戏)的简单示例。

WvStreams使用更复杂的设置,生成几个二进制文件、库和脚本。它显示了如何在与源文件不同的目录中生成输出文件。

WvBuild可以交叉编译几个依赖项,如OpenSSL和zlib,然后使用这些相同的库构建WvStream。它是重做/建立互操作和复杂依赖关系的一个很好的例子。

Buildroot有一个实验性的变体,它使用重做来清理其依赖逻辑。

您可以在食谱中找到一些精选的教程示例,例如使用LaTeX进行git变量替换和文本处理(包括使用R和ggplot2生成绘图)。

如果您将您的程序的构建过程切换为使用重做,请让我们知道,我们可以在这里链接到它,以获得一些免费的宣传。

(请不要使用重做项目t/目录中的集成测试代码作为如何使用重做的严肃示例。许多测试都是故意用精神错乱的方式来做事情,以强调重做代码并找出错误。另一方面,如果您正在构建自己的重做实现,使用我们的测试套件是个不错的主意。)

DJB从未发布过他的版本,所以其他人已经基于他发布的规范实现了他们自己的变体。

这个版本,有时称为Apenwarr/Redo,可能是最先进的版本,包括对并行构建的支持、弹性时间戳和校验和、构建日志线性化以及有用的调试特性。它目前是用python编写的,以便于实验,但计划最终将其移植到纯C语言。(有些人喜欢把这个版本叫做python-redo;,但我不喜欢这个名字。当我们稍后将代码音译为C时,我们不应该将其重命名。)。

以下是其他一些重做变体(感谢Nils Dagsson Moskopp收集了许多这样的链接):

Alan Grosskurth的重做论文和相关的sh实现。(可以说,这篇论文让我们所有人都开始了。)。

Apenwarr';的最小/DO包含在此重做副本中。它也是基于sh的,但它的目的是简单和故障保护,所以它不知道如何多次重做目标。

最初的重做设计是如此简单和优雅,以至于许多人都受到了启发,想(并且能够)写出他们自己的版本。按照Unix制造的光荣传统,它们(几乎)都使用相同的名称,重做。不幸的是,这些实现中的许多都没有得到维护,与重做语义标准稍有不兼容,和/或很少或没有自动化测试。

在撰写本文时,除了Apenwarr/Redo(即。此项目)正确支持并行构建(redo-j)或日志线性化(redo-log)。对于大型项目,并行构建通常被认为是必不可少的。

此版本的Redo中的自动化测试几乎(但不完全)适用于测试任何Redo实现。如果您确实必须编写新版本的重做,我们邀请您针对现有的测试套件对其进行彻底测试,以确保兼容性。您还可以窃取我们的测试(当然,带有属性),并将它们包含在您自己的源包中。我们也希望您在发现错误时提供更多的自动化测试,或者如果您发现测试意外地与其他重做实现不兼容(而不是发现真正的错误),请将补丁发送给我们。