来源: http://grid.tsinghua.edu.cn/home/liulk/wiki/computer/AntStudy.html

</p>

   ant 的学习笔记

  Ant笔记

Ant处理方式

ant从的build文件从本质上来说,就是一个特殊的xml文件,从有真正意义的结果上来说(忽略掉里面的DataType定义),这个树只有两层.以为根结点,里面是target元素,target里面是task元素.ant再处理的时候是对于不同的level是采用不同的处理方式.对于project level来说,ant采用宽度优先遍历的方式,搜索所有的target元素,并对他们进行登记(以便后面处理),同时处理所有的非target元素,主要是一些property元素,和DataType元素.对于target level,ant采用的深度优先遍历的方式,处理所有的task元素.一边遍历,一边处理.正是因此:

  • ant会抛出两类错误:project level error和 target level error.
  • 对于target level,task之间的顺序就显得很重要了

ant的两种错误类型

  • project level error
  • target level error

    ant如何处理错误

  • 再缺省的情况下,ant在遇到第一个错误的时候就会停下
  • ant没有回滚策略.这一点应该特别注意,因为ant在处理target level的时候是一边解析一边处理的,之前没有任何的语法检查过程,如果你的build.xml文件存在target类型的错误,就很可能使得你的工程已经处理了一部分的时候,ant突然发现一个错误而停止.这时候,很可能你的工程就处于一种不确定的状态.
  • 你可以通过写listener的方式来扩展ant的错误处理.

ant的数据类型

数据类型的优先级

ant里面的property的类型可以通过命令行的-Dname=value的方式指定参数,按这种方式指定的参数的优先级大于,ant的build.xml赋值的优先级.也即使说,会覆盖掉内部的缺省的变量值.

条件测试

ant里面的条件测试可以用condition元素实现 当condition作为一个嵌套元素实现时,不支持属性操作,也就是说不能给其设施property属性,否则会出错.
当condition作为一个任务执行执行时,可以指定属性property,condtion会根据内部的逻辑计算的值,设置property指定的变量名称的变量. 那么这两个用起来有什么不同呢?

  1. 对于第一种用法,一般来说时决定程序的执行流程中用的比较多.比如fail task,里面既可以用第一种方式指定在适当的时候结束任务.如下所示:
      <!-- this is use for test whether the namespace propertiy is setted -->
      <target name="testnamespace">
        <fail message="ERROR:need namespace" >
          <!--       <condition property="namespace.available">  -->
          <condition>
        <not>
          <isset property="namespace" />
        </not>
          </condition>
        </fail>
      </target>
  2. 对于第二种方法,主要用变量和条件的检测,进而控制某些步骤是否需要执行.如:
      <target name="testShare">
            <condition property="share.available">
                <isset property="garshare.id"/>
            </condition>
        </target>
       <!-- 只有当测试为真的时候才执行这个任务 -->
        <target name="copyShare" depends="testShare" if="share.available">
            <copy todir="${gar.dest}/share">
                <fileset refid="${garshare.id}"/>
            </copy>
        </target>

[note]:当然这不是绝对的,只是那种方式更自然一点的问题.仅供参考哟.

arg

属性:

  • file (all, File,*) 代表当前目录下的一个文件,用文件名作参数
  • line (all,string,*) 一系列用空白分割的多个参数
  • path (all,string,*) 一个路径,执行的时候,会被转化为基于操作系统的绝对路径
  • value (all,string,*) 单个的commandline argument,如果你的参数中有空格,那么必须用这种方式指定

    env

  1. 属性:
  • file(同上)
  • key(all,string,Y) 环境变量的名称
  • path (同上)
  • value (同上)
  1. 获取环境变量的另一种方好方式
      <!-- Set up the 'env' prefix for environment variables -->
      <property environment="env"/>
      <property name="xalan.home" value="${env.XALAN_HOME}"/>
      <!-- Abort the build if XALAN_HOME is not set -->
      <target name="checkXalanHome" unless="env.XALAN_HOME">
        <fail message="XALAN_HOME must be set!"/>
      </target>

FileList

