原文地址: http://blog.csdn.net/smilewater/archive/2007/07/09/1683808.aspx
Rake和Make及Ant
Rake的意思是Ruby Make,一個用ruby開發(fā)的代碼構(gòu)建工具。Rake的英文意思是耙子,一種很樸實的勞動工具。真的是很貼切,Rake正是一個功能強大、勤勤懇懇的勞動工具。
Rake會經(jīng)常跟C/C++領(lǐng)域的make和Java世界的Ant進(jìn)行對照,事實上,它們有很多相似的地方。我們先來看一下make和ant的歷史。
make的出現(xiàn)是為了解決批量編譯的問題。對于一個小型的項目來說,用一個腳本文件或者批處理命令來進(jìn)行批量編譯就已經(jīng)足夠好。但是對于大型的項目來說,僅僅為了少數(shù)幾個文件的改變就全部重新進(jìn)行一次編譯無疑是耗時且不必要的。而且,在大型的項目中,往往會有很復(fù)雜的依賴關(guān)系。
Make的出現(xiàn)就是為了解決這兩個問題,make有兩個優(yōu)點:
-
Make了解自上次Make運行以來哪些文件發(fā)生了變化,它會僅僅編譯那些發(fā)生變化的文件。
-
Make會跟蹤文件之間的依賴性,如果文件A依賴于文件B,那么如果兩者都沒有編譯時,Make會首先編譯文件B。
Ant算是一個Java世界的make,它要比make年輕許多(想想make是出現(xiàn)在1972年吧),它除了支持批量編譯之外,還支持單元測試、JavaDoc等任務(wù)。因此,Ant在Java世界中比Make更加流行。
但是,為什么Ruby需要Rake?
Ruby代碼不需要編譯,為什么需要Rake?其實,與其說Rake是一個代碼構(gòu)建工具,不如說Rake是一個任務(wù)管理工具,通過Rake我們可以得到兩個好處:
-
以任務(wù)的方式創(chuàng)建和運行腳本
當(dāng)然,你可以用腳本來創(chuàng)建每一個你希望自動運行的任務(wù)。但是,對于大型的應(yīng)用來說,你幾乎總是需要為數(shù)據(jù)庫遷移(比如Rails中db:migrate任務(wù))、清空緩存、或者代碼維護(hù)等等編寫腳本。對于每一項任務(wù),你可能都需要寫若干腳本,這會讓你的管理變得復(fù)雜。那么,把它們用任務(wù)的方式整理到一起,會讓管理變得輕松很多。
-
追蹤和管理任務(wù)之間的依賴
Rake還提供了輕松管理任務(wù)之間依賴的方式。比如,“migrate”任務(wù)和“schema:dump”任務(wù)都依賴于 “connect_to_database”任務(wù),那么在“migrate”任務(wù)調(diào)用之前,“connect_to_database”任務(wù)都會被執(zhí)行。
在哪里可以獲得Rake?
Rake的主頁是在http://rake.rubyforge.org/,在這里你可以獲得Rake的簡單介紹,API以及一些有用文檔的鏈接。可以在http://rubyforge.org/frs/?group_id=50獲得最新版的Rake,在作者寫作時,最新版本是0.7.3。
Rake腳本編寫
一個簡單腳本
Rake的腳本相當(dāng)簡單,下面用一個例子進(jìn)行說明。假設(shè)你是一個勤勞的家庭型程序員,在周末你打算為你的家人做一些貢獻(xiàn)。所以你為自己制定了三個任務(wù):買菜、做飯和洗衣服。打開你的文本編輯器,創(chuàng)建一個名叫rakefile的文件(Rake會在當(dāng)前路徑下尋找名叫Rakefile、rakefile、RakeFile.rb和rakefile.rb的rake文件),并輸入如下內(nèi)容:
desc "任務(wù)1 -- 買菜"
task :purchaseVegetables do
puts "到沃爾瑪去買菜。"
end
desc "任務(wù)2 -- 做飯"
task :cook do
puts "做一頓香噴噴的飯菜。"
end
desc "任務(wù)3 -- 洗衣服"
task :laundry do
puts "把所有衣服扔進(jìn)洗衣機(jī)。"
end
打開命令行工具,進(jìn)入這個文件所在目錄,然后運行下面的命令,大致應(yīng)該類似如下結(jié)果:
D:"work"ruby_works"ruby_book>rake purchaseVegetables
(in D:/work/ruby_works/ruby_book)
到沃爾瑪去買菜。
D:"work"ruby_works"ruby_book>rake cook
(in D:/work/ruby_works/ruby_book)
做一頓香噴噴的飯菜。
D:"work"ruby_works"ruby_book>rake laundry
(in D:/work/ruby_works/ruby_book)
把所有衣服扔進(jìn)洗衣機(jī)。
分析
很簡單,也很易讀,對吧。這個文件一共定義了3個任務(wù),desc是Rake定義的方法,表示對下面定義任務(wù)的描述。這個描述會在使用Rake --tasks(或者Rake -T,為懶人準(zhǔn)備的快捷方式)命令時輸出在屏幕上。
D:"work"ruby_works"ruby_book>rake --tasks
(in D:/work/ruby_works/ruby_book)
rake cook # 任務(wù)2 -- 做飯
rake laundry # 任務(wù)3 -- 洗衣服
rake purchaseVegetables # 任務(wù)1 -- 買菜
下面的語句定義了purchaseVegetables這個任務(wù),task是Rake最重要的方法。它的方法定義是:task(args, &block)。任務(wù)體是一個block,本例中只是簡單輸出你所要做的工作。需要注意的是代碼
puts "到沃爾瑪去買菜。"
完全是一個普通的Ruby語句,puts是Ruby中進(jìn)行輸出的一般性方法,可以看出,Rake任務(wù)可以完全使用Ruby的能力,這使得它非常強大。
加入依賴關(guān)系
很顯然,在我們定義的任務(wù)中,做飯是依賴于買菜的(我相信大多數(shù)程序員在周末的冰箱里除了可樂沒有別的)。那么,我們需要在我們的任務(wù)定義中加入這個依賴關(guān)系,修改后的文件如下:
desc "任務(wù)1 -- 買菜"
task :purchaseVegetables do
puts "到沃爾瑪去買菜。"
end
desc "任務(wù)2 -- 做飯"
task :cook => :purchaseVegetables do
puts "做一頓香噴噴的飯菜。"
end
desc "任務(wù)3 -- 洗衣服"
task :laundry do
puts "把所有衣服扔進(jìn)洗衣機(jī)。"
end
再次運行做飯任務(wù),你會得到如下結(jié)果:
D:"work"ruby_works"ruby_book>rake cook
(in D:/work/ruby_works/ruby_book)
到沃爾瑪去買菜。
做一頓香噴噴的飯菜。
是的,你當(dāng)然需要先買菜,誰讓你是一個冰箱空空如野的程序員呢。
命名空間
跟任何編程語言類似,當(dāng)你的rake文件很多時,當(dāng)你有很多任務(wù)的時候,你需要關(guān)注它們的命名沖突問題,命名空間(namespace)就是一個自然的解決方案。你可以為上面的三個任務(wù)定義一個叫做home的命名空間。
namespace :home do
desc "任務(wù)1 -- 買菜"
task :purchaseVegetables do
puts "到沃爾瑪去買菜。"
end
……
end
再次運行rake --tasks,你會得到如下的結(jié)果:
D:"work"ruby_works"ruby_book >rake --tasks
(in D:/work/ruby_works/ruby_book)
rake home:cook # 任務(wù)2 -- 做飯
rake home:laundry # 任務(wù)3 -- 洗衣服
rake home:purchaseVegetables # 任務(wù)1 -- 買菜
你現(xiàn)在需要使用rake home:cook才能啟動做飯這個任務(wù)了。當(dāng)然,你可以在你的rakefile中使用多個命名空間,對任務(wù)進(jìn)行分類。
在一個任務(wù)中調(diào)用另外一個任務(wù)
當(dāng)任務(wù)眾多的時候,你很可能需要在一個任務(wù)中調(diào)用另外一個任務(wù),假設(shè)我們把今天所有要做的工作定義為一個任務(wù):today。在這個任務(wù)中,有兩個任務(wù)需要被調(diào)用,一個是做飯,一個是洗衣服。當(dāng)然,由于做飯依賴于買菜,我們還是需要買菜的(這一步是逃不過去的,呵呵)。在文件的頂部定義一個today的任務(wù):
desc "今天的任務(wù)"
task :today do
Rake::Task["home:cook"].invoke
Rake::Task["home:laundry"].invoke
end
namespace :home do
……
end
可以看出,調(diào)用其它任務(wù)的方式很簡單,只需要調(diào)用Rake::Task["task_name"].invoke 方法就可以了。在命令行中啟動rake today,可以得到:
D:"work"ruby_works"ruby_book >rake today
(in D:/work/ruby_works/ruby_book)
到沃爾瑪去買菜。
做一頓香噴噴的飯菜。
把所有衣服扔進(jìn)洗衣機(jī)。
默認(rèn)任務(wù)
可以為Rake增加一個默認(rèn)任務(wù),這樣可以簡單地用Rake命令來觸發(fā)這個默認(rèn)任務(wù),在上面的rakefile中,我們可以用如下方式把“today”任務(wù)作為默認(rèn)任務(wù)。
task :default => [:today]
然后調(diào)用直接在命令行中調(diào)用rake,可以得到跟調(diào)用rake today同樣的輸出結(jié)果。
這就是我們簡單的一個Rake任務(wù)定義,下面是完整的修改后的rakefile:
task :default => [:today]
desc "今天的任務(wù)"
task :today do
Rake::Task["home:cook"].invoke
Rake::Task["home:laundry"].invoke
end
namespace :home do
desc "任務(wù)1 -- 買菜"
task :purchaseVegetables do
puts "到沃爾瑪去買菜。"
end
desc "任務(wù)2 -- 做飯"
task :cook => :purchaseVegetables do
puts "做一頓香噴噴的飯菜。"
end
desc "任務(wù)3 -- 洗衣服"
task :laundry do
puts "把所有衣服扔進(jìn)洗衣機(jī)。"
end
end
Rails中的Rake任務(wù)
Rails預(yù)定義了大量的Rake任務(wù),在Rails應(yīng)用的開發(fā)過程中,你想必已經(jīng)在大量使用它們了。在Rails中,所有的Rake任務(wù)都放在rails目錄的lib/tasks目錄下(在作者的環(huán)境下是c:"ruby"lib"ruby"gems"1.8"gems"rails-1.1.4"lib"tasks"),所有的rake任務(wù)都以.rake作為后綴名,這些以.rake結(jié)尾的文件會被自動加載到你的環(huán)境中。你可以到一個已有的Rails工程根目錄下鍵入rake --tasks,可以看到很多的rake任務(wù)已經(jīng)為你整裝待發(fā)了。
在Rails中,最常使用的Rake任務(wù)之一是進(jìn)行數(shù)據(jù)庫的遷移(migration)。數(shù)據(jù)庫遷移程序允許你使用Ruby腳本來定義數(shù)據(jù)庫模式,而db:migrate就是進(jìn)行這個工作的rake任務(wù)。下面我們來分析這個rake任務(wù)。
db:migrate任務(wù)
db:migrate任務(wù)存放在lib/tasks/databases.rake文件中。這個文件中定義了所有與數(shù)據(jù)庫操作相關(guān)的任務(wù),我們僅僅抽出db:migrate的定義:
namespace :db do
desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
task :migrate => :environment do
ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
end
……
end
分析
首先是命名空間的聲明,migrate任務(wù)的命名空間是db。這也就是我們用db:migrate來引用它的原因。
下面是一個描述,說明該任務(wù)的功能是把定義在db/migrate目錄下(相對于你的Rails應(yīng)用程序的根目錄)的遷移腳本遷移到數(shù)據(jù)庫中,如果不指定VERSION的話,默認(rèn)是最新版本,否則可以恢復(fù)到一個指定的版本。
接著是任務(wù)的定義,該任務(wù)依賴于enviroment任務(wù),這個任務(wù)在misc.rake中定義,用來加載Rails的環(huán)境,它的定義相當(dāng)簡單:
task :environment do
require(File.join(RAILS_ROOT, 'config', 'environment'))
end
用來加載config/environment.rb文件,該文件會加載Rails工作所需要加載的環(huán)境。由于加載了這個環(huán)境,所以ActiveRecord對象現(xiàn)在可以使用,下面就是調(diào)用ActiveRecord::Migrator.migrate方法對每個db/migrate/下的腳本文件進(jìn)行遷移。
最后會調(diào)用db:schema:dump任務(wù),該任務(wù)的主要作用是產(chǎn)生db/schema.rb文件。該文件用來記錄不同版本的數(shù)據(jù)庫模式。這個任務(wù)的定義就在db:migrate任務(wù)下面不遠(yuǎn)的地方,有興趣的讀者可以自行進(jìn)行分析。