使用Ant進行增量快速構建實現

發表于:2007-06-22來源:作者:點擊數: 標簽:
在多次的構建過程中,一個好的構建工具或構建過程,不應有不必要和冗余耗時的工作花費在那些尚未改變的代碼上.換句話說,它應該做的就是把那些變動過的地方添加進來進行重新構建.如果你的構建工具或構建過程沒有以上所述的表現,那么可以考慮是否能避免做無用功,

   

  在多次的構建過程中,一個好的構建工具或構建過程,不應有不必要和冗余耗時的工作花費在那些尚未改變的代碼上.換句話說,它應該做的就是把那些變動過的地方添加進來進行重新構建.如果你的構建工具或構建過程沒有以上所述的表現,那么可以考慮是否能避免做無用功,從而優化你的構建.

  舉個例子,假設有一個自上而下的構建過程,即此工程是從持久層到更高一層的構建.通常,這類工程因為那些所用的代碼生成器和反轉引擎工具會導致非常長的構建時間.如圖1所示,首先,構建過程通過數據庫管理系統(比如,MySQL)運行一段sql腳本,生成一個數據庫(譯者注:針對某些可以生成新的數據庫的數據庫系統),添加測試數據.之后,Middlegen任務將生成CMP(container-managed Persistence)實體;接著,XDoclet 任務繼續生成romote 接口,local接口 和 home 接口 以及值實體(value object)和上一步生成的CMP實體bean的部署描述。(獲取更多Middlegen和Xdoclet的情況,請參閱資源)。接著,產生的代碼加上開發者所寫的java源代碼一起生成class文件,最后把這些class文件,和其它的資源一起打包成.jar,.war..ear后綴的文件。(譯者注: jar (Java Archive), .war (Web Archive) and .ear (Enterprise Archive))

使用Ant進行增量快速構建實現(圖一)
圖1. 構建的步驟和輸出

  現在假設,某個正在從事此系統代碼設計的開發人員,需要稍微變動某個if-else塊,并且希望看到變動以后的運行結果。所以他必須重新構建,那么構建過程可能要清空之前構建好的所有的東西,從零開始構建。對于開發者而言,這意味著大量的無聊的等待,而所有這些的出現僅僅因為做了一些微小的變動,因為變動而必須要更新編譯修改了的類,以便更新所用的相關jar或war文件。

  在這篇文章中,我將介紹一些技巧.通過這些技巧你可以通過只僅僅構建那些被更新變動了的部分,從而使得構建變得快速,也就相應的節約了時間。

持續集成

  在開始我們討論之前,我們先對持續集成達成一下共識,所謂的持續集成就是說只要工程的代碼庫發生了變動,那么這些變動將被構建和測試,并隨時得到相關的報告。所做的一切將降低團隊整合開發過程的成本和時間。這個過程需要以下條件:

* 一個源碼的版本控制系統比如CVS,這樣的話你可以把代碼放到某一中心地進行維護。
* 一個全自動的構建和測試過程。(比如,用Ant)
*一個可選的,但我們強烈推薦的自動的持續集成工具,類似CruiseControl

  我們參照下圖(圖2所示)看一下一個完全的采用了以上原則的開發團隊及其開發人員所做的一切.每個開發者都有工程的本地拷貝.在他被分配新的任務之前,首先他必須根據已經提交給了代碼庫的變動來更新自己當前的本地拷貝,完成最新下達的任務(比如改變一些代碼),在接下來他自己的開發過程中,他有可能會修改代碼從而更新自己的工作目錄,然后進行構建,測試,如果測試無誤的話,那么就可以把自己的代碼的變動提交給代碼庫.

  自動化的持續集成工具監控著代碼庫的變動(CVS),在代碼庫發生變動時, 持續集成工具會開始一個新的構建,以判斷這個變動是否能成功地整合到了代碼庫.如果變動不能成功的整合,提交變動的開發者就會收到惶跬ㄖ?畔?那么他必須撤銷自己的提交操作,使代碼庫的代碼恢復到提交前的樣子,接著解決整合出錯的問題,解決完后再次提交.

