用Rakefile管理工程
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
遊戲專案可能是所有軟體專案中需要在編譯時處理資源最多的專案, 一般的專案都有下面幾種常見需求:
- 將文字格式的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則提供了
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}"endrule ".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_send
其中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 -Trake clean # clean the all generated resourcerake 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 = niltask :pack_texture do // generate the textures // the code generated_texs = FileList[...]endtask :png2webp => [:pack_texture] + generated_texs doend
這個例子中, 雖然我們可以肯定的說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等依賴關係的工具了.
參考
Rakefile Readme
Rakefile Format