为什么选择 Rake?

好吧,我一开始就声明,我从没打算编写这段代码。我不确定它是否有用,也不认为会有人对它感兴趣。我只能说,Why 的洋葱卡车一定经过了俄亥俄河谷。

我在说什么?…一个 Ruby 版本的 Make。

看,我感觉你已经开始畏缩了,我同意。世界肯定不需要另一个“make”程序的重制版。我的意思是,我们已经有“ant”了。难道还不够吗?

它开始于昨天。我正在帮助一位同事修复我们项目中使用的 Makefile 中的一个问题。不是一个特别棘手的问题,但在谈话过程中,我开始抱怨 make 的一些缺点。特别是,在我的一个 makefile 中,我想要动态地确定一个文件的名称,不得不求助于一些简单的脚本(用 Ruby 编写)才能使其工作。“如果能在 Makefile 中直接使用 Ruby 那该多好,”我说。

我的同事(最近刚转用 Ruby)同意了,但想知道它会是什么样子。所以我在白板上画了以下内容…

"What if you could specify the make tasks in Ruby, like this ..."

  task "build" do
    java_compile(...args, etc ...)
  end

"The task function would register "build" as a target to be made,
and the block would be the action executed whenever the build
system determined that it was time to do the build target."

我们一致认为这会很酷,但从头开始编写 make 工作量太大了。就这样结束了!

… 除非我无法把这个想法从我的脑海中赶走。要使上面的语法作为 make 文件工作,到底需要什么?嗯,你需要注册任务,你需要某种方式来指定任务之间的依赖关系,以及某种方式来启动这个过程。嘿!如果我们这样做会怎么样… 十五分钟后,我得到了一个可工作的 Ruby make 原型,包括依赖关系和操作。

我把代码给我的同事看了,我们都笑了。它只有一页左右的代码,却重现了 make 的大部分功能。我们都被 Ruby 的强大功能震惊了。

但它并没有做 make 所做的所有事情。特别是,它没有基于时间戳的文件依赖关系(如果任何先决文件的的时间戳较晚,则重新构建该文件)。显然,添加这个功能会很麻烦,所以 Ruby Make 仍然是一个有趣的实验。

… 除非当我走回我的办公桌时,我开始思考基于文件的依赖关系到底需要什么。糟糕!我又被它吸引了,通过添加一个新类和两个新方法,实现了基于文件/时间戳的依赖关系。

好吧,现在我真的被吸引住了。昨晚(在看《犯罪现场调查》时!)我整理了代码并做了一些清理。结果是一个仅用 100 行代码的精简 make 替代品。

有兴趣的人可以在这里看到它…

哦,关于名字。当我在白板上写 Ruby Make 任务示例时,我的同事惊呼:“哦!我有一个完美的名字:Rake…明白了吗?Ruby-Make。Rake!” 他说他把任务想象成叶子,Rake 会清理它们…或者类似的东西。不管怎样,这个名字就定下来了。

一些简单的例子…

一个删除备份文件的简单任务…

task :clean do
  Dir['*~'].each {|fn| rm fn rescue nil}
end

请注意,任务名称是符号(它们比带引号的字符串更容易输入……但如果您愿意,也可以使用带引号的字符串)。Rake 直接提供 FileUtils 模块的方法,因此我们利用 rm 命令。还要注意使用“rescue nil”来捕获并忽略 rm 命令中的错误。

要运行它,只需键入“rake clean”。Rake 将自动在当前目录(或上方!)中查找 Rakefile,并调用命令行中命名的目标。如果未显式命名任何目标,则 rake 将调用任务“default”。

这是另一个带有依赖项的任务…

task :clobber => [:clean] do
  rm_r "tempdir"
end

任务 :clobber 依赖于任务 :clean,因此 :clean 将在 :clobber 执行之前运行。

文件通过使用“file”命令指定。它类似于 task 命令,不同之处在于任务名称表示一个文件,并且只有当该文件不存在,或者其修改时间早于任何先决条件时,才会运行该任务。

这是一个基于文件的依赖关系,它将“hello.cc”编译为“hello.o”。

file "hello.cc"
file "hello.o" => ["hello.cc"] do |t|
  srcfile = t.name.sub(/\.o$/, ".cc")
  sh %{g++ #{srcfile} -c -o #{t.name}}
end

我通常使用字符串(而不是符号)来指定文件任务。有些文件名无法用符号表示。此外,它使休闲读者更容易区分它们。

目前,为项目中的每个文件编写任务充其量是很乏味的。我设想一套库来使这项工作更容易。例如,也许像这样…

require 'rake/ctools'
Dir['*.c'].each do |fn|
  c_source_file(fn)
end

其中 “c_source_file” 将创建编译目录中所有 C 源文件所需的所有任务。可以为 rake 创建任意数量的有用库。

就是这样。没有文档(除了这条消息中的内容)。这听起来对任何人有意思吗?如果是这样,我将继续清理它并编写它,并将其发布到 RAA 上。否则,我将把它作为一个有趣的练习和对 Ruby 强大功能的致敬。

为什么 Rake /可能/ 对 Ruby 程序员有吸引力。我不知道,也许…

所以…抱歉发了这么长的絮絮叨叨的消息。就像我说的,我从没打算编写这段代码。