使用Ant進行增量快速構建實現(圖二)
圖2. 持續集成系統中的各個角色

  在這篇文章中,我們主要關注的情形是:一個開發者操作著自己的本地工作目錄,他對工程做了一些改動,并想盡快知道這些改動的結果,以及反饋信息. 在完成最后一個任務并把它提交給代碼庫之前,開發者希望做幾個快速的增量式構建.通過使用這篇文章所介紹的技巧,你可以加快你的構建過程,自然而然地節約了開發時間.

注釋
  加快構建服務器的構建過程本身有它專門的工具和技巧,比如介于多服務器的集群構建(clustering builds)

  在進一步探討之前,我們定義一些對我們的討論很重要也很有必要的基本術語和概念.

*全構建 (干凈構建) 是指從零開始構建,執行構建所要求的全部的步驟.它把所有的資源當作是從未見過的全新的資源來操作,它會完全忽略之前的操作.

*增量式構建: 一種優化了的構建,由最近一次構建以來所產生的變動觸發.它只對那些變動過但是目前為止尚未被構建的資源進行構建.

*依賴性檢測  所謂的依賴型檢測是指查找當前的工程資源和上次的構建生成的產品的異同,并確定資源只是被修改還是是一個新的或其它的資源。通過這種檢測,構建工作就會只針對那些需要重新構建的資源,從而體現所謂的增量式構建。

  大多數情況,依賴性檢測是基于代碼的時間標簽和這些代碼相關的產品。也就是代碼的修正時間標簽和已經生成了的產品的修正時間標簽做比較。如果現在產品的時間標簽比生成此產品的代碼的時間標簽陳舊,那么這個產品就會被標識出來,以便于下一次的重構建。

  然而,基于zip的任務(zip,jar,和其他)在依賴性檢查中表現得更好。如果我們這些任務的更新參數設為“yes”,那么這些zip文件就會被自己包含得所有入口文件所更新(如果zip文件已經存在)。新的文件將會增加進來,而已過時的文件將會被更新到新的版本。

  我將依賴性檢查和構建優化分成兩個層次:
*Task級 比如,編譯任務只編譯那些被修改過的資源以及這些資源所依賴的類。
*Target級 完全略過那些不必要的任務的執行。這種優化,將不會進行一個一個地檢測所有任務資源的變動的多余工作。

Make構建工具

  Make的文件是遵從依賴性原則的。Make是Unix系統下一個能夠自動并可以起優化程序結構作用的工具?!癿ake”的效用就是用于自動決定一個大程序中哪些是需要被重編譯,從而觸發命令進行編譯。為了進行Make的工作,必須先寫一個稱為“makefile”的文件,這個文件將描述你的程序中文件之間的關系,以及更新每一個文件的命令行?!癿akefile”就是由所有的規則組成的。每一條規則都是解釋用什么方法以及在什么時間去構造那些特定文件,而這些文件是某個特殊文件的 target。每一規則由三部分組成:一個或多個的 target,零個或多個的先決條件,零條或多條的命令。

接下來是一個很簡單的makefile文件的片斷:
Listing 1. Sample makefile
    Prog1:  main.o    file1.o
       cc -o prog1 main.o file1.o
     main.o:     main.c    mydefs.h
       cc –c main c

  Make 程序運行時,先讀當前目錄中的“makefile”文件,并開始執行第一個 target。Make會檢測每一個執行 target的” target依賴”(或稱為prerequisites)屬性,看看這個 target的執行所依賴的其他 target的是否也是作為一個 target出現。Make程序會順著這條依賴鏈,查找依賴性 target(dependencies屬性中提到的 target),這個過程是一個遞歸的過程,返回的條件是查找到的當前的 target沒有執行所必須的先決條件,或者這個 target的先決條件沒有不受控制.當查找依賴鏈到達鏈的末端,程序將以遞歸的形式依次執行 target中的規則的命令行.

  Ant 和Make區別于他們對執行過程的不同看法。Make需要你說明資源依賴,非資源依賴以及轉換這些的命令行。Ant 則需要你說明構建步驟,以及這些步驟的順序(很像一個流水線)。
