Apache Ant site Apache Ant logo

Apache Ant 网站
主页
项目
 

常见问题解答

问题

关于此常见问题解答

一般

安装

如何...

它不起作用(按预期)

Apache Ant 和 IDE/编辑器

高级问题

已知问题

答案

我在哪里可以找到此文档的最新版本?

最新版本始终可以在 Apache Ant 的主页 https://ant.apache.org/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 软件基金会。它是由 James Duncan Davidson 创建的,他也是 Tomcat 的最初作者。Ant 当时用于构建 Tomcat,仅此而已。

此后不久,几个开源 Java 项目意识到 Ant 可以解决他们在 Makefile 中遇到的问题。从托管在 Jakarta 和旧的 Java Apache 项目中的项目开始,Ant 像病毒一样传播,现在已成为许多项目的首选构建工具。

2000 年 1 月,Ant 被移到一个单独的 CVS 模块,并被提升为一个独立于 Tomcat 的项目,成为 Apache Ant。

第一个面向更广泛受众的 Ant 版本是与 2000 年 4 月 19 日发布的 Tomcat 3.1 版本一起发布的版本。这个版本后来被称为 Ant 0.3.1。

Ant 作为独立产品的第一个正式版本是 2000 年 7 月 19 日发布的 Ant 1.1。完整的发布历史

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 用于长期支持 Ant 1.9.x 版本,这些版本可以使用 Java 1.5 构建和运行。

如果系统中只有 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 master 分支 1.8

当我尝试解压缩 tar.gz 分发文件时,出现校验和错误。为什么?

Ant 的分发包含文件名超过 100 个字符,这不受标准 tar 文件格式支持。几种不同的 tar 实现使用不同的、不兼容的方式来解决此限制。

Ant 的 <tar> 任务可以创建使用 GNU tar 扩展的 tar 存档,并且在打包分发时使用了此扩展。如果您使用的是其他版本的 tar(例如,与 Solaris 一起提供的版本),则无法使用它来解压缩存档。

解决方案是安装 GNU tar(可以在 这里 找到),或者使用 zip 存档(可以使用 jar xf 解压缩)。

如何在 RedHat ES 3 上运行 ant-1.6.x(或任何高于 1.5.2 的版本)?

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>

这不会按您预期的那样工作。输出重定向由您的 shell 执行,而不是由命令本身执行,因此应该读取

<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 执行批处理文件或 shell 脚本?

在原生 Unix 系统上,您应该能够直接运行 shell 脚本。在运行 Unix 类型 shell 的系统(例如,Windows 上的 Cygwin)上,执行(命令)shell - 批处理文件为 cmd,shell 脚本为 sh - 然后将批处理文件或 shell 脚本(以及脚本的任何参数)作为单个命令传递,使用 /c-c 开关,分别。请参阅 上一节 以获取执行 sh 的示例 <exec> 任务。对于批处理文件,使用类似以下内容

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

我希望仅在多个条件为真时执行特定目标。

实际上,这个问题有几个答案。

如果您只有一个已设置的属性和一个未设置的属性要测试,可以为目标指定 ifunless 属性,它们的行为就像“与”在一起一样。

如果您使用的是 Ant 1.3 或更早的版本,处理所有其他情况的方法是将目标链接在一起以确定要测试的特定状态。

要了解它是如何工作的,假设您有三个属性:prop1prop2prop3。您要测试 prop1prop2 是否已设置,以及 prop3 是否未设置。如果条件为真,您希望回显“yes”。

以下是 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 将 <isset> 元素引入 <condition> 任务。

以下是使用 <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> 的 encoding 属性。

如何执行类似 <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} - 不仅在 property 任务中 - 而是您构建文件中的任何地方(在注册所需的属性助手之后)。

<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 的 bug。请查看我们的 遇到问题? 页面,其中包含可能有助于确定问题原因的提示。

为什么 Ant 总是重新编译我所有的 Java 文件?

