1. 程式人生 > >用Rakefile管理工程

用Rakefile管理工程

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

遊戲專案可能是所有軟體專案中需要在編譯時處理資源最多的專案, 一般的專案都有下面幾種常見需求:

  1. 將文字格式的Json, XML等配置換成二進位制
  2. 將Json, XML等配置加密
  3. 將tga, png的圖壓縮成壓 縮比更高的pvr, webp等格式
  4. 用texturepacker等工具打包小圖
  5. 將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來完成的.

優點

官方說明有如下優點:

  1. Ruby語法
  2. 可以設定task的依賴
  3. 支援patterns的規則
  4. 靈活的FileList類, 行為像array, 但是可以方便的操作檔名和路徑
  5. 有一個預先包裝好的庫, 可以方便的實現類似build tarball和釋出到ssh網站等功能.
  6. 支援並行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

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述