無論任務自身是否可以執行依賴性檢查,Make構建器擁有顯式的依賴性檢測的機制,用戶都可以通過編寫makefile,使得構建器執行之。然而,相比Ant,Make并非平臺獨立??v觀兩點,他們都各有千秋,如果能各取所長,那么將皆大歡喜。

技巧和準則

  在我們回顧了一些概念和定義之后,我將對增量式構建提出一些如何加快構建以及如何優化構建的技巧和準則。請注意我只是簡要的介紹這些技巧,也就是說這篇文章只是一個起點。更多的關于工具本身的介紹,可以參考資源.


注釋
  Jonathon Rasmusson 在他的文章中討論了長構建以及解決這些問題的技巧,”解決長構建指引”( "Long Build Trouble Shooting Guide.") 在這篇文章中,他關注于如何提速以及解決自動測試過程的問題。

  避免不必要的 target執行
  一方面要保證你的構建 target的正確的以及符合邏輯的依賴關系,同時避免在依賴性執行環節發生的不必要的 target執行。忽視 target之間的依賴關系而進行所謂的優化,是極其錯誤的習慣,因為它要求編程者詳細地記住一系列特殊順序的 target,以得到正確的構建(請參閱Eric M. Burke 寫的“十五個最佳Ant使用習慣“(“Top 15 Ant Best Practices“),發表于ONJAVA.com 2003年12月)。實際中應該是讓構建文件自己記住正確的依賴關系和同時執行最優化的構建。

  回到之前提到的自下而上的構建過程,每次我們執行構建,并不需要去執行SQL命令,Middlegen,Xdoclet等等。不過我們希望保持 target之間那種正確的依從關系,然而,某些情況下的依賴性檢測本身卻極其耗時(比如,基于數據庫來檢查實體Beans是否正確),如果可能的話,我們希望徹底地跳過這些工作。
 
  我介紹一個很簡單的技巧,通過這個技巧你可以略過那些不必要的 target:檢查那些將有可能被忽略的 target的最后執行動作的時間標簽和它所依賴的 target的最后執行動作的時間標簽,從而決定 target的輸出是否是最新的。舉例說明一下,如果有 targetA,它的執行依賴與 targetB和C,如果B 和C在A上次執行以來重新執行過,那么就有必要再次執行A以保持數據的一致性,反之則可以跳過A的執行。這種規則依據的條件時,所有的A的輸入都有由B和C提供,并且在 target執行期間B和C的輸出沒有經過人為的修改, target之間的完全銜接。

  為了能增加這個功能,我們使用Ant工具的“touch“ 任務來實現。 “touch” 任務將創建一個新的臨時文件,如果這個文件已經建立,那么它只要去更新臨時文件中不必要的 target和它所依賴的 target的執行的時間標簽。接著,在執行不必要的 target之前,通過”uptodate”任務我們檢查最近一次 target執行所生成的時間標簽異與最近一次通過更新 target而執行的依賴性 target生成的時間標簽。這個任務將會設置一個跳躍屬性,我們把這個屬性作為不必要執行的 target的unless屬性值,從而使Ant跳過這個 target的執行。

  現在讓我們先回到我們給出的例子。很明顯,當我們改變數據庫schema時,我們的這個例子將要執行Middlegen target,這樣就可以從數據庫產生實體Bean。另一方面,通過改變SQL腳本文件來改變原數據庫schema,并通過執行SQL target來執行這種差異。為了把數據庫schema在沒有改變的情況下不執行Middlegen target的這種邏輯嵌入到構建中,我們需要對比SQL target上一次執行的時間標簽和Middlegen target上一次執行的時間標簽。如果SQL target的執行時間標簽不比Middlegen target執行的時間標簽早,我們可以跳過Middlegen target的執行。

Listing 2. Sample Ant file that skips unnecessary targets
<project name="sample-build" default="" basedir=".">
<target name="init-skip-properties" description="initializes the skip properties" depends="init">
   <uptodate srcfile="create-database.timestamp" targetfile="middlegen.timestamp"
      property="middlegen.skip" value="true"/>
