Stroll:一个实验构建系统

我想分享一个开发语言无关构建系统的实验,该系统不需要用户指定各个构建任务之间的依赖关系。我所说的“语言无关”是指用户不需要学习新的语言或特殊的文件格式来描述构建任务——现有的脚本或可执行文件可以直接用作构建任务,无需修改。

我称之为构建系统漫步因为它的构建算法让我想起了在一个你从未去过的公园里漫步,试图找出通往目标目的地的最佳路径,有时可能会绕圈,直到你建立起一个完整的公园心理地图。

主要想法

大多数构建系统都要求用户指定生成任务以及依赖关系任务之间:了解依赖项可以让构建系统在修改某些源文件时确定需要执行哪些任务。从个人经验来看,准确地描述依赖关系是困难的,而且常常是沮丧的根源。

构建任务及其依赖项通常使用特定于域的任务描述语言进行描述:制造商使用生成文件巴泽尔使用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(包括注释)中实现,除标准库外,还使用以下库:

  • 摇一摇cmd公司函数用于执行生成任务,跟踪其文件访问。这取决于Shake对fsatrace公司效用
  • 代数图库阿尔加用于图形构建、遍历和可视化。
  • 图书馆隐晶石山药用于计算文件内容哈希和序列化/反序列化YAML文件。

非常感谢为这些项目做出贡献的每一个人!

欢迎您加入浏览源代码漫步和/或玩它,但要注意:它有一些严重的限制(下面讨论),我不确定它们是否会被修复。

限制

漫游很有趣,我认为它是一个成功的实验,但当前的实现有一些严重的限制:

  • 这个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。这个名单中的一些人对这个想法持怀疑态度,所以我并不是说他们支持斯特罗尔——我只是感谢每个人的见解。

关于“Stroll:一个实验构建系统

  1. 假设您跟踪使用ptrace或等效工具打开的文件,另一种情况是,在访问黑盒列表中的一个文件时,在文件访问期间进行阻止,同时启动另一个任务生成的资源。您将导致依赖性-深度额外的进程驻留,但完全避免“重启”。不过,您仍然可能会遇到列出给定目录中所有文件并进行处理的进程的问题。

    1. 我使用Shake的函数“cmd”跟踪文件访问,该函数执行所有无聊的管道操作,因此它只是Stroll的一行代码:

      https://github.com/snowpeard/walk/blob/722c77faed2f7e6ced28035be13ae4636604032a/src/Development/stroll/Script.hs#L33

      Shake的“cmd”不支持暂停,但从原则上讲,看看Stroll的暂停版本是如何实现的,这将是一件有趣的事情。

      有一些棘手的案例,尽管无可否认,它们有点做作。在第一次运行时,情况特别复杂,因为没有可用的依赖关系信息,因此Stroll需要盲目地插入脚本。

      *如果您对构建顺序不满意,可能会遇到需要挂起N-1个任务(总共N个任务),耗尽内存的情况。

      *更奇怪的是,可能会出现所有N个任务都被挂起的情况,这将是一个死锁,除非我们准备开始逐个释放它们,希望其中一个不会崩溃,而是从被阻止的丢失文件中恢复,并生成其他任务正在等待的输出。

      (我认为,尽管未能读取其中一个输入文件,但任务仍成功终止的情况并不罕见,因此总是在读取时挂起任务可能是次优的。)

      重启方法似乎更简单、更健壮,尤其是对于第一次运行。在最坏的情况下,它带有一个二次开销,但我认为典型的情况是恒定的。

    1. 是的,我知道拉托。我的理解是,它与Fabricate非常相似:它跟踪构建任务的文件访问,但需要用户预先指定任务执行的顺序。Stroll自动计算构建顺序。

      与Neil谈论Rattle and Fabricate是这个实验的动机之一🙂

    1. (很抱歉这么晚才回复!由于某种原因,我没有收到关于你评论的通知。)

      我不能在像Stroll这样的真实构建系统中真正使用链接方法,因为真实的构建规则需要执行直接在文件系统上操作的外部工具(如编译器),也就是说,这些工具不想使用我的Get和Put回调!

      也许,可以以某种方式拦截外部工具的所有文件访问,本质上是将它们包装在某种文件系统monad中,但我还没有尝试过。听起来像是一个有趣的项目!

  2. 顺便说一句,读得太棒了!只是好奇,你有时间看看瑞克吗?它声称(在某些情况下)可以实现与Make相当的构建。

    *我很想知道你对他们的做法有什么看法。在这一领域,它们似乎是最接近完全可扩展解决方案的。你认为这是一个最低限度的解决方案吗?

    *你认为使用带过滤器的ptrace可以比fsatrace更快地读取吗?

    *你有没有试着对照其他项目来衡量Stroll的表现?

    1. 谢谢!

      是的,我了解Riker,并与作者交谈过。Riker背后的主要思想很可爱。我还没有在任何实际的代码库上尝试过Riker,所以我不知道它在实践中的效果如何,但我希望有一天能尝试一下。

      >你认为使用带过滤器的ptrace可以比fsatrace更快地读取吗?

      也许吧。到目前为止,我只尝试了fsatrace,因为它被方便地打包在Shake库中。你认为ptrace会更快吗?

      >你有没有试着对照其他项目来衡量Stroll的表现?

      可悲的是,我真的没有时间在练习中尝试Stroll。目前,这只是一个奇怪的实验,等待着被转化为可用的产品。但尝试Stroll肯定是我不断增长的任务清单上的一件事!

    1. 依赖关系图(可以从以前的构建中获得)对于推测任务的顺序很有用,例如用于使用并行加速构建。当依赖关系很少更改时,只需遵循先前构建的依赖关系图就可以了——在这种情况下,跟踪仅用于验证我们的知识是否仍然正确。

留下回复

您的电子邮件地址将不会被发布。 已标记必填字段*