使用Ant执行程序

之前我们讲解了一个构建过程来编译和测试JAVA源代码,在测试表明代码是正确的之后,我们就该运行它了。这意味着应该探索Ant执行外部程序的能力的时候了。这些可外部程序可以是JAVA程序也可以是本地程序。 

1.       使用Ant执行外部程序的原因: 
由于Ant得益于其内置的任务,所以可以自己完成很多工作而不需要求助于外部代码。但是在大型工程中,很快就会发现它们需要使用外部程序,既可能使用本地程序,又可能使用JAVA应用程序。 
      实际的构建或测试应用程序是最常见的Ant内部运行的程序,这些程序的任务就是在主程序中进行单元测试、系统测试和载入测试。其他常见的外部程序就是“遗留构建步骤”:软件的某些部分需要一个本地编译器、Perl脚本或仅仅是一些用在构建中的本地工具程序。 
      从Ant内部运行程序时,有两个方案: 
l         编写一个定制的Ant任务来调用程序,适用于需要在很多构建文件中使用同一个外部程序的情况。(适用于长期项目,经常工程编写Ant任务包装器)。 
l         编写一个新的Ant任务,这个任务只是从构建文件中调用程序。从构建文件中直接调用程序是编写定制任务的基础。 
Ant可以包装本地应用程序,而JAVA应用程序可以在Ant的JVM中运行,也可以在其外部运行。无论执行什么类型的应用程序,也无论怎样执行它,Ant都会挂起构建本身直至程序结束。程序的所有控制台输出都会加入到Ant的日志工具里,通常显示到屏幕里。被包装的程序无法读取控制台的输出,因此需要用户输入的程序无法运行。 
2.       运行JAVA程序: 
启动JAVA程序是Ant的拿手好戏。这种方法最好的一个特性就是可以轻而易举的指定classpath。它比在编写批处理或者是SHELL脚本时手工指定每个类库要容易得多;它可以将classpath中的lib/**/*.jar拥有的 所有文件统统包含近来。 
Ant执行JAVA程序另一个好处是它可以在当前的JVM中运行程序。即使指定的classpath是由定制的类载入器提供的,也可以做到。在当前的JVM内运行的程序可以减少启动延迟;它只在载入新类时消耗时间,因此有助于构建保持快速。然而,在新的JVM中执行代码也有很多理由,”forking”(建立新进程),它在一些情形下更适用: 
l         如果不建立新进程,就无法指定一个新的工作路径。 
l         当你在建立新进程时,如果遇到与类载入器有关的奇怪的错误或者安全冲突,这很可能是因为在两个载入器里载入了同一个类:原先Ant中的类载入器和一个新的类载入器。在父类载入器或子类载入器中建立新进程或者追踪错误的JAR,然后移除它。 
l         不能在同一个的JVM中执行JAR;而必须建立新的进程。 
l         需要大量内存或leaky的JAVA程序应当运行在它们自己的JVM里,并给这个JVM分配适当的内存空间。 
l         建立一个新进程也允许在另一个版本的虚拟机中运行代码。这个版本可以不同于启动ANT的虚拟机版本。 
a)       首先介绍一下<java>任务: 
建立一个JAVA类,然后打印入口参数,为了讲解classpath,这里我们引入一个记录日志的软件包,log4j来记录日志: 
工程目录如下: 


类代码如下: 
package com.neusoft.test; 

import org.apache.log4j.Logger; 

