Apache Ant site Apache Ant logo

Apache Ant 網站
首頁
專案
 

常見問題

問題

關於此常見問題

一般

安裝

我如何...

它無法正常運作(如預期)

Apache Ant 和 IDE/編輯器

進階問題

已知問題

解答

我可以在哪裡找到此文件的最新版本?

最新版本永遠可以在 Apache Ant 的首頁 https://ant.dev.org.tw/faq.html 中找到。

我該如何協助此常見問答集?

您正在瀏覽的頁面是由 文件產生。如果您想要新增新問題,請將針對此文件的修補程式提交至 Ant 的某個郵件清單;希望結構能不言自明。

修補程式可以由 svn diff 指令建立。另請參閱 此頁面 中的「修正錯誤」段落。

您如何建立此常見問答集的 HTML 版本?

我們使用 Anakia 從原始 XML 檔案呈現 HTML 版本。

用於處理 XML 檔案的 Velocity 樣式表可以在 Ant 網站 SVN 儲存庫的 sources/stylesheets 子目錄中找到 - ant 網站 SVN 模組頂層的建置檔案 build.xml 用於驅動 Anakia。

Apache Ant 是什麼?

Ant 是一個基於 Java 的建置工具。理論上,它有點像 Make,但沒有 Make 的缺點,且具有純 Java 程式碼的完整可攜性。

為什麼您稱它為 Ant?

根據 Ant 的原始作者 James Duncan Davidson 的說法,這個名稱是「Another Neat Tool」的首字母縮寫。

後來的解釋是「螞蟻在建構事物方面做得非常好」,或「螞蟻非常小,可以承載比自己重好幾十倍的重量」 - 描述 Ant 的預期用途。

告訴我們一些關於 Ant 歷史的資訊。

最初,Ant 是 Tomcat 程式碼庫的一部分,後來捐贈給 Apache 軟體基金會。Ant 是由 James Duncan Davidson 所建立,他也是 Tomcat 的原始作者。Ant 的目的是為了建置 Tomcat,沒有其他用途。

不久之後,許多開源 Java 專案發現 Ant 可以解決他們使用 Makefiles 所遇到的問題。從 Jakarta 和舊的 Java Apache 專案開始,Ant 像病毒一樣散布開來,現在已成為許多專案的首選建置工具。

2000 年 1 月,Ant 移至獨立的 CVS 模組,並升格為獨立於 Tomcat 的專案,成為 Apache Ant。

第一個向大眾公開的 Ant 版本,是 2000 年 4 月 19 日隨 Tomcat 3.1 發布的版本。此版本後來被稱為 Ant 0.3.1。

Ant 作為獨立產品的第一個正式版本是 Ant 1.1,於 2000 年 7 月 19 日發布。完整的發布記錄

Ant 版本 發布日期
1.1 2000 年 7 月 19 日
1.2 2000 年 10 月 24 日
1.3 2001 年 3 月 3 日
1.4 2001 年 9 月 3 日
1.4.1 2001 年 10 月 11 日
1.5 2002 年 7 月 10 日
1.5.1 2002 年 10 月 3 日
1.5.2 2003 年 3 月 3 日
1.5.3 2003 年 4 月 9 日
1.5.4 2003 年 8 月 12 日
1.6.0 2003 年 12 月 18 日
1.6.1 2004 年 2 月 12 日
1.6.2 2004 年 7 月 16 日
1.6.3 2005 年 4 月 28 日
1.6.4 2005 年 5 月 19 日
1.6.5 2005 年 6 月 2 日
1.7.0 2006 年 12 月 19 日
1.7.1 2008 年 6 月 27 日
1.8.0 2010 年 2 月 8 日
1.8.1 2010 年 5 月 7 日
1.8.2 2010 年 12 月 27 日
1.8.3 2012 年 2 月 29 日
1.8.4 2012 年 5 月 23 日
1.9.0 2013 年 3 月 7 日
1.9.1 2013 年 5 月 21 日
1.9.2 2013 年 7 月 12 日
1.9.3 2013 年 12 月 29 日
1.9.4 2014 年 5 月 5 日
1.9.5 2015 年 6 月 3 日
1.9.6 2015 年 7 月 2 日
1.9.7 2016 年 4 月 12 日
1.9.8 2016 年 12 月 31 日
1.9.9 2017 年 2 月 6 日
1.9.10 2018 年 2 月 6 日
1.9.11 2018 年 3 月 27 日
1.9.12 2018 年 6 月 22 日
1.9.13 2018 年 7 月 13 日
1.9.14 2019 年 3 月 17 日
1.9.15 2020 年 5 月 13 日
1.9.16 2021 年 7 月 13 日
1.10.0 2016 年 12 月 31 日
1.10.1 2017 年 2 月 6 日
1.10.2 2018 年 2 月 6 日
1.10.3 2018 年 3 月 27 日
1.10.4 2018 年 6 月 22 日
1.10.6 2019 年 5 月 8 日
1.10.7 2019 年 9 月 5 日
1.10.8 2020 年 5 月 13 日
1.10.9 2020 年 9 月 30 日
1.10.10 2021 年 4 月 17 日
1.10.11 2021 年 7 月 13 日
1.10.12 2021 年 10 月 19 日
1.10.13 2023 年 1 月 10 日
1.10.14 2023 年 8 月 20 日

執行 Apache Ant 需要哪個版本的 Java?

您需要在系統上安裝 Java,需要 1.8 或更新版本。Java 版本越新,您獲得的 Ant 任務就越多。

git 分支 1.9.x 用於長期支援可使用 Java 1.5 建置和執行的 Ant 1.9.x 版本。

如果只存在 JRE 但沒有完整的 JDK,則許多任務將無法運作。

下表列出編譯和執行 Ant 所需的最低 Java 版本。請注意,大多數提交者使用較新版本的 JDK,而且 Ant 並未針對較舊版本進行大量測試。

Ant 版本 最低 Java 版本
1.1 至 1.5.4 1.1
1.6.0 至 1.6.5 1.2
1.7.0 至 1.7.1 1.3
1.8.0 至 1.8.3 1.4
任何 1.9.x 版本和 git 分支 1.9.x 1.5
任何 1.10.x 版本和目前的 git 主分支 1.8

