18 10月 2010

Maven2 : Release Plugin

在專案的後期,要進入大量測試的階段,或是要完成測試要打包完整檔案給客戶時,通常都會在source code上加上一個tag,例如XXX-RC1,XXXX-RELEASE,以方便追踪各項問題,當然透過其他SCM提供的GUI Tools或是Commadn line來執行這種上tag的工作是沒有任何疑慮的,只不過Maven也提供了一個Plugin讓我們可以在沒在錯誤下完成一個Build時一併進行上Tag及其他release的工作,如修改pom.xml的version。

Maven Release Plugin請參考http://maven.apache.org/plugins/maven-release-plugin/index.html

基本上只要在pom.xml的plugin中加上

<plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-release-plugin</artifactid>
    <version>2.1</version>
</plugin> 

然後在goals裡加上release:prepare即可

執行時會詢問你要簽入的version與tag/label的資料

[INFO] Working directory: /Users/elliot/tmp/target/checkout
[INFO] Checking dependencies and plugins for snapshots ...
What is the release version for "BuildDemo"? (idv.elliot:BuildDemo) 0.0.3: : 
What is SCM release tag or label for "BuildDemo"? (idv.elliot:BuildDemo) BuildDemo-0.0.3: : 
What is the new development version for "BuildDemo"? (idv.elliot:BuildDemo) 0.0.4-SNAPSHOT: :

當整個build在沒錯誤的情況下結束時,release plugin會依你提共的資訊對source code加上tag,然後修改pom.xml的version,再將pom.xml checkin到SCM中。

如果希望不要出現提示,可以用-D的方式將相關版本資訊帶入,或是修改release.properties,然後在build 時加上--batch-mode即可

例如:

mvn --batch-mode -Dtag=my-proj-1.2 -DreleaseVersion=1.2 -DdevelopmentVersion=2.0-SNAPSHOT release:prepare 


Maven2 : SCM

通常在Nightly Build的環節,我比較喜歡Clean Build,也就是從Checkout、Compile、Test、Deploy都是一個動作完成的Compile、Test、Deploy都可以靠Maven或Ant完成,但是自VCS中checkout source code就要看plugin的支援程度了。即便是plugin支援度不夠,只要該種VCS有提供command line的模式,我想就能利用shell或script的方式來達到完整的Clean Build。 例如:

git clone [url] #checkout source code from VCS
cd [project]
mvn site -Pprod

當然利用Maven提供的SCM來處理也是不錯,除了checkin, checkout之外也可以利用scm plugin來列出changelog或add、remove檔案,不過我個人是比較少用到這些功能就是。

Maven提供的SCM Plugin請參考 http://maven.apache.org/scm/plugins/index.html, 而支援的SCM的完整度則可以考http://maven.apache.org/scm/matrix.html


為了使用Maven的SCM Plugin,通常單獨將<scm>建一個pom.xml,其他plugin或build的東西就免寫了,當然要用原來project的pom.xml也無不可,簡單看個例子。

<project xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
  <modelversion>4.0.0</modelversion>

  <groupid>idv.elliot</groupid>
  <artifactid>BuildDemo</artifactid>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>

  <name>BuildDemo</name>
  <url>http://github.com/ElliotChen/BuildDemo </url>

  <scm>
    <!-- 僅供讀取的Connection URL, 前面必需加上scm:xxx -->
    <connection>scm:git:git://github.com/ElliotChen/BuildDemo.git </connection>
    <!-- 可以執行checkin的Connection URL, 前面必需加上scm:xxx -->
    <developerconnection>scm:git:git://github.com/ElliotChen/BuildDemo.git </developerconnection>
    <url>http://github.com/ElliotChen/BuildDemo </url>
  </scm>
  <build>
    <plugins>
      <plugin>
        <groupid>org.apache.maven.plugins</groupid>
        <artifactid>maven-scm-plugin</artifactid>
        <version>1.3</version>
        <!-- Checkout之後要執行的Goal -->
        <configuration>
          <goals>site</goals>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project> 


簡單來說,只要在<scm>裡的<connection>填入SCM checkout source code的url,然後在url之前依你使用的SCM種類加上指定的prefix,然後在<maven-scm-plugin>的<configuration>中填上接下來要執行的goal,最後執行