</target>
<target name="create-database" description="runs sql script file on dbms to create db"
   depends="init-skip-properties">

   <sql
      src=" MySQL.sql"
       ...
    />
   <touch file="create-database.timestamp"/>
</target>

<target
   name="middlegen"
   description="Runs Middlegen to create Entity Beans "
   depends="create-database"
   unless="middlegen.skip" >
       ...

   <middlegen
      <cmp20
       ...  
      </cmp20>

   </middlegen>
   <touch file="middlegen.timestamp"/>
</target>
</project>

  在例子中我們完成了target級的優化。如果一個 target文件或一個 target集合比他們的源文件還新的話,Ant的”uptodate”任務將設置一個屬性?!眛ouch”任務將改變文件的修正時間,或者可能是同時創建一個修正時間。

  在我們樣例的構建過程,我們可以在”create-database” target中做另外一個優化和自動化處理,這個”create-database”  target是在SQL腳本改變時要運行的。如此一來,我們將對比SQL腳本文件的標簽時間和SQL target最后一次執行時所產生的臨時文件的時間標簽。

  需要牢記在心的是,自動的跳過”create-database” target的執行必須仔細考量,因為一個多余的SQL文件的改變(比如,敲了一個回車或加了一個空格)都有可能重建數據庫甚至有可能損壞現有的數據,而這些在工程中發生都是我們所不愿看到的,特別是團隊開發時使用一個通用的數據庫服務器。在這種情況下,把”create-database”  target獨立出來,手工的單獨執行這個 target。但是在一個數據庫schema改變比較頻繁而你又處于開發底層的工程項目中,這種執行以及自動化的跳過”create-database” target將有助與你,敲更少的鍵盤,而獲得更加自動化的構建。

我們通過加入下列這句來跳過”create-database” target的執行。
init-skip-properties target:
<uptodate srcfile="MySQL.sql" targetfile="create-database.TimeStamp"
   property="create-database.skip" value="true"/>
同時改變”create-databas”的 target定義,如下所示:
<target name="create-database" description="runs sql commands file on dbms to create db"
   depends="init"
   unless="create-database.skip" >
  在你想要比較 target的最近一次執行的時間標簽和此 target所依賴的兩個或兩個以上的 target的最后一次執行的時間標簽的情況下,利用”condition” target可以實現你的要求。下面舉例,Ant腳本將對比 target3與 target1和2:
isting 3. Ant's condition task
<condition property="target3-skip">
   <and>
      <uptodate srcfile="target1.timestamp" targetfile="target3.timestamp" />                          
      <uptodate srcfile="target2.timestamp" targetfile="target3.timestamp" />                          
   </and>
  </condition>
  要銘記在心的是,這種跳過不必要的 target執行的技巧更適合于構建的某些部分,這些部分就是一階段接一階段的代碼是自動生成的,也就是說,在這些階段在 target執行期間,數據的輸出沒有人為的去變動。如果想獲取更多的Ant target信息,請參閱資源。

小技巧

  對于那些熟悉CruiseControl的人,你可以通過設置配置表CVS標簽的property/ondeleteproperty屬性值,使得是CruiseControl服務器上的構建工作體現出增量式和條件化。這些屬性可以在CVS標簽中特別標明的模塊產生變化或被刪除等動作自動被置值。之后你就可以通過這些屬性是否被置值,來有條件的進行構建工作。獲取更多關于CruiseControl的信息,請參閱資源

在構建中使用快速和敏捷的任務

  如果可能的話,盡可能的使用快速的任務來替代,不要在你的構建中夾雜著那些顯得遲鈍的任務。如果一個任務沒有做依賴性檢測或者做的檢測顯得不準確不適當,那么試著找到一個更為簡潔的任務版本,即使有困難,也要試著延展當前的任務在盡可能的情況做敏捷的依賴性檢測。這樣的決心,可以使你的構建真正的變成快速的增量式構建。