publicclass TestJava { 
      privatestatic Logger logger = Logger.getLogger(TestJava.class); 

      /** 
       *enterpointmethod 
       * 
       *@paramargs 
       *            enterpoingarguments 
       */ 
      publicstaticvoid main(String[] args) { 
           if (args.length <= 0) { 
                 logger.error("Bad arguments!"); 
                 System.out.println("Usage:\n\tjava TestJava arg1 args2 ..."); 
                 System.exit(-1); 
           } 
           for (int i = 0; i < args.length; i++) { 
                 logger.info("arg" + i + "=" + args[i]); 
                 System.out.println("arg" + i + "=" + args[i]); 
           } 
      } 

在不输入入口参数的情况下,打印帮助信息,并退出程序,在输入入口参数的情况下,执行程序,并打印参数。 
其中log4j需要一个配置文件,示例如下: 
<?xml version="1.0" encoding="UTF-8" ?> 
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> 
      <appender name="error" class="org.apache.log4j.FileAppender"> 
           <param name="File" value="${user.dir}/logs/error.log" /> 
           <layout class="org.apache.log4j.PatternLayout"> 
                 <param name="ConversionPattern" 
                      value="%d{yyyy/MM/dd HH:mm:ss},%p,%C.%M,%m%n" /> 
           </layout> 
      </appender> 
      <root> 
           <priority value="DEBUG" /> 
           <appender-ref ref="error" /> 
      </root> 
</log4j:configuration> 
这里设置了一个文件类型的日志输出器,所有日志输出都将被输出到用户目录的logs/error.log文件中,关于log4j的知识,请参考本blog其他文章。运行完成后的工程目录如下图所示: 

error.log中将记录日志信息。 

下面我们来创建构建文件: 
<target name="run " depends="compile"> 
<echo>running a search</echo> 
<java classname="com.neusoft.test.TestJava"> 
<arg value="The"/> 
<arg value="current"/> 
<arg value="dir"/> 
<arg value="is"/> 
<arg file="."/> 
</java> 
</target> 

这样设置还不够,还需要指定classpath,否则可能会出现如下错误: 
Buildfile: C:\eclipse\workspace\Ant_Chapater5\build.xml 
init: 
    [mkdir] Created dir: C:\eclipse\workspace\Ant_Chapater5\build 
    [mkdir] Created dir: C:\eclipse\workspace\Ant_Chapater5\build\classes 
compile: 
    [javac] Compiling 1 source file to C:\eclipse\workspace\Ant_Chapater5\build\classes 
run: 
     [java] Could not find com.neusoft.test.TestJava. Make sure you have it in your classpath 
      …… 
BUILD SUCCESSFUL 
Total time: 4 seconds 

因为我们没有配置classpath。当任何指定的classpath都不存在时,<java>任务就使用Ant自身的classpath:诸如ant.jar以及其他任何位于ANT_HOME/lib目录下的类库,再加上系统CLASSPATH环境变量的内容。就几乎所有<java>任务而言,应该另外指定一个classpath。当这样做的时候,已经存在的所有非java和javax包的classpath中的内容就不再可用了。这种方式与<javac>不同,在<javac>中如果够文件不指定其他值,则自动包含Ant运行时classpath。 

添加classpath很容易:在<classpath>元素中写路径或者提供classpath属性一个字符串形式的路径。如果想做多个不同的地方使用classpath,最好先设置好classpath,然后再通过classpathref属性引用它。一个常见的惯例是: 
用第二个包含有新的生成类的classpath来扩展第一个编译时classpath,而不管这个新的生成类是档案文件形式还是.class文件的目录树形式。 
我们也采用这种办法,一个用于编译,一个用于执行: 
<path id="compile.classpath"> 
<pathelement path="src/log4j-1.2.13.jar"/> 
</path> 
<path id="run.classpath"> 
<path refid="compile.classpath"/> 
<pathelement location="${build.dir}/classes"/> 
</path> 
第一个classpath包含编译时所需要的类库,第二个添加我们刚刚编写的代码。易于维护,任何编译时所需要新的类库都会自动传递到运行时classpath。 
      此时可以修改<java>任务如下: 
<java classname=" com.neusoft.test.TestJava" 
classpathref="run.classpath"> 
<arg value="The"/> 
<arg value="current"/> 
<arg value="dir"/> 
<arg value="is"/> 
<arg file="."/> 
</java> 

现在构建文件如下: 
<?xml version="1.0" encoding="UTF-8" ?> 
<projectname="run-java-application"default="run"> 

      <propertyname="build.dir"value="build"/> 
      <pathid="compile.classpath"> 
           <pathelementlocation="src/log4j-1.2.13.jar"/> 
      </path> 
      <pathid="run.classpath"> 
           <pathrefid="compile.classpath"/> 
           <pathelementlocation="${build.dir}/classes"/> 
      </path> 
      <targetname="init"> 
           <deletedir="${build.dir}"/> 
           <deletedir="logs"/> 
           <mkdirdir="${build.dir}"/> 
           <mkdirdir="${build.dir}/classes"/> 
      </target> 
      <targetname="compile"depends="init"> 
           <javacsrcdir="src"destdir="${build.dir}/classes"> 
                 <classpathrefid="compile.classpath"/> 
           </javac> 
      </target> 
      <targetname="run"depends="compile"> 
           <javaclassname="com.neusoft.test.TestJava"> 
                 <classpathrefid="run.classpath"/> 
                 <argvalue="The"/> 
                 <argvalue="current"/> 
                 <argvalue="dir"/> 
                 <argvalue="is"/> 
                 <argfile="."/> 
           </java> 
      </target> 
</project> 

构建过程运行如下: 
Buildfile: C:\eclipse\workspace\Ant_Chapater5\build.xml 
init: 
    [mkdir] Created dir: C:\eclipse\workspace\Ant_Chapater5\build 
    [mkdir] Created dir: C:\eclipse\workspace\Ant_Chapater5\build\classes 
compile: 
    [javac] Compiling 1 source file to C:\eclipse\workspace\Ant_Chapater5\build\classes 
run: 
     [java] log4j:WARN No appenders could be found for logger (com.neusoft.test.TestJava). 
     [java] log4j:WARN Please initialize the log4j system properly. 
     [java] arg0=The 
     [java] arg1=current 
     [java] arg2=dir 
     [java] arg3=is 
     [java] arg4=C:\eclipse\workspace\Ant_Chapater5 
BUILD SUCCESSFUL 
Total time: 4 seconds 

执行完成后工程目录如下: 


但是,可以看到,由于构建过程中不能找到log4j的配置文件,所以有如下警告: 
[java] log4j:WARN No appenders could be found for logger (com.neusoft.test.TestJava). 
[java] log4j:WARN Please initialize the log4j system properly. 

也就没有生成logs/error.log日志文件。这个原因本人也在探索中,试图将log4j.xml添加到classpath中,但是还是出问题。如果有遇到类似问题的朋友,希望帮忙解决。 
个人初步分析原因是:不能在同一个的JVM中执行JAR;而必须建立新的进程。因为写日志相当于执行了log4j档案文件中的类,所以不能在同一个JVM中运行。 

3.       参数: 
在<java>任务中,最重要的可选参数是嵌套参数列表。可以通过一个简单值,一行文本,一个用于参数列表优先级解析的文件,或者一个路径来命名一个参数。可以在任务的<arg>元素里指定它们,<arg>元素支持以下四个属性,Ant按这些参数声明的顺序将它们传递给JAVA程序: 
value:字符串值。(对XML的特殊记号要进行转义:>变为&gt;   &#x0a;代表换行) 
file:文件或目录,在使用之前解析为绝对地址。然后再传递。 
line:传递给程序的整个行 
path:一个字符串,包含有用冒号或分号隔开的文件或目录。 

4.       定义系统特性: 
系统是那些要传给JAVA命令行(如:-Dproperty=value)的参数的定义。嵌套的<sysproperty>元素可以用来定义要传递的特性。 
<sysproperty key="socksProxyHost" value="socks-server"/> 
<sysproperty key="socksProxyPort" value="1080"/> 
有两种可选方法能代替value参数:file和path。就如带参数一样,file属性为文件命名;Ant对相对引用进行解析并传入绝对文件名,并使用本地平台的文件分隔符。path属性与之类似,只是允许列入多个文件: 
<sysproperty key="configuration.file" file="./config.properties"/> 
<sysproperty key="searchpath" path="build/classes:lib/j2ee.jar" /> 

5.       在新的JVM中运行程序 
<java>任务将运行在当前的JVM中,除非fork属性被设置为true,这将减少程序启动的时间。做为实验,我们可以将TestJava放在一个新的JVM中运行。 
<target name="run-search-fork" depends="create-jar"> 
<echo>running a search</echo> 
<java classname="com.neusoft.test.TestJava" 
classpathref="run.classpath" 
fork="true"> 
<argvalue="The"/> 
                 <argvalue="current"/> 
                 <argvalue="dir"/> 
                 <argvalue="is"/> 
<argfile="."/> 
</java> 
</target> 

6.       设置环境变量: 
你可以在一个建立新进程的JVM里使用嵌套元素<env>来设置环境变量。这个元素的语法与<sysproperty>元素是一样的。 

7.       控制新的JVM 

可以选择不同于Ant的JAVA运行时环境,只要通过jvm属性设置JVM命令就可以了,这对与从一个旧的JVM中运行程序时是非常有用的,诸如运行在JAVA1.1系统里的测试,或者可能在JAVA的一个未来版本的BETA版。可以给这个JVM指定参数来控制它,最常用的选项就是设置内存数量,属性maxmemory,这个属性具有一些幕后只能 可以区分JAVA 1.1和1.2系统,生成合适的命令。 
可以通过<java>嵌套的<jvmarg>元素提供通用的 JVM参数。这些参数的确切用法与<arg>元素相同。 
<target name="run-search-jvmargs" depends="create-jar"> 
<property name="Search.JVM.extra.args" value="-Xincgc"/> 
<java classname=" com.neusoft.test.TestJava" 
classpathref="run.classpath" 
fork="true" 
maxmemory="64m"> 
<jvmarg line="${Search.JVM.extra.args}"/> 
<argvalue="The"/> 
                 <argvalue="current"/> 
                 <argvalue="dir"/> 
                 <argvalue="is"/> 
<argfile="."/> 
</java> 
</target> 

当fork=”false”时,所有的JVM选项都不起作用;仅仅显示一行警告信息。 
8.       使用failonerror处理错误 
尽管核心的构建步骤(compile和JAR)必须完成后才可以认为整个构建是成功的,但是一些其他的构建过程中的任务,它们的失败并不重要。例如发送进度报告的电子邮件并不因为邮件服务器确实而终止构建,同样配置过程中的很多方面,诸如停止一个WEB服务器等,并不会因失败而终止构建。 
一些Ant任务有一个共同的属性,failonerror,它可以控制当任务失败时是否终止构建。多数任务的默认值是”true”,意思是任何构建过程中任务的失败都会导致构建失败,并导致ANT的BUILD FAILED信息。 
<java>任务支持这个属性,但仅在新的JVM中才支持,如果JAVA程序的返回值不等于0,则终止构建。当JVM内部调用System.exit()时,整个构建过程会因为JAVA停止运行而突然停止并且不显示BUILD FAILED信息:这个调用退出了ANT以及程序。 
例如我们将failonerror设置为true,并且不传递任何参数给程序,将调用System.exit(-1),此时JAVA程序的输出为: 

<target name="run-search-invalid" depends="compile"> 
<echo>running a search</echo> 
<java classname="com.neusoft.test.TestMain" 
classpathref="run.classpath" 
failοnerrοr="true" 
fork="true"> 
<argvalue="The"/> 
                 <argvalue="current"/> 
                 <argvalue="dir"/> 
                 <argvalue="is"/> 
<argfile="."/> 
</java> 
</target> 
构建输出如下: 
run-search-invalid: 
[echo] running a search 
[java] Usage: 
java TestJava arg1 args2 ... 
BUILD FAILED 
C:\AntBook\app\tools\build.xml:532: Java returned: -1 

9.       执行JAR文件: 
当一个JAR文件由命令行中的java –jar命令启动时,该JAR文件可以在清单(manifest)上列出一个类的名字做为执行入口,ANT也可以类似地运行JAR文件,但只能在建立新进程的JVM中。这是因为执行JAR文件的过程会载入classpath列表中所列的文件,而其他细节则与JAVA的“扩展”有关。通过一个jar属性设置为文件的文职可以运行JAR文件: 
<target name="run-search-jar" depends="create-jar"> 
<echo>running a search</echo> 
<java 
jar="${jarfile.path}" 
classpathref="run.classpath" 
failοnerrοr="true" 
fork="true"> 
<arg value="The" /> 
                 <arg value="current" /> 
                 <arg value="dir" /> 
                 <arg value="is" /> 
<arg file="." /> 
</java> 
</target> 

这个目标并不能实际工作,因为还没有设置清单(manifest),这些内容等到下一章再讲解。 

10.       调用第三方程序: 
可以使用任务运行由第三方提供的程序,假设我们部署过程的一个环节中包含了停止WEB SERVER的操作,比如TOMCAT 5.5,这在部署中是个常见的动作;为了从构建中进行部署,必须实现自动化每个步骤。幸运的是多数WEB SERVER都提供了这样或那样的方法来做到这一点:从TOMCAT的启动脚本中提取命令来完成<javac>任务: 
<property environment="env"/> //获取环境变量 
<target name="stop-tomcat" 
description="stop tomcat if it is running"> 
<java classname="org.apache.tomcat.startup.Tomcat"> 
<classpath> 
<fileset dir="${env.TOMCAT_HOME}/lib"> 
<include name="**/*.jar"/> 
</fileset> 
</classpath> 
<arg value="-stop"/> 
<sysproperty key="tomcat.home" 
value="${env.TOMCAT_HOME}"/> //将TOMCAT的HOME目录向下传递 
</java> 
</target> 
执行该目标有以下几种结果: 
l         TOMCAT存在而且库文件位于预先假设的位置,成功停止TOMCAT 
l         本地目前没有任何版本的TOMCAT在运行,无法停止TOMCAT 
l         即使已经设置classpath,但是因为TOMCAT未安装或者环境变量未正确设置,因此造成库目录确实而无法创建classpath 
这时我们需要预先判断。 

11.       在调用之前探察JAVA程序: 
在准备调用一个JAVA类之前,在classpath上查找它是很容易的,这样做使得显示警告信息成为可能。对于TOMCAT问题,可以使用<available>任务,或者采用<condition>任务会更好,后者能够通过检测环境变量组合<available>测试: 
<target name="validate-tomcat" 
<condition property="tomcat.available"> 
<and> 
<isset property="env.TOMCAT_HOME"/> 
<available classname="org.apache.tomcat.startup.Tomcat"> 
<classpath> 
<fileset dir="${env.TOMCAT_HOME}/lib"> 
<include name="**/*.jar"/> 
</fileset> 
</classpath> 
</available> 
</and> 
</condition> 
<echo>tomcat.available=${tomcat.available}</echo> 
</target> 

这里声明了,当且仅当env.TOMCAT_HOME已经定义,且要调用的类org.apache.tomcat.startup.Tomcat位于TOMCAT目录下的classpath时,特性tomcat.available才会被设置成true。由于<and>的捷径效应,第一个测试失败时不运行第二个,因为第一个失败时,classpath是不合法的。 
      这个测试可以用语带条件的任务,或者当一个程序必须存在时,带条件的<fail>任务将被用于立即终止构建。当找不到TOMCAT时,通过使用将目标依赖于检验目标,且将条件设置为tomcat.available特性而选择了简单的略过测试: 
<target name="stop-tomcat" 
if="tomcat.available" 
depends="validate-tomcat" 
description="stop tomcat if it is running"> 
<java classname="org.apache.tomcat.startup.Tomcat"> 
<classpath> 
<fileset dir="${env.TOMCAT_HOME}/lib"> 
<include name="**/*.jar"/> 
</fileset> 
</classpath> 
<arg value="-stop"/> 
<!— 传递给JAVA命令行的参数的定义 --> 
<sysproperty key="tomcat.home" 
value="${env.TOMCAT_HOME}"/> 
</java> 
</target> 

12.       设置超时 
Ant1.5为<java>任务扩展了timeout属性,允许以毫秒的方式指定一个JAVA应用程序最大的运行时间。只能在建立新进程的JVM中使用这一个特性。 

第二部分:使用<exec>启动本地程序 
      JAVA执行并不能给予构建文件访问底层操作系统的全部能力,或者访问本地平台的构建步骤,除非JAVA程序调用本地程序。由于本地程序的移植性很差,因此为了实现以跨平台的方式定制任务,应提供可移植性的包装方式。 
      在运行程序之前让构建文件去探测这些程序存在与否(用上一节的知识)。这是个功能强大的技巧,就如同多数与维护相关的编码,通过不懈的努力能满足绝大部分的情形。 
      要在Ant内运行一个外部程序,应该使用<exec>任务,它可以执行以下操作: 
l         指定程序名称和要传入的参数 
l         命名运行目录 
l         使用failonerror标志来控制当应用程序失败时是否终止构建 
l         指定一个最大程序持续时间,时间超过则终止程序。任务在这时被认为是失败,但是至少构建会终止,而不是挂起,这对于自动构建是很重要的。 
l         将输出存入一个文件或特性 
l         指定JAVA调用本地程序时需要预先设定的环境变量 
有一件事是它所做不到的,那就是使用一个OsFamily标志将操作限制到操作系统家族,诸如Windows或者Unix,否则将是非常方便的。<condition>任务确实拥有一个OsFamily测试可用于具有条理性的操作系统测试,但是这样的话整个目标都变成有条件的。 
将<exec>与特定的操作系统绑定在一起是不好的做法,除非这个调用直接对应操作系统的底层功能。而检测相应程序,如果存在就调用它则好的多。 
例子: 
<exec executable="ln"> 
<arg value="-s"/> 
<arg location="execution.xml"/> 
<arg location="symlink.xml"/> 
</exec> 
这个任务的用途是给文件创建符号连接,因为内置JAVA命令没有这个功能。如果可执行程序在当前路径或者在系统path目录中,则无需指定其完整路径。 
1.       设置环境变量: 
正如<java>任务支持将系统属性作为嵌套元素一样,<exec>任务允许<env>子元素设置环境变量。它与<sysproperty>元素具有相同的语法,以不同的元素名分隔。<exec>的一个特别的功能,是你可以选择程序是否继承当前的环境。通常情况下继承当前的所有设置是有意义的,诸如PATH和TEMP,但是有时候可能希望通过参数进行绝对控制: 
<exec executable="preprocess" 
newenvironment="true" > 
<env key="PATH" path="${dist.dir}/win32;${env.PATH}"/> 
<env key="TEMPLATE" file="${src.dir}/include/template.html"/> 
<env key="USER" value="self"/> 
</exec> 

即使通过newenvironment=”false”(默认值)继承已有的环境,任何显示定义的环境变量也将覆盖所传入环境变量的值。 

2.       处理错误 
<exec>属于这样一类Ant任务:这类任务默认情况下failοnerrοr=”false”。这是有原因的:最初没有返回值校验,因此当它被实现时,校验将被设为false以避免与已有构建冲突。即使JAVA任务与其他多数任务不同,但它至少和本地执行任务有统一的默认值。 
      声明failonerror为true和false,而忽略其默认值,这样使构建文件可读性好。”什么情况下出错就终止”/”什么情况下出错但不终止”。 
      failonerror并不控制在程序运行时系统对失败如何反映,在Ant 1.5中,<exec>加入了第二个失败检查,failIfExecuteFails,它控制了实际的执行失败是否被忽略。 
3.       处理超时 
运行超时杀死任务,以免陷入挂起。<exec>支持一个timeout属性,以毫秒为单位。如果设置了该属性则一个监视计时器就会启动运行。当外部程序占用时间超时后就“杀死”它。当超时发生时,监视器不会明确告诉你超时发生了,但是执行文件的返回代码被设为”1”。如果failonerror被设置,则将会终止构建。如果没有设置,那么它就会悄悄的不被忽略。 
<target name="sleep-fifteen-seconds" > 
<echo message="sleeping for 15 seconds" /> 
<exec executable="sleep" 
failοnerrοr="true" 
timeout="2000"> 
<arg value="15" /> 
</exec> 
</target> 
运行该目标,当遇到超时就会产生一个错误: 
sleep-fifteen-seconds: 
[echo] sleeping for 15 seconds 
[exec] Timeout: killed the sub-process 
BUILD FAILED 
execution.xml:18: exec returned: 1 

如果外部程序设置成将结果传入一个特性,并将failonerror设置为off,那么就没办法将为1的结果和超时区分开。 
需要在构建中插入一个暂停时,可以使用<sleep>任务,该任务可以工作在所有平台上。 

4.       运行shell命令 
使用: 
<exec executable="cmd" failοnerrοr="true"/> 
<arg line="/c echo hello &gt; hello.txt"/> 
</exec> 
而不是 
<exec command="/c echo hello &gt; hello.txt" failοnerrοr="true"/> 
</exec> 

在shell中也是一样:应该使用 
<exec command="ps -ef | grep java &gt; processes.txt" 
failοnerrοr="false"/> 

而不是 
<exec executable="sh" failοnerrοr="true"/> 
<arg line="-c 'ps -ef | grep java &gt; processes.txt'"/> 
</exec> 

因为整个一行的内容需要shell来解释。 

5.       在程序被调用之前进行探测 
有时候当程序不可用时,可以略过一个构建步骤,或者提供有帮助的错误并终止。如果你知道程序所在的位置,就可以调用<available>任务对其进行测试,但是假如只是要求该程序在指定目录中怎么办呢?<available>任务可以在整个文件路径下搜索指定的文件,因此探测一个程序是否存在就是个简单的事情了,只要沿着环境变量PATH查找程序名字就可以了,当然,在跨平台的方式下,这并不简单:MS-DOC和UNIX系统以不同的方式命名可执行文件,有时甚至连路径变量也不同。将这些纳入考虑之中,对文件的探测就变成了一个多情况的测试,测试必须以带.exe和不带.exe扩展名两种方式来寻找可执行文件,并且MS-DOC/Windows可执行文件必须同时通过环境变量的两个选项进行搜索。Path和PATH: 
<target name="probe_for_gcc" > 
<condition property="found.gcc"> 
<or> 
<available file="gcc" filepath="${env.PATH}" /> 
<available file="gcc.exe" filepath="${env.PATH}" /> 
<available file="gcc.exe" filepath="${env.Path}" /> 
</or> 
</condition> 
</target> 

可以通过编写依赖的目标,如果程序不存在,要么使用<fail>任务,要么仅仅跳出某个执行步骤: 
<target name="compile_cpp" depends="verify_gcc" if="found.gcc"> 
<exec executable="gcc" ... /> 
</target> 

第三部分: 使用<apply>进行批量运行 
将一个文件列表传递给外部可执行文件程序的特性问题,可以使用<apply>任务来解决,这个任务接受一个文件集并将其传递给指定的应用程序,既可以一次传完,也可以逐个传递。 
      <apply>是作为<exec>的一个子类而被实现,所以<exec>任务的所有属性,都可以用于<apply>;除此之外,它还包括一个批处理的附加功能。 
例如有一个本地程序,可以将XML文件转换为PDF,它有两个参数:XML文件的路径,以及对应的PDF文件的路径: 
<apply executable="cmd" dest="docs"> 
<arg line="/c echo"/> 
<arg value="convert"/> 
<srcfile/> 
<targetfile/> 
<fileset dir ="." includes="*.xml"/> 
<mapper type="glob" from="*.xml" to="*.pdf"/> 
</apply> 

在Windows平台上运行,并使用内置echo命令,必须将可执行文件设置为cmd以便echo可以正常工作,打开/c开关使得命令行shell在echo完成后自动退出。对于同一个目录下数个xml文件的执行,输出如下: 
[apply] convert C:\AntBook\Sections\Learning\callingotherprograms\apply.xml 
C:\AntBook\Sections\Learning\callingotherprograms\docs\apply.pdf 
[apply] convert C:\AntBook\Sections\Learning\callingotherprograms\execution. 
xml C:\AntBook\Sections\Learning\callingotherprograms\docs\execution.pdf 
[apply] convert C:\AntBook\Sections\Learning\callingotherprograms\java.xml C 
:\AntBook\Sections\Learning\callingotherprograms\docs\java.pdf 
[apply] convert C:\AntBook\Sections\Learning\callingotherprograms\probes.xml 
C:\AntBook\Sections\Learning\callingotherprograms\docs\probes.pdf 
[apply] convert C:\AntBook\Sections\Learning\callingotherprograms\shells.xml 
C:\AntBook\Sections\Learning\callingotherprograms\docs\shells.pdf 

到目前为止,它所做的全部工作就是显示我们要执行的命令,但是并没有实际执行它。 
<apply>隐含依赖关系检查。目标文件新于源文件,它就被忽略。 

一旦对echo的输出结果满意,看到它为每个文件将执行预期的命令行,就可以把属性值由convert修改为executable,去掉/c echo参数,然后再回到正题。 

该任务的parallel选项的意思是”一次传入全部的文件”,而不是”并行执行该任务多次”。 

第四部分:处理输出: 
以上介绍的三个任务:<java>,<exec>和<apply>这三个任务,都允许你使用output参数将输出结果保存到文件中,可以将这个文件传入别的程序,或者一个ant任务,其中的两个任务<exec>和<apply>,还可以将调用的值存入特性,该特性接下来可以被扩展应用为其他任务的参数。例如:可以将构建阶段的结果发email给其他人: 
<exec executable="unregbean" output="beans.txt" > 
<arg value="-d"/> 
</exec> 
<mail from="build" tolist="operations" 
subject="list of installed beans for ${user.name}" 
failοnerrοr="false" 
files="beans.txt"/> 
这种将生成的文件和报告用email发出去的办法,在自动化的构建和测试系统中是常见的功能。 
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页