在多次的構(gòu)建過(guò)程中,一個(gè)好的構(gòu)建工具或構(gòu)建過(guò)程,不應(yīng)有不必要和冗余耗時(shí)的工作花費(fèi)在那些尚未改變的代碼上.換句話說(shuō),它應(yīng)該做的就是把那些變動(dòng)過(guò)的地方添加進(jìn)來(lái)進(jìn)行重新構(gòu)建.假如你的構(gòu)建工具或構(gòu)建過(guò)程沒(méi)有以上所述的表現(xiàn),那么可以考慮是否能避免做無(wú)用功,從而優(yōu)化你的構(gòu)建.
舉個(gè)例子,假設(shè)有一個(gè)自上而下的構(gòu)建過(guò)程,即此工程是從持久層到更高一層的構(gòu)建.通常,這類工程因?yàn)槟切┧玫拇a生成器和反轉(zhuǎn)引擎工具會(huì)導(dǎo)致非常長(zhǎng)的構(gòu)建時(shí)間.如圖1所示,首先,構(gòu)建過(guò)程通過(guò)數(shù)據(jù)庫(kù)治理系統(tǒng)(比如,MySQL)運(yùn)行一段sql腳本,生成一個(gè)數(shù)據(jù)庫(kù)(譯者注:針對(duì)某些可以生成新的數(shù)據(jù)庫(kù)的數(shù)據(jù)庫(kù)系統(tǒng)),添加測(cè)試數(shù)據(jù).之后,Middlegen任務(wù)將生成CMP(container-managed Persistence)實(shí)體;接著,XDoclet 任務(wù)繼續(xù)生成romote 接口,local接口 和 home 接口 以及值實(shí)體(value object)和上一步生成的CMP實(shí)體bean的部署描述。(獲取更多Middlegen和Xdoclet的情況,請(qǐng)參閱資源)。接著,產(chǎn)生的代碼加上開發(fā)者所寫的java源代碼一起生成class文件,最后把這些class文件,和其它的資源一起打包成.jar,.war..ear后綴的文件。(譯者注: jar (Java Archive), .war (Web Archive) and .ear (EnterPRise Archive))
圖1. 構(gòu)建的步驟和輸出
現(xiàn)在假設(shè),某個(gè)正在從事此系統(tǒng)代碼設(shè)計(jì)的開發(fā)人員,需要稍微變動(dòng)某個(gè)if-else塊,并且希望看到變動(dòng)以后的運(yùn)行結(jié)果。所以他必須重新構(gòu)建,那么構(gòu)建過(guò)程可能要清空之前構(gòu)建好的所有的東西,從零開始構(gòu)建。對(duì)于開發(fā)者而言,這意味著大量的無(wú)聊的等待,而所有這些的出現(xiàn)僅僅因?yàn)樽隽艘恍┪⑿〉淖儎?dòng),因?yàn)樽儎?dòng)而必須要更新編譯修改了的類,以便更新所用的相關(guān)jar或war文件。
在這篇文章中,我將介紹一些技巧.通過(guò)這些技巧你可以通過(guò)只僅僅構(gòu)建那些被更新變動(dòng)了的部分,從而使得構(gòu)建變得快速,也就相應(yīng)的節(jié)約了時(shí)間。
持續(xù)集成
在開始我們討論之前,我們先對(duì)持續(xù)集成達(dá)成一下共識(shí),所謂的持續(xù)集成就是說(shuō)只要工程的代碼庫(kù)發(fā)生了變動(dòng),那么這些變動(dòng)將被構(gòu)建和測(cè)試,并隨時(shí)得到相關(guān)的報(bào)告。所做的一切將降低團(tuán)隊(duì)整合開發(fā)過(guò)程的成本和時(shí)間。這個(gè)過(guò)程需要以下條件:
* 一個(gè)源碼的版本控制系統(tǒng)比如CVS,這樣的話你可以把代碼放到某一中心地進(jìn)行維護(hù)。
* 一個(gè)全自動(dòng)的構(gòu)建和測(cè)試過(guò)程。(比如,用Ant)
*一個(gè)可選的,但我們強(qiáng)烈推薦的自動(dòng)的持續(xù)集成工具,類似CruiseControl
我們參照下圖(圖2所示)看一下一個(gè)完全的采用了以上原則的開發(fā)團(tuán)隊(duì)及其開發(fā)人員所做的一切.每個(gè)開發(fā)者都有工程的本地拷貝.在他被分配新的任務(wù)之前,首先他必須根據(jù)已經(jīng)提交給了代碼庫(kù)的變動(dòng)來(lái)更新自己當(dāng)前的本地拷貝,完成最新下達(dá)的任務(wù)(比如改變一些代碼),在接下來(lái)他自己的開發(fā)過(guò)程中,他有可能會(huì)修改代碼從而更新自己的工作目錄,然后進(jìn)行構(gòu)建,測(cè)試,假如測(cè)試無(wú)誤的話,那么就可以把自己的代碼的變動(dòng)提交給代碼庫(kù).
自動(dòng)化的持續(xù)集成工具監(jiān)控著代碼庫(kù)的變動(dòng)(CVS),在代碼庫(kù)發(fā)生變動(dòng)時(shí), 持續(xù)集成工具會(huì)開始一個(gè)新的構(gòu)建,以判定這個(gè)變動(dòng)是否能成功地整合到了代碼庫(kù).假如變動(dòng)不能成功的整合,提交變動(dòng)的開發(fā)者就會(huì)收到惶跬ㄖ畔?那么他必須撤銷自己的提交操作,使代碼庫(kù)的代碼恢復(fù)到提交前的樣子,接著解決整合出錯(cuò)的問(wèn)題,解決完后再次提交.
圖2. 持續(xù)集成系統(tǒng)中的各個(gè)角色
在這篇文章中,我們主要關(guān)注的情形是:一個(gè)開發(fā)者操作著自己的本地工作目錄,他對(duì)工程做了一些改動(dòng),并想盡快知道這些改動(dòng)的結(jié)果,以及反饋信息. 在完成最后一個(gè)任務(wù)并把它提交給代碼庫(kù)之前,開發(fā)者希望做幾個(gè)快速的增量式構(gòu)建.通過(guò)使用這篇文章所介紹的技巧,你可以加快你的構(gòu)建過(guò)程,自然而然地節(jié)約了開發(fā)時(shí)間.
注釋
加快構(gòu)建服務(wù)器的構(gòu)建過(guò)程本身有它專門的工具和技巧,比如介于多服務(wù)器的集群構(gòu)建(clustering builds)
在進(jìn)一步探討之前,我們定義一些對(duì)我們的討論很重要也很有必要的基本術(shù)語(yǔ)和概念.
*全構(gòu)建 (干凈構(gòu)建) 是指從零開始構(gòu)建,執(zhí)行構(gòu)建所要求的全部的步驟.它把所有的資源當(dāng)作是從未見(jiàn)過(guò)的全新的資源來(lái)操作,它會(huì)完全忽略之前的操作.
*增量式構(gòu)建: 一種優(yōu)化了的構(gòu)建,由最近一次構(gòu)建以來(lái)所產(chǎn)生的變動(dòng)觸發(fā).它只對(duì)那些變動(dòng)過(guò)但是目前為止尚未被構(gòu)建的資源進(jìn)行構(gòu)建.
*依靠性檢測(cè) 所謂的依靠型檢測(cè)是指查找當(dāng)前的工程資源和上次的構(gòu)建生成的產(chǎn)品的異同,并確定資源只是被修改還是是一個(gè)新的或其它的資源。通過(guò)這種檢測(cè),構(gòu)建工作就會(huì)只針對(duì)那些需要重新構(gòu)建的資源,從而體現(xiàn)所謂的增量式構(gòu)建。
大多數(shù)情況,依靠性檢測(cè)是基于代碼的時(shí)間標(biāo)簽和這些代碼相關(guān)的產(chǎn)品。也就是代碼的修正時(shí)間標(biāo)簽和已經(jīng)生成了的產(chǎn)品的修正時(shí)間標(biāo)簽做比較。假如現(xiàn)在產(chǎn)品的時(shí)間標(biāo)簽比生成此產(chǎn)品的代碼的時(shí)間標(biāo)簽陳舊,那么這個(gè)產(chǎn)品就會(huì)被標(biāo)識(shí)出來(lái),以便于下一次的重構(gòu)建。
然而,基于zip的任務(wù)(zip,jar,和其他)在依靠性檢查中表現(xiàn)得更好。假如我們這些任務(wù)的更新參數(shù)設(shè)為“yes”,那么這些zip文件就會(huì)被自己包含得所有入口文件所更新(假如zip文件已經(jīng)存在)。新的文件將會(huì)增加進(jìn)來(lái),而已過(guò)時(shí)的文件將會(huì)被更新到新的版本。
我將依靠性檢查和構(gòu)建優(yōu)化分成兩個(gè)層次:
*Task級(jí) 比如,編譯任務(wù)只編譯那些被修改過(guò)的資源以及這些資源所依靠的類。
*Target級(jí) 完全略過(guò)那些不必要的任務(wù)的執(zhí)行。這種優(yōu)化,將不會(huì)進(jìn)行一個(gè)一個(gè)地檢測(cè)所有任務(wù)資源的變動(dòng)的多余工作。
Make構(gòu)建工具
Make的文件是遵從依靠性原則的。Make是Unix系統(tǒng)下一個(gè)能夠自動(dòng)并可以起優(yōu)化程序結(jié)構(gòu)作用的工具。“make”的效用就是用于自動(dòng)決定一個(gè)大程序中哪些是需要被重編譯,從而觸發(fā)命令進(jìn)行編譯。為了進(jìn)行Make的工作,必須先寫一個(gè)稱為“makefile”的文件,這個(gè)文件將描述你的程序中文件之間的關(guān)系,以及更新每一個(gè)文件的命令行。“makefile”就是由所有的規(guī)則組成的。每一條規(guī)則都是解釋用什么方法以及在什么時(shí)間去構(gòu)造那些特定文件,而這些文件是某個(gè)非凡文件的 target。每一規(guī)則由三部分組成:一個(gè)或多個(gè)的 target,零個(gè)或多個(gè)的先決條件,零條或多條的命令。
接下來(lái)是一個(gè)很簡(jiǎn)單的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 程序運(yùn)行時(shí),先讀當(dāng)前目錄中的“makefile”文件,并開始執(zhí)行第一個(gè) target。Make會(huì)檢測(cè)每一個(gè)執(zhí)行 target的” target依靠”(或稱為prerequisites)屬性,看看這個(gè) target的執(zhí)行所依靠的其他 target的是否也是作為一個(gè) target出現(xiàn)。Make程序會(huì)順著這條依靠鏈,查找依靠性 target(dependencies屬性中提到的 target),這個(gè)過(guò)程是一個(gè)遞歸的過(guò)程,返回的條件是查找到的當(dāng)前的 target沒(méi)有執(zhí)行所必須的先決條件,或者這個(gè) target的先決條件沒(méi)有不受控制.當(dāng)查找依靠鏈到達(dá)鏈的末端,程序?qū)⒁赃f歸的形式依次執(zhí)行 target中的規(guī)則的命令行.
Ant 和Make區(qū)別于他們對(duì)執(zhí)行過(guò)程的不同看法。Make需要你說(shuō)明資源依靠,非資源依靠以及轉(zhuǎn)換這些的命令行。Ant 則需要你說(shuō)明構(gòu)建步驟,以及這些步驟的順序(很像一個(gè)流水線)。
無(wú)論任務(wù)自身是否可以執(zhí)行依靠性檢查,Make構(gòu)建器擁有顯式的依靠性檢測(cè)的機(jī)制,用戶都可以通過(guò)編寫makefile,使得構(gòu)建器執(zhí)行之。然而,相比Ant,Make并非平臺(tái)獨(dú)立。縱觀兩點(diǎn),他們都各有千秋,假如能各取所長(zhǎng),那么將皆大歡喜。
技巧和準(zhǔn)則
在我們回顧了一些概念和定義之后,我將對(duì)增量式構(gòu)建提出一些如何加快構(gòu)建以及如何優(yōu)化構(gòu)建的技巧和準(zhǔn)則。請(qǐng)注重我只是簡(jiǎn)要的介紹這些技巧,也就是說(shuō)這篇文章只是一個(gè)起點(diǎn)。更多的關(guān)于工具本身的介紹,可以參考資源.
注釋
Jonathon Rasmusson 在他的文章中討論了長(zhǎng)構(gòu)建以及解決這些問(wèn)題的技巧,”解決長(zhǎng)構(gòu)建指引”( "Long Build Trouble Shooting Guide.") 在這篇文章中,他關(guān)注于如何提速以及解決自動(dòng)測(cè)試過(guò)程的問(wèn)題。
避免不必要的 target執(zhí)行
一方面要保證你的構(gòu)建 target的正確的以及符合邏輯的依靠關(guān)系,同時(shí)避免在依靠性執(zhí)行環(huán)節(jié)發(fā)生的不必要的 target執(zhí)行。忽視 target之間的依靠關(guān)系而進(jìn)行所謂的優(yōu)化,是極其錯(cuò)誤的習(xí)慣,因?yàn)樗缶幊陶呔唧w地記住一系列非凡順序的 target,以得到正確的構(gòu)建(請(qǐng)參閱Eric M. Burke 寫的“十五個(gè)最佳Ant使用習(xí)慣“(“Top 15 Ant Best Practices“),發(fā)表于ONJAVA.com 2003年12月)。實(shí)際中應(yīng)該是讓構(gòu)建文件自己記住正確的依靠關(guān)系和同時(shí)執(zhí)行最優(yōu)化的構(gòu)建。
回到之前提到的自下而上的構(gòu)建過(guò)程,每次我們執(zhí)行構(gòu)建,并不需要去執(zhí)行SQL命令,Middlegen,Xdoclet等等。不過(guò)我們希望保持 target之間那種正確的依從關(guān)系,然而,某些情況下的依靠性檢測(cè)本身卻極其耗時(shí)(比如,基于數(shù)據(jù)庫(kù)來(lái)檢查實(shí)體Beans是否正確),假如可能的話,我們希望徹底地跳過(guò)這些工作。
我介紹一個(gè)很簡(jiǎn)單的技巧,通過(guò)這個(gè)技巧你可以略過(guò)那些不必要的 target:檢查那些將有可能被忽略的 target的最后執(zhí)行動(dòng)作的時(shí)間標(biāo)簽和它所依靠的 target的最后執(zhí)行動(dòng)作的時(shí)間標(biāo)簽,從而決定 target的輸出是否是最新的。舉例說(shuō)明一下,假如有 targetA,它的執(zhí)行依靠與 targetB和C,假如B 和C在A上次執(zhí)行以來(lái)重新執(zhí)行過(guò),那么就有必要再次執(zhí)行A以保持?jǐn)?shù)據(jù)的一致性,反之則可以跳過(guò)A的執(zhí)行。這種規(guī)則依據(jù)的條件時(shí),所有的A的輸入都有由B和C提供,并且在 target執(zhí)行期間B和C的輸出沒(méi)有經(jīng)過(guò)人為的修改, target之間的完全銜接。
為了能增加這個(gè)功能,我們使用Ant工具的“toUCh“ 任務(wù)來(lái)實(shí)現(xiàn)。 “touch” 任務(wù)將創(chuàng)建一個(gè)新的臨時(shí)文件,假如這個(gè)文件已經(jīng)建立,那么它只要去更新臨時(shí)文件中不必要的 target和它所依靠的 target的執(zhí)行的時(shí)間標(biāo)簽。接著,在執(zhí)行不必要的 target之前,通過(guò)”uptodate”任務(wù)我們檢查最近一次 target執(zhí)行所生成的時(shí)間標(biāo)簽異與最近一次通過(guò)更新 target而執(zhí)行的依靠性 target生成的時(shí)間標(biāo)簽。這個(gè)任務(wù)將會(huì)設(shè)置一個(gè)跳躍屬性,我們把這個(gè)屬性作為不必要執(zhí)行的 target的unless屬性值,從而使Ant跳過(guò)這個(gè) target的執(zhí)行。
現(xiàn)在讓我們先回到我們給出的例子。很明顯,當(dāng)我們改變數(shù)據(jù)庫(kù)schema時(shí),我們的這個(gè)例子將要執(zhí)行Middlegen target,這樣就可以從數(shù)據(jù)庫(kù)產(chǎn)生實(shí)體Bean。另一方面,通過(guò)改變SQL腳本文件來(lái)改變?cè)瓟?shù)據(jù)庫(kù)schema,并通過(guò)執(zhí)行SQL target來(lái)執(zhí)行這種差異。為了把數(shù)據(jù)庫(kù)schema在沒(méi)有改變的情況下不執(zhí)行Middlegen target的這種邏輯嵌入到構(gòu)建中,我們需要對(duì)比SQL target上一次執(zhí)行的時(shí)間標(biāo)簽和Middlegen target上一次執(zhí)行的時(shí)間標(biāo)簽。假如SQL target的執(zhí)行時(shí)間標(biāo)簽不比Middlegen target執(zhí)行的時(shí)間標(biāo)簽早,我們可以跳過(guò)Middlegen target的執(zhí)行。
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>
|
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注