遊戲項目可能是所有軟件項目中需要在編譯時處理資源最多的項目, 一般的項目都有下面幾種常見需求:
- 將文本格式的Json, XML等配置換成二進制
- 將Json, XML等配置加密
- 將tga, png的圖壓縮成壓 縮比更高的pvr, webp等格式
- 用texturepacker等工具打包小圖
- 將UI編輯器, 動畫編輯器的編輯時格式(往往是文本格式)編譯成二進制的發佈格式.
特別是圖片相關的的資源生成, 時間消耗較多, 需要儘量減少重複生成. 此時像makefile這種東西就很有價值了.
目錄:
Makefile的利弊
Makefile最大的好處自然是依賴關係的作用, 在正確設置後, 能做到當原始文件(源文件, 原始的資源等)沒有更改時, 不生成目標文件, 更改時才生成, 並且可以自定義生成的規則.
缺點也很明顯, Makefile太難寫了, 傳統的Makefile格式獨特, 甚至tab敏感, 而功能相對單一(功能強大基本靠shell). 所以很多人都弄了一套別的東西, 比如傳統的Unix/Linux開發環境的Automake和Autoconf, 可以跨平臺生成工程的CMake, Qt的qmake, Java的ant等, 而Ruby則提供了Rake.
Rakefile使用
簡單的說Rakefile就是使用Ruby語法的makefile, 對應make的工具就是rake. 在Ruby on Rails裏面, 不管是數據庫的初始化, 內容初始化, 刪除, 還是測試, 都是用rake來完成的.
優點
官方說明有如下優點:
- Ruby語法
- 可以設定task的依賴
- 支持patterns的規則
- 靈活的FileList類, 行爲像array, 但是可以方便的操作文件名和路徑
- 有一個預先包裝好的庫, 可以方便的實現類似build tarball和發佈到ssh網站等功能.
- 支持並行task.
其實想像一下, 在makefile文件中能使用完整的ruby功能, 不僅僅是ruby的語法, 還支持ruby現有的所有庫, gems, 光聽聽就讓人高興.
碰到複雜工程時, 不管邏輯需要多複雜, 你都有一個完整, 強大的語言可以使用, 不再需要藉助其他的東西就能夠完全hold住.
假如有缺點的話, 那就是ruby畢竟還是需要學習的....並且, 總體的內容比一般的makefile要複雜一些.
使用說明
Rakefile分幾個基本的build規則, 用"=>"來表示依賴關係.
比如常見的helloworld工程, 我們可以輸入完整的命令:
g++ helloworld.cc -o hello.o
也可以在源代碼目錄中新建Rakefile文件來管理, Rakefile文件如下:
file "helloworld" => "helloworld.cc" do |t|
sh "g++ #{t.prerequisites.join(' ')} -o #{t.name}"
end
然後運行rake helloworld, 來編譯, 好處就是當helloworld.cc文件沒有改變時, 實際根本不會編譯.
上面的例子中我們是用了一個file task, 當我們要想要直接運行rake, 省略helloworld的話, 可以利用rake的default task.
task :default => "helloworld"
file "helloworld" => "helloworld.cc" do |t|
sh "g++ #{t.prerequisites.join(' ')} -o #{t.name}"
end
這個default的task就是一個simple task, 會在直接運行rake的時候運行, 並且, 可以看到, task之間也是可以用"=>"表示依賴的.
當文件比較多時, 一個一個的寫file task可能會比較累, 於是rake加入了rule特性, 比如, 我們可以用下列的rule來編譯所有的".cc"文件.
比如, 我自建一個my_print函數, 現在就有my_print.cc, helloworld.cc兩個源文件了, 可以通過下面這種方式來生成代碼:
task :default => "helloworld"
file "helloworld" => ["helloworld.o", "my_print.o"] do |t|
sh "g++ #{t.prerequisites.join(' ')} -o #{t.name}"
end
rule ".o" => [".cc", ".h"] do |t|
sh "g++ -c #{t.source} -o #{t.name}"
end
當然, 雖然rake很強大, 但是還是沒有強大到能夠分析理解C++代碼的地步, 所以, 這種規則和以前的makefile文件一樣, 設定後, 僅僅是同名文件的頭文件, 源文件能夠產生依賴關係(更改後能夠觸發重編譯), 但是此例中, helloworld.cc也include了my_print.h, 也是對my_print.h的實際依賴, 但是rake就理解不了了.
而事實上, 我們幾乎不可能都手動的將所有的這種include關係輸入到rakefile中, 那簡直就是自虐. 我們通常的做法是, 碰到有改頭文件的時候, 直接clean項目, 然後再重新編譯.
task :clean do
sh "rm *.o"
end
同樣的, 我們也能實現makefile中常有的install任務, 這裏就不再累述了.
實例
這裏用一個遊戲項目的實例來說明:
首先, 我們一般通過base_dir = File.dirname(__FILE__)
的方式來獲得當前目錄, 以方便解決目錄相關的問題, 手動的從相對目錄轉爲絕對目錄.
然後, 爲了從png格式壓縮爲webp格式, 建立以下規則:
quality = 90
rule '.webp' => '.png' do |t|
puts "webp convert begin:" + t.source.to_s
if !File.exist?(converted_dir)
sh "mkdir #{converted_dir}"
end
sh "/usr/bin/env cwebp -q #{quality} -quiet #{t.source} -o #{t.name}"
sh "cp #{t.name} " + converted_dir + "/"
puts "webp convert end:" + t.source.to_s
end
其中converted_dir就是我們實際資源需要移動到的目錄. 這裏之所以用cp, 而不是用mv來移動, 是爲了在源目錄保留有轉換後的副本, 當圖片沒有更改的時候, 就不需要重新壓縮圖片. 這裏, 有個疑問, 最佳的方式是直接將converted_dir的資源和源文件形成依賴, 就可以省掉一次拷貝的過程, 但是, 不知道怎樣使用跨目錄的rule.
再比如說, 使用TexturePacker對小圖片進行打包, 這個依賴關係本來是一個大圖片對需要打包的所有小圖片, 特別適合rakefile/makefile, 不過TexturePacker自己就實現了這種機制, 我們也就沒有必要重複實現了, 即使其實比較容易.
desc "pack texture with texture packer."
task :pack_texture do
puts "pack texture begin."
tps_files = FileList["#{tps_dir}" + "/*.tps"]
puts "tps files:" + tps_files.to_s
tps_files.each { |file|
sh "/usr/local/bin/TexturePacker --quiet #{file}"
}
end
這裏的desc是Rakefile專用的註釋, 可以在運行rake -T
時, 看到較爲友好的命令說明:
$rake -T
rake clean # clean the all generated resource
rake clean_packed # clean the packed resource.
rake default # generate all the resouce neeed.
rake pack_texture # pack texture with texture packer.
rake png2webp # convert all the png to webp format.
這裏又有另外一個較爲不好的地方, 我們首先用TexturePacker把小圖都打包成大圖了(見前面pack_texture task的例子), 我們可以完全用FileList動態生成需要打包的tps文件, 而只有打包後纔能有我們想要轉換爲webp的png圖文件, 但是, 當我想要動態的用FileList獲取到生成的所有的png作爲file task的任務時, 發現rakefile並不支持. 簡單的說, 當file task依賴的文件是另一個task的結果時, 我們無法處理這種依賴關係, 如下例:
generated_texs = nil
task :pack_texture do
// generate the textures
// the code
generated_texs = FileList[...]
end
task :png2webp => [:pack_texture] + generated_texs do
end
這個例子中, 雖然我們可以肯定的說png2webp task運行時genereated_texs會獲得正確的值, 無論我們是通過default task運行, 還是直接運行png2webp這個task(因爲png2webp本身依賴pack_texutre task), 但是實際上, 無論你用那種方式運行png2webp, genereated_texs總是爲nil, 就算你實際上在pack_texture task中改變了generated_texs的值. 這個挺讓人鬱悶的.
總結
總的來說, Rakefile算是那種一勞永逸的工程管理解決方案, 因爲ruby語言本身的強大和相關庫的豐富, 基本上不會再需要用其他方式來管理你的工程了. 也許, 還要更好的話, 那就是自動的理解代碼, 瞭解諸如include, import等依賴關係的工具了.