使用Jikes來快速的編譯代碼

  在這個單元,我將討論在構建中發生最多的Java代碼的編譯任務。想獲得一個更加快速得編譯,我們不是用javac而是使用Jikes來實現,因為它表現的更為快速和敏捷,并且依賴性檢測執行的很好。唯一遺憾的是Jikes的移植性較差,因為它不像javac一樣用java編寫,而是用C++來編寫的。         

  把Jikes設置為工程默認的編譯工具,同時把構建配置文件的構建編譯器屬性設為Jikes。如果你設置了構建編譯的”完全依賴檢測”(full dependency check)這個屬性屬性,那么Jikes將執行完整的依賴性檢測。Jikes的全依賴性檢測顯得更可靠,因為它同時也會去檢測那些已被更新的類所使用的類,以及間接檢測。

  為了更好的說明發生在Jikes中的全依賴,我們假設有三個類,分別為A.java B.java和C.java。類A對類B有某種依賴,而類B對類C有某種依賴。Jikes的全依賴分析機制將使我們在修改類C而后如果執行類A的Jikes編譯的話,也會同時進行類C的編譯。如果沒有選擇完全依賴,那么編譯器只會重新編譯直接依賴的那些類。在我們運行Jikes時如果沒有選擇完全依賴這個選項,那么類A的Jikes編譯不會觸發類C的編譯工作。


  Jikes是如此快速的進行一次重編譯(清除掉原來的類文件,重新進行編譯),所以備受推崇。我還要提到一點的就是,在使用Make的時候你可以用Jikes生成依賴性信息。

  Ant 使用普通的編譯器時,只通過資源的名字和類文件來確定是否需要重構建。它并不關注源代碼本身,所以它也不清楚類層次以及類的命名等不同于源代碼的地方。你可以利用Ant的”depend” 任務去改變這種情況。

  “depend “任務是基于某種原則,而不僅僅通過存在物或修正的次數。它的工作方式是先判斷那些類對比于其源碼已經過期,然后把那些依賴于已經過期了的類統統的移除。為了決定類的依賴性,”depend” 任務分析類文件而不以任何方式去解析源代碼。這種分析通過編譯器編譯時編碼到類文件的參考信息來執行。這種方法比直接去解析源碼快的多。

  一旦”depend”任務挖掘出所有的類的依賴性關系,它把這種關系轉化為某種信息,這種信息就是對于任何一個類,是否有其它的類對它有依賴性。這樣就會產生一個“影響表”,通過這個表可以判斷那些類是受已經過期了的類的影響的而成為無效文件的。這些無效的類文件會被移除,以此觸發相關的類的編譯工作 。這個”depend”任務支持“關閉”屬性,這個屬性控制著”depend”任務是否只是考慮類與類之間有直接關系的,或可以是傳遞關系,甚至可以是間接的關系的。這個任務可以用來完善那些編譯顯得不是那么優秀而且缺乏合適的依賴性分析的編譯器.

注釋
  對于Ant的”javac”任務,有必要確定輸出和源代碼處于同一個目錄結構,也就是說,源文件的目錄結構和它們的package必須是一樣的,否則,依賴性檢測將不會工作,因為它是在那個相對與源文件夾的文件夾中查找一個類文件的Java源文件,而且這個文件夾也是相對與輸出的基本文件的文件夾就是放置類文件的文件夾。當你每一次執行”javac”任務時,編譯器將都隨之執行一個完全的重編譯。

注意
  你可以從javac中獲得”depend”任務的功能,通過設置支持依賴性屬性的編譯器的依賴性屬性置,但是眾所周知的是常規的javac編譯器的依賴性選項是個臭蟲。

需要說明一下的是有些編譯器的依賴性檢測顯得更加優化而且更為準確,比如Eclipse的增量式編譯甚至可以只是重編譯一個方法。

  把你的工程分解成高聚合,低耦合的模塊
