为什么要有 Rake?
容我先声明:我从来没想过要写这段代码。我不确定它是否真有用,也不确定有没有人会对它感兴趣。只能说,Why(Ruby 社区有名的“why the lucky stiff”)的洋葱卡车那天一定经过了俄亥俄河谷。
我在说什么?——一个用 Ruby 写的 Make。
看,我已经能感觉到你在皱眉了,我也同意:世界确实不需要再来一个 “make” 的翻版。我们已经有了 “ant”,还不够吗?
事情始于昨天。我帮同事修项目里的一个 Makefile。问题本身并不复杂,但聊着聊着我开始抱怨 make 的某些缺点。比如,在我的某个 Makefile 里,我想动态地算出一个文件名,不得不写点简单脚本(用 Ruby)才能搞定。“要是能在 Makefile 里直接用 Ruby 就好了,” 我说。
同事(最近刚迷上 Ruby)也觉得不错,但想知道那会长什么样。于是我在白板上随手画了:
task "build" do
java_compile(...args, etc ...)
end
“task 函数会把 ‘build’ 注册成一个目标,随后的块就是当构建系统判断该执行 build 时真正跑的动作。”
我们都觉得挺酷,但从头写一个 make 工作量太大,话题就此结束。
……然而我脑子里挥之不去。要让上面那段代码真的像 make 那样跑起来,到底缺什么?嗯,需要注册任务、需要声明依赖、需要触发流程。嘿!要是……十五分钟后,我就得到了一个能跑的原型,带依赖、带动作,完整实现了 make 的惊人功能,却只写了一页纸的 Ruby。我俩看着代码哈哈大笑:区区几十行就复刻了 make 的大部分本事,Ruby 的威力把我们震住了。
但它还没实现 make 的全部特性,尤其是“按时间戳判断是否过期”的文件依赖(某个文件若比它的先决文件旧就要重新生成)。显然,这会很痛苦,于是 Ruby Make 只能算是个有趣的小实验。
……然而走回工位的路上,我又开始想:文件依赖到底需要什么?可恶,我又上钩了。再加一个新类、两个新方法,文件/时间戳依赖就搞定了。
现在我彻底上瘾。昨晚(看《犯罪现场调查》时)我把代码又揉了揉、理了理,最后成了一个 100 行的 make 替代品,放在:
-
doc/proto_rake.rdoc
至于名字——我在白板上写完示例时,同事大叫:“哦!完美名字:Rake!懂吗?Ruby-make → Rake!” 他说想象任务像落叶,Rake 会把它们耙起来……总之,名字就这么定了。
几个快速示例:
简单任务:删除备份文件
task :clean do
Dir['*~'].each { |fn| rm fn rescue nil }
end
任务名用符号(比引号省敲几下,当然你愿意也可以用字符串)。Rake 直接把 FileUtils 模块的方法混进来,所以 rm
随手可用。rescue nil
忽略删不掉的错误。
跑它只需敲 rake clean
。Rake 会自动在当前目录(或上层目录)找 Rakefile,并执行命令行指定的目标;如果没给目标,就执行默认任务 default
。
带依赖的任务:
task :clobber => [:clean] do
rm_r "tempdir"
end
:clobber
依赖 :clean
,所以先跑 :clean
,再跑 :clobber
。
文件型任务用 file
,名字代表真实文件,只有当文件不存在或比先决文件旧时才会执行。示例:把 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
我一般用字符串给文件任务命名,因为文件名里可能有符号打不出来的字符,也更醒目。
如果每个文件都手写 task 就太烦了。我打算搞一套库,比如:
require 'rake/ctools'
Dir['*.c'].each { |fn| c_source_file(fn) }
c_source_file
会自动生成编译该 C 文件所需的所有任务。类似的库还能写一大堆。
就这些。目前没有文档(除了这封信)。有人感兴趣吗?有,我就继续整理、写文档、发到 RAA;没有,就让它继续当一个有趣的练习,纪念 Ruby 的魔力。
为什么 Ruby 程序员可能会喜欢 Rake?也许因为:
-
没有古怪的 make 语法(只有古怪的 Ruby 语法:-)
-
不用编辑或阅读 XML(咳咳,ant)
-
构建脚本跨平台
-
只要 Ruby 能跑的地方就能跑,不需要系统自带 make。如果不用
sh
而用 ftools 之类,就能做到纯 Ruby 跨平台。而且 Rake 只有 100 行,随手就能塞进项目里。
好了,碎碎念到此为止。正如我开头所说,我真没打算写这段代码。