经常问的问题
问题
关于此常见问题解答
一般的
安装
- 运行 Apache Ant 需要哪个版本的 Java?
-
当我尝试提取
tar.gz
分发文件时出现校验和错误。为什么? - 如何让 ant-1.6.x(或 1.5.2 之后的任何版本)在 RedHat ES 3 上运行?
我如何 ...
- 如何预编译 Java Server Pages (JSP)?
- 如何实现特定于操作系统的配置?
- 如何将我编写的外部任务添加到“外部工具和任务”页面?
- 如何创建新任务?
- 如何将参数从命令行传递到我的构建文件?
- 如何使用 Jikes 特定的命令行开关?
- 如何在命令行参数中包含 < 字符?
-
如何重定向
<exec>
任务中的标准输入或标准输出? - 如何从 Ant 执行批处理文件或 shell 脚本?
- 我只想在多个条件成立时才执行特定目标。
- 如何在我的构建文件中包含德国变音符号等国家字符?
-
如何使用
jar
的开关M
?我不需要清单。 -
我怎样才能做类似的事情
<property name="prop" value="${${anotherprop}}"/>
(双重扩展财产)? - 如何删除特定目录下的所有内容,同时保留目录本身?
- 当且仅当特定目录为空时,如何删除该目录?
它不起作用(如预期)
- 一般建议
- 为什么 Ant 总是重新编译我的所有 Java 文件?
-
我使用了一个
<delete>
任务来删除不需要的 SourceSafe 控制文件(CVS 文件、编辑器备份文件等),但它似乎不起作用;文件永远不会被删除。怎么了? -
我有一个目标,如果设置了属性,我想跳过,因此我将其作为
unless="property"
目标的属性,但该目标依赖的所有目标仍会执行。为什么? -
在我的 中
<fileset>
,我放入了<exclude>
所有文件,然后是<include>
我想要的文件,但它根本没有给我任何文件。怎么了? -
ant
即使我将所需的 jar 放在外部文件中并通过或build.properties
引用它们, 也无法通过 javac 构建我的程序 。pathelement
classpath refid
-
Ant 创建带有小写目录的 WAR 文件
web-inf
或带有小写meta-inf
目录的 JAR 文件。 -
我安装了 Ant 1.6.x 现在得到
Exception in thread "main" java.lang.NoClassDefFoundError:
-
我安装了 Ant 1.6.x 现在得到
java.lang.InstantiationException: org.apache.tools.ant.Main
- 每当我使用 Ant jar 或清单相关任务时,清单中的长行都会以 70 个字符换行,并且生成的 jar 在我的应用程序服务器中不起作用。 Ant为什么要这么做?
-
<exec>
在 Windows 上失败"Cannot run program "...":CreateProcess error=2"
。 -
我的
<junit>
报告缺少包含失败消息的第一行。 -
在我的 ed 任务中,属性扩展了两次
macrodef
。
Apache Ant 和 IDE/编辑器
高级问题
已知问题
-
<exec>
导致其他任务挂起或导致<input>
任务出现奇怪的行为。 -
<javac>
导致 StackOverflowError - Ant 1.7.0 不会从没有 JUnit 的源代码构建
- <chmod> 或 <exec> 在 Unix 上的 Ant 1.3 中不起作用
- <style> 或 <junit> 忽略我的 <classpath>
- <style> 或 <junit> 忽略我的 <classpath> - Ant 1.5.x 版本
- <style> 或 <junit> 忽略我的 <classpath> - Ant 1.6.x 版本
- 为什么我的自定义任务容器在 Ant 1.6 中看到未知元素 - 它们在 Ant 1.5 中工作?
- 当我在 Mac OS X 下编译项目时,Ant 遇到无限循环/抛出 OutOfMemoryError。
-
extension-point
不能import
像文档所述那样工作。 - 如何处理javadoc漏洞CVE-2013-1571
答案
最新版本始终可以在 Apache Ant 的主页 /faq.html中找到。
您正在查看的页面是从此文档生成 的 。如果您想添加新问题,请将针对本文档的补丁提交到 Ant 的邮件列表之一;希望该结构是不言自明的。
可以通过 svn diff 命令创建补丁。另请参阅本页中的“修复错误”段落。
我们使用 Anakia 从原始 XML 文件渲染 HTML 版本。
用于处理 XML 文件的 Velocity 样式表可以在 Ant 站点 SVN 存储库的子目录中找到- ant 站点 SVN 模块顶层的sources/stylesheets
构建文件
用于驱动 Anakia。build.xml
Ant 是一个基于Java 的构建工具。理论上,它有点像 Make,没有 Make 的缺陷,并且具有纯 Java 代码的完全可移植性。
根据 Ant 的原作者 James Duncan Davidson 的说法,这个名字是“Another Neat Tool”的缩写。
后来的解释大致是“ Ant在建造东西方面做得非常好”,或者“ Ant非常小,可以承受自身数十倍的重量”——描述了 Ant 的意图。
最初,Ant 是 Tomcat 代码库的一部分,当时它被捐赠给 Apache 软件基金会。它由 James Duncan Davidson 创建,他也是 Tomcat 的原作者。Ant 的作用是构建 Tomcat,仅此而已。
此后不久,几个开源 Java 项目意识到 Ant 可以解决他们在 Makefile 方面遇到的问题。从雅加达托管的项目和旧的 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 日 |
11.9.1 | 2018 年 3 月 27 日 |
1.9.12 | 2018 年 6 月 22 日 |
1.9.13 | 2018 年 7 月 13 日 |
14年1月9日 | 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 日 |
10年1月10日 | 2021 年 4 月 17 日 |
2011年1月10日 | 2021 年 7 月 13 日 |
2012年10月10日 | 2021 年 10 月 19 日 |
2013年10月10日 | 2023 年 1 月 10 日 |
2014年10月10日 | 2023 年 8 月 20 日 |
您需要在系统上安装 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 master 分支 | 1.8 |
当我尝试提取
tar.gz
分发文件时出现校验和错误。为什么?
Ant 的发行版包含长度超过 100 个字符的文件名,标准 tar 文件格式不支持这种情况。tar 的几种不同实现使用不同且不兼容的方式来解决此限制。
Ant 的 <tar> 任务可以创建使用 GNU tar 扩展的 tar 档案,这在整理发行版时已被使用。如果您使用不同版本的 tar(例如,Solaris 附带的版本),则无法使用它来提取存档。
解决方案是安装 GNU tar(可以在此处找到),或者使用 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 应用程序运行测试套件(例如使用 Cactus 或HttpUnit) 。这样您将获得测试结果和编译后的 JSP。
核心思想是使用名称与操作系统名称一致的属性文件。然后只需使用内置属性os.name。
为了更好地使用,您还应该提供一个带有默认值的文件。但要小心正确的操作系统名称。对于测试,只需在所有计算机上 <echo> ${os.name} 即可,您可以确保使用正确的文件名。
<property file="${os.name}.properties"/> <property file="default.properties"/>
加入开发者或用户邮件列表并向其发布消息(一个列表就足够了),包括以下信息:
- 任务/工具的名称
- 任务/工具的简短描述
- a 兼容性:说明该工具/任务与 Ant 的哪个版本兼容的条目
- URL:链接到工具/任务主页的条目
- 联系人:包含针对与工具/任务相关问题的联系人或列表的电子邮件地址或网页 URL 的条目。 请注意,我们将在页面上添加一个链接,因此添加的任何电子邮件地址都不会被混淆,并且可能(并且可能会)被机器人滥用,以获取垃圾邮件地址的网站。
- 许可证:包含工具/任务许可证类型的条目
此信息的首选格式是此文档的补丁 。
如果您向 Ant 编写了比“简单插件”更大的内容,最好将链接添加到projects.html。添加它的过程是相同的。要修补的文件是此 文档。该文件的语法是相同的。
除了有关使用 Ant 的大量信息外,该 手册还包含有关如何使用新任务扩展 Ant 的信息。此信息可以在“使用 Ant 进行开发”下找到。
很可能其他人已经创建了您想要创建的任务,因此 首先查看外部工具和任务以及 相关项目可能是明智的做法。
使用属性。使用可让您在 Ant 命令行上定义属性值。然后,这些属性可以像任何普通属性一样在构建文件中使用:将放入
.ant
-Dname=value
${name}
value
通过“魔法”属性支持几个开关:
转变 | 财产 | 默认 |
---|---|---|
+E | 编译器.emacs | false == 未设置 |
+P | 构建.编译器.迂腐 | false == 未设置 |
+F | 构建.编译器.fulldepend | false == 未设置 |
(仅适用于 Ant < 1.4;之后替换为
任务nowarn
的属性<javac> 。) -nowarn |
构建.编译器.警告 | true == 未设置 |
对于 Ant >= 1.5,您还可以
<compilerarg>
在任务中使用嵌套元素
<javac>
。
简短的答案是“使用:<
”。
长答案是,这可能无论如何都不会达到您想要的效果(请参阅下一节)。
假设您想重定向
m4
命令的标准输出流以写入文件,例如:
shell-prompt> m4 foo.m4 > foo
并尝试将其翻译成
<exec executable="m4"> <arg value="foo.m4"/> <arg value=">"/> <arg value="foo"/> </exec>
这不会达到您的预期。输出重定向是由 shell 执行的,而不是命令本身,因此应为:
<exec executable="/bin/sh"> <arg value="-c" /> <arg value="m4 foo.m4 > foo" /> </exec>
请注意,您必须在最后一个元素中使用value
属性
<arg>
,以便将命令作为单个带引号的参数传递。或者,您可以使用:
<exec executable="/bin/sh"> <arg line='-c "m4 foo.m4 > foo"'/> </exec>
请注意嵌套在单引号内的双引号。
在本机 Unix 系统上,您应该能够直接运行 shell 脚本。在运行 Unix 类型 shell 的系统上(例如 Windows 上的 Cygwin),改为执行(命令)shell -cmd
对于批处理文件、sh
shell 脚本 - 然后将批处理文件或 shell 脚本(加上脚本的任何参数)作为单个命令,分别使用/c
或
-c
开关。
有关正在执行的示例任务,请参阅
上面的部分。对于批处理文件,请使用以下内容:<exec>
sh
<exec dir="." executable="cmd" os="Windows NT"> <arg line="/c test.bat"/> </exec>
这个问题其实有好几种答案。
如果您只有一个已设置的属性和一个未设置的属性要测试,则可以为目标同时指定一个属性if
和一个unless
属性,它们的行为就像“与”在一起一样。
如果您使用的是 Ant 1.3 或更早版本,则处理所有其他情况的方法是将目标链接在一起以确定您要测试的特定状态。
要了解其工作原理,假设您具有三个属性:
prop1
、prop2
和prop3
。你想要测试它prop1
并且prop2
已经设置好了,但事实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>
任务不会将属性更改传递回调用它们的环境,因此您无法
result
在cond-if-3
目标中设置属性,然后
<echo message="result is ${result}"/>
在cond
目标中执行操作。
从 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>
这个版本有两个优点:
a
如果尚未设置 属性,${a}
则计算结果为${a}
。- 要在 Ant 中获得一个文字
$
,您必须用另一个文字对其进行转义$
- 这也会破坏${
序列的特殊处理。
由于测试文字${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" ?>
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#{${anotherprop}}
(外部 Ant 插件),您不仅
可以在 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 会将源文件的时间戳与结果文件的时间戳进行比较.class
。打开所有源文件来找出它们属于哪个包是非常低效的。相反,Ant 希望您将源文件放置在反映包层次结构的目录层次结构中,并使用 属性将 Ant 指向此目录树的根目录srcdir
。
说你有<javac srcdir="src"
destdir="dest"/>
。如果 Ant 找到一个文件
src/a/b/C.java
,它会期望该文件位于包中
a.b
,因此生成的.class
文件将是dest/a/b/C.class
.
如果您的源树目录结构与您的包结构不匹配,Ant 的启发式方法将不起作用,并且它将重新编译最新的类。Ant 并不是唯一需要这样的源代码树布局的工具。
如果您的 Java 源文件未声明为任何包的一部分,您仍然可以使用该<javac>
任务正确编译这些文件 - 只需将
srcdir
和destdir
属性设置为源文件所在的实际目录和类文件的目录应该分别进入。
我使用了一个<delete>
任务来删除不需要的 SourceSafe 控制文件(CVS 文件、编辑器备份文件等),但它似乎不起作用;文件永远不会被删除。怎么了?
发生这种情况的原因可能是,默认情况下,Antvssver.scc
从 FileSet 中排除 SourceSafe 控制文件 ( ) 和某些其他文件。
这就是你可能所做的:
<delete> <fileset dir="${build.src}" includes="**/vssver.scc"/> </delete>
您需要关闭默认排除,它将起作用:
<delete> <fileset dir="${build.src}" includes="**/vssver.scc" defaultexcludes="no"/> </delete>
有关默认排除的模式的完整列表,请参阅用户手册。
我有一个目标,如果设置了属性,我想跳过,因此我将其作为unless="property"
目标的属性,但该目标依赖的所有目标仍会执行。为什么?
依赖项列表是在运行任何目标之前由 Ant 生成的。这允许依赖目标(例如目标
init
)设置可以控制依赖关系图中较高目标的执行的属性。这是一件好事。
但是,当您的依赖项将较高级别的任务分解为几个较小的步骤时,这种行为就会变得违反直觉。有几种可用的解决方案:
- 对每个依赖目标设置相同的条件。
- 使用 执行步骤
<antcall>
,而不是在depends
属性内指定它们。
在我的 中<fileset>
,我放入了
<exclude>
所有文件,然后是
<include>
我想要的文件,但它根本没有给我任何文件。怎么了?
创建文件集时,a 中的<include>
和
标记的顺序将被忽略。相反,所有
元素都会一起处理,然后再处理所有元素
。这意味着这些
元素仅适用于由这些元素生成的文件列表
。<exclude>
<fileset>
<include>
<exclude>
<exclude>
<include>
要获取所需的文件,请仅关注
<include>
获取这些文件所需的模式。如果您发现需要修剪元素生成的列表
<include>
,请使用
<exclude>
elements。
ant
即使我将所需的 jar 放在外部文件中并通过或build.properties
引用它们,
也无法通过 javac 构建我的程序
。
pathelement
classpath refid
当ant
从外部文件加载属性时,它不会触及属性的值,例如,尾随空白不会被修剪。
如果该值表示文件路径,例如需要编译的 jar,则需要该值的任务(例如 javac)将无法编译,因为由于尾随空格而无法找到该文件。
Ant 创建带有小写目录的 WAR 文件
web-inf
或带有小写
meta-inf
目录的 JAR 文件。
不,没有。
您可能在 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 或清单相关任务时,清单中的长行都会以 70 个字符换行,并且生成的 jar 在我的应用程序服务器中不起作用。 Ant为什么要这么做?
Ant 实现 Java Jar 文件规范。请参阅注释部分,其中讨论了行的最大允许长度和连续字符的概念。
如果 Ant 生成的 jar 文件在您的应用程序服务器中不起作用,并且该失败是由于包装的清单造成的,那么您需要咨询您的应用程序服务器提供商,因为这是他们的应用程序服务器中的错误。然而,更有可能的是您的类路径规范存在问题。问题不在于 Ant 对类路径的包装。
在检查并确保问题不是由您的类路径规范引起之前,请勿提出有关此问题的错误。
<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
从 Ant 1.8.0 开始,文本“more”已添加到行集中,如果filtertrace
已设置该属性,这些行将从堆栈跟踪中过滤掉。目的是抑制迹线底部的“24 more ...”线。
如果失败消息包含“更多”一词,则包含该消息的行也将被删除。Ant 1.8.3 发布后应该会修复此问题。
唯一现有的解决方法是禁用filtertrace
或更改失败消息以不包含“更多”一词。
macrodef
例如,如果您的ed 任务包含属性,则属性引用将扩展两次
<macrodef name="echotest"> <attribute name="message" /> <sequential> <echo message="@{message}" /> </sequential> </macrodef> <echotest message="$${basedir}" />
回显 basedir 属性的值,而不是人们期望的文本 ${basedir} 。
发生这种情况是因为${}
序列在扩展序列之前和之后扩展了一次@{}
。这是使本常见问题解答中的宏定义等内容正常工作所必需的。它使
<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 集成的部分。
为什么 (X)Emacs/vi/MacOS X 的项目构建器不能正确解析 Ant 生成的错误消息?
Ant 在所有日志消息前面添加了一个带有当前任务名称的“横幅” - 并且编辑器中没有内置的正则表达式可以解释这一点。
您可以通过使用开关调用 Ant 来禁用此横幅
-emacs
。要使 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>
- 但这有一些问题:
- 它不知道所需的属性。只有手动调整该文件才有帮助。
- 它并不完整 - 如果您通过
<taskdef>
它添加新任务,它不会知道。请参阅 Michel Casabianca 的此页面,了解此问题的解决方案。请注意,您可以在此页面下载的 DTD 基于 Apache Ant 0.3.1。 - 它甚至可能是无效的 DTD。由于 Ant 允许任务编写者定义任意元素,因此名称冲突会经常发生 - 如果您的 Ant 版本包含可选的
<test>
和<junit>
任务,则有两个名为test
(任务 和 的嵌套子元素<junit>
)的 XML 元素具有不同的属性列表。这个问题无法解决;DTD 没有提供足够丰富的语法来支持这一点。
您可以使用 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
有关所需属性的详细信息,请参阅侦听器和记录器文档。
对于旧版本的 Ant,您可以使用自定义 BuildListener 在 buildFinished() 方法中发送电子邮件。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
确保mail.jar
来自 JavaMail 和
activation.jar
来自
Java Beans Activation Framework 的文件位于您的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 无法知道分叉进程是否会读取任何输入,因此即使该进程不需要一个线程,它也会启动这样一个线程。
这种行为会导致奇怪的副作用,例如当构建分叉新进程作为类 Unix 系统上的
后台进程运行时,Ant 进程会被挂起,或者<input>
任务在任务之后需要额外的输入<exec>
。
幸运的是,有一种解决方法可以解决此问题,
如果您知道分叉进程不消耗任何输入,则始终inputstring=""
为任何任务(或其同级任务之一)指定。<exec>
对于某些 Java 源文件,可能会导致Sun 的 javac 编译器抛出StackOverlowError 。据我们所知,这不是由 Ant 中的错误触发的。
<javac>
通过将的 fork 属性设置为 true 可以解决此问题
。
当从没有 junit.jar 的源版本构建 Ant 1.7.0 时,构建失败并显示消息“除非存在 JUnit,否则我们无法构建测试 jar”。
在 Ant 1.7.0 中,我们开始添加 ant-testutil.jar 作为发行版的一部分,这会导致对 JUnit 的硬依赖 - 至少在版本 1.7.0 中是这样。不幸的是安装文档没有这么说。
有两种解决方法:
- 构建 Ant 时将 junit.jar 添加到 CLASSPATH 中。
- 更改 Ant 的构建文件并从 dist-lite 目标的依赖列表中删除 test-jar。
<chmod> 或 <exec> 在 Unix 上的 Ant 1.3 中不起作用
antRun
中的脚本具有ANT_HOME/bin
DOS 而不是 Unix 行结尾;您必须从此文件中删除回车符。<fixcrlf>
这可以通过使用 Ant 的任务或类似的东西来完成:
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
这个问题收集了一个常见的问题类型:一个任务需要一个外部库,并且它有一个嵌套的类路径元素,以便您可以将其指向这个外部库,但是除非您将外部库放入或放置它,否则这不起作用在
ANT_HOME/lib
.
在我们讨论Ant 1.5.x和Ant 1.6.x的解决方案之前,有必要了解一些背景知识。
当您在 Ant 中指定嵌套时<classpath>
,Ant 会创建一个使用您指定的路径的新类加载器。然后它尝试从此类加载器加载其他类。
在大多数情况下 - 例如使用 <style> 或 <junit> - Ant 不会直接加载外部库,而是加载的类会这样做。
在这种情况下<junit>
是任务实现本身,在这种情况下
<style>
是类的实现
org.apache.tools.ant.taskdefs.XSLTLiaison
。
从 Ant 1.7 开始, <junit>
不再要求您将其包含junit.jar
在 Ant 的启动类路径中,即使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 的包装脚本(ant
或ant.bat
)将所有
.jar
文件添加到ANT_HOME/lib
到
CLASSPATH
,因此
对于本答案的其余部分, “in CLASSPATH
”应表示“在您的
CLASSPATH
环境变量中或
”。ANT_HOME/lib
问题的根源在于需要外部库的类位于CLASSPATH
.
让我们看看加载 <junit> 任务时会发生什么。Ant 的类加载器将首先参考引导类加载器,它尝试从
CLASSPATH
. 引导类加载器不知道有关 Ant 类加载器的任何信息,甚至不知道您指定的路径。
如果引导类加载器可以加载 Ant 要求它加载的类(如果是 的optional.jar
一部分,则可以CLASSPATH
),该类也会尝试从中加载外部库CLASSPATH
- 它不知道其他任何内容 - 并且不会找到除非图书馆也在里面CLASSPATH
。
要解决这个问题,您有两个主要选择:
- 将您需要的所有外部库也放入其中,
CLASSPATH
这不是您想要的,否则您将找不到此常见问题解答条目。 - 从 .xml 文件中删除加载外部库的类
CLASSPATH
。
optional.jar
最简单的方法是从 中删除
ANT_HOME/lib
。如果这样做,您将必须执行<taskdef>
所有可选任务并在指向 的新位置的任务<classpath>
中使用嵌套元素。另外,不要忘记将新位置添加
到
您的
任务或
任务中。<taskdef>
optional.jar
optional.jar
<classpath>
<style>
<junit>
如果您想避免<taskdef>
所需的所有可选任务,唯一的其他选择是删除不应通过引导类加载器加载的类,optional.jar
并将它们放入单独的存档中。将此单独的存档添加到
<classpath>
您的
<style>
或<junit>
任务的 中 - 并确保单独的存档不在
CLASSPATH
.
如果<junit>
您必须删除
org/apache/tools/ant/taskdefs/optional/junit
目录中的所有类(如果<style>
它*Liaison
是
org/apache/tools/ant/taskdefs/optional
.
如果您使用选项来分解optional.jar
或<junit>
删除
ant-junit.jar
,您仍然必须使用
<taskdef>
带有嵌套的
a<classpath>
来定义 junit 任务。
<style> 或 <junit> 忽略我的 <classpath> - Ant 1.6.x 版本
请先阅读一般条目,然后再继续。
Ant 1.6.x 的包装器脚本不再添加ANT_HOME/lib
toCLASSPATH
的内容,相反,Ant 将在引导类加载器之上创建一个类加载器 - 让我们将其称为本答案其余部分的核心加载器 - 它保存 的内容
ANT_HOME/lib
。Ant 的核心及其任务将通过该类加载器而不是引导类加载器加载。
这导致 Ant 1.5.x 和 1.6.x 之间存在一些微小但显着的差异。最重要的是,属于 Ant 1.6.x 一部分的第三方任务CLASSPATH
将不再在 Ant 1.6.x 中工作,因为该任务现在无法找到 Ant 的类。从某种意义上说,这与本条目所涉及的问题相同,只是
ant.jar
现在已成为有问题的外部库。
该 coreloader 还保存使用 Ant命令行参数~/.ant/lib
指定的任何文件或目录的内容
。-lib
让我们看看加载 <junit> 任务时会发生什么。Ant 的类加载器将首先参考引导类加载器,它尝试从
CLASSPATH
. 引导类加载器不知道有关 Ant 类加载器的任何信息,甚至不知道您指定的路径。如果使用引导类加载器找不到该类,它将接下来尝试核心加载器。同样,核心加载器对您的路径一无所知。
如果 coreloader 可以加载 Ant 要求它加载的类(如果是ant-junit.jar
in
则可以ANT_HOME/lib
),该类也会尝试从 coreloader 加载外部库 - 它不知道其他任何东西 - 并且不会找到它,除非该库也在CLASSPATH
coreloader 中。
为了解决这个问题,您有以下主要选择:
- 将您需要的所有外部库也放入其中,
CLASSPATH
这不是您想要的,否则您将找不到此常见问题解答条目。 - 将您需要的所有外部库放入
ANT_HOME/lib
或中.ant/lib
。这可能仍然不是您想要的,但您可能会重新考虑该.ant/lib
选项。 - 始终使用命令行开关启动 Ant
-lib
并指向外部库(或保存它们的目录)。 - 从 coreloader 中删除加载外部库的类。
在 Ant 1.6 中optional.jar
,已被拆分为多个 jar,每个 jar 都包含对外部库具有相同依赖关系的类。您可以将“有问题的”罐子移出ANT_HOME/lib
。对于
<junit>
任务来说就是这样
ant-junit.jar
。
如果这样做,您将必须执行<taskdef>
所有需要外部库的可选任务,并在指向 的新位置的任务<classpath>
中使用嵌套元素
。另外,不要忘记将新位置添加到
您的
任务或
任务中。<taskdef>
ant-*.jar
ant-*.jar
<classpath>
<style>
<junit>
例如
<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 发布后才被注意到。
您的容器类将需要修改以检查任务是否是 UnknownElement 并对其调用执行以将其转换为任务并执行它。(参见 apache.tools.ant.taskdefs.Sequential)
如果你想对任务进行更多处理,你需要使用 apache.tools.ant.taskdefs.Antlib#execute() 中的技术,这确实利用了一个 1.6 方法调用 (UE#getRealObject()),你需要使用 UE#getTask() 代替 - 这将为非任务返回 null(例如文件集 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.Z
并且JAVA_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
符号链接并最终递归到所有已安装的虚拟机中。更糟糕的是,它将进入/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 中存在一个帧注入错误,由 Java 7 更新 25 之前的所有 Oracle JDK 的 javadoc 工具生成。
如果您无法升级 JDK,您可以使用 Oracle 提供的 patchtool。或者,作为问题 55132macrodef
的一部分提供的内容也可以用作构建过程的一部分。
Ant 1.9.2 将对生成的 javadoc 作为 javadoc 任务的一部分进行后处理。