經常性的檢測你的代碼,提取出package中和layer中的依賴性關系。之后你可以指出那些依賴是錯誤的并據此重新構建你的系統設計。把你的系統細化,分解成一個個高聚合,低耦合的組件,同時把可以共享的或通用的組件作為一個獨立的模塊。Java的類必須正確地放置于包中并合理的部署于系統結構的層中。處于不同層中的Java類彼此之間必須擁有正確的外部依賴性。比如,用戶接口層的代碼就不應該對商業層的代碼有什么依賴性。

  這種分解的習慣有非常多的優勢:它積極倡導面向對象的編程方法。編程質量,諸如外部擴展性,代碼重用性,系統可維護性都受到設計的包中類之間相互依賴性的影響。同時它也減少了構建時間。在某個層中編譯時,通過去除其它層中的錯誤的依賴關系,避免了在其它層中無謂的代碼重編譯工作。

  同時一個組件如果獨立出來了,那么單獨開發一個組件的開發者只需運行和測試屬于自己組件范圍內的東西。這也就保證了不用去執行其他模塊的所帶來的重構和測試。很多免費的工具和類包具有這種分析特性。比如,你可以使用JDepend工具,這種工具將分析包與包之間的依賴關系并通過這些關系生成報告。這個報告包括某種對稱性,如傳入的耦合(包中使用一個其它的包)和傳出的耦合(被其他包所使用)。你可以通過命令行的方式或Ant去執行它。它同時提供圖形化和命令行式的輸出。在Ant中使用它只需簡單的把下面的代碼片斷加入到你的Ant文件中。(首先你必須到網上去下載JDepend包,請參閱資源)

Listing 4. Run JDepend from Ant
<jdepend outputfile="jdepend.xml" fork="yes" format="xml">
   <sourcespath>
      <pathelement location="src"/>
   </sourcespath>
   <classpath>
      <pathelement location="classes"/>
      <pathelement location="lib/jdepend.jar"/>
   </classpath>
</jdepend>
這段代碼將產生一個包含分析結果的XML文件

并行的執行任務或 target

  你可以使用”parallel”任務在Ant中并行任務。并行的任務各自執行一個線程.因此可以盡可能的獲取進程的資源以降低構建時間.
Listing 5. Ant's parallel task
<Parallel>
     <task1 ...>
     <task2 ...>
   </parallel>
  這個任務一般只是用于測試。程序服務器插入一個線程而測試執行的是另外一個線程。想獲取更多關于并行任務的資料,請參閱資源。需要注意的是,這些并行執行的任務之間不能存在依賴性的關系。

使用”apply”任務執行系統命令更新源文件
  如果你只是想在一些源文件被更新時執行某一個系統命令,你可以使用”apply”任務。某種程度上他類似make的規則定義。如果你特別的指定一個嵌套的mapper和dest屬性,那么每一個源文件的時間標簽將與在嵌套的mapper中定義而在dest中搜索所得的 target文件的時間標簽作對比。然后,命令只在更新源碼時執行。在Ant中調用系統命令,會使構建文件的移植性收到限制。

Listing 6. Ant's apply task
<apply executable="cc" dest="src/C" >
   <arg value="-c"/>
   <arg value="-o"/>
   <targetfile/>
   <srcfile/>
   <fileset dir="src/C" includes="*.c"/>
   <mapper type="glob" from="*.c" to="*.o"/>
</apply>

  想要獲取更多關于”apply”任務的信息,請參閱資源

結論
  增量式快速構建對工程的進度有極大的影響力,特別時團隊開發采用XP的持續集成原則時。

  下面我們對文章提到的關于如何加快構建與如何優化構建的技巧進行總結。
1.保持任務之間準確的邏輯的依賴關系,并且使你的構建略過那些不必要的 target的執行。
2.使用諸如Jikes的快捷和簡潔的任務。
3.把你的系統分解的小巧,且具有高聚合,低耦合的特性的組件。
4.在更新源文件時使用Ant的”apply”任務來執行系統命令。
5.如果可能的話,盡量地并行執行任務。

  你也可以開發一個Ant啟動程序 ,這個程序內在地實現了跳過 target的技巧,這也將使得你的原Ant文件只需更少的修改.

原文轉自:http://www.anti-gravitydesign.com

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97