目的:用来支持一个文件列表,这些文件不一定存在

  1. 属性:
  • dir(file,*) 用于计算绝对路径的路径名
  • files(string,*) 用逗号分割的文件名列表
  • refif(Reference,*) 对于另一个=FileList=的引用
  1. 备注: FileList DataType主要是用来于dependset任务连用,实现文件的依赖性检查.当dependset里面定义的任何输入文件比输出文件新的话(或者输入文件被删除),所有的输出文件将被删除,以便重新生成.
      <?xml version="1.0"?>
      <project name="filelist demo" default="xslt" basedir=".">
        <filelist id="stylesheets" dir="."
              files="header.xslt,footer.xslt,body.xslt"/>
        <filelist id="xmlfiles" dir="." files="employees.xml"/>
        <target name="xslt">
          <!-- erase employeeDirectory.html if any of the XML files or
           XSLT stylesheets are newer -->
          <dependset>
        <srcfilelist refid="stylesheets"/>
        <srcfilelist refid="xmlfiles"/>
        <targetfilelist dir="." files="employeeDirectory.html"/>
          </dependset>
          <echo message="Transforming Files..."/>
          ...
        </target>
      </project>

FileSet

目的:定义一系列文件的列表,该列表中的文件必须存在,给元素支持子元素的嵌套

  1. 属性:
  • dir(path,Y) the base dir for the fileset
  • casesencitive(boolean,N) 但为false时,在进行文件名的匹配的时候是不区分大小写的,默认情况下是区分大小写的.
  • defaultexcludes(boolean,N) 从fileset中排除的文件.缺省为true,缺省排出的包括**/*~, /#*#, /.#*, /%*%, /CVS, /CVS/, /.cvsignore,/SCCS, /SCCS/, and /vssver.scc.
  • </p>

  • excludes(string,N) 对于缺省排出的附加,一个用逗号分割的pattern串.
  • excludefiles(string,N) 对于缺省排出的附加,The name of a file containing one exclude pattern per line.
  • includes(string,N) 一个逗号分割的被包含的fileList
  • </strong>

  • includesfiles(files,N) The name of a file containing one include pattern per line. 2.例子: 下面的例子定义了一个fileset,递归包含目录src下的所有.java文件,但除去该目录树下所有test子树下的.java文件
    <fileset id="sources1" dir="src"
         includes="/*.java"
         excludes="**/test/**/*.java">
    </fileset> 
  • </ul>

    等价于下面的例子

    <fileset id="sources2" dir="src">
      <include name="**/*.java"/>
      <exclude name="**/test/**/*.java"/>
    </fileset>

    下面是一个fileset和patternset结合使用的例子

      <patternset id="non.test.source">
        <include name="**/*.java"/>
        <exclude name="**/test/**/*.java"/>
      </patternset>
      <!-- later in the same buildfile -->
      <fileset id="sources4" dir="src">
        <patternset refid="non.test.source"/>
      </fileset>
      <fileset id="sources5" dir="othersrc">
        <patternset refid="non.test.source"/>
      </fileset>

    PatternSet

    1. 目的: patternset使用和fileset连用,实现一系列文件的选择.pattern支持利用pattern来实现文件的include 和 exculde.他有四个属性,include,exclude,includefiles和excludefiles.每个属性又可以用name属性指定文件pattern,用if或unless属性指定条件.
    2. 例子:
          <patternset id="xml.files">
            <include name="**/*.dtd,**/*.xml,**/*.xslt"/>
          </patternset>
          <copy todir="${deploy.dir}">
            <!-- select the files to copy -->
            <fileset dir="${src.dir}">
          <patternset refid="${xml.files}"/>
            </fileset>
          </copy>
        

    下面是另一个例子,该例子中我们排出了所有的测试单元,除非设置了includetests变量

      <?xml version="1.0"?>
      <project name="patternset_test_project" default="compile" basedir=".">
        <!-- exclude tests unless the 'includetests' property is set -->
        <patternset id="sources">
          <include name="**/*.java"/>
          <exclude name="**/*Test.java" unless="includetests"/>
        </patternset>
        ...remainder of buildfile omitted
        <target name="compile" depends="prepare">
          <javac destdir="build">
        <!--the directory from which the patternset finds files to compile-->
        <src path="src"/>
        <!-- refer to the patternset which selects the source files -->
        <patternset refid="sources"/>
          </javac>
        </target>
      </project>

    FilterSet

    1. 目的:移动或者拷贝文件的时候的,对文件的内容进行替换.该datatype具有如下属性:begintoken,endtoken,id,refid等,内部可以嵌套如下子元素:
    • filter: 有两个属性:name,知道要替代的token的名字,value:指定替代值.可用变量
    • filtersfile: 用于指定一个java properties文件,利用该文件的属性名值对进行替换.有一个必要的属性:file,指定属性文件名.
    1. 例子: 这是在拷贝文件的时候,对于文件的版权和创建时间进行替换的例子.
        <target name="tokenFilterDemo" depends="prepare">
          <!-- set up the timestamp -->
          <tstamp>
            <format property="now" pattern="MMMM d yyyy hh:mm aa"/>
          </tstamp>
          <copy todir="build" filtering="true">
            <fileset dir="src">
          <include name="**/*.java"/>
            </fileset>
            <!-- search for %COPYRIGHT! and %BUILD_DATE! -->
            <filterset begintoken="%" endtoken="!">
          <filter token="BUILD_DATE" value="${now}"/>
          <filter token="COPYRIGHT" value="Copyright (C) 2002 O'Reilly"/>
            </filterset>
          </copy>
        </target>

    Path

    1. 目的:代表文件和路径,主要用于表示classpath之类的东西.可以作为一个属性使用.当用作属性的时候,path的entry用";"或者":"分割.当作为datatype使用的时候支持以下属性:
    • location: 代表单个的文件或者目录,ant将其扩充成绝对路径
    • path: 一系列用;或:分割的文件或目录名
    • refid:这个不用说了把. 支持以下嵌套的子元素:
    • pathelement: 定义一个或者多个被包含进改路径的文件.该子元素也支持location和path属性
    • fileset: 另一种将文件和路径包含进path的语法
    • path:用于路径的嵌套
    1. 例子: 定义一个包含两个jar文件和两个目录的path元素
        <path>
          <pathelement location="${libdir}/servlet.jar"/>
          <pathelement location="${libdir}/logging.jar"/>
          <pathelement path="${builddir}"/>
          <pathelement path="${utilpath}"/>
        </path>

    下面是定义classpath的两种等价的方法

      <!-- The classpath element is implemented with the path DataType -->
      <classpath>
        <pathelement path="${builddir}"/>
      </classpath>
      <classpath path="${builddir}"/>

    Mapper

    1. 目的:主要用于指定操作的源文件和目标文件的相关性. 主要有以下属性:
    • classname: 实现mapper的类名.
    • classpath: 查找自定义mapper的类路径,改路径将被附加到系统的classpath中进行搜索
    • classpathref:引用一个定义的classpath
    • from: 操作的源
    • refid: 不用说了把
    • to:操作的目标
    • type: 指定mapper的类型,这个很重要的.
    1. 例子:
      Example 4-2. Backing up files with a glob mapper
      <?xml version="1.0"?>
      <project name="mapper demo" default="backupFiles" basedir=".">
        <!-- define a mapper for backing up files -->
        <mapper id="backupMapper" type="glob" from="*.java" to="*.java.bak"/>
        <target name="clean">
          <delete dir="bak"/>
        </target>
        <target name="prepare">
          <mkdir dir="bak"/>
        </target>
        <target name="backupFiles" depends="prepare">
          <copy todir="bak">
            <!-- select the files to copy with a fileset -->
            <fileset dir="src" includes="**/*.java"/>
            <mapper refid="backupMapper"/>
          </copy>
        </target>
      </project>
    2. mapper的类型: 主要有以下几种类型:identity, flatten, glob, merge, or regexp.下面一一介绍:
    • identity mapper maps the source files and the target files with the same name.
      <mapper type="identity"/>

    Table Identity mapper result

    source file target file
    Customer.java Customer.java
    com/oreilly/ant/Account.java com/oreilly/ant/Account.java
    • flatten mapper removes all the path imformation from files.这在拷贝多个文件到一个目录中时很有用.
      <mapper type="flatten"/>

    map result:

    Source file Target file
    Customer.java Customer.java
    com/oreilly/data/Account.java Account.java
    • glob mapper The glob mapper determines target filenames based on simple wildcard patterns. This is useful when you want to rename groups of files that already have consistent filenames
      <mapper type="glob" from="*Test.java" to="*UnitTest.java">

    map result:

    Source file Target file
    Customer.java none
    com/oreilly/data/Account.java none
    CustomerTest.java CustomerUnitTest.java
    com/oreilly/tests/CustomerTest.java com/oreilly/tests/CustomerUnitTest.java
    • merge mapper The merge mapper maps any set of source filenames to the same target filename, as specified by the to attribute. The from attribute is ignored. The merge mapper is useful when you want to compare timestamps of a set of source files against a single target file.
      <mapper type="merge" to="oreilly.zip">

    map result:

    Source file Target file
    Customer.java oreilly.zip
    com/oreilly/data/Account.java oreilly.zip
    • regxp mapper 与glob类似,出了用正则表达式代替了简单的*之外,但该mapper的结果依赖于底层(underlying)的正则表达式的实现

    自定义任务

    你可以通过改ant的ant.jar里面的default.properities文件来给ant正加新的任务,但这需要重新编译ant.推荐的方式是在build.xml终进行任务的定义.例如:

    <taskdef name="jar" classname="org.apache.tools.ant.taskdefs.Jar"/>

    Use listener

    给ant正加listener,可以将listener打成jar包,放在ANT_HOME/lib目录下,然后指定参数 -listener listenerClass

    ant -listener org.apache.tools.ant.<nop>XmlLogger

    Task

    echo

    this is the simplest test. 可以向一个文件或者标准控制台写入一个字符串. 其中写入的字符窜以message属性指定或者卸载元素中间的text section.

    ttribute     Description                             Required                                                                                
    message      the message to echo.                    Yes, unless data is included in a character section within this element.                

    下面是这个task的几个例子

    <echo file="runner.csh" append="false">#!/bin/tcsh
    java-1.3.1 -mx1024m ${project.entrypoint} $$*
    </echo>

    Generate a shell script by echoing to a file. Note the use of a double $ symbol to stop Ant filtering out the single $ during variable expansion

    <echo level="error">
    Imminent failure in the antimatter containment facility.${line.separator}
    Please withdraw to safe location at least 50km away.
    </echo>

    里面可以使用变量,比如${line.separator},里面的文本元素会照原样输出.

    script

    这个task比较有意思.她能够使你在task内写一段脚本代码.然后执行之. 参数:

    Attribute Description Required
    language The programming language the script is written in. Must be a supported Apache BSF language Yes
    src The location of the script as a file, if not inline No

    下面是几个简单的例子:

    <project name="squares" default="main" basedir=".">
    
      <target name="setup">
    
        <script language="javascript"> <![CDATA[
    
          for (i=1; i<=10; i++) {
            echo = squares.createTask("echo");
            main.addTask(echo);
            echo.setMessage(i*i);
          }
    
        ]]> </script>
    
      </target>
    
      <target name="main" depends="setup"/>
    
    </project>

    输出:

    setup:
    
    main:
    1
    4
    9
    16
    25
    36
    49
    64
    81
    100
    
    BUILD SUCCESSFUL

    另一个例子:

    <project name="testscript" default="main">
      <target name="sub">
        <echo id="theEcho"/>
      </target>
    
      <target name="sub1">
        <script language="netrexx"><![CDATA[
          theEcho.setMessage("In sub1")
          sub.execute
        ]]></script>
      </target>
    
      <target name="sub2">
        <script language="javascript"><![CDATA[
          theEcho.setMessage("In sub2");
          sub.execute();
        ]]></script>
      </target>
    
      <target name="main" depends="sub1,sub2"/>
    </project>
    generates
    
    sub1:
    In sub1
    
    sub2:
    In sub2
    
    main:
    
    BUILD SUCCESSFUL

    a more complex example:

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <!-- Now a more complex example using the Java API and the Ant API. The goal is to list the filesizes of all files a <fileset/> caught. -->
    <project name="MyProject" basedir="." default="main">
    
      <property name="fs.dir" value="src"/>
      <property name="fs.includes" value="**/*.txt"/>
      <property name="fs.excludes" value="**/*.tmp"/>
    
      <target name="main">
        <script language="javascript"> <![CDATA[
    
          // import statements
          // importPackage(java.io);
          importClass(java.io.File);
    
          // Access to Ant-Properties by their names
          dir      = project.getProperty("fs.dir");
          includes = MyProject.getProperty("fs.includes");
          excludes = self.getProject()  .getProperty("fs.excludes");
    
          // Create a <fileset dir="" includes="" />
          fs = project.createDataType("fileset");
          fs.setDir( new File(dir) );
          fs.setIncludes(includes);
          fs.setExcludes(excludes);
    
          // Get the files (array) of that fileset
          ds = fs.getDirectoryScanner(project);
          srcFiles = ds.getIncludedFiles();
    
          // iterate over that array
          for (i=0; i<srcFiles.length; i++) {
    
            // get the values via Java API
            var basedir  = fs.getDir(project);
            var filename = srcFiles[i];
            var file = new File(basedir, filename);
            var size = file.length();
    
            // create and use a Task via Ant API
            echo = MyProject.createTask("echo");
            echo.setMessage(filename + ": " + size + " byte");
            echo.perform();
          }
        ]]></script>
      </target>
    </project>

    下面时我写的一个ant脚本,用来根据一个url生成对应的globus的namespace2package.mappings文件.

    要运行该脚本请保证你安装了ant的bsf.jar扩展和js.jar扩展.可以到http://jakarta.apache.org/bsf/  http://www.mozilla.org/rhino/ 两个地方区下载响应的软件包

    这个脚本有两个参数:

    • -Dnamespace (required) : this is the namespace to be tranlated, such as : http://www.chinagrid.edu.cn/WelcomeService/Welcome
    • -DemptyMappings (optional) : if you want the script empty the old mapping, set the this true; [note]: this script will omint the www prefix, that means the "http://www.chinagrid.edu.cn/WelcomeService/Welcome" will be tranlated into the pacakge "cn.edu.chinagrid.WelcomeService.Welcome"
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <project name="squares" default="main" basedir=".">
      <description>
        GSBT GT4 buildfile
      </description>
    
      <!-- Define the environment variable -->
      <property environment="env" />
    
      <!-- 
           Properties in these files will override the properties defined in this file.
        -->
      <property file="build.properties" />
      <property file="${user.home}/build.properties" />
    
    
      <!-- translate the  namespace to package in this file  -->
      <property name="mappings.file"    value="${basedir}/namespace2package.mappings" />
    
    
      <!-- this is use for test whether the namespace propertiy is setted -->
      <target name="testnamespace">
        <fail message="ERROR:need namespace" >
          <!--       <condition property="namespace.available">  -->
          <condition>
        <not>
          <isset property="namespace" />
        </not>
          </condition>
        </fail>
      </target>
    
      <!-- test whether the mapping need to be empty -->
      <target name="testEmpty" >
        <condition property="needEmptyMappings">
          <and>
        <isset property="emptyMappings"/>
        <istrue value="${emptyMappings}" />
          </and>
        </condition>
      </target>
    
    
      <!-- generate  the namespace2package.mappings file  -->
      <target name="generateNamespace2PackageMapping" depends="testnamespace,testEmpty"  description="creat the base directory structure">
        <!-- <echo file="${mappings.file}" append="false">ok${line.separator}</echo> -->
        <!-- <echo file="${mappings.file}" append="true">hehe${mappings.file}</echo> -->
        <script language="javascript"> <![CDATA[
    
          var namespace= project.getProperty("namespace");
          //var s1= new java.lang.String("http://www.chinagrid.edu.cn/WelcomeService/Welcome");
          if(namespace.charAt(namespace.length()-1) == '/' ) namespace=namespace.substring(0,namespace.length()-1);
          var s1= new java.lang.String(namespace);
          //      var s2=s1.replaceFirst("http://.*?/","").replace("/",".");
    
          //delete the url's  prefix, such as "http://", "https","ftp://" etc.
          if(s1.startsWith("http://")) s1=s1.substring(7);
          else if(s1.startsWith("ftp://")) s1=s1.substring(6);
          else if(s1.startsWith("https://")) s1=s1.substring(8);
    
          //devide the string at the firt "/"
          // ex: www.chinagrid.edu.cn/WelcomeService/Welcome => "www.chinagrid.edu.cn" "WelcomeSevice/Welcome"
          var i=s1.indexOf('/');
          var s2="";
          var s3="";
          if( i != -1 ) {
              s2=s1.substring(i+1).replace('/','.');
              s3=s1.substring(0,i);
          }
    
          //reserve the domain name and delete the "www" prefix:
          //ex: www.chinagrid.edu.cn => "cn.edu.chiangrid"
          var stack=new java.util.Stack();
          var st=new java.util.StringTokenizer(s3,".");
          while(st.hasMoreTokens()){
             stack.push(st.nextToken());
          }
          var sb=new java.lang.StringBuffer();
          sb.append(stack.pop());
          while ( ! stack.empty() ){
             s3=stack.pop();
             if( !s3.equals( "www" ) ) {
                sb.append('.');
                sb.append(s3);
             }
          }
    
          //get the whole package name
          // cn.edu.chiangrid.WelcomeService.Welcome
          if(s2 != ""){
             sb.append('.');
             sb.append(s2);
          }
          s3 = sb.toString();
    
          //convert the "http://" => "http://",
          // do thsi bicause the properties file trade the ":" as a special char
          namespace=namespace.replaceAll(":","\\:");
    
    
          //create the echo task
          mappingFile = project.getProperty("mappings.file");
          lineEnd = project.getProperty("line.separator");
          emptyMap= project.getProperty("needEmptyMappings");
          echo = project.createTask("echo");
          if(emptyMap) echo.setAppend(false);
          else echo.setAppend(true);
          //main.addTask(echo);
          //set the file for echo task
          var file=new java.io.File(mappingFile);
          echo.setFile(file);
          echo.setMessage(namespace+"="+s3+".stubs"+lineEnd);
          echo.perform();
          //      echo.setMessage(mappingFile);
          echo.setAppend(true);
          echo.setMessage(namespace+"/bindings="+s3+".stubs.bindings"+lineEnd);
          echo.perform();
          echo.setMessage(namespace+"/service="+s3+".stubs.service"+lineEnd);
          echo.perform();
    
        ]]> </script>
    
    
      </target>
    
    
      <target name="main" depends="generateNamespace2PackageMapping"/>
    
    </project>

    属性的覆盖

    ant 可以通过指定一个属性文件,覆盖掉ant里面定义的属性,这和命令行方式是一样的.ant默认采用的java个格式的属性文件.请看下面的示例

    <?xml version="1.0"?>
    <!-- ======================================================================
         2005-7-25 14:07:18
         file: a.xml
         project: project
         description : description
    
         author: Liu Likun
         ====================================================================== -->
    <project name="project" default="all">
        <description>
                description
        </description>
    
        <property file="b.properities" />
        <!-- =================================
              target: all
             ================================= -->
        <target name="all" depends="init" description="--> description">
          <echo message="${msg}" />
        </target>
    
        <!-- - - - - - - - - - - - - - - - - -
              target: init
             - - - - - - - - - - - - - - - - - -->
        <target name="init">
        </target>
    
    </project>

    下面是b.properties的内容

    msg=hehe world n welcome to ant world 

    下面是ant的输出:

    h:/development/project/eclipseroot/testbuild # ant -file a.xml
    Buildfile: a.xml
    
    init:
    
    all:
         [echo] hehe world
         [echo]  welcome to ant world
    
    BUILD SUCCESSFUL
    Total time: 0 seconds

    ant表示一个目录和这个目录下所有的文件

    dir/**/*.java 表示dir目录及其子目录下所有的.java文件.

    /*.java <=> .//*.java当前目录及其子目录下所有的.java文件</span></span>

    转载请注明:WebLogic Android 博客 » ant 的学习笔记[转]