Apache Ant 101:使Java項目生成易如反掌 本教程所講述的內容 |
|
在本教程中,您將學習 Ant 這個 Java TM 項目生成工具。由於其靈活性和易用性,Ant 很快在 Java 開發人員中流行開來,因此您有必要了解關於它的更多信息。
在繼續學習本教程之前,你不需要具備先前的 Ant 經驗或知識。我們將首先查看 Ant 生成文件(build file)的基本結構,並學習如何調用這個工具。我們將逐步完成爲一個簡單 Java 項目編寫生成文件的步驟,然後考察 Ant 的其他一些有用功能,包括文件系統操作和模式匹配。最後編寫一個擴展 Ant 功能的自己的 Java 類來結束本教程。
在學習本教程的過程中,我們將同時展示如何從命令行以及從其他開放源代碼 Eclipse IDE 運行 Ant。試驗本教程中的例子不需要同時具備這兩種環境;您可以選擇其一,甚至選擇某種不同的開發環境,只要該環境支持 Ant。如果選擇從命令行使用 Ant,並且 Ant 還沒有安裝到機器上,您需要遵循 Ant 主頁上的安裝說明(請參閱參考資料以獲得相關鏈接)。相反,如果決定僅使用 Eclipse 環境,您不需要單獨安裝 Ant,因爲 Eclipse 已經包括了它。如果還沒有 Eclipse,您可以從 Eclipse.org(請參閱參考資料)下載 Eclipse。
入門
誰應該學習本教程? |
|
如果您正在編寫 Java 代碼卻還沒有使用 Ant,那麼本教程就是爲您準備的。不管您當前是否在使用某種不同的生成工具,或者根本就沒有使用生成工具,瞭解關於 Ant 的更多知識或許會促使您轉而使用它。
如果已經在使用 Ant,那麼您仍然可能在本教程中發現一些有趣的東西。或許您會發現一些預料之外或無法完全理解的 Ant 行爲;本教程將會幫助您。或者,也許您熟悉 Ant 的基礎,但是還想知道諸如將生成文件鏈接起來、使用 CVS 知識庫或編寫自定義任務等高級主題;本教程將會介紹所有這些主題。
Ant 主要是設計用於生成 Java 項目的,但這並不是它唯一的用途。許多人發現它對其他任務也很有幫助。比如以跨平臺的方式執行文件系統操作。此外,還有許多可用的第三方 Ant 任務,而編寫自定義的 Ant 任務也是相對簡單的,因此很容易針對特定的應用程序定製 Ant。
關於作者 |
|
Matt Chapman 1996 是英國 Hursley 的 IBM Centre for Java Technology 的諮詢軟件工程師。他過去七年來一直致力於 Java 技術,包括 Java 虛擬機實現和各類平臺、用戶界面工具包 Swing 和 AWT,以及近來爲 Eclipse 平臺所編寫的工具。Matt 擁有計算機科學方面的學位,並且還是一名 Sun 認證的 Java 程序員。可通過[email protected]與他聯繫。
Ant基礎
簡介
本節將概述 Ant 的功能和優勢,並討論它的歷史概況和日漸提高的普及性。然後我們通過考察一個最基礎的生成文件的基本結構,直接進入對 Ant 基礎的討論。我們還會介紹 屬性 和 依賴關係 的概念。
Ant 是什麼? |
|
Apache Ant 是一個基於 Java 的生成工具。據最初的創始人 James Duncan Davidson 介紹,這個工具的名稱是 another neat tool(另一個整潔的工具) 的首字母縮寫。
生 成工具在軟件開發中用來將源代碼和其他輸入文件轉換爲可執行文件的形式(也有可能轉換爲可安裝的產品映像形式)。隨着應用程序的生成過程變得更加複雜,確 保在每次生成期間都使用精確相同的生成步驟,同時實現儘可能多的自動化,以便及時產生一致的生成版本,這就變得更加重要了。C 或 C++ 中的傳統項目經常使用 make 工具來做這件事情,其中生成任務是通過調用 shell 命令來執行的,而依賴關係定義在每個生成文件之間,以便它們總是以必需的順序執行。
Ant 與 make 類似,它也定義生成文件之間的依賴關係;然而,與使用特定於平臺的 shell 命令來實現生成過程所不同的是,它使用跨平臺的 Java 類。使用 Ant,您能夠編寫單個生成文件,這個生成文件在任何 Java 平臺上都一致地操作(因爲 Ant 本身也是使用 Java 語言來實現的);這就是 Ant 最大的優勢。
Ant 的其他關鍵優勢包括其突出的簡單性和無縫地使用自定義功能來擴展它的能力。但願您在完成本教程其餘內容的學習之後,會欣賞 Ant 的這些優勢。
Ant 簡史 |
|
Ant 最初是 Tomcat 的一個內部組件,Tomcat 是 Java Servlet 和 JavaServer Pages (JSP) 參考實現中使用的 servlet 容器。Tomcat 代碼基被捐贈給了 Apache 軟件基金會;在那裏它又成了 Apache Jakarta 項目的組成部分,該項目致力於爲 Java 平臺產生開放源代碼的服務器端解決方案。Ant 的有用性很快得到了認可,對它的使用遍佈在其他 Jakarta 子項目中。因而,它自己也成了一個 Jakarta 子項目,其第一個獨立版本於 2000 年 7 月發佈。
從那以後,Ant 的普及性已經不斷地提高。它贏得了無數的行業大獎,併成爲用於生成開放源代碼 Java 項目的 事實上 的標準。2002 年 11 月,這些成功得到了確認,Ant 被提升爲頂級 Apache 項目。
在本文編寫之際,Ant 的當前穩定版本是
Ant 生成文件剖析 |
|
Ant 沒有定義它自己的自定義語法;相反,它的生成文件是用 XML 編寫的(請參閱參考資料)。存在一組 Ant 能夠理解的預定義 XML 元素,而且就像您將在下一節中看到的一樣,還可以定義新的元素來擴展 Ant 的功能。每個生成文件由單個 project 元素組成,該元素又包含一個或多個 target 元素。一個目標(target)是生成過程中已定義的一個步驟,它執行任意數量的操作,比如編譯一組源文件。這些操作本身是由其他專用任務標籤執行的,我們將在後面看到這一點。然後這些任務將根據需要被分組到各個 target 元素中。一次生成過程所必需的所有操作可以放入單個 target 元素中,但是那樣會降低靈活性。將那些操作劃分爲邏輯生成步驟,每個步驟包含在它自己的 target 元素中,這樣通常更爲可取。這樣可以執行整體生成過程的單獨部分,卻不一定要執行其他部分。例如,通過僅調用某些目標,您可以編譯項目的源代碼,卻不必創建可安裝的項目映像。
頂級 project 元素需要包含一個 default 屬性,如果在 Ant 被調用時沒有指定目標,這個屬性將指定要執行的目標。然後需要使用 target 元素來定義該目標本身。下面是一個最基本的生成文件:
運行Ant
簡介 |
|
Apache Ant 可通過各種不同的方式來調用。就其本身而言,Ant 是一個命令行形式的工具,通常從 UNIX 或 Linux shell 提示符或者 Windows 命令提示符調用,生成文件則使用您自己選擇的文本編輯器來編寫。如果要生成的項目是以這種方式開發的,那麼這樣調用 Ant 很好,但是許多人發現 IDE 更方便。大多數 IDE 都對 Ant 提供了某種程度的支持,因此在使用 IDE 的情況下,最起碼,您不必麻煩地離開 IDE 來執行命令行操作就能調用 Ant 生成任務。
在本節中,我們將考察如何從命令行使用 Ant,並瞭解一些有用的命令行選項。 然後簡要了解一下開放源代碼的 Eclipse 平臺提供的 Ant 支持。(爲了最充分地利用下面這些小節講述的內容,您至少應該被動地熟悉 Eclipse。)
從命令行運行 Ant |
|
從命令提示符調用 Ant 可以簡單得只需鍵入單獨的 ant。如果您這樣做,Ant 將使用默認的生成文件;該生成文件中指定的默認目標就是 Ant 嘗試要生成的目標。還可以指定許多命令行選項,後面跟着任意數量的生成目標,Ant 將按順序生成這其中的每個目標,並在此過程中解決所有依賴關係。
下面是從命令行執行的 Ant 生成任務的一些典型輸出:
Buildfile: build.xml
init:
[mkdir] Created dir: E:/tutorials/ant/example/build
[mkdir] Created dir: E:/tutorials/ant/example/dist
compile:
[javac] Compiling 8 source files to E:/tutorials/ant/example/build
dist:
[jar] Building jar: E:/tutorials/ant/example/dist/example.jar
BUILD SUCCESSFUL
Total time: 2 seconds
隨着我們繼續本教程的學習,我們將弄明白所有這些輸出意味着什麼。
命令行選項 |
|
就像 make 工具默認情況下尋找一個名爲 makefile 的生成文件一樣,Ant 尋找一個名爲 build.xml 的文件。因此,如果您的生成文件使用這個名稱,就不需要在命令行指定它。當然,有時使用具有其他名稱的生成文件更方便,在那樣的情況下,您需要對 Ant 使用 -buildfile <file> 參數(-f <file> 是其簡寫形式)。
另一個有用的選項是 -D,它用於設置隨後可以在生成文件中使用的屬性。這對於配置您想要以某種方式開始的生成過程是非常有用的。例如,爲了將 name 屬性設置爲某個特定的值,您會使用一個類似下面這樣的選項:
-Dmetal=beryllium
這個功能可用於覆蓋生成文件中的初始屬性設置。正如前面指出過的,屬性的值一經設置就不能改變。-D 標誌在讀取生成文件中的任何信息之前設置某個屬性;由於生成文件中的指派落在這個初始指派之後,因此它不會改變其值。
IDE 集成 |
|
由於 Ant 的普及性,大多數現代 IDE 現在都集成了對它的支持,其他許多 IDE 則在插件提供對它的支持。受支持的環境列表包括 JEdit 和 Jext 編輯器、Borland JBuilder、IntelliJ IDEA、Java Development Environment for Emacs (JDEE)、NetBeans IDE、Eclipse 以及 WebSphere? Studio Application Developer。
請參閱參考資料以瞭解關於 Ant 的廣泛 IDE 支持的更多信息。在本節的其餘部分,我們將探討開放源代碼的 Eclipse 環境所包括的 Ant 支持。
Eclipse 對 Ant 的支持 |
|
開放源代碼的 Eclipse 項目提供了對 Ant 的大量支持。 這些支持的核心是 Eclipse 的 Ant 編輯器,它以語法高亮顯示爲特色。該編輯器圖示如下:
這個編輯器提供內容輔助 ―― 例如,鍵入 <pro 然後按 Ctrl-Space,這樣將會顯示一個自動完成列表,其中包含 <property> 標籤,以及對該任務的簡要說明。
您還可以看到一個大綱視圖,它顯示生成文件的結構,並提供在該文件中導航的便捷方式。此外還有一個 Ant 視圖,它允許根據許多不同的 Ant 文件生成目標。
從 Eclipse 內運行 Ant 生成任務 |
|
名爲 build.xml 的文件在 Eclipse 的導航程序視圖中使用一個 Ant 圖標來標識和裝飾。(如果您的生成文件具有不同的名稱,請參閱Eclipse 中的文件關聯。)右鍵單擊這些文件會提供一個 Run Ant... 菜單選項,選擇這個菜單選項將打開一個類似如下的對話框:
來自該生成文件的所有目標都顯示出來了,而默認的目標則處於選中狀態。在您決定是否要改變默認目標之後,請按 Run 按鈕來運行 Ant。Eclipse 將切換到 Console 視圖,如下圖所示。錯誤將以不同的顏色顯示出來,可以單擊輸出中的任務名稱來跳到生成文件中的對應調用點。
Eclipse 中的文件關聯 |
|
默認情況下,Eclipse 僅對名爲 build.xml 的文件使用 Ant 編輯器,不過可以容易地配置該編輯器,使其識別具有其他名稱的文件。從菜單上選擇 Window=>Preferences,然後展開Workbench 組,再選擇 File Associations 參數設置頁面。然後爲預期的文件名添加一種新的文件類型。例如,可以爲名爲 mybuild.xml 的所有文件添加一種新的文件類型。如果想對具有 .xml 後綴的所有文件(特殊文件名除外,比如 plugin.xml,它在 Eclipse 中覆蓋通配符指定)做同樣的事情,您甚至可以使用 *.xml。 最後爲這種新的文件類型添加一個關聯的編輯器,然後從編輯器列表上選擇 Ant editor,如下所示:
生成一個簡單Java項目
簡介 |
|
現在我們已經清楚了 Ant 生成文件的格式,並瞭解瞭如何定義屬性和依賴關係以及如何運行 Ant,下面可以開始爲一個基本的 Java 項目構建一個生成環境了。這將包括學習用於編譯源代碼和組合 JAR 文件的 Ant 任務。
編譯源代碼 |
|
由於 Ant 的主要目標是生成 Java 應用程序,它能夠內在地、出色地支持調用 javac 編譯器以及其他 Java 相關任務就毫不奇怪了。下面是編譯 Java 代碼的任務的編寫方式:
<javac srcdir="src"/>
這個標籤尋找 src 目錄中以 .java 爲擴展名的所有文件,並對它們調用 javac 編譯器,從而在相同的目錄中生成類文件。當然,將類文件放在一個單獨的目錄結構中通常會更清晰;可以通過添加 destdir 屬性來讓 Ant 做到這點。其他有用的屬性包括:
- classpath:等價於 javac 的 -classpath 選項。
- debug="true":指示編譯器應該帶調試信息編譯源文件。
javac 任務的一個重要特點在於,它僅編譯那些它認爲需要編譯的源文件。如果某個類文件已經存在,並且對應的源文件自從該類文件生成以來還沒有改變過,那麼該源文件就不會被重新編譯。javac 任務的輸出顯示了實際被編譯的源文件的數目。編寫一個 clean 目標來從目標目錄移除生成的任何類文件是個很好的習慣。如果想要確保所有源文件都已編譯,就可以使用這個任務。這種行爲刻畫了 Ant 的許多任務的特點:如果某個任務能夠確定所請求的操作不需要執行,那麼該操作就會被跳過。
像 Ant 一樣,javac 編譯器本身也是用 Java 語言實現的。這對 Ant 中的 javac 任務的使用來說非常有利,因爲它通常調用 Ant 運行所在的相同 Java 虛擬機(JVM)中的編譯器類。在每次需要編譯 Java 代碼時,其他生成工具通常需要運行一個新的 javac 進程,從而需要一個新的 JVM 實例。但是在使用 Ant 的情況下,只需要單個 JVM 實例,它既用於運行 Ant 本身,也用於執行所有必需的編譯任務(以及其他相關任務,比如處理 JAR 文件)。這是一種高效得多的資源使用方式,能夠極大地縮短項目生成時間。
編譯器選項 |
|
正如我們從前一小節看到的,Ant 的 javac 任務的默認行爲是調用運行 Ant 本身的任何 JVM 的標準編譯器。然而,有時您可能想要單獨地調用編譯器 ―― 例如當你希望指定編譯器的某些內存選項,或者需要使用一種不同級別的編譯器的時候。爲實現這個目的,只需將 javac 的 fork 屬性設置爲 true,比如像下面這樣:
<javac srcdir="src" fork="true"/>
如果想要指定一個不同的 javac 可執行文件,並向它傳遞一個最大內存設置,您可以像下面這樣做:
<javac srcdir="src" fork="true" executable="d:/sdk141/bin/javac"
memoryMaximumSize="
甚至可以將 Ant 配置爲使用某種不同的編譯器。受支持的編譯器包括開放源代碼的 Jikes 編譯器和來自 GNU 編譯器集(GNU Compiler Collection,GCC)的 GCI 編譯器。(請參閱參考資料以瞭解關於這兩種編譯器的更多信息。)可以通過兩種方式指定這些編譯器:可以設置 build.compiler 屬性,這將應用於使用 javac 任務的所有場合;或根據需要設置每個javac 任務中的 compiler 屬性。
javac 任務還支持其他許多選項。請參考 Ant 手冊以瞭解更多細節(請參閱參考資料)。
創建 JAR 文件 |
|
在編譯 Java 源文件之後,結果類文件通常被打包到一個 JAR 文件中,這個文件類似 zip 歸檔文件。每個 JAR 文件都包含一個清單文件,它可以指定該 JAR 文件的屬性。
下面是 Ant 中 jar 任務的一個簡單使用例子:
<jar destfile="package.jar" basedir="classes"/>
這將創建一個名爲 package.jar 的 JAR 文件,並把 classes 目錄中的所有文件添加到其中(JAR 文件能夠包含任意類型的文件,而不只是類文件)。此處沒有指定清單文件,因此 Ant 將提供一個基本的清單文件。
manifest 屬性允許指定一個用作該 JAR 文件的清單的文件。清單文件的內容還可以使用 manifest 任務在生成文件中指定。這個任務能夠像文件系統寫入一個清單文件,或者能夠實際嵌套在 jar 之內,以便一次性地創建清單文件和 JAR 文件。 例如:
<jar destfile="package.jar" basedir="classes">
<manifest>
<attribute name="Built-By" value="${user.name}"/>
<attribute name="Main-class" value="package.Main"/>
</manifest>
</jar>
時間戳生成 |
|
在生成環境中使用當前時間和日期,以某種方式標記某個生成任務的輸出,以便記錄它是何時生成的,這經常是可取的。這可能涉及編輯一個文件,以便插入一個字符串來指定日期和時間,或將這個信息合併到 JAR 或 zip 文件的文件名中。
這種需要是通過簡單但是非常有用的 tstamp 任務來解決的。這個任務通常在某次生成過程開始時調用,比如在一個 init 目標中。這個任務不需要屬性,許多情況下只需 <tstamp/> 就足夠了。
tstamp 不產生任何輸出;相反,它根據當前系統時間和日期設置 Ant 屬性。下面是 tstamp 設置的一些屬性、對每個屬性的說明,以及這些屬性可被設置到的值的例子:
屬性 |
說明 |
例子 |
DSTAMP |
設置爲當前日期,默認格式爲yyyymmdd |
20031217 |
TSTAMP |
設置爲當前時間,默認格式爲 hhmm |
1603 |
TODAY |
設置爲當前日期,帶完整的月份 |
|
例如,在前一小節中,我們按如下方式創建了一個 JAR 文件:
<jar destfile="package.jar" basedir="classes"/>
在調用 tstamp 任務之後,我們能夠根據日期命名該 JAR 文件,如下所示:
<jar destfile="package-${DSTAMP}.jar" basedir="classes"/>
因此,如果這個任務在 2003 年 12 月 17 日調用,該 JAR 文件將被命名爲 package-20031217.jar。
還可以配置 tstamp 任務來設置不同的屬性,應用一個當前時間之前或之後的時間偏移,或以不同的方式格式化該字符串。所有這些都是使用一個嵌套的 format 元素來完成的,如下所示:
<tstamp>
<format property="OFFSET_TIME"
pattern="HH:mm:ss"
offset="10" unit="minute"/>
</tstamp>
上面的清單將 OFFSET_TIME 屬性設置爲距離當前時間 10 分鐘之後的小時數、分鐘數和秒數。
用於定義格式字符串的字符與 java.text.SimpleDateFormat 類所定義的那些格式字符相同。
綜合 |
|
前面幾小節爲我們提供了生成簡單 Java 項目所需的足夠知識。下面將把這些代碼片斷組合成一個完整的生成文件,它將編譯 src 目錄下的所有源代碼,將結果類文件放在 build 目錄下,然後把所有類文件打包到 dist 目錄中的一個 JAR 文件中。要自己試驗這個生成文件,您所需要的就是包含一個或多個 Java 源代碼文件的 src 目錄 ―― 這個目錄可以包含從簡單的“Hell World”程序到來自某個現有項目的大量源文件的任何內容。如果需要向 Java classpath 添加 JAR 文件或其他任何內容,以便成功地編譯源代碼,您只需在 javac 任務中爲其添加一個 classpath 屬性。
該生成文件看起來如下:
<?xml version="1.0"?>
<project default="dist" name="Project Argon">
<description>A simple Java project</description>
<property name="srcDir" location="src"/>
<property name="buildDir" location="build"/>
<property name="distDir" location="dist"/>
<target name="init">
<tstamp/>
<mkdir dir="${buildDir}"/>
<mkdir dir="${distDir}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${srcDir}" destdir="${buildDir}"/>
</target>
<target name="dist" depends="compile">
<jar destfile="${distDir}/package-${DSTAMP}.jar" basedir="${buildDir}">
<manifest>
<attribute name="Built-By" value="${user.name}"/>
<attribute name="Main-Class" value="package.Main"/>
</manifest>
</jar>
<jar destfile="${distDir}/package-src-${DSTAMP}.jar" basedir="${srcDir}"/>
</target>
<target name="clean">
<delete dir="${buildDir}"/>
<delete dir="${distDir}"/>
</target>
</project>
下面是使用該文件執行的某次生成過程的示例輸出(您得到的輸出可能不一樣,具體取決於 src 目錄的內容):
Buildfile: build.xml
init:
[mkdir] Created dir: E:/tutorial/javaexample/build
[mkdir] Created dir: E:/tutorial/javaexample/dist
compile:
[javac] Compiling 10 source files to E:/tutorial/javaexample/build
dist:
[jar] Building jar: E:/tutorial/javaexample/dist/package-20031217.jar
[jar] Building jar: E:/tutorial/javaexample/dist/package-src-20031217.jar
BUILD SUCCESSFUL
Total time: 5 seconds
注意 JAR 文件是根據當前日期來命名的,並且爲應用程序的主類設置了一個清單條目,以便主類能夠通過一個簡單的命令 java -jar package-20031217.jar 來直接運行。我們還創建了一個 JAR 文件,它僅包含項目的源代碼。
文件系統操作
簡介
我們瞭解了關於 Ant 的足夠多的知識,現在能夠生成一個基本的 Java 項目了,不過現實中的項目當然很少像我們的例子那樣簡單。在下面幾節中,我們將考察 Ant 的許多附加功能中的一部分,以及能夠使用它們的場合。
在本節中,我們將考察如何執行常見文件操作,比如創建目錄和解壓縮文件。 Ant 的優秀特性之一在於,執行這些操作的任務一般在所有平臺上都是相同的。
創建和刪除目錄 |
|
最基本的文件系統操作之一就是創建目錄或文件夾。做這項工作的任務名爲 mkdir,毫不奇怪,它非常類似於具有相同名稱的 Windows 和 UNIX/Linux 命令。
<mkdir dir="archive/metals/zinc"/>
首先要注意 / 被用作目錄分隔符,這是 UNIX 和 Linux 的慣例。您可能認爲這不是很平臺無關的,但是 Ant 知道如何處理它,並針對它運行所在的平臺做恰當的事情,這與我們在前面定義基於位置的屬性時所看到的方式相同。我們能夠同樣容易地使用 /,而不管平臺是什麼 ―― Ant 能夠處理任一種形式,甚至能夠處理兩種形式的混合。
mkdir 任務的另一個有用特性是它的如下能力:在父目錄還不存在時創建它們。考慮一下上面的清單,設想 archive 目錄存在,但是 metals 目錄不存在。如果使用底層平臺的 mkdir 命令,您需要首先顯式地創建 metals 目錄,然後第二次調用 mkdir 命令來創建 zinc 目錄。但是 Ant 任務比這更加智能,它能夠一次性創建這兩個目錄。類似地,如果目標目錄已經存在,mkdir 任務不會發出錯誤消息,而只是假設它的工作已經完成,從而什麼也不做。
刪除目錄同樣也很容易:
<delete dir="archive/metals/zinc"/>
這將刪除指定的目錄連同它包含的所有文件以及子目錄。使用 file 屬性而不是 dir 屬性可以指定要刪除的單個文件。
複製和移動文件及目錄 |
|
在 Ant 中製作文件的一份拷貝很簡單。例如:
<copy file="src/Test.java" tofile="src/TestCopy.java"/>
您還可以使用 move 來執行重命名操作而不是拷貝文件:
<move file="src/Test.java" tofile="src/TestCopy.java"/>
另一個常用的文件系統操作是將文件複製或移動到另一個目錄。做這項工作的 Ant 語法同樣也很簡單:
<copy file="src/Test.java" todir="archive"/>
<move file="src/Test.java" todir="archive"/>
默認情況下,Ant 僅輸出它執行的移動和複製操作的摘要,包括諸如已移動或複製的文件的數量等信息。如果想看到更詳細的信息,包括涉及的文件名稱等,您可以將 verbose 屬性設置爲true。
創建和解壓縮 zip 及 tar 文件 |
|
在前一節中,我們看到了如何創建 JAR 文件。創建其他歸檔文件的過程幾乎完全相同。下面是創建 zip 文件的 Ant 任務:
<zip destfile="output.zip" basedir="output"/>
相同的語法也可用於創建 tar 文件。 還可以使用 GZip 和 BZip 任務來壓縮文件。例如:
<gzip src="/developerWorks/cn/education/java/j-apant/tutorial/output.tar" zipfile="output.tar.gz"/>
解壓縮和提取文件同樣也很簡單:
<unzip src="/developerWorks/cn/education/java/j-apant/tutorial/output.tar.gz" dest="extractDir"/>
還可以包括 overwrite 屬性來控制覆蓋行爲。默認設置是覆蓋與正在被提取的歸檔文件中的條目相匹配的所有現有文件。相關的任務名稱是 untar、unjar、gunzip 和 bunzip2。
替換文件中的標記 |
|
我們將在本節考察的最後一個文件系統操作是 replace 任務,它執行文件中的查找和替換操作。token 屬性指定要查找的字符串,value 屬性指定一個新的字符串,查找到的標記字符串的所有實例都被替換爲這個新的字符串。例如:
<replace file="input.txt" token="old" value="new"/>
替換操作將在文件本身之內的適當位置進行。爲了提供更詳細的輸出,可把 summary 屬性設置爲 true。這將導致該任務輸出找到和替換的標記字符串實例的數目。
其他有用的任務和技術
簡介 |
|
在考察自定義的任務之前,我們首先介紹一些還沒遇到過的有用功能。Ant 標準地附帶了大量的功能,因此這裏僅經挑選其中幾個最有用的功能。模式匹配和文件選擇器是功能強大的機制,它們極大地增強了我們已看到過的一些任務的功能;將生成任務鏈接起來以及與 CVS 知識庫協同工作,是已發現的這些機制的兩個主要實際應用領域。
模式匹配 |
|
在前面考察文件系統任務時,我們僅使用了單獨地命名的文件和目錄。然而,一次對一組文件執行那些操作經常是有用的 ―― 例如對給定目錄中以 .java 結尾的所有文件執行操作。正如等價的 DOS 和 UNIX 命令提供了這樣的功能一樣,Ant 也提供了這樣的功能。這是使用通配符字符來完成的:*,它匹配零個或多個字符;以及 ?,它僅匹配一個字符。因而匹配以 .java 結尾的所有文件的模式不過就是 *.java。
也可以對目錄執行模式匹配。例如,模式 src*/*.java 將匹配帶 src 前綴的任何目錄中的所有 Java 文件。 還有另一種模式結構:**,它匹配任意數量的目錄。例如,模式 **/*.java 將匹配當前目錄結構下的所有 Java 文件。
您能夠以相當一致的方式對文件系統任務使用模式,比如嵌套的 fileset 元素。先前,我們使用這個任務來複制單個文件:
<copy file="src/Test.java" todir="archive"/>
如果我們想要使用一個模式,可以將 file 屬性替換爲一個 fileset 元素,如下所示:
<copy todir="archive">
<fileset dir="src">
<include name="*.java"/>
</fileset>
</copy>
fileset 默認情況下包含指定 src 目錄下的所有文件,因此爲了僅選擇 Java 文件,我們對模式使用一個 include 元素。類似地,我們可以對另一個模式添加一個 exclude 元素,從而潛在地排除include指定的匹配項。甚至可以指定多個include 和 exclude 元素;這樣將得到一組文件和目錄,它們包含 include 模式的所有匹配項的並集,但排除了 exclude 模式的所有匹配項。
注意還有一個通常很有用的文件集特性,但是對於沒有意識到它的人來說,這個特性偶爾會產生混淆。這個特性稱爲 默認排除:即自動從文件集內容中排除的內置模式列表。該列表包括與名爲 CVS 的目錄相匹配的條目,以及以 ~ 字符結尾的文件,它們可能是備份文件。您通常不想在文件系統操作中包括這類文件和目錄,因此排除這些文件是默認行爲。然而,如果確實想無例外地選擇 所有 文件和目錄,可以將文件集的 defaultexcludes 屬性設置爲 no。
使用選擇器 |
|
正如我們已經看到的,文件集用於指定一組文件,並且這個組的內容可以使用 include 和 exclude 模式來指定。也可以結合稱爲 選擇器 的特殊元素使用include 和 exclude 來選擇文件。下面是對 Ant 可用的核心選擇器的列表:
- size:這個選擇器用於根據文件的字節大小選擇文件(除非使用 units 屬性來指定了不同的單位)。when 屬性用於設置比較的性質(less、more 或者 equal),value 屬性定義每個文件將與之作比較的目標大小。
- contains:只有包含給定文本字符串(由text 屬性指定)的文件才匹配這個選擇器。默認情況下,查找操作是大小寫敏感的;添加casesensitive="no" 可以改變默認設置。
- filename:name 屬性指定文件名要與之匹配的模式。它本質上與 include 元素相同,以及與指定了negate="yes" 時的 exclude 元素相同。
- present:從當前目錄結構中選擇如下文件:它們與指定的 targetdir 目錄中的文件具有相同的名稱和相對目錄結構。
- depend:這個選擇器與 present 選擇器具有相同的效果,只不過匹配的文件被限制到相對於 targetdir 位置中的對應文件來說,最近已修改過的那些文件。
- date:這個選擇器基於其最後修改日期選擇文件。when 屬性指定作比較的性質是 before、after 還是 equal,datetime 屬性指定與之作比較的日期和時間,這個日期和時間具有給定的固定格式 MM/DD/YYYY HH:MM AM_or_PM。注意 Windows 平臺上有一個內置的 2 秒偏移,以允許底層文件系統的不精確性 ―― 這可能導致匹配的文件數量超過預期。允許的迴旋時間量可以使用 granularity 屬性來更改(以毫秒爲單位來指定)。
- depth:這個選擇器檢查每個文件的目錄結構層次數目。min 和/或 max 屬性用於選擇具有想要的目錄層次數目的的文件。
還可以通過在一個選擇器 容器 內嵌套一個或多個選擇器來組合選擇器。 最常用的選擇器容器 and 僅選擇它包含的所有選擇器都選擇了的文件。其他選擇其容器包括 or、not、none 和 majority。
下面是一個文件集的例子,它僅選擇那些大於 512 字節並且包含字符串“hello”的文件。
<fileset dir="dir">
<and>
<contains text="hello"/>
<size value="512" when="more"/>
</and>
</fileset>
將生成文件鏈接起來 |
|
有兩種生成大型項目的不同方法。一種是讓一個單一的生成文件做所有事情;另一種是讓高級別的生成文件調用其它生成文件以執行特定任務,從而將生成過程劃分爲許多較小的部分。
使用 ant 任務來從一個 Ant 生成中調用另一個 Ant 生成是很容易的。在簡單的情況下,您可以使用 antfile 屬性,僅指定那些要使用的生成文件,Ant 將生成該生成文件中的默認目標。例如:
<ant antfile="sub-build.xml"/>
在父生成文件中定義的任何屬性默認將傳遞給子生成文件,雖然這可以通過指定 inheritAll="false"來避免。通過使用 property 元素來傳入顯式的屬性也是可以做到的 ―― 即使將 inheritAll 設置爲 false,這些屬性也仍然適用於子生成文件。這個功能很適合用於給子生成文件傳入參數。
讓我們來考慮一個例子。下面是我們想要調用的一個生成文件:
<?xml version="1.0"?>
<project default="showMessage">
<target name="showMessage">
<echo message="Message=${message}"/>
</target>
</project>
(我們在前面還沒有遇到過 echo 任務 ―― 它簡單地輸出給定的消息。)
下面是調用第一個生成文件的第二生成文件,它還給第一個生成文件傳入 message 屬性:
<?xml version="1.0"?>
<project default="callSub">
<target name="callSub">
<ant antfile="sub.xml" target="showMessage" inheritAll="false">
<property name="message" value="Hello from parent build"/>
</ant>
</target>
</project>
運行第二個生成文件所得到的輸出如下:
Buildfile: build.xml
callSub:
showMessage:
[echo] Message=Hello from parent build
BUILD SUCCESSFUL
Total time: 0 seconds
使用 CVS 知識庫 |
|
CVS 是 concurrent versions system(併發版本控制系統) 的縮寫。它是一個源代碼控制系統,設計用於跟蹤許多不同開發人員做出的更改。它非常流行,在開放源代碼項目中特別受歡迎。Ant 提供了與 CVS 的緊密集成。這對於自動化生成環境是非常有用的,因爲單個生成文件也可以從源代碼知識庫中提取出一個或多個模塊,生成項目,甚至基於自從前次執行生成以來所作的變更生成批處理文件。
注意,爲了利用 Ant 中的 cvs 任務,您需要在機器上安裝 cvs 命令,並使其從命令行可用。這個命令包括在大多數 Linux 發行套件中;它也以多種形式對 Windows 可用 ―― 例如作爲寶貴的 Cygwin 環境的一部分。(請參閱參考資料以瞭解關於 Cygwin 的更多信息。)
下面是從 CVS 知識庫提取模塊的一個例子生成文件:
<?xml version="1.0"?>
<project name="CVS Extract" default="extract" basedir=".">
<property name="cvsRoot" value=":pserver:[email protected]:/home/eclipse"/>
<target name="extract">
<cvs cvsRoot="${cvsRoot}"
package="org.eclipse.swt.examples"
dest="${basedir}"/>
</target>
</project>
cvs 任務的主要屬性是 cvsRoot,它是對 CVS 知識庫的完整引用,包括連接方法和用戶詳細信息。這個參數的格式如下:
[:method:][[user][:password]@]hostname[:[port]]/path/to/repository
在上面的例子中,我們作爲匿名用戶連接到 Eclipse 項目的中央知識庫。然後其他屬性指定了我們希望提取的模塊以及放置提取文件的目的地。提取是 CVS 任務的默認操作;其他操作可通過使用 command 屬性來指定。
請參閱參考資料以瞭解關於 CVS 的更多信息。
使用自定義任務來擴展Ant
簡介 |
|
正如我們從前述幾節中所看到的,Ant 非常強大,具有涵蓋廣泛功能集的許多核心任務。它還有許多這裏沒有介紹的附加任務,再加上提供廣泛附加功能的許多可選任務,以及作爲 Ant-Contrib 項目的一部分來提供的其他任務;最後,Apache Ant 主頁上還列出了外部可用的更多任務。面對 Ant 提供的所有這些任務,您似乎再也不需要其他任務了,但是 Ant 的真正力量在於它的易於擴展性。事實上,恰恰正是這種可擴展性促使人們開發瞭如此多的附加任務。
可能會存在這樣的場合,在那樣的場合下創建自定義的任務更爲合適。例如,假設您創建了一個命令行工具來執行某個特定操作;這個工具可能是將對 Ant 可用的任務的恰當候選者(當該工具是用 Java 語言編寫的時更是這樣,雖然該工具不一定是用 Java 語言編寫的。)與其讓 Ant 使用 exec 任務外部地調用該工具(這樣將引入依賴關係,並使得生成文件在跨越不同平臺時更難於使用),您可以將它直接合併到生成文件中。還可以使得 Ant 的常規文件集和通配符匹配功能對自定義的任務可用。
在本節中,我們將考察一個簡單自定義任務的構造過程。這個任務將對文件中的行執行排序操作,並將排序後的行集寫到一個新文件中。
創建自定義的任務 |
|
爲實現一個簡單的自定義任務,我們所需做的就是擴展 org.apache.tools.ant.Task 類,並重寫 execute() 方法。因此,作爲這個文件排序自定義任務的框架,我們將編寫如下代碼:
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
public class FileSorter extends Task {
// The method executing the task
public void execute() throws BuildException {}
}
注意我們聲明 execute() 方法拋出一個 BuildException 異常。如果這個任務出了任何錯誤,我們將拋出這個異常以便向 Ant 指出故障。
大多數任務,不管是核心任務還是自定義任務,都利用屬性來控制它們的行爲。對於這個簡單任務,我們需要一個屬性來指定要排序的文件,需要另一個屬性來指定排序內容的輸出。我們把這兩個屬性分別叫做 file 和 tofile。
Ant 使得支持自定義任務中的屬性非常容易。爲此,我們只需實現一個具有特別格式化的名稱的方法,Ant 能夠使用生成文件中指定的對應屬性的值來調用這個方法。這個方法的名稱需要是 set 加上屬性的名稱,因此在這個例子中,我們需要名爲 setFile() 和 setTofile() 的方法。當 Ant 遇到生成文件中的一個屬性設置時,它會尋找相關任務中具有適當名稱的方法(稱爲 setter 方法)。
生成文件中的屬性是作爲字符串來指定的,因此我們的 setter 方法的參數可以是一個字符串。在這樣的情況下,Ant 將在展開值所引用的任何屬性之後,使用該屬性的字符串值來調用我們的方法。但有時我們想把屬性的值看作是一種不同的類型。這裏的示例任務就是這種情況,其中的屬性值引用文件系統上的文件,而不只是引用任意的字符串。可以通過將方法參數聲明爲 java.io.File 類型來容易地做到這點。Ant 將接受屬性的字符串值,並把它解釋爲一個文件,然後傳遞給我們的方法。如果文件是使用相對路徑名稱來指定的,則會被轉換爲相對於項目基目錄的絕對路徑。Ant 能夠對其他類型執行類似的轉換,比如 boolean 和 int 類型。如果您提供具有相同名稱但是具有不同參數的兩個方法,Ant 將使用更明確的那一個方法,因此文件類型將優先於字符串類型。
這個自定義任務需要的兩個 setter 方法類似如下:
// The setter for the "file" attribute
public void setFile(File file) {}
// The setter for the "tofile" attribute
public void setTofile(File tofile) {}
實現自定義的任務 |
|
使用前一小節開發的框架,現在我們能夠完成這個簡單的文件排序任務的實現:
import java.io.*;
import java.util.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* A simple example task to sort a file
*/
public class FileSorter extends Task {
private File file, tofile;
// The method executing the task
public void execute() throws BuildException {
System.out.println("Sorting file="+file);
try {
BufferedReader from =
new BufferedReader(new FileReader(file));
BufferedWriter to =
new BufferedWriter(new FileWriter(tofile));
List allLines = new ArrayList();
// read in the input file
String line = from.readLine();
while (line != null) {
allLines.add(line);
line = from.readLine();
}
from.close();
// sort the list
Collections.sort(allLines);
// write out the sorted list
for (ListIterator i=allLines.listIterator(); i.hasNext(); ) {
String s = (String)i.next();
to.write(s); to.newLine();
}
to.close();
} catch (FileNotFoundException e) {
throw new BuildException(e);
} catch (IOException e) {
throw new BuildException(e);
}
}
// The setter for the "file" attribute
public void setFile(File file) {
this.file = file;
}
// The setter for the "tofile" attribute
public void setTofile(File tofile) {
this.tofile = tofile;
}
}
兩個 setter 方法簡單地對屬性的值排序,以便這些值能夠在 execute() 方法中使用。這裏,輸入文件被逐行地讀入一個列表中,然後被排序並逐行地輸出到輸出文件。注意,爲簡單起見,我們很少執行錯誤檢查 ―― 例如,我們甚至沒有檢查生成文件是否設置了必需的屬性。不過我們的確至少捕捉了所執行的操作拋出的 I/O 異常,並將這些異常作爲 BuildExceptions 重新拋出。
現在可以使用 javac 編譯器或從某個 IDE 內編譯這個自定義的任務。爲了解決所使用的 Ant 類的引用問題,您需要把 ant.jar 文件的位置添加到 classpath 中。這個文件應該在 Ant 安裝路徑下的 lib 目錄。
使用自定義的任務 |
|
現在我們已經開發和編譯了這個自定義的任務,下面可以從生成文件中利用它了。
在能夠調用自定義的任務之前,我們需要給它指定一個名稱來 定義 它,並告訴 Ant 關於實現這個任務的類文件的信息,以及定位該類文件所必需的任何 classpath 設置。這是使用 taskdef 任務來完成的,如下所示:
<taskdef name="filesorter"
classname="FileSorter"
classpath="."/>
大功告成!現在可以像使用 Ant 的核心任務一樣使用這個自定義的任務了。下面是一個完整的生成文件,它顯示了這個自定義任務的定義和用法:
<?xml version="1.0"?>
<project name="CustomTaskExample" default="main" basedir=".">
<taskdef name="filesorter"
classname="FileSorter
classpath="."/>
<target name="main">
<filesorter file="input.txt" tofile="output.txt"/>
</target>
</project>
現在在當前工作目錄中創建一個 input.txt 文件來測試這個自定義的任務。例如:
Hello there
This is a line
And here is another one
下面是運行上面的生成文件之後產生的控制檯輸出:
Buildfile: build.xml
main:
[filesorter] Sorting file=E:/tutorial/custom/input.txt
BUILD SUCCESSFUL
Total time: 0 seconds
注意 input.txt 的相對路徑名稱被轉換成了當前目錄中的一個絕對路徑名稱。這是因爲我們將 setter 方法的參數指定爲 java.io.File 類型而不是 java.lang.String 類型。
現在看一下這個任務實際是否能工作。這時應該已經在同一目錄中創建了名爲 output.txt 的文件,它包含以下內容:
And here is another one
Hello there
This is a line
您可以嘗試指定一個不存在的輸入文件,以確定該任務是如何向 Ant 報告 “file not found”異常的。
祝賀您:您現在已經開發和使用了一個自定義的 Ant 任務!創建更復雜的任務還會涉及其他許多方面,參考資料包含了指向此主題的進一步信息源的鏈接。
參考資料 |
|
- 訪問Apache Ant 主頁。 這是一個極好的信息源,它包括下載、Ant 手冊以及指向其他參考資料的鏈接。
- 查找關於 Ant 的IDE 和編輯器集成的更多信息。
- 瞭解關於Eclipse這個通用工具平臺的更多信息。
- developerWorks 開放源代碼項目專區包含豐富的基於 Eclipse 的內容。
- 如果您是 XML 新手,請訪問 developerWorksXML 專區。
- Cygwin是一個用於 Windows 的類 Linux 環境。
- CVS版本控制的開放標準。
- 瞭解關於WebSphere Studio Application Developer的更多信息,它基於 Eclipse 平臺。
- 查找關於 WebSphere Application ServerAnt 對Ant 任務的支持的更多信息。
- 在“JAR files revealed”中探索 JAR 文件格式的強大能力(developerWorks,2003 年 10 月)。
- developerWorks 特別貢獻了許多關於 Ant 的文章:
- “Incremental development with Ant and JUnit”,Malcolm Davis 撰稿,(2000 年 11 月)。
- “Extending Ant to support interactive builds”,Anthony Young-Garner 撰稿,(2001 年 11 月)。
- “Enhance Ant with XSL transformations”,Jim Creasman 撰稿(2003 年 9 月)。
- Ant 還支持開放源代碼的Jikes 編譯器和GNU Compiler for Java(GCJ)。
- 在 developerWorks Java 技術專區可以找到關於 Java 編程的方方面面的數百篇文章。
- 還可以參閱 Java 技術專區教程主頁,從 developerWorks 獲得免費的 Java 相關教程的完整列表。