雅加達螞蟻:新一代Java產品生成器
雅加達螞蟻是一種類似于Nmake,專門為爪哇語言設計的產品生成器。
為什么需要雅加達螞蟻
假如你是一個爪哇應用軟件項目的開發部負責人,那么你的任務就是設計軟件,編寫原代碼,并把已初步寫好的爪哇用戶端應用程序編譯部署到用戶端,或是把已寫好的爪哇伺服器端應用程序編譯部署到伺服器端。而這要涉及到以下幾個步驟:
從原代碼庫中取出一套最新版本的原代碼文件,
將原代碼文件編譯為類文件,
將類文件,HTML文件及要用到的圖像文件捆綁成一些jar文件,
將這些jar文件送到一個QA(也即質量保障)目錄或伺服器中,供檢測人員檢測,
將檢測人員檢測出的產品缺陷一一改過,產生更新版的原代碼。
從上的過程周而復始,直到你認為產品質量已達到這一次的產品發布的要求為止.
在這個周而復始的過程中,你可能負責管理一個三個人的小組,一個中小型的項目。這三個人如果有一個人在送出成品的關鍵時刻不在,就會影響到你按時送出成品。這時,你就需要一個事先設置好的自動化的程式,可以使小組中的任何人都可以啟動這個自動化的程式,生成產品。
你也有可能負責管理一個大型項目,包括十幾個分項目,幾十個程序設計人員,幾百個文件。每個月都有新的程序員參加進來, 又有舊的程序員離開公司另謀高就.在這種動態過程中,要做到有條不紊地按時送出成品,你就必須有一個非常理想的產品生成過程,和一個非常理想的產品生成器。
這種產品生成器,就包括大家所熟悉的Make, GNU Make, Nmake, Jam等。Nmake尤其為使用C++作Windows 編程的程序師所熟悉和使用。微軟的Visual C++更提供了makefile的編輯器。
NMake在原則上講,可以用作任何語言,包括爪哇語言的產品生成器。然而,NMake有很多缺點,而且新一代的爪哇語言程序師不一定有Visual C++的經驗,甚至不一定有興趣。在這種情況下,我們很自然地需要有一種和爪哇語言匹配的,彌補了 Nmake的缺陷的,超越操作系統的,專為爪哇語言設計的產品生成器。
Apache.org正在組織開發的,原代碼公開(Open Source)的雅加達螞蟻項目,就是為滿足這一需要而作的一個產品生成器。雅加達螞蟻本身是用純爪哇語言開發的,配置文件采用XML格式。在下一個版本里還可能會提供一個圖形介面來讓用戶更直觀地修改系統的配置。
筆者在本文中,將簡單舉例介紹這一工具軟件的使用.由于大多數用戶可能是在Win32系統上使用這一工具,因此筆者決定以NT機上情況為例加以說明。實際上本文所舉的例子并不難移植到Unix機上。
怎樣安裝雅加達螞蟻
首先,你需要下面列出的軟件:
JDK1.1或更新版.我使用的是JDK1.3版。你可以從升陽公司的網站下載這一軟件(在你這樣做時,請詳細閱讀升陽公司的版權說明)。在后面,我假設計你的JDK安裝在D:JDK1.3。
Jakarta Ant。你可以從apache.org下載這一軟件的編譯版(Binary),而且如果你對原代碼感興趣的話,還可從下載它的原代碼版。(在你這樣做時,請詳細閱讀apache.org的版權說明。)
安裝的過程實際上就是解壓縮的過程。由于我想把它安裝到我的D:驅動器上,在缺省的情況下,1.3版的編譯版和原代碼版會被釋放到D:jakarta-ant-1.3。為了簡單起見,我把目錄改名為D:AntTest。
在D:AntTest目錄下,應當有四個目錄:bin,docs,lib和src。如果你沒有安裝原代碼版,就不會有src。
怎樣修改雅加達螞蟻
你可以修改src目錄中的文件。如果你更改了原代碼,那么編譯它的方法如下。
修改D:AntTestuild.bat文件,在開頭加入以下的語句
SET PATH=D:jdk1.3;D:jdk1.3in; SET JAVA_HOME=D:jdk1.3 SET ANT_HOME=D:AntTestlib SET CLASSPATH=.;%ANT_HOME%ant.jar;%ANT_HOME%jaxp.jar;%ANT_HOME%parser.jar; |
換到D:AntTest目錄下,執行build.bat。
這將產生一個新的目錄D:AntTestuild,其中lib子目錄里面應該有兩個jar文件,它們就是運行的結果。
在下面,我就舉例說明怎樣使用這個功能強大的工具。
一個實例
在D:AntTest目錄下建立一個新的目錄,叫Test,并在它下面建立一些子目錄。關于這些目錄的結構,請見圖1。
![]() 圖1.實例的目錄的結構 |
建立一個爪哇語言類,叫做AntTest.java,其內容如下。
package com.myCompany.myPackage; public class AntTest { public static void main(String[] args) { System.out.println("This is a test."); } } |
顯然,這是一個最簡單的類文件,它屬于一個叫做commyCompanymyPackage的包,大家在路徑結構可以看出,原代碼文件應當在最里層的myPackage目錄下。我們利用這個最簡單的類來演示并不簡單的三件任務:
編譯,這將把包結構復制到你指定的目標目錄下,并把.java文件編譯成.class文件。
在指定的目錄下生成.jar文件。
把.jar文件復制到產品目錄去,以供質量保證人員進行檢測。
怎樣寫雅加達螞蟻的XML生成文件
雅加達螞蟻需要你給它指定運行的規則,這些規則存放在一個叫做build.xml的XML生成文件里。我在 D:AntTestTest下寫這個build.xml文件。
項目
首先,我們需要在XML生成文件的開始給出項目的基本屬性,包括項目名,項目的基目錄名,以及項目的缺省目標名。由于每個XML生成文件只能有一個項目,因此項目名并不重要。項目的基目錄名確定以后,下面再使用相對目錄名就都是基于此基目錄名的。項目的缺省目標名是一個重要的屬性,它指定當用戶不提供要運行的目標時,項目應運行此缺省目標。
<project name="Ant" default="build" basedir="."> |
每個雅加達螞蟻項目都包括若干目標,每個目標相當于一個要干的事情,每個目標又都可以依賴于其它的零個到多個目標。
由于我們有三個事情要做,可以初步確定,我們需要三個目標。但是,通常人們在第一個目標開始之前,先確定一些參數,這樣我們就需要一個在所有三個目標之前的目標。因此,我們實際上需要四個目標。
初始化目標,名為init,給出必要的參量。
編譯目標,名為build,它應當在初始化目標init完成之后執行。
在指定的目錄下生成.jar文件目標,名為jar。它應當在編譯目標build完成之后執行。
把.jar文件復制到產品目錄去的目標,名為copy。,它應當在生成.jar文件目標jar完成之后執行。
目標
那么,怎樣建立一個目標呢?首先讓我們看看第一個目標。
<target name="init"> <property name="jardir" value="jar"/> <property name="Src" value="src"/> <property name="Dest" value="classes"/> </target> |
每個目標必有目標名,這個目標的名字叫init。由于它不依賴于其它的目標,因此它沒有depends屬性。這個目標的任務是建立三個變量, Src, Dest 和 jardir。這三個變量實際上就是源代碼所在的位置,編譯后文件所在的位置,產品所在的位置。我在這里均使用相對路徑,是相對于項目的基路徑而言的。你也可以使用絕對路徑。
現在,我們來看看第二個目標。這個目標的depends屬性是init,意為它必須在第一個目標(名為init)完成之后才能執行。如果本目標還取決于另一個目標(X)的執行,那么depends的值應為"init,X",即中間用逗號隔開。
<target name="build" depends="init"> <javac destdir="${Dest}" debug="on" > <src path="${Src}"/> <include name="**"/> </javac> </target> |
這個目標內部包括有<javac/>標識符,這是一個雅加達螞蟻的系統內定目標,它會啟動爪哇語言的編譯器,編譯處于<src/>所指明的目錄下的所有符合<include/>條件的文件。在<include/>里,*意為所有的文件,而**意為所有的文件和子目錄。至于雅加達螞蟻系統怎樣找到爪哇語言的編譯器,則依賴于一個必須由用戶自已指明的環境變量,JAVA_HOME,請見后面所給出的build.bat文件內容。
在第三個目標內,也使用了一個系統內定目標<jar>。它會啟動爪哇語言的jar生成器,在basedir處運行, 并生成名為jarfile的jar文件。
<target name="jar" depends="build"> <jar jarfile="test.jar" basedir="${Dest}"/> <jar jarfile="testSrc.jar" basedir="${Src}"/> </target> |
在第四個目標內,也使用了一個系統內定目標<copy>。它會把所有在內指明的文件, 也就是在項目的基目錄處的所有jar文件,復制到todir屬性所指明的位置。includes屬性規定在尋找文件時應尋找滿足所指定條件的文件。
<target name="copy" depends="jar"> <copy todir="${jardir}"> <fileset dir="${basedir}" includes="*.jar" /> </copy> </target> |
從上就是所有的四個目標。它們被執行的順序與它們在XML文件里出現的順序并沒有關系,但在這里作者有意做得使它們相同,以使讀者易于讀懂。
完整的生成文件如下:
<?xml version="1.0"?> <project name="Ant" default="build" basedir="."> <target name="init"> <property name="jardir" value="jar"/> <property name="Src" value="src"/> <property name="Dest" value="classes"/> </target> <target name="build" depends="init"> <javac destdir="${Dest}" debug="on"> <src path="${Src}"/> <include name="**"/> </javac> </target> <target name="jar" depends="build"> <jar jarfile="test.jar" basedir="${Dest}"/> <jar jarfile="testSrc.jar" basedir="${Src}"/> </target> <target name="copy" depends="jar"> <copy todir="${jardir}"> <fileset dir="${basedir}" includes="*.jar" /> </copy> </target> </project> |
批處理命令
在執行這一XML生成文件時,我們需要指明一些環境變量,因此作者建議寫一個批處理文件來做這些事。下面就是build.bat的內容。
SET ANT_HOME=D:AntTest SET JAVA_HOME=D:JDK1.3 %ANT_HOME%inant.bat %1 |
這個批處理文件接受一個可有可無的參量,亦即要執行的目標名。如果不提供目標名,系統就會選擇缺省目標。因此發出build的命令只會執行第一個和第二個目標,而build jar會執行第一、二、三個目標。要執行所有的目標需要鍵入build copy指令。
雅加達螞蟻的圖形用戶界面
截至本文成文之日,雅加達螞蟻的圖形用戶界面仍然未正式發行。但是,在它的原代碼版中已經包括了它的圖形用戶界面部分,叫做Antidote。
下面的插圖就是雅加達螞蟻的圖形用戶界面的示范。可以看到,Antidote 可以用來打開一個XML生成文件,瀏覽文件內容,修改文件內容,并且在圖形用戶界面內運行雅加達螞蟻,生成項目成品。圖形用戶界面的下部就可顯示雅加達螞蟻的運行結果。
![]() 圖2.雅加達螞蟻的圖形用戶界面的示范 |
怎樣編譯生成雅加達螞蟻的圖形用戶界面
編譯生成Antidote是一個非常好的練習。如果讀者已經安裝了原代碼版,那么只需要編輯一個XML生成文件即可使用 雅加達螞蟻來生成Antidote。首先,我們需要建立一些環境變量。
在D:AntTestsrcantidote下,建立一個叫build.bat的批處理文件,內容如下。
rem @echo off SET PATH=D:jdk1.3;D:jdk1.3in;C:Winnt4WsSystem32 SET JAVA_HOME=D:jdk1.3 SET ANT_HOME=D:AntTest SET CLASSPATH=.;%ANT_HOME%libant.jar;%ANT_HOME%libjaxp.jar;%ANT_HOME% libparser.jar; call %ANT_HOME%inant.bat -Dant.install="%ANT_HOME%" %1 %2 %3 %4 %5 %6 %7 %8 %9 |
其中,C:Winnt4Ws是我的Windows NT安裝的位置。不指明這個路徑,系統可能找不到xcopy這個外部命令。
在同樣的地方,建立一個叫build.xml的生成文件,內容如下。
<project name="Antidote" default="jar" basedir="."> <!-- Give user a chance to override without editing this file (and without typing -D each time it compiles it) --> <property file="${user.home}/.ant.properties" /> <property name="Name" value="Antidote" /> <property name="name" value="antidote" /> <property name="version" value="0.1" /> <property name="ant.home" value="http://www.zhujiangroad.com/http://www.zhujiangroad.com/" /> <property name="src.etc.dir" value="etc" /> <property name="src.dir" value="." /> <property name="build.dir" value="build/antidote" /> <property name="ant.build.dir" value="build/antidote" /> <property name="lib.dir" value="${ant.build.dir}/lib" /> <property name="build.classes" value="${build.dir}/classes" /> <property name="ant.dist.dir" value="dist/antidote" /> <path id="classpath"> <pathelement location="${lib.dir}/ant.jar" /> </path> <property name="packages" value="org.apache.tools.ant.gui.*" /> <property name="manifest" value="etc/manifest" /> <!-- ================================ --> <!-- Set some the defaults the user can override in .ant.properties --> <!-- ================================= --> <property name="build.compiler" value="modern" /> <property name="build.compiler.emacs" value="on" /> <!-- ===================================== --> <!-- Define a global set of patterns that can be referenced by --> <!-- its id attribute --> <!-- ============================== --> <patternset id="chmod.patterns"> <include name="**/antidote" /> </patternset> <!-- ============================== --> <!-- Prepares the build directory --> <!-- ============================= --> <target name="prepare"> <mkdir dir="${build.dir}" /> <mkdir dir="${ant.build.dir}" /> <tstamp /> </target> <!-- ============================= --> <!-- Compiles the source code --> <!-- ============================== --> <target name="compile" depends="prepare"> <mkdir dir="${build.classes}" /> <javac srcdir="${src.dir}" destdir="${build.classes}" debug="on" deprecation="on" optimize="off"> <classpath refid="classpath" /> </javac> <copy todir="${build.classes}"> <fileset dir="${src.dir}"> <include name="**/*.properties" /> <include name="**/*.gif" /> </fileset> </copy> <filter token="VERSION" value="${version}" /> <filter token="DATE" value="${TODAY}" /> <filter token="TIME" value="${TSTAMP}" /> <copy todir="${build.classes}" overwrite="true" filtering="on"> <fileset dir="${src.dir}"> <include name="**/version.txt" /> </fileset> </copy> </target> <!-- ========================= --> <!-- Creates the jar archive --> <!-- =============================== --> <target name="jar" depends="compile" description="Build a jar file"> <mkdir dir="${lib.dir}" /> <jar jarfile="${lib.dir}/${name}.jar" basedir="${build.classes}" includes="org/**" manifest="${manifest}" /> </target> <!-- ============================== --> <!-- Creates the distribution --> <!-- ========================== --> <target name="dist" depends="jar" description="Creates the distribution"> <mkdir dir="${ant.dist.dir}" /> <mkdir dir="${ant.dist.dir}/lib" /> <mkdir dir="${ant.dist.dir}/src" /> <copy todir="${ant.dist.dir}/src"> <fileset dir="${src.dir}" /> </copy> <copy todir="${ant.dist.dir}/lib"> <fileset dir="${lib.dir}" /> </copy> <copy file="README" tofile="${ant.dist.dir}/README" /> <copy file="WHATSNEW" tofile="${ant.dist.dir}/WHATSNEW" /> <copy file="TODO" tofile="${ant.dist.dir}/TODO" /> <copy file="LICENSE" tofile="${ant.dist.dir}/LICENSE" /> </target> <!-- ============================= --> <!-- Packages the distribution with ZIP --> <!-- ============================ --> <target name="dist-zip" depends="dist"> <zip zipfile="${Name}-${version}.zip" basedir="${ant.dist.dir}" includes="**" /> </target> <!-- ============================= --> <!-- Packages the distribution with TAR-GZIP --> <!-- ===================================== --> <target name="dist-tgz" depends="dist"> <tar tarfile="${Name}-${version}.tar" basedir="${ant.dist.dir}" includes="**" /> <gzip zipfile="${Name}-${version}.tar.gz" src="${Name}-${version}.tar" /> </target> <!-- ======================================== --> <!-- Cleans up generated stuff --> <!-- ======================================== --> <target name="clean"> <delete dir="${build.dir}" /> <delete dir="${ant.dist.dir}" /> </target> <!-- ============================================== --> <!-- Total cleanup --> <!-- ===================================== --> <target name="total-clean" depends="clean"> <delete file="${lib.dir}/${name}.jar" /> <delete file="${Name}-${version}.zip" /> <delete file="${Name}-${version}.tar" /> <delete file="${Name}-${version}.tar.gz" /> </target> </project> |
運行build.bat,然后在build/antidote/子目錄下,找到antidote.jar文件,把它復制到D:AntTestlib處。回到 D:AntTestsrcAntidote,建立一個文件,名為run.bat,內容如下:
SET ANT_HOME=D:AntTest SET JAVA_HOME=D:jdk1.3 SET CLASSPATH=.;%ANT_HOME%libant.jar;%ANT_HOME%lib jaxp.jar;%ANT_HOME%libparser.jar;%ANT_HOME% libantidote.jar; SET PATH=D:jdk1.3;D:jdk1.3in;C:Winnt4WsSystem32 %JAVA_HOME%injava org.apache.tools.ant.gui.Main |
運行run.bat,你就應該看到如圖2所示的antidote圖形界面。