为了找出哪些文件应该被编译,Ant 会比较源文件的 timestamps 和生成的 .class 文件的 timestamps。打开所有源文件以找出它们属于哪个包效率非常低。相反,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 文件,那么需要该值的 task(例如 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(在这种情况下为 1.5.x)和新版本的 ant 脚本文件出现问题。

可以通过执行 ant --noconfig -version 检查是否为这种情况。

每当我使用 Ant jar 或 manifest 相关任务时,manifest 中的长行都会在 70 个字符处换行,导致生成的 jar 文件在我的应用程序服务器中无法工作。为什么 Ant 会这样做?

Ant 实现 Java Jar 文件规范。请参阅讨论允许的最大行长和续行字符概念的注释部分。

如果 Ant 生成的 jar 文件在你的应用程序服务器中无法工作,并且该故障是由于换行的 manifest 造成的,那么你需要咨询你的应用程序服务器提供商,因为这是他们的应用程序服务器中的一个 bug。然而,更可能的是,你的类路径规范中存在问题。问题不在于 Ant 对你的类路径的换行。

在确认问题不是由于你的类路径规范造成的之前,不要提交关于此问题的 bug。

<exec> 在 Windows 上失败,并显示 "Cannot run program "...":CreateProcess error=2"

一个常见的问题是可执行文件不在 PATH 中。如果你收到错误消息 Cannot run program "...":CreateProcess error=2. The system cannot find the path specified.,请查看你的 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 [email protected] - 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 - 但它有一些问题

如何在我的构建文件中包含 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,您可以使用自定义 BuildListener,它在 buildFinished() 方法中发送电子邮件。Will Glozer <[email protected]> 编写了这样一个基于 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 <[email protected]>

build.log=build.log

build.failed.notify=true
[email protected]
build.failed.email.subject=Nightly build failed!

build.succeeded.notify=true
[email protected]
build.succeeded.email.subject=Nightly build succeeded!

monitor.properties 应该放在您编译的 BuildMonitor.class 旁边。要使用它,请像这样调用 Ant

ant -listener BuildMonitor -logfile build.log

确保 JavaMail 中的 mail.jarJava Beans Activation Framework 中的 activation.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 来解决此问题。

Ant 1.7.0 在没有 JUnit 的情况下无法从源代码构建

在没有 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 的构建文件,并将 test-jar 从 dist-lite 目标的 depends 列表中删除。

<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 开始<junit> 不再要求您在 Ant 的启动类路径中包含 junit.jar,即使 ant-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 的类加载器或您指定的路径。

如果引导类加载器可以加载 Ant 请求它加载的类(如果 optional.jarCLASSPATH 的一部分,它就可以做到),这个类也会尝试从 CLASSPATH 加载外部库 - 它不知道其他任何东西 - 并且除非库也在 CLASSPATH 中,否则它将找不到它。

要解决这个问题,您有两个主要选择

  1. 将您需要的所有外部库也放入 CLASSPATH 中,这不是您想要的,否则您就不会找到这个常见问题解答条目。
  2. CLASSPATH 中删除加载外部库的类。

最简单的方法是从 ANT_HOME/lib 中删除 optional.jar。如果您这样做,您将不得不 <taskdef> 所有可选任务,并在 <taskdef> 任务中使用嵌套的 <classpath> 元素,这些元素指向 optional.jar 的新位置。此外,不要忘记将 optional.jar 的新位置添加到 <style><junit> 任务的 <classpath> 中。

如果您想避免 <taskdef> 所有需要的可选任务,唯一的选择是从 optional.jar 中删除不应该通过引导类加载器加载的类,并将它们放入一个单独的存档中。将这个单独的存档添加到 <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 会在引导类加载器之上创建一个类加载器 - 让我们在本文的其余部分将其称为核心加载器 - 它包含 ANT_HOME/lib 的内容。Ant 的核心及其任务将通过这个类加载器加载,而不是引导类加载器。

这会导致 Ant 1.5.x 和 1.6.x 之间出现一些细微但明显的差异。最重要的是,作为 CLASSPATH 一部分的第三方任务在 Ant 1.6.x 中将不再起作用,因为该任务现在无法找到 Ant 的类。从某种意义上说,这与本文所述的问题相同,只是 ant.jar 现在已成为所讨论的外部库。

此核心加载器还包含 ~/.ant/lib 的内容以及使用 Ant 的 -lib 命令行参数指定的任何文件或目录。

让我们看看加载 <junit> 任务时会发生什么。Ant 的类加载器将首先咨询引导类加载器,后者尝试从 CLASSPATH 加载类。引导类加载器不知道 Ant 的类加载器,甚至不知道您指定的路径。如果它使用引导类加载器无法找到该类,它将尝试使用核心加载器。同样,核心加载器不知道您的路径。

如果核心加载器可以加载 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() 代替 - 这将对非任务(如 fileset id=x 之类的类型)返回 null。

所以.. 遍历任务,如果它们是 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 版本将检测到它们所处的无限循环,但生成的 fileset 可能仍然太大而无法处理,特别是如果您安装了许多不同的 VM 版本。由于每个安装的版本中都有一个 shared_bundle 符号链接,因此问题会加剧。

一种解决方案是不允许 fileset 跟踪符号链接,例如

<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 可能还不够,因为还有一个指向 Home 目录的符号链接 bundle,它也会导致无限递归。

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

在所有 Oracle JDK 的 javadoc 工具(早于 Java 7 更新 25)生成的 Javadoc 中存在一个框架注入漏洞。

如果您无法升级 JDK,可以使用 Oracle 提供的补丁工具。或者,可以将作为 问题 55132 一部分提供的 macrodef 用作构建过程的一部分。

Ant 1.9.2 将在 javadoc 任务中对生成的 javadoc 进行后处理。