我想分享一个开发语言无关构建系统的实验,该系统不需要用户指定各个构建任务之间的依赖关系。我所说的“语言无关”是指用户不需要学习新的语言或特殊的文件格式来描述构建任务——现有的脚本或可执行文件可以直接用作构建任务,无需修改。
我称之为构建系统漫步因为它的构建算法让我想起了在一个你从未去过的公园里漫步,试图找出通往目标目的地的最佳路径,有时可能会绕圈,直到你建立起一个完整的公园心理地图。
主要想法
大多数构建系统都要求用户指定生成任务以及依赖关系任务之间:了解依赖项可以让构建系统在修改某些源文件时确定需要执行哪些任务。从个人经验来看,准确地描述依赖关系是困难的,而且常常是沮丧的根源。
构建任务及其依赖项通常使用特定于域的任务描述语言进行描述:制造商使用生成文件巴泽尔使用Python灵感Starlark公司, 摇一摇使用Haskell等。虽然学习一种新的任务描述语言并不是什么大事,但将现有的构建系统翻译成一种新语言可能需要几年的时间(或者我可能只是速度较慢)。
漫步是不同的。您使用自己喜欢的语言来描述构建任务,将它们放在一个目录中,然后让Stroll执行它们。漫步不看任务内部;它将任务视为黑盒,并通过跟踪它们的文件访问来查找它们之间的依赖关系。这个过程并不是最佳的,因为任务可能会失败,因为它的依赖关系还没有准备好,因此需要稍后再次构建,但最终,Stroll将学习完整准确的依赖关系图,并将其存储起来以加快未来的构建。
有一个名为制造它还跟踪文件访问以自动计算准确的任务依赖关系,但它要求用户在轻量级Python DSL中描述构建任务,并通过按正确的顺序调用任务来预先安排它们,本质上是要求用户自己完成漫游部分。Fabricate很酷,它给了Stroll灵感,但我想进一步探索构建系统空间的这个角落。
演示
Stroll只是一个实验,我不确定它背后的主要思想是否可行,但它已经可以用于自动化一些简单的构建任务集合。让我给你演示一下。下面我使用的是Windows,但演示也可以在Linux上运行。
考虑文件中存储的一个简单的“Hello,World!”程序src/main。c(c)以下为:
#包括<stdio.h>
整数 主要的(){打印(“你好,世界!\n个");返回 0;}
我们可以构建一个可执行文件bin/main.exe通过使用以下简单的构建脚本构建/main.bat以下为:
mkdir(主目录)箱子海湾合作委员会型钢混凝土/主要的。c(c)-o垃圾箱/主要的。可执行文件
让Stroll构建我们的小项目:
$漫步建造正在执行build/main.bat。。。
多恩
$bin/main.exe你好,世界!
在执行构建任务时,Stroll通过附加扩展名来跟踪其文件访问并将发现的信息存储在构建脚本旁边.漫步他们的名字:
$cat构建/main.bat.strollexit-code:退出成功
操作:
bin/main.exe:
书写:efc851e573be26cf8fe726caf70facf924ccdbae5cfce241fdbe728b3abde76
src/main.c:
阅读:bc31bb10c238be7ee34fd86edec0dc99d145f56067b13b82c351608efd38763c
build/main.bat:
阅读:da9a4390693741b8d52388f18b1c5cc418531bc3b0a64900890c381a31e7839
如您所见,Stroll记录了脚本的退出代码、两次文件读取和一次文件写入,以及相应的文件内容哈希。
我们可以让Stroll将发现的依赖关系图可视化:
$漫步-g构建|dot-Tpng-Gdpi=600-o graph.png
国旗-克告诉Stroll打印出在DOT格式我们随后将其转换为以下PNG文件:
绿色框表示唯一的生成任务主要的是最新的。这意味着Stroll不会在下一次运行中执行它,除非它的任何输入或输出发生变化。如果我们修改src/main。c(c)并重新生成依赖关系图,我们可以看到任务主要的现已过期:
过期的特定依赖项由虚线边缘显示。请注意,Stroll是一个自跟踪构建系统也就是说,它不仅跟踪源代码和构建工件中的更改,还跟踪构建任务中的更改。
为了使示例更有趣,让我们添加一个提供问候功能的库打招呼在个文件中src/lib/lib。小时和src/lib/lib。c(c)以下为:
$猫src/lib/lib。小时void greet(字符*名称);
$猫src/lib/lib。c(c)#包括<stdio.h>#包括<lib.h>void greet(字符*名称)
{
printf(“你好,%s!\n”,name);
}
为了编译库,我们将添加一个新的构建任务构建/lib.bat以下为:
mkdir(主目录)箱子/图书馆海湾合作委员会 -Isrc公司/图书馆-c型钢混凝土/图书馆/图书馆。c(c)-o垃圾箱/图书馆/图书馆。o(o)
如果在运行Stroll之前查看依存关系图,我们将看到:
新任务图书馆出现在图表中,但没有任何依赖项;它被标记为过期,因为Stroll从未执行过它。如果我们现在运行Stroll,它将执行两个任务,达到以下状态:
这些任务当前是独立的,可以并行构建,但让我们通过修改主要的源文件如下:
#包括<lib.h>
整数 主要的(){问候(“世界”);返回 0;}
如果我们现在运行Stroll,构建将失败:
$漫步建造正在执行build/main.bat。。。
脚本build/main.bat失败。
多恩
$cat构建/main.bat.stderr子目录或文件箱已存在。
src\main.c:2:17:致命错误:lib.h:没有这样的文件或目录
编译已终止。
通过检查文件构建/main.bat.stderrStroll创建的很有用,我们可以看到构建失败了,因为我们忘记修改构建脚本和点海湾合作委员会去我们的图书馆。带有双边界的任务显示错误:
让我们修改脚本主/内部.bat以下为:
mkdir(主目录)箱子海湾合作委员会 -Isrc公司/库src/主要的。c垃圾箱/图书馆/图书馆。o(o)-o垃圾箱/主要的。可执行文件
通过此修复程序,Stroll成功完成并生成以下依存关系图:
现在我们可以演示提前截止向文件添加注释src/lib/lib。c(c)。在运行Stroll之前,让我们检查一下此更改是否同时实现了图书馆和主要的太 土 了:
如您所见,这两个任务现在都标记为过期:图书馆的直接输入已更改,这也会间接影响主要的.让我们漫步:
$漫步建造正在执行build/lib.bat。。。
多恩
Stroll重建了库,但文件bin/lib/lib。o(o)没有更改,因此恢复了任务的最新状态主要的。
最后,为了在实验结束后进行清理,让我们创建一个新任务清洁的并将其放置到不同的目录中:清洁/清洁.bat。
$cat清洁/清洁.batrm bin/lib/lib。o(o)
rm bin/main.exe
$漫步清洁正在执行clean/clean.bat。。。
多恩
相应的依赖关系图显示了两个输出-删除的二进制文件。
您可能已经注意到,Stroll使用目录作为与公共构建目标相关的构建任务的集合。要构建目标,我们只需在相应的目录中运行Stroll。如果只想从目录中生成单个任务,可以指定完整路径,例如:
$漫步构建/lib.bat正在执行build/lib.bat。。。
这将执行图书馆任务(无论其当前状态如何)。请注意主要的任务当前已过期,因为清洁的删除了其输出:
要完成生成,请运行漫步建造,它将只执行主要的任务。
挑战: 尝试安排一种情况,Stroll将其中一个任务执行两次,从而证明Stroll不是最小的构建系统。
发动机罩下面
Stroll在约400行Haskell(包括注释)中实现,除标准库外,还使用以下库:
非常感谢为这些项目做出贡献的每一个人!
欢迎您加入浏览源代码漫步和/或玩它,但要注意:它有一些严重的限制(下面讨论),我不确定它们是否会被修复。
限制
漫游很有趣,我认为它是一个成功的实验,但当前的实现有一些严重的限制:
- 这个fsatrace公司该实用程序不跟踪对不存在文件的读取,这意味着Stroll无法确定任务是否过期,因为以前丢失的文件现在已经出现。目录扫描也没有被跟踪,这就是为什么我没有使用像这样的命令rm-射频箱/*在中清洁的脚本。有一个无这些限制的理想跟踪漫游模型在这里。
- 尽管fsatrace公司可以跟踪文件移动,例如。中波src-dst,Stroll尚不支持它,如果检测到错误,它将终止。
- 当前的实现是完全顺序的,即构建任务是一个接一个地执行的。使Stroll并行是可能的,但这需要相当多的工程。我可能有一天会这样做。
- Stroll不是一个云构建系统,尽管可以通过添加由散列键控的工件共享存储来实现。有关更多详细信息,请参阅构建系统即购物车文件。
最后备注
上面我没有演示过的一个有趣的方面是,在编写单个构建任务时,如何混合和匹配不同的语言。例如,我们可以使用摇一摇用于将C源文件编译为目标文件。请注意,Shake本身是一个构建系统,它保持自己的状态,但它仍然运行良好:如果Shake决定只重建一个对象文件(因为其他文件是最新的),Stroll可以安全地记住这个决定-只要所有输入文件都相同,Shake的数据库保持不变,我们可以假设Shake之前得出的结果仍然有效。这意味着,即使Stroll的任务粒度可能相当粗(例如,一个任务对应100个对象文件),这些粗粒度任务也可以从其他构建系统(如Shake)支持的细粒度增量和并行性中受益。如果您碰巧有两个用不同语言编写的现有构建系统,则无需重写任何内容:您可以只需将遗留构建系统放在同一目录中并使用Stroll即可组合它们。
不寻常的是,Stroll可以处理循环任务描述,其中一些构建任务形成依赖循环,以及生成新构建任务的构建任务!Stroll只需继续构建,直到到达所有任务都是最新的固定点。
Stroll不符合构建系统即购物车文件,其中构建任务具有静态已知输出:Stroll支持动态输入和动态输出.我猜想这样的构建系统不能是最小的,即它们基本上需要Stroll使用的试错法,在发现完整的依赖关系图时可能会执行不必要的工作。
致谢
我思考这个想法已经有一段时间了,并与乌尔夫·亚当斯(Ulf Adams)、阿塞尼·阿列克塞耶夫(Arseniy Alekseyev)、杰里米·迪米诺(Jeremie Dimino)、乔治·卢基亚诺夫(Georgy Lukyanov)、尼尔·米切尔(Neil Mitchell)、伊曼·纳拉萨姆迪亚(Iman Narasamdya)、西蒙·佩顿·琼斯(Simon。这个名单中的一些人对这个想法持怀疑态度,所以我并不是说他们支持斯特罗尔——我只是感谢每个人的见解。