mvn clean scm:bootstrap 
這樣就可以看到像下列的輸出:
[INFO] [scm:bootstrap {execution: default-cli}]
[INFO] Removing /Users/elliot/tmp/target/checkout
[INFO] Executing: /bin/sh -c cd /Users/elliot/tmp/target && git clone git://github.com/ElliotChen/BuildDemo.git /Users/elliot/tmp/target/checkout
[INFO] Working directory: /Users/elliot/tmp/target
[INFO] Executing: /bin/sh -c cd /Users/elliot/tmp/target/checkout && git pull git://github.com/ElliotChen/BuildDemo.git master
[INFO] Working directory: /Users/elliot/tmp/target/checkout
[INFO] Executing: /bin/sh -c cd /Users/elliot/tmp/target/checkout && git checkout
[INFO] Working directory: /Users/elliot/tmp/target/checkout
[INFO] Executing: /bin/sh -c cd /Users/elliot/tmp/target/checkout && git ls-files
[INFO] Working directory: /Users/elliot/tmp/target/checkout
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building BuildDemo
[INFO]    task-segment: [site]
[INFO] ------------------------------------------------------------------------
[INFO] [site:site {execution: default-site}]


13 10月 2010

Maven2 : Portable Configuration

最近Support中x信托個金的WebATM案子,非常訝異,SCM checkout出來的source code不能compile,四個環境上跑的是哪個版本也不能確定,環境上的configuration也都不能完全確定哪個是正確的,再來改版時的patch是一個個class複製到deployment目錄下,XML或Properties也都是手動一個個修改....拜託,什麼年代了!?

能用程式做的就不要手動,寫個maven或ant的設定有這麼麻煩嗎?時間要花在更有義意的事情上,減少手動的工作,也減少人為的錯誤,多出來的時間回去陪家人不是更好些嗎....

XML及Properties的變動不外乎兩種方式解決
(1)Merge : 在範本檔案以${變數名}做為標記,然後再為不同環境寫不同的設定檔,為每個變數定出符合該環境的值,在Build的時候將範本與變數檔進行Merge,產生該環境專用的檔案,該檔案應與範本檔案同名
(2)Replace:為不同環境另存不同名的檔案,在Build的時以更名覆蓋的方式產生該環境專用的檔案

Merge的變數值有幾種設定方式
(1)放在系統變數中,範本檔以${env.變數名}取得該值,例如 export profilename=ABC;
(2)放在java 啟動參數數,以 -D變數名=變數值 帶入,範本檔則以${變數名}取得該值,例如 mvn -Dprofilename=ABC
(3)放在pom.xml中,以<properties><變數名>變數值</變數名></properties>設定,範本檔則以${變數名}取得該值,各profile可以自訂不同的properties
(4)放在其他檔案中,以一般java的properties檔案格式設定,如 變數名=變數值,在pom.xml中以<filters><filter>變數值設定檔</filter></filters>,範本檔則以${變數名}取得該值。

簡單看一下Maven的例子,下圖是一個非常簡單的maven project

其中
conf/prod/env.properties是prod環境Merge用的變數值設定檔案

setting.envname=prod

conf/template/system.properties則是上列所提Merge用的範本檔

profile.name=${profilename} #取自pom.xml中的設定
package.envname=${setting.envname} #取自env.properties
M2=${env.M2_HOME} #取自系統變數

src/main/resource下的兩個檔案則是為了展示利用antrun來執行Replace的操作,裡面的內容隨便設定,只要不一樣即可

再來列出pom.xml中的profiles設定

<profiles>
    <profile>
        <id>prod</id>
        <!-- 在Maven內自訂變數值 -->
        <properties>
            <profilename>PROD</profilename>
        </properties>
        <build>
        <!-- 將變數訂在特定檔案(env.properties) -->
            <filters>
                <filter>${basedir}/conf/prod/env.properties</filter>
            </filters>
            <resources>
                <!-- 系統原用的resource -->
                <resource>
                    <directory>${basedir}/src/main/resources</directory>
                </resource>
    <!-- 因不同環境會有變動的設定檔範本 -->
    <resource>
        <directory>${basedir}/conf/template</directory>
        <filtering>true</filtering>
                </resource>
            </resources>
            <plugins>
            <!-- 利用Ant Run的Copy來取代檔案 -->
                <plugin>
                    <artifactid>maven-antrun-plugin</artifactid>
                    <executions>
                        <execution>
                            <phase>process-resources</phase>
                            <goals>
        <goal>run</goal>
                </goals>
                <configuration>
        <tasks>
            <delete file="${project.build.outputDirectory}/antrun.replace">
            </delete><copy tofile="${project.build.outputDirectory}/antrun.replace" file="${basedir}/src/main/resources/antrun.replace.prod">
        </copy></tasks>
                </configuration>
            </execution>
        </executions>
    </plugin>
            </plugins>
        </build>
    </profile>
</profiles>
</profiles>

再執行 mvn package -Pprod就可以看到結果了