写一个漂亮Rakefile的方法
Rake我就不再介绍了,Ruby的Make,许多方面都比Make要更好用一些。和Makefile不同的是,Rakefile本身其实就是一段Ruby代码,这样的好处有很多,一方面在Rake里面就可以很直接地做任何Ruby能做的事了,另一方面由于Ruby对DSL支持良好,所以Rakefile通常看起来也并不那么“代码”。
不过,代码始终是代码,Makefile尚且可以写得很乱,Rakefile要写乱就更容易了,幸运地是,Rake提供了一些功能让我们可以来对Rakefile做一些组织工作。
其中之一就是import功能,把不同功能的task写到不同的文件中,例如,像这个样子:
Rakefile task/ +--doc.rake +--compile.rake `--deploy.rake
这样,在Rakefile里写上
import("task/doc.rake")
这样的语句导入各个子任务即可,不同的任务写到不同的文件里面就不会一团糟了。而且,import同Ruby自己的require不一样,import并不是立即进行导入的,而是在整个Rakefile执行结束之后才全部导入,因此,可以在任意的地方写import,而不用担心依赖关系,需要共享的变量之类的只要在主Rakefile中定义了即可。
import是组织不同的功能模块,除此之外,Rake还允许我们对一些重复性的任务进行抽象,具体来说,就是自定义的task。通常情况下,我们使用Rake提供的通用task和文件task来构造我们需要完成的工作,除此之外,Rake还自带了一些针对特殊任务的task类型,例如构建rdoc或者运行test等。实际上,一种任务就是一个普通的Ruby类,我们可以继承Rake里的Task类并重新定义相关的函数来实现自定义的task类型。不过,这样多少有些麻烦,实际上,很多时候我们要定义的任务都可以分解为一些小任务用内置的通用task和filetask来实现的,这个时候可以用Tasklib来更方便地定义自定义的任务。
具体地来说,就是写一个类,继承自Tasklib(虽然实际上只是约定而并不是必须的),然后在这个类的初始化函数里用task或者file来定义实际完成任务的子task即可。用一个实际的例子来说,比如说,我们可以定义一个ErlcTask,可以用来把一些Erlang文件编译到某个目录下,并在clean的时候自动能把编译出来的.beam文件清理掉:
require'rake' require'rake/clean' require'rake/tasklib' classErlcTask<Rake::TaskLib attr_accessor:name attr_accessor:sources attr_accessor:dest_dir attr_accessor:include_path attr_accessor:flags attr_accessor:extra_dep definitialize(name=:erlc) #defaultvalues ifname.is_a?Hash @name=name.keys.first @extra_dep=name.values.first else @name=name @extra_dep=[] end @sources=FileList[] @dest_dir='.' @include_path=[] @flags="-W+warn_unused_vars+warn_unused_import" yieldselfifblock_given? define end defdefine beams=@sources.pathmap(File.join(@dest_dir,'%n.beam')) include_path=Array(@include_path).map{|incl|"-I"+incl}.join("") directory@dest_dir beams.zip(@sources).eachdo|beam,source| filebeam=>sourcedo sh"erlc-pa#{@dest_dir}#{@flags}#{include_path}-o#{@dest_dir}#{source}" end end task@name=>beams+Array(@extra_dep) CLEAN.include(beams) end end
首先定义一些Task相关的属性,在初始化函数里设置初值,然后调用block来填充实际的值,最后调用define函数,define函数就使用directory、file和task分别定义了建立目录、编译和清理的任务。如果了解Ruby和Rake的基本语法的话,应该很容易看明白了。
接下来把这个文件保存到某个.rb里,然后在Rakefile里require之,就可以这样写了:
ErlcTask.new:compiledo|t| t.sources=FileList['src/*.erl'] t.dest_dir='../ebin' t.include_path='../include' t.extra_dep=:library end
看起来就清爽多了!并且可以重复利用。 末了,顺便再感叹一下,虽然最近都是用Python用得多一些,但是每次再写Ruby都能感觉到写起来很舒服,这是基本不可能在Python里找到的感觉啊!