Rake 我就不再介紹了,Ruby 的 Make ,許多方面都比 Make 要更好用一些。和 Makefile 不同的是,Rakefile 本身其實(shí)就是一段 Ruby 代碼,這樣的好處有很多,一方面在 Rake 里面就可以很直接地做任何 Ruby 能做的事了,另一方面由于 Ruby 對(duì) DSL 支持良好,所以 Rakefile 通??雌饋?lái)也并不那么“代碼”。
不過(guò),代碼始終是代碼,Makefile 尚且可以寫得很亂,Rakefile 要寫亂就更容易了,幸運(yùn)地是,Rake 提供了一些功能讓我們可以來(lái)對(duì) Rakefile 做一些組織工作。
其中之一就是 import 功能,把不同功能的 task 寫到不同的文件中,例如,像這個(gè)樣子:
復(fù)制代碼 代碼如下:
Rakefile
task/
+-- doc.rake
+-- compile.rake
`-- deploy.rake
這樣,在 Rakefile 里寫上
復(fù)制代碼 代碼如下:
import("task/doc.rake")
這樣的語(yǔ)句導(dǎo)入各個(gè)子任務(wù)即可,不同的任務(wù)寫到不同的文件里面就不會(huì)一團(tuán)糟了。而且,import 同 Ruby 自己的 require 不一樣,import 并不是立即進(jìn)行導(dǎo)入的,而是在整個(gè) Rakefile 執(zhí)行結(jié)束之后才全部導(dǎo)入,因此,可以在任意的地方寫 import ,而不用擔(dān)心依賴關(guān)系,需要共享的變量之類的只要在主 Rakefile 中定義了即可。
import 是組織不同的功能模塊,除此之外,Rake 還允許我們對(duì)一些重復(fù)性的任務(wù)進(jìn)行抽象,具體來(lái)說(shuō),就是自定義的 task 。通常情況下,我們使用 Rake 提供的通用 task 和文件 task 來(lái)構(gòu)造我們需要完成的工作,除此之外,Rake 還自帶了一些針對(duì)特殊任務(wù)的 task 類型,例如構(gòu)建 rdoc 或者運(yùn)行 test 等。實(shí)際上,一種任務(wù)就是一個(gè)普通的 Ruby 類,我們可以繼承 Rake 里的 Task 類并重新定義相關(guān)的函數(shù)來(lái)實(shí)現(xiàn)自定義的 task 類型。不過(guò),這樣多少有些麻煩,實(shí)際上,很多時(shí)候我們要定義的任務(wù)都可以分解為一些小任務(wù)用內(nèi)置的通用 task 和 file task 來(lái)實(shí)現(xiàn)的,這個(gè)時(shí)候可以用 Tasklib 來(lái)更方便地定義自定義的任務(wù)。
具體地來(lái)說(shuō),就是寫一個(gè)類,繼承自 Tasklib (雖然實(shí)際上只是約定而并不是必須的),然后在這個(gè)類的初始化函數(shù)里用 task 或者 file 來(lái)定義實(shí)際完成任務(wù)的子 task 即可。用一個(gè)實(shí)際的例子來(lái)說(shuō),比如說(shuō),我們可以定義一個(gè) ErlcTask ,可以用來(lái)把一些 Erlang 文件編譯到某個(gè)目錄下,并在 clean 的時(shí)候自動(dòng)能把編譯出來(lái)的 .beam 文件清理掉:
復(fù)制代碼 代碼如下:
require 'rake'
require 'rake/clean'
require 'rake/tasklib'
class ErlcTask Rake::TaskLib
attr_accessor :name
attr_accessor :sources
attr_accessor :dest_dir
attr_accessor :include_path
attr_accessor :flags
attr_accessor :extra_dep
def initialize(name = :erlc)
# default values
if name.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"
yield self if block_given?
define
end
def define
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).each do |beam, source|
file beam => source do
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 相關(guān)的屬性,在初始化函數(shù)里設(shè)置初值,然后調(diào)用 block 來(lái)填充實(shí)際的值,最后調(diào)用 define 函數(shù),define 函數(shù)就使用 directory 、file 和 task 分別定義了建立目錄、編譯和清理的任務(wù)。如果了解 Ruby 和 Rake 的基本語(yǔ)法的話,應(yīng)該很容易看明白了。
接下來(lái)把這個(gè)文件保存到某個(gè) .rb 里,然后在 Rakefile 里 require 之,就可以這樣寫了:
復(fù)制代碼 代碼如下:
ErlcTask.new :compile do |t|
t.sources = FileList['src/*.erl']
t.dest_dir = '../ebin'
t.include_path = '../include'
t.extra_dep = :library
end
看起來(lái)就清爽多了!并且可以重復(fù)利用。 末了,順便再感嘆一下,雖然最近都是用 Python 用得多一些,但是每次再寫 Ruby 都能感覺(jué)到寫起來(lái)很舒服,這是基本不可能在 Python 里找到的感覺(jué)?。?