當大家寫好程式,準備打包安裝檔或者佈署至Server時,通常都怎麼做?

    10多年前,筆者還在研究所打工寫專案時,都是透過壓縮軟體(WinRAR或WinZip),壓縮成一個一個的zip檔,然後再copy至server。

    後來開始工作後,接觸版本控制軟體,如Microsoft VSS或SVN後,就改成直接到Server Check out最新版本的程式。以上兩種傳統方法都很簡單也很方便,但其實有更好的選擇 - Build System

何謂Build System? Google以下幾種關鍵字就可以大致了解。
"Build Automation"
"Build System" 

一般的Build System通常都包含以下以個步驟: (wikipedia:http://en.wikipedia.org/wiki/Build_automation)

  1. 編譯原始碼
  2. 打包編譯後的結果
  3. 進行測試 (通常指的是Unit Test)
  4. 佈署到上線的機器
  5. 製作版本說明文件


    以上這幾個步驟都是參考,不一定要全部做完才是build system. 以筆者創業團隊而言,並沒有這麼多時間去製作自動產生的release note,而且build完之後,也不能直接上線,必須要至測試機器進行測試,確定沒有太大的bug,才進行佈署(deployment)。

    雖然了解什麼是build system,但為什麼要做這件事情?創業光是想出idea,快速開發出來,隨時想改就改,哪有時間去做啥build system?在此提一些個人的看法:

  • 其實做一個build script只需要不到半天的時間,但卻可以重複使用。
  • 以往程式有一些環境變數,例如測試資料庫跟正式資料庫的IP不同,但不想寫死在程式裡面
  • Server有Javascript跟CSS的程式,需要做minify,但又不想開發時就做,這樣很難Debug. (做過的人就能體會)
  • 程式碼coding convention檢查。一人開發就算了,但如果有兩三個同伴一起開發,總是要先談好coding的風格。但是透過code review檢查又太浪費時間,code review應該是要抓出更有意義的問題才對。
  • 打包的目錄結構跟寫程式的目錄結構可能是不同的 
  • 隨時都可以rollback到某一個版本,尤其是流量大的網站


    以上僅列出一些常遇到的問題,但build system可不只這些,例如compile code時,要先執行一些指令,或者執行單元測試前,需要restore DB,這些都是可以透過build system來做到。

本篇分享後段,會分享一個小小的build script,也是我們正在使用的build script. 

    大致了解Build System之後,那怎麼開始呢?當然是先選擇build tool來實作囉!以下是筆者接觸過的程式語言跟可以用的build tool. 

  • Java: 當然就是推薦Ant這套廣為使用的Build Tool. 很多其他程式的build tool都是模仿Ant而生出來的。(Ant: http://ant.apache.org/)
  • Net:早期.Net並沒有很好的Build System,筆者使用過NAnt,但比起Java的Ant而言,陽春許多。後來選擇有GUI介面的FinalBuilder。FinalBuilder可以想像就是有GUI介面的Ant,也提供Server版本。非常的好用!NAnt: http://nant.sourceforge.net/ 或 FinalBuilder: http://www.finalbuilder.com/
  • PHP+Linux:筆者這一兩年從Windows陣營轉至Linux平台,目前有幾項產品也是透過PHP+Linux開發,基於對Ant的認識,所以也採用了系出同門的Phing(模仿Ant,但是有更多PHP相關的Task)。(Phing: http://www.phing.info/)

以下介紹的就是Phing Build Tool的觀念,Ant或者NAnt都是相同的觀念。
NewImage
圖1: Ant,NAnt,Phing基本觀念

    Ant, NAnt, Phing等的Build Tool都是基於Build.xml來設計流程。如圖1,在一個Build.xml中,可以有很多Target(或者稱執行區塊)。每一個Target可以獨立存在與獨立執行。舉例來說,"取得最新程式碼"或"單元測試"就可以是一個Target。
    而Target裡面就可以設定各種不同的Task(任務),例如,刪除檔案,重新命名檔案等。當設計完build.xml之後,就可以透過Ant, NAnt, Phing來執行此build.xml.

    以下是以團隊其中一個服務的build.xml當範例(有些不適合秀出來的流程,就拿掉了,請見諒)。由於這是符合我們的需求,不見得適用每一個專案。但歡迎大家提出問題來討論。

先來看看這個build.xml的架構
     NewImage

在<project>包起來的所有節點,就是build.xml的主體。先來介紹一些基本的Tag。

  • <project> : 宣告整個build.xml,包含了名字,預設的Target,還有執行的目錄。預設的Target是指執行build.xml而沒有指定target的話,那預設會執行哪一個Target. 
  • <property> : 可以想像成build.xml的global variable (全域變數)。可以寫死常數,若宣告override=true,則可透過執行時,由外在的指令傳遞進來。
  • <target>: 每一個的Target(執行區塊),就是我們真正需要設計的地方。它包含了name, description與depends. 什麼是depends?顧名思義,就是要倚賴哪一個target先執行。透過depends就可以串起每一個不同的執行區塊與執行順序。


這個build.xml有6個target,接下來逐一介紹裡面的內容,也順便介紹一些常用的task.


  • Clean Target: 主要將一些暫存檔案刪除 (請參考上圖)
    • <delete> : 這就是圖1提到的Task。在這個Target使用<delete>將暫存檔案或一些中間產物給刪除。

  • Init Target: 準備開始進行build的程序,可以看到這個target depends在clean上,所以執行init之前,會先執行clean。(請參考上圖)
    • <echo> : 就是echo,可以把message印出來,這是非常重要的,尤其是需要build比較久的系統,透過echo的設計,可以清楚的知道目前build的進度。筆者有時候也會透過echo來做build.xml的debug. 
    • <mkdir> : 應該看名字就知道是建立目錄
    • ${build} : 這是什麼東西?這就是在build.xml中變數的寫法。在property中有定義了這一個變數,當要使用它時,就可以透過這個格式來使用。

  • Build Target: 真正進行Build的Target,也是最重要的。(細節參考上圖)
    • <exec> : 這是執行外在指令的寫法。由於筆者團隊是使用SVN做程式碼版本管理,所以透過<exec> check out (簽出)最新的程式碼。當然,Phing也提供內建的版本控制task,但因為我們使用EC2的 server,是透過private key登入,目前Phing的Task不支援,所以只好透過Command做。
    • <phplint>: 這個專案使用PHP程式,所以透過PHPLint這個工具做程式碼語法檢查。這需要比較久的時間,若是希望快一點的人,可以略過這一步。
    • <version> : 這是個特別的Task,基本上我們採用的版本號碼跟一般open source相同。也就是[大版本.小版本.build號碼]。當設定releasetype="Bugfix"時,那會在version.txt中遞增最後一個build number. 筆者就是透過這個Task來進行版本號碼的遞增的。當然還有其他的作法,例如將版本號碼check-in至版本控制server,然後再不停的update跟check-in。
    • <foreach> : 在build.xml中,也可以類似寫程式一般,使用For-Each的邏輯。由於團隊有三種開發環境,Dev+ Testing+ Production,所以必須要做出不同環境的build. 這也是Build對團隊最大的幫助。因為不同的環境有許多不同的設定。
    • <property name="phases" value="prod,test" />,在phases中,設定了prod跟test, 所以<foreach>會執行兩次,並且把prod跟test帶入"phase"這個參數。由於Dev就是每一個開發者自己的環境,所以就不用特別作一個build了。

  • Phase_Build : 剛剛提到的ForEach,會執行兩次phase_build (test跟prod) (細節參考上圖)
    • <property file="${temp_src}/deployment/${phase}/${phase}.properties"/>:由於每個環境都有一些不同的設定,所以讀外部property檔案。這邊比較特別的是,property檔案中的key會自動變成build.xml中的變數,方便後續使用。
    • <copy> : 準備開始copy檔案,一邊copy檔案,也一邊進行一些變數的修改。例如,修改CDN的網址,修改DB的URL,修改Facebook API Key等等。
    • <JsMin> : 為了前端的performance,我們將所有的Javascript進行minify。不過後來我們採用另外一套更好的解法,之後再與大家分享。
    • <delete> : 刪除一些不必要的檔案

  • Dist與Phase_dist: 準備好各個環境的檔案後,就準備打包了。為什麼取名dist? 這只是慣例,沒有特別理由。(細節參考上圖)
    • <foreach> : 一樣進行兩種不同環境的的打包。由於之前Target已經宣告過phase變數,這邊直接使用即可。
    • <mkdir> : 準備打包的目錄
    • <tar>: 透過Tar的指令來打包檔案。這邊可以看到每一個tgz檔案,都會帶有版本號碼。執行完這一個task之後,就可以成功產生出build files了。


底下就是產生出來的build file. 可以看到每個build都帶有版本號碼。
NewImage 

    既然透過Phing或Ant可以做出Build,那可不可以也進行自動化佈署?當然是可以,筆者也透過Phing來進行多台Server的同時佈署,至於如何做?那就留待下一篇文章了!

若各位有使用上的經驗或者問題,也歡迎提出來一起討論!謝謝!

下載本文章的build.xml: https://docs.google.com/open?id=0Bx_RyouzvSXHb05iZm5xR0VuZE0

Leave a Reply