當我嘗試解壓縮 tar.gz 分配檔案時,會出現檢查總和錯誤。為什麼?

Ant 的分配包含長度超過 100 個字元的檔案名稱,標準 tar 檔案格式不支援這種情況。tar 的幾個不同實作使用不同且不相容的方式來解決此限制。

Ant 的 <tar> 任務可以建立使用 GNU tar 擴充功能的 tar 檔案,而且在組合分配時已使用此功能。如果您使用不同版本的 tar(例如,隨 Solaris 附帶的版本),您無法使用它來解壓縮檔案。

解決方案是安裝 GNU tar(可在此處找到:https://gnu.dev.org.tw/software/tar/tar.html),或改用 zip 檔案(您可以使用 jar xf 來解壓縮)。

如何讓 ant-1.6.x(或任何 1.5.2 之後版本)在 RedHat ES 3 上運作?

Redhat ES 3.0 預先安裝了 ant 1.5.2。即使您已正確將 PATH 和 ANT_HOME 變數設定為較新版本的 ant,您仍將被迫使用預先安裝的版本。

若要在這個作業系統上使用較新版本的 ant,您可以執行下列動作

$ ant -version
Apache Ant version 1.5.2-23 compiled on November 12 2003
$ su -
# rpm -e ant ant-libs
# exit
$ hash -r
$ ant -version
Apache Ant version 1.6.2 compiled on July 16 2004

如何預先編譯 Java Server Pages (JSP)?

Apache Ant 有內建的選用任務 <jspc>,其目的是用於此目的。但此任務已棄用。以下是手冊建議的替代方案

我們建議部署原始檔案 (*.jsp) 並使用容器內建函數,而不是依賴容器特定的 JSP 編譯器:在部署後,針對已部署的 Web 應用程式執行測試套件(例如,使用 CactusHttpUnit)。這樣,您將取得測試結果和已編譯的 JSP。

我如何實現特定於作業系統的組態?

核心概念是使用符合作業系統名稱的屬性檔案。然後,只要使用內建屬性 os.name 即可。

為了更佳使用,您也應該提供一個具有預設值的檔案。但請謹慎使用正確的作業系統名稱。對於測試,只要在所有機器上 <echo> ${os.name},就能確定使用正確的檔案名稱。

          <property file="${os.name}.properties"/>
          <property file="default.properties"/>

我如何將自己撰寫的外掛程式加入「外部工具和任務」頁面?

加入開發人員或使用者郵件清單並發布訊息(加入一個清單就夠了),並包含下列資訊

這些資訊的首選格式是對 文件的修補程式。

如果您已為 Ant 撰寫比「簡單外掛程式」更大的內容,最好將連結加入 projects.html。加入程序相同。要修補的檔案是 文件。該檔案的語法相同。

我如何建立新任務?

除了大量關於使用 Ant 的資訊,手冊 也包含如何使用新任務擴充 Ant 的資訊。這些資訊可以在「使用 Ant 開發」中找到。

很可能其他人已經建立您想要建立的任務,最好先查看 外部工具和任務相關專案

我如何從命令列傳遞參數到我的建置檔案?

使用屬性。使用 ant -Dname=value 讓您可以在 Ant 命令列中定義屬性的值。然後,這些屬性可以在您的建置檔案中當成任何一般屬性使用:${name} 會放入 value

我如何使用 Jikes 特定的命令列開關?

幾個開關透過「神奇」屬性獲得支援

開關 屬性 預設
+E build.compiler.emacs false == 未設定
+P build.compiler.pedantic false == 未設定
+F build.compiler.fulldepend false == 未設定
(僅適用於 Ant < 1.4;之後由 <javac> 任務的 nowarn 屬性取代。)
-nowarn
build.compiler.warnings true == 未設定

對於 Ant >= 1.5,您也可以使用 <javac> 任務的巢狀 <compilerarg> 元素。

如何在命令列引數中包含 < 字元?

簡短的回答是「使用:&lt;」。

冗長的回答是,這可能無法達到您的目的(請參閱 下一節)。

如何在 <exec> 任務中重新導向標準輸入或標準輸出?

假設您想要重新導向 m4 命令的標準輸出串流,以寫入檔案,類似於

shell-prompt> m4 foo.m4 > foo

並嘗試轉換成

<exec executable="m4">
  <arg value="foo.m4"/>
  <arg value="&gt;"/>
  <arg value="foo"/>
</exec>

這不會產生您預期的結果。輸出重新導向是由您的殼層執行,而不是命令本身,因此這應該讀取為

<exec executable="/bin/sh">
  <arg value="-c" />
  <arg value="m4 foo.m4 &gt; foo" />
</exec>

請注意,您必須在最後一個元素中使用 <arg>value 屬性,才能將命令傳遞為單一引號引用的引數。或者,您可以使用

<exec executable="/bin/sh">
  <arg line='-c "m4 foo.m4 &gt; foo"'/>
</exec>

請注意單引號中巢狀的雙引號。

如何從 Ant 執行批次檔或殼層指令碼?

在原生 Unix 系統上,您應該可以直接執行殼層指令碼。在執行 Unix 類型殼層的系統上(例如,Windows 上的 Cygwin),請改為執行(命令)殼層 - 批次檔為 cmd,殼層指令碼為 sh - 然後傳遞批次檔或殼層指令碼(加上指令碼的任何引數)作為單一命令,分別使用 /c-c 開關。請參閱 上述部分,例如執行 sh<exec> 任務。對於批次檔,請使用類似於

<exec dir="." executable="cmd" os="Windows NT">
  <arg line="/c test.bat"/>
</exec>

我想要僅在多個條件為真時執行特定目標。

這個問題實際上有多個答案。

如果您只有一個設定和一個未設定的屬性要測試,您可以為目標指定 ifunless 屬性,它們將作用於「與」在一起。

如果您使用的是 Ant 1.3 或更早版本,處理所有其他情況的方法是將目標串連在一起,以確定您要測試的特定狀態。

要了解此運作方式,假設您有三個屬性:prop1prop2prop3。您要測試 prop1prop2 已設定,而 prop3 則未設定。如果條件成立,您要顯示「是」。

以下是 Ant 1.3 及更早版本中的實作

<target name="cond" depends="cond-if"/>

<target name="cond-if" if="prop1">
  <antcall target="cond-if-2"/>
</target>

<target name="cond-if-2" if="prop2">
  <antcall target="cond-if-3"/>
</target>

<target name="cond-if-3" unless="prop3">
  <echo message="yes"/>
</target>

注意:<antcall> 任務不會將屬性變更傳回呼叫它們的環境,因此您無法在 cond-if-3 目標中設定 result 屬性,然後在 cond 目標中執行 <echo message="result is ${result}"/>

從 Ant 1.4 開始,您可以使用 <condition> 任務。

<target name="cond" depends="cond-if,cond-else"/>

<target name="check-cond">
  <condition property="cond-is-true">
    <and>
      <not>
        <equals arg1="${prop1}" arg2="$${prop1}" />
      </not>
      <not>
        <equals arg1="${prop2}" arg2="$${prop2}" />
      </not>
      <equals arg1="${prop3}" arg2="$${prop3}" />
    </and>
  </condition>
</target>

<target name="cond-if" depends="check-cond" if="cond-is-true">
  <echo message="yes"/>
</target>

<target name="cond-else" depends="check-cond" unless="cond-is-true">
  <echo message="no"/>
</target>

此版本利用兩件事

由於測試文字 ${property} 字串並非易讀或易於理解,因此 1.4.1 版後的 Ant 在 <condition> 任務中引入了 <isset> 元素。

以下是使用 <isset> 執行的先前範例

<target name="check-cond">
  <condition property="cond-is-true">
    <and>
      <isset property="prop1"/>
      <isset property="prop2"/>
      <not>
        <isset property="prop3"/>
      </not>
    </and>
  </condition>
</target>

最後一個選項是使用指令碼語言設定屬性。當您需要比這裡顯示的簡單條件更精細的控制時,這可能會特別方便,但當然會增加 JAR 檔案以支援語言的負擔,更不用說需要兩種語言來實作單一系統的額外維護。請參閱 <script> 任務文件 以取得更多詳細資訊。

我如何在建置檔案中包含德文變音符號等國家字元?

您需要告訴 XML 解析器您的建置檔案使用哪種字元編碼,這是在 XML 宣告 中完成的。

預設情況下,解析器假設您使用 UTF-8 編碼,而不是平台的預設編碼。對於大多數西歐國家,您應該將編碼設定為 ISO-8859-1。為此,請讓建置檔案的第一行讀取

<?xml version="1.0" encoding="ISO-8859-1" ?>

我如何使用 jarM 開關?我不要 MANIFEST。

JAR 檔案是 ZIP 檔案,所以如果你不想要 MANIFEST,你可以簡單地使用 <zip>

如果你的檔案名稱包含國家字元,你應該知道 Sun 的 jar 程式工具就像 Ant 的 <jar> 使用 UTF-8 來編碼它們的名稱,而 <zip> 使用你的平台預設編碼。如果需要,請使用 <zip> 的編碼屬性。

我如何做類似 <property name="prop" value="${${anotherprop}}"/>(雙重展開屬性)的事情?

沒有任何外部協助,這很棘手。

使用需要外部函式庫的 <script/>,你可以執行

<script language="javascript">
    propname = project.getProperty("anotherprop");
    project.setNewProperty("prop", propname);
</script>

使用 AntContrib(外部任務函式庫),你可以執行 <propertycopy name="prop" from="${anotherprop}"/>

使用 Ant 1.6,你可以模擬 AntContribs <propertycopy> 並避免需要外部函式庫

<macrodef name="propertycopy">
  <attribute name="name"/>
  <attribute name="from"/>
  <sequential>
    <property name="@{name}" value="${@{from}}"/>
  </sequential>
</macrodef>

使用「props」antlib(外部,但也是來自 Ant),你可以使用 ${${anotherprop} 進行解除參照,而不仅仅在屬性任務中,而是在建置檔中的任何地方(在註冊所需的屬性輔助程式之後)。

<propertyhelper>
  <props:nested />
</propertyhelper>
<property name="foo" value="foo.value" />
<property name="var" value="foo" />
<echo> ${${var}} = foo.value </echo>

使用 Flaka(外部 Ant 外掛程式),你可以使用 #{${anotherprop}} 進行解除參照,而不仅仅在 flaka 任務中,而是在安裝 flaka 的屬性處理常式後的所有任務中。

<project xmlns:fl="antlib:it.haefelinger.flaka">
  <fl:install-property-handler/>
  <property name="foo" value="foo.value"/>
  <property name="var" value="foo" />
  <property name="buildtype" value="test"/>
  <property name="appserv_test" value="//testserver"/>
  <echo>
    #{${var}} = foo.value
    <!-- nested property -->
    #{appserv_${buildtype}}
  </echo>
</project>

我如何刪除特定目錄下的所有內容,同時保留目錄本身?

大多數走上這條路徑的使用者都沒有問題找出 <delete includeemptydirs="true" /> 將會對他們有所幫助。看似棘手的部分是保留基本目錄本身,Ant 將其包含在目錄掃描中。幸運的是,答案很簡單

<delete includeemptydirs="true">
  <fileset dir="dirtokeep" includes="**/*" />
</delete>

我如何刪除特定目錄,如果且僅如果它為空?

大多數走上這條路徑的使用者都沒有問題找出 <delete includeemptydirs="true" /> 將會對他們有所幫助。看似棘手的部分是保留非空目錄,Ant 將其包含在目錄掃描中。幸運的是,答案很簡單

<delete includeemptydirs="true">
  <fileset dir="dirtokeepifnotempty" excludes="**/*" />
</delete>

一般建議

Apache Ant 無法按預期運作的原因有很多,並非所有原因都是由於 Ant 錯誤。請參閱我們的 遇到問題? 頁面,以取得可能有助於找出問題原因的提示。

為什麼 Ant 總是重新編譯我所有的 Java 檔案?

為了找出應該編譯哪些檔案,Ant 會將來源檔案的時間戳記與產生的 .class 檔案的時間戳記進行比較。開啟所有來源檔案以找出它們屬於哪個套件會非常沒有效率。相反地,Ant 預期你將來源檔案放在鏡像你的套件層級目錄階層中,並使用 srcdir 屬性將 Ant 指向此目錄樹的根目錄。

假設您有 <javac srcdir="src" destdir="dest"/>。如果 Ant 找到檔案 src/a/b/C.java,它會預期它在套件 a.b 中,因此產生的 .class 檔案會是 dest/a/b/C.class

如果您的原始碼樹目錄結構與您的套件結構不符,Ant 的啟發式方法將無法運作,而且它會重新編譯已更新的類別。Ant 並非唯一預期原始碼樹配置為此類型的工具。

如果您有未宣告為任何套件一部分的 Java 原始碼檔案,您仍可以使用 <javac> 工作來正確編譯這些檔案 - 只要將 srcdirdestdir 屬性分別設定為原始碼檔案所在的實際目錄和類別檔案應該進入的目錄即可。

我已使用 <delete> 工作來刪除不需要的 SourceSafe 控制檔案 (CVS 檔案、編輯器備份檔案等),但它似乎無法運作;檔案從未被刪除。出了什麼問題?

這可能是因為,預設情況下,Ant 會從 FileSets 排除 SourceSafe 控制檔案 (vssver.scc) 和某些其他檔案。

以下是您可能執行的動作

<delete>
  <fileset dir="${build.src}" includes="**/vssver.scc"/>
</delete>

您需要關閉預設排除,它就會運作

<delete>
  <fileset dir="${build.src}" includes="**/vssver.scc"
           defaultexcludes="no"/>
</delete>

如需預設排除的模式完整清單,請參閱 使用者手冊

我有一個目標,如果設定了屬性,我希望略過它,因此我將 unless="property" 作為目標的屬性,但此目標依賴的所有目標仍會執行。為什麼?

在執行任何目標之前,Ant 會產生依賴清單。這允許依賴目標(例如 init 目標)設定屬性,這些屬性可以控制依賴圖中較高層級的目標執行。這是好事。

然而,當您的依賴關係將較高層級的工作分解為幾個較小的步驟時,此行為會變得違反直覺。有幾個可用的解決方案

  1. 對每個依賴目標套用相同的條件。
  2. 使用 <antcall> 執行步驟,而不是在 depends 屬性內指定它們。

在我的 <fileset> 中,我放入了所有檔案的 <exclude>,接著放入了我要的檔案的 <include>,但它完全沒有給我任何檔案。出了什麼問題?

在建立 FileSet 時,會忽略 <fileset><include><exclude> 標籤的順序。相反地,會先處理所有 <include> 元素,接著處理所有 <exclude> 元素。這表示 <exclude> 元素只會套用在 <include> 元素產生的檔案清單上。

若要取得您要的檔案,請只專注在取得這些檔案所需的 <include> 模式。如果您發現需要修剪 <include> 元素產生的清單,請使用 <exclude> 元素。

即使我將所需的 jar 放入外部 build.properties 檔案中,並透過 pathelementclasspath refid 參照它們,ant 仍無法透過 javac 建立我的程式。

ant 從外部檔案載入屬性時,它不會變更屬性的值,例如不會修剪尾端空白。

如果該值表示檔案路徑,例如編譯所需的 jar,則需要該值的任務(例如 javac)會無法編譯,因為它找不到該檔案,原因是尾端有空白。

Ant 建立的 WAR 檔案有小寫的 web-inf 或 JAR 檔案有小寫的 meta-inf 目錄。

它沒有。

您可能在 WinZIP 中看過這些小寫目錄名稱,但 WinZIP 只是想提供協助(但失敗了)。如果 WinZIP 遇到完全是大寫的檔案名稱,它會假設該檔案來自舊的 DOS 盒,並將其全部改為小寫。

如果您使用 jar 萃取(或只是檢查)檔案,您會看到名稱有正確的大小寫。

在 WinZIP(至少為 8.1 版)中,可以在組態中修正此問題。在選項/組態功能表中,在檢視標籤的「一般」區段中,勾選「允許所有大寫檔案名稱」方塊。META-INF 和 WEB-INF 會看起來正確。

我安裝了 Ant 1.6.x,現在會收到 Exception in thread "main" java.lang.NoClassDefFoundError:

這是因為在類別路徑或組態中某處有舊版的 ant。

此問題的一個版本會發生在包含嵌入式 ant 類別副本的 jar 中。weblogic.jar 的某些副本就是一個例子。

可以透過執行下列指令(在 unix/sh 中)來檢查是否為此情況:

        unset CLASSPATH
        ant -version
        

我已安裝 Ant 1.6.x,現在會收到 java.lang.InstantiationException: org.apache.tools.ant.Main

這是因為在類別路徑或組態中某處有舊版的 ant。

此問題的一個版本可能出現在某些 Linux 系統上。某些 Linux 系統(例如 Fedora Core 2)附帶預先安裝的 Ant 版本。有一個名為 /etc/ant.conf 的組態檔,如果存在,ant shell 腳本將「點」包含。在 Fedora Core 2 上,/etc/ant.conf 檔會將 ANT_HOME 環境變數重設為 /usr/share/ant。這會導致使用新版本的 ant 腳本檔時,會使用舊版本的 ant(在此情況下為 1.5.x)。

可以透過執行 ant --noconfig -version 來檢查是否為此情況。

每當我使用 Ant jar 或明細相關工作時,明細中的長行會在 70 個字元處換行,而產生的 jar 檔無法在我的應用程式伺服器中運作。為什麼 Ant 會這樣做?

Ant 實作 Java Jar 檔規格。請參閱討論一行最大允許長度和延續字元概念的注意事項部分。

如果由 Ant 產生的 jar 檔無法在您的應用程式伺服器中運作,而且該失敗是因換行的明細所造成,則您需要諮詢您的應用程式伺服器提供者,因為這是他們的應用程式伺服器中的錯誤。不過,更有可能是您的類別路徑規格有問題。問題不在於 Ant 對您的類別路徑進行換行。

在您檢查並確定問題不是由於您的類別路徑規格所造成之前,請勿提出關於此問題的錯誤。

<exec> 在 Windows 上會失敗,並顯示 "無法執行程式 "...":CreateProcess 錯誤=2"

常見問題是 PATH 中沒有可執行檔。如果您收到錯誤訊息 無法執行程式 "...":CreateProcess 錯誤=2。系統找不到指定的路徑。,請查看您的 PATH 變數。

請直接在命令列中輸入指令,如果 Windows 找到它,Ant 也應該會找到。(否則,請在使用者郵件清單中尋求協助。)如果 Windows 無法執行程式,請將程式目錄新增至 PATH(set PATH=%PATH%;dirOfProgram)或在建置檔中 executable 屬性中指定絕對路徑。

我的 <junit> 報告遺漏包含失敗訊息的第一行。

從 Ant 1.8.0 開始,如果已設定 filtertrace 屬性,將會將「more」文字新增至將從堆疊追蹤中濾出的行組。目的是要移除追蹤底部的「24 more ...」行。

如果失敗訊息包含「more」字詞,也會移除包含訊息的行。這應會在 Ant 1.8.3 發行後修復。

目前唯一可用的解決方法是停用 filtertrace 或變更失敗訊息,使其不包含「more」字詞。

屬性會在我的 macrodefed 工作中擴充兩次。

如果您的 macrodefed 工作包含屬性,屬性參照會擴充兩次,例如

  <macrodef name="echotest">
    <attribute name="message" />
    <sequential>
      <echo message="@{message}" />
    </sequential>
  </macrodef>
  <echotest message="$${basedir}" />

會呼應 basedir 屬性的值,而不是預期的 ${basedir} 文字。

這是因為 ${} 序列會在擴充 @{} 序列之前擴充一次,然後再擴充一次。這需要讓類似於 此常見問題集 中 macrodef 的項目運作。這會啟用

<property name="choice" value="2"/>
<property name="thing.1" value="one"/>
<property name="thing.2" value="two"/>
<property name="thing.3" value="three"/>
<propertycopy to="thing" from="thing.${choice}"/>

如果屬性沒有擴充兩次,這將無法執行。

如果您想避免雙重擴充,從 Ant 1.8.3 開始,您可以明確關閉它

  <macrodef name="echotest">
    <attribute name="message" doubleexpanding="false" />
    <sequential>
      <echo message="@{message}" />
    </sequential>
  </macrodef>
  <echotest message="$${basedir}" />

我的 IDE/編輯器是否支援 Apache Ant?

請參閱我們的外部工具與工作頁面上的 IDE 整合區段

為什麼 (X)Emacs/vi/MacOS X 的專案建置器無法正確剖析 Ant 產生的錯誤訊息?

Ant 會在所有記錄訊息前面加上一個「橫幅」,其中包含目前工作的名稱,而且您的編輯器中沒有內建的正規表示式可以說明這一點。

您可以使用 -emacs 參數呼叫 Ant 來停用此橫幅。若要讓 Ant 自動偵測 Emacs 的編譯模式,請將此程式碼放入您的 .antrc 中(由 Ville Skyttä 提供)。

# Detect (X)Emacs compile mode
if [ "$EMACS" = "t" ] ; then
  ANT_ARGS="$ANT_ARGS -emacs"
  ANT_OPTS="$ANT_OPTS -Dbuild.compiler.emacs=true"
fi

或者,您可以將下列程式碼片段新增至您的 .emacs 中,讓 Emacs 了解 Ant 的輸出。

(require 'compile)
(setq compilation-error-regexp-alist
  (append (list
     ;; works for jikes
     '("^\\s-*\\[[^]]*\\]\\s-*\\(.+\\):\\([0-9]+\\):\\([0-9]+\\):[0-9]+:[0-9]+:" 1 2 3)
     ;; works for javac
     '("^\\s-*\\[[^]]*\\]\\s-*\\(.+\\):\\([0-9]+\\):" 1 2))
  compilation-error-regexp-alist))

另一個可以保留大部分 Ant 格式的替代方案是透過 Dirk-Willem van Gulik 的下列 Perl 腳本來傳遞 Ant 的輸出

#!/usr/bin/perl
#
# May 2001 dirkx@apache.org - remove any
# [foo] lines from the output; keeping
# spacing more or less there.
#
$|=1;
while(<STDIN>) {
        if (s/^(\s+)\[(\w+)\]//) {
                if ($2 ne $last) {
                        print "$1\[$2\]";
                        $s = ' ' x length($2);
                } else {
                        print "$1 $s ";
                };
                $last = $2;
        };
        print;
};

有沒有我可以用來驗證我的建置檔案的 DTD?

<antstructure> 工作可以建立不完整的 DTD,但這個 DTD 有一些問題

如何在我的建置檔案中包含 XML 片段?

你可以使用 XML 的方式包含外部檔案,並讓剖析器為 Ant 執行這項工作

<?xml version="1.0"?>

<!DOCTYPE project [
       <!ENTITY common SYSTEM "common.xml">
]>

<project name="test" default="test" basedir=".">

  <target name="setup">
    ...
  </target>

  &common;

  ...

</project>

將實際包含 common.xml 的內容,你已將 &common; 實體置於其中。

(在此範例中,檔案名稱 common.xml 是由 XML 剖析器相對於包含的 XML 檔案解析的。你也可以使用絕對的 file: 協定 URI。)

與 DTD 結合,看起來會像這樣

<!DOCTYPE project PUBLIC "-//ANT//DTD project//EN" "ant.dtd" [
   <!ENTITY include SYSTEM "header.xml">
]>

從 Ant 1.6 開始,有一個新的 <import> 工作,也可以用於包含建置檔案片段。與實體包含中使用的片段不同,不過,所引用的檔案必須是完整的 Ant 建置檔案。

上述範例將變成

<?xml version="1.0"?>
<project name="test" default="test" basedir=".">

  <target name="setup">
    ...
  </target>

  <import file="./common.xml"/>

  ...

</project>

與實體包含不同,<import> 會讓你可以在檔案名稱中使用 Ant 屬性。

如何使用建置程序的結果傳送電子郵件?

如果你在 2001-12-14 之後使用 Ant 1.5 的夜間建置,你可以使用內建的 MailLogger

         ant -logger org.apache.tools.ant.listener.MailLogger

請參閱 Listeners & Loggers 文件,以取得所需屬性的詳細資料。

對於較舊版本的 Ant,你可以在 buildFinished() 方法中使用自訂 BuildListener 傳送電子郵件。Will Glozer <will.glozer@jda.com> 已根據 JavaMail 編寫此類型的監聽器。來源是

import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.tools.ant.*;

/**
 * A simple listener that waits for a build to finish and sends an email
 * of the results.  The settings are stored in "monitor.properties" and
 * are fairly self explanatory.
 *
 * @author      Will Glozer
 * @version     1.05a 09/06/2000
 */
public class BuildMonitor implements BuildListener {
    protected Properties props;

    /**
     * Create a new BuildMonitor.
     */
    public BuildMonitor() throws Exception {
        props = new Properties();
        InputStream is = getClass().getResourceAsStream("monitor.properties");
        props.load(is);
        is.close();
    }

    public void buildStarted(BuildEvent e) {
    }

    /**
     * Determine the status of the build and the actions to follow, now that
     * the build has completed.
     *
     * @param       e       Event describing the build status.
     */
    public void buildFinished(BuildEvent e) {
        Throwable th = e.getException();
        String status = (th != null) ? "failed" : "succeeded";

        try {
            String key = "build." + status;
            if (props.getProperty(key + ".notify").equalsIgnoreCase("false")) {
                    return;
            }

            Session session = Session.getDefaultInstance(props, null);

            MimeMessage message = new MimeMessage(session);
            message.addRecipients(Message.RecipientType.TO, parseAddresses(
                props.getProperty(key + ".email.to")));
            message.setSubject(props.getProperty(key + ".email.subject"));

            BufferedReader br = new BufferedReader(new FileReader(
                props.getProperty("build.log")));
            StringWriter sw = new StringWriter();

            String line = br.readLine();
            while (line != null) {
                sw.write(line);
                sw.write("\n");
                line = br.readLine();
            }
            br.close();

            message.setText(sw.toString(), "UTF-8");
            sw.close();

            Transport transport = session.getTransport();
            transport.connect();
            transport.send(message);
            transport.close();
        } catch (Exception ex) {
            System.out.println("BuildMonitor failed to send email!");
            ex.printStackTrace();
        }
    }

    /**
     * Parse a comma separated list of internet email addresses.
     *
     * @param       s       The list of addresses.
     * @return      Array of Addresses.
     */
    protected Address[] parseAddresses(String s) throws Exception {
        StringTokenizer st = new StringTokenizer(s, ",");
        Address[] addrs = new Address[st.countTokens()];

        for (int i = 0; i < addrs.length; i++) {
            addrs[i] = new InternetAddress(st.nextToken());
        }
        return addrs;
    }

    public void messageLogged(BuildEvent e) {
    }

    public void targetStarted(BuildEvent e) {
    }

    public void targetFinished(BuildEvent e) {
    }

    public void taskStarted(BuildEvent e) {
    }

    public void taskFinished(BuildEvent e) {
    }
}

使用類似這樣的 monitor.properties

# configuration for build monitor

mail.transport.protocol=smtp
mail.smtp.host=<host>
mail.from=Will Glozer <will.glozer@jda.com>

build.log=build.log

build.failed.notify=true
build.failed.email.to=will.glozer@jda.com
build.failed.email.subject=Nightly build failed!

build.succeeded.notify=true
build.succeeded.email.to=will.glozer@jda.com
build.succeeded.email.subject=Nightly build succeeded!

monitor.properties 應置於編譯後的 BuildMonitor.class 旁邊。若要使用它,請像這樣呼叫 Ant

ant -listener BuildMonitor -logfile build.log

請確定 JavaMail 的 mail.jarJava Beans Activation Frameworkactivation.jar 在你的 CLASSPATH 中。

如何從 BuildListener 內部取得 Ant 執行的屬性?

你可以透過 BuildEvent 參數取得一個雜湊表,其中包含 Ant 已使用的所有屬性。例如

public void buildFinished(BuildEvent e) {
    Hashtable table = e.getProject().getProperties();
    String buildpath = (String)table.get("build.path");
    ...
}

這比只讀取專案執行的相同屬性檔案更準確,因為它會提供 Ant 命令列中指定的屬性的正確結果。

<exec> 會導致其他工作暫停,或導致 <input> 工作出現異常行為。

當 Apache Ant 分岔一個新程序,例如使用 <exec><apply><java> 工作時,它也會啟動一個新的執行緒,從標準輸入讀取資料,並將讀取到的所有內容傳送給該程序。

很不幸的是,Ant 沒有辦法知道分叉的程序是否會讀取任何輸入,因此即使程序不需要,它也會啟動一個執行緒。

這種行為會導致奇怪的副作用,例如 Ant 程序會在類 Unix 系統上以背景程序執行分叉新程序的建置時暫停,或如果 <input> 任務在 <exec> 任務之後,則會需要額外的輸入

很幸運地,有一個解決方法,如果你知道分叉的程序不會使用任何輸入,請務必為任何 <exec> 任務(或其同層任務之一)指定 inputstring=""

<javac> 導致 StackOverflowError

對於某些 Java 原始檔,有可能在 Sun 的 javac 編譯器內部會擲出StackOverlowError。就我們所知,這並非由 Ant 中的錯誤所觸發。

可以透過將 <javac> 的 fork 屬性設定為 true 來解決這個問題。

沒有 JUnit 的 Ant 1.7.0 無法從原始檔建置

在沒有 junit.jar 的情況下從原始檔版本建置 Ant 1.7.0 時,建置會失敗並顯示訊息「除非有 JUnit,否則我們無法建置測試 jar」。

從 Ant 1.7.0 開始,我們已開始將 ant-testutil.jar 加入發行版中,這會導致對 JUnit 的硬依賴關係,至少在版本 1.7.0 中是如此。很不幸地,安裝文件並未說明這一點。

有兩個解決方法

  1. 在建置 Ant 時將 junit.jar 加入你的 CLASSPATH。
  2. 變更 Ant 的建置檔,並從 dist-lite 目標的 depends 清單中移除 test-jar。

<chmod> 或 <exec> 在 Unix 上的 Ant 1.3 中不起作用

ANT_HOME/bin 中的 antRun 腳本使用 DOS 而非 Unix 行尾;你必須從這個檔案中移除換行符號。這可以使用 Ant 的 <fixcrlf> 任務或類似以下的指令來完成

tr -d '\r' < $ANT_HOME/bin/antRun > /tmp/foo
mv /tmp/foo $ANT_HOME/bin/antRun

<style> 或 <junit> 忽略我的 <classpath>

從 Ant 1.7.0 開始,<junit> 將會尊重你巢狀的 <classpath>。

這些任務不會忽略您的 classpath 設定,您會遇到委派類別載入器常見的問題。

此問題收集常見的類型問題:任務需要外部程式庫,且具有巢狀 classpath 元素,以便您可以將其指向此外部程式庫,但除非您將外部程式庫放入 CLASSPATH 或放置在 ANT_HOME/lib 中,否則無法運作。

在討論 Ant 1.5.xAnt 1.6.x 的解決方案之前,需要一些背景知識。

當您在 Ant 中指定巢狀 <classpath> 時,Ant 會建立一個新的類別載入器,使用您指定的路徑。然後,它會嘗試從此類別載入器載入其他類別。

在大部分情況下(例如使用 <style> 或 <junit>),Ant 不會直接載入外部程式庫,而是由載入的類別執行此動作。

<junit> 的情況下,是任務實作本身,而在 <style> 的情況下,是 org.apache.tools.ant.taskdefs.XSLTLiaison 類別的實作。

從 Ant 1.7 開始,即使 ant-junit.jar 存在於 Ant 的啟動 classpath 中,<junit> 不再要求您在其中擁有 junit.jar

Ant 的類別載入器實作使用 Java 的委派模型,請參閱 https://download.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html 段落

ClassLoader 類別使用委派模型來搜尋類別和資源。每個 ClassLoader 執行個體都有一個關聯的父類別載入器。當被呼叫以尋找類別或資源時,ClassLoader 執行個體會將類別或資源的搜尋委派給其父類別載入器,然後再嘗試自行尋找類別或資源。虛擬機內建的類別載入器(稱為引導程式類別載入器)本身沒有父類別載入器,但可以擔任 ClassLoader 執行個體的父類別載入器。

可能的解決方案取決於您使用的 Ant 版本,請參閱下一個區段。

<style> 或 <junit> 忽略我的 <classpath> - Ant 1.5.x 版本

請在繼續之前閱讀 前一個條目

首先,讓我們說明 Ant 的包裝器指令碼 (antant.bat) 會將 ANT_HOME/lib 中的所有 .jar 檔案新增至 CLASSPATH,因此在這個答案的其餘部分中,「在 CLASSPATH 中」表示「在您的 CLASSPATH 環境變數或 ANT_HOME/lib 中」。

此問題的根源在於需要外部程式庫的類別在 CLASSPATH 中。

讓我們看看當您載入 <junit> 任務時會發生什麼事。Ant 的類別載入器會先諮詢引導程式類別載入器,它會嘗試從 CLASSPATH 載入類別。引導程式類別載入器不知道 Ant 的類別載入器,甚至不知道您指定的路徑。

如果 bootstrap 類別載入器可以載入 Ant 要求載入的類別(如果 optional.jarCLASSPATH 的一部分,則可以載入),這個類別也會嘗試從 CLASSPATH 載入外部函式庫 - 它不知道其他任何東西 - 而且除非函式庫也在 CLASSPATH 中,否則找不到它。

要解決此問題,您有兩個主要選項

  1. 將您需要的外部函式庫全部放入 CLASSPATH 中,這不是您想要的,否則您不會找到此常見問題解答條目。
  2. CLASSPATH 中移除載入外部函式庫的類別。

執行此操作最簡單的方法是從 ANT_HOME/lib 中移除 optional.jar。如果您這樣做,您將必須 <taskdef> 所有選用任務,並在指向 optional.jar 新位置的 <taskdef> 任務中使用巢狀 <classpath> 元素。此外,別忘了將 optional.jar 的新位置新增到 <style><junit> 任務的 <classpath> 中。

如果您想避免 <taskdef> 所有您需要的選用任務,唯一的其他選項是從 optional.jar 中移除不應透過 bootstrap 類別載入器載入的類別,並將它們放入一個獨立的檔案庫中。將這個獨立的檔案庫新增到 <style><junit> 任務的 <classpath> 中 - 並確保獨立的檔案庫不在 CLASSPATH 中。

<junit> 的情況下,您必須移除 org/apache/tools/ant/taskdefs/optional/junit 目錄中的所有類別,在 <style> 的情況下,它是 org/apache/tools/ant/taskdefs/optional 中的 *Liaison 類別之一。

如果您使用拆分 optional.jar 的選項來執行 <junit> 或移除 ant-junit.jar,您仍然必須使用帶有巢狀 <classpath><taskdef> 來定義 junit 任務。

<style> 或 <junit> 忽略我的 <classpath> - Ant 1.6.x 版本

請在繼續之前閱讀 一般條目

Ant 1.6.x 的包裝腳本不再將 ANT_HOME/lib 的內容新增至 CLASSPATH,而是 Ant 會在 bootstrap 類別載入器之上建立一個類別載入器(讓我們在本文的其餘部分稱之為核心載入器),其中包含 ANT_HOME/lib 的內容。Ant 的核心及其任務將透過此類別載入器載入,而非 bootstrap 類別載入器。

這會造成 Ant 1.5.x 和 1.6.x 之間一些細微但顯著的差異。最重要的是,CLASSPATH 中的第三方任務將不再能在 Ant 1.6.x 中運作,因為任務現在找不到 Ant 的類別。從某種意義上來說,這與此條目所探討的問題相同,只不過 ant.jar 已成為現在有問題的外部程式庫。

此核心載入器也包含 ~/.ant/lib 的內容,以及使用 Ant 的 -lib 命令列引數指定的所有檔案或目錄。

讓我們看看當您載入 <junit> 任務時會發生什麼事。Ant 的類別載入器會先諮詢 bootstrap 類別載入器,它會嘗試從 CLASSPATH 載入類別。bootstrap 類別載入器不了解 Ant 的類別載入器,甚至您指定的路徑。如果它無法使用 bootstrap 類別載入器找到類別,它會接著嘗試核心載入器。同樣地,核心載入器不了解您的路徑。

如果核心載入器可以載入 Ant 要求它載入的類別(如果 ant-junit.jarANT_HOME/lib 中,它就可以),此類別也會嘗試從核心載入器載入外部程式庫(它不知道其他任何東西),並且找不到它,除非程式庫也在 CLASSPATH 或核心載入器中。

要解決此問題,您有以下幾個主要選項

  1. 將您需要的外部函式庫全部放入 CLASSPATH 中,這不是您想要的,否則您不會找到此常見問題解答條目。
  2. 將您需要的所有外部程式庫放入 ANT_HOME/lib.ant/lib。這可能仍然不是您想要的,但您可以重新考慮 .ant/lib 選項。
  3. 總是使用 -lib 命令列開關啟動 Ant,並指向您的外部程式庫(或包含它們的目錄)。
  4. 從核心載入器中移除載入外部程式庫的類別。

在 Ant 1.6 中,optional.jar 已拆分為多個 jar,每個 jar 都包含對外部程式庫具有相同依賴項的類別。您可以將「有問題的」jar 移出 ANT_HOME/lib。對於 <junit> 任務,它將是 ant-junit.jar

如果您這樣做,您必須對需要外部程式庫的所有選用任務使用 <taskdef>,並在指向 ant-*.jar 新位置的 <taskdef> 任務中使用巢狀 <classpath> 元素。此外,別忘了將 ant-*.jar 的新位置新增到 <style><junit> 任務的 <classpath> 中。

例如

    <taskdef name="junit"
            class="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask">
      <classpath>
        <pathelement location="HOME-OF/junit.jar"/>
        <pathelement location="NEW-HOME-OF/ant-junit.jar"/>
      </classpath>
    </taskdef>

為什麼我的自訂任務容器會在 Ant 1.6 中看到未知元素 - 它們在 Ant 1.5 中運作良好?

在 TaskContainer.addTask(Task task) 中新增的物件已從 Tasks 變更為 UnknownElements。

這個變更有多項有效原因。但是,直到 Ant 1.6.0 發布後才注意到向後相容性的問題。

您的容器類別需要修改,以檢查 Task 是否為 UnknownElement,並呼叫 perform 來將其轉換為 Task 並執行它。(請參閱 apache.tools.ant.taskdefs.Sequential)

如果您想要對任務執行更多處理,您需要使用 apache.tools.ant.taskdefs.Antlib#execute() 中的技術。這確實會使用一個 1.6 方法呼叫 (UE#getRealObject()),您需要改用 UE#getTask() - 這會為非任務傳回 null(例如 fileset id=x)。

因此.. 遍歷任務,如果它們是 UE,請使用 UE#maybeConfigure 和 UE#getTask() 將它們轉換為任務

        for (Iterator i = tasks.iterator(); i.hasNext();) {
           Task t = (Task) i.next();
           if (t instanceof UnknownElement) {
              ((UnknownElement) t).maybeConfigure();
              t = ((UnknownElement) t).getTask();
              if (t == null) {
                  continue;
              }
           }
           // .... original Custom code
        }
        

這種方法應該適用於 ant1.5 和 ant1.6。

當我在 Mac OS X 下編譯專案時,Ant 會執行無限迴圈/擲出 OutOfMemoryError。

Apple 的 Java VM 位於 /System/Library/Frameworks/JavaVM.framework/Versions/X.Y.ZJAVA_HOME 通常會類似於 /System/Library/Frameworks/JavaVM.framework/Versions/X.Y.Z/Home

在此家目錄內有一個名為 shared_bundle 的符號連結,它連結到三個層級以上,即 /System/Library/Frameworks/JavaVM.framework

如果您的建置檔案包含類似這樣的 fileset

<fileset dir="${java.home}" includes="**/*.jar"/>

Ant 將會追隨 shared_bundle 符號連結,並最終遞迴到您安裝的所有 VM 中。更糟的是,它會進入 /System/Library/Frameworks/JavaVM.framework/Versions/X.Y.Z/Home,並再次追隨相同的符號連結。

Ant 1.7.1 之後版本的 Ant 會偵測到它們陷入無限迴圈,但產生的檔案集可能仍然太大而無法處理,特別是在您安裝許多不同 VM 版本的情況下。問題在於每個已安裝版本在其中都有 shared_bundle 符號連結。

一個解決方案是不允許檔案集追蹤符號連結,就像

<fileset dir="${java.home}" includes="**/*.jar" followsymlinks="false"/>

另一個排除 shared_bundle 目錄

<fileset dir="${java.home}" includes="**/*.jar" excludes="**/shared_bundle/**"/>

對於 Ant 1.7.1 及更早版本,排除 shared_bundle 可能還不夠,因為還有另一個符號連結 bundle 指向 Home 目錄,也會造成無限遞迴。

extension-point 無法與 import 搭配使用,如文件所述。

是的,Ant 1.8.0 中有一個錯誤

在使用兩個建置檔案時,例如

importing.xml:
<project>
   ...
   <import file="imported.xml"/>
   <target name="bar" extensionOf="foo"/>
</project>
imported.xml:
<project>
   <extension-point name="foo"/>
</project>

Ant 1.8.0 將會失敗,並宣稱沒有名為「foo」的延伸點。

此錯誤已在 Ant 1.8.1 中修復。對於 Ant 1.8.0,有一個解決方法:新增一層匯入,例如

importing.xml:
<project>
   <target name="bar" extensionOf="foo"/>
</project>
imported.xml:
<project>
   <extension-point name="foo"/>
</project>
build.xml:
<project>
   <import file="imported.xml"/>
   <import file="importing.xml"/>
</project>

如何處理 javadoc 漏洞 CVE-2013-1571

在 Java 7 更新 25 之前,所有 Oracle JDK 的 javadoc 工具所產生的 Javadoc 中都有 frame 注入錯誤。

如果您無法升級您的 JDK,您可以使用 Oracle 提供的 patchtool。或者,Issue 55132 中提供的 macrodef 可用於您的建置程序中。

Ant 1.9.2 將在 javadoc 任務中後處理產生的 javadoc。