使用 Apache Ant 开发

编写您自己的任务

编写您自己的任务非常容易

  1. 创建一个扩展 org.apache.tools.ant.Task另一个设计为可扩展的类 的 Java 类。
  2. 对于每个属性,编写一个setter 方法。setter 方法必须是一个接受单个参数的 public void 方法。方法名称必须以 set 开头,后跟属性名称,名称的第一个字符大写,其余字符小写*。也就是说,要支持名为 file 的属性,您需要创建一个名为 setFile 的方法。根据参数的类型,Ant 会为您执行一些转换,请参阅 下方
  3. 如果您的任务应包含其他任务作为嵌套元素(如 parallel),您的类必须实现接口 org.apache.tools.ant.TaskContainer。如果您这样做,您的任务将不支持任何其他嵌套元素。请参阅 下方
  4. 如果任务应支持字符数据(开始和结束标签之间嵌套的文本),请编写一个 public void addText(String) 方法。请注意,Ant 不会扩展传递给任务的文本中的属性。
  5. 对于每个嵌套元素,编写一个createaddaddConfigured 方法。create 方法必须是一个不带参数并返回 Object 类型的 public 方法。create 方法的名称必须以 create 开头,后跟元素名称。add(或 addConfigured)方法必须是一个接受单个参数的 public void 方法,该参数是具有无参数构造函数的 Object 类型。add(addConfigured)方法的名称必须以 addaddConfigured)开头,后跟元素名称。有关更完整的讨论,请参阅 下方
  6. 编写一个不带参数并抛出 BuildExceptionpublic void execute() 方法。此方法实现任务本身。

* 实际上,第一个字母之后的字母的大小写对 Ant 来说并不重要,使用全小写是一个很好的约定。

任务的生命周期

  1. 包含与任务相对应的标签的 xml 元素在解析时被转换为 UnknownElement。此 UnknownElement 被放置在目标对象内的列表中,或者递归地放置在另一个 UnknownElement 内。
  2. 当执行目标时,每个 UnknownElement 都使用 perform() 方法调用。这将实例化任务。这意味着任务仅在运行时实例化。
  3. 任务通过其继承的 projectlocation 变量获取对构建文件中的项目和位置的引用。
  4. 如果用户为该任务指定了 id 属性,则项目将在运行时注册对这个新创建的任务的引用。
  5. 任务通过其继承的 target 变量获取对它所属的目标的引用。
  6. init() 在运行时调用。
  7. 与该任务相对应的 XML 元素的所有子元素都通过该任务的 createXXX() 方法创建,或者在运行时实例化并通过其 addXXX() 方法添加到该任务。与 addConfiguredXXX() 相对应的子元素在此处创建,但实际的 addConfigured 方法不会被调用。
  8. 该任务的所有属性都通过其相应的 setXXX() 方法在运行时设置。
  9. 与该任务相对应的 XML 元素内的内容字符数据部分在运行时通过其 addText() 方法添加到任务。
  10. 所有子元素的所有属性都通过其相应的 setXXX() 方法在运行时设置。
  11. 如果与该任务相对应的 XML 元素的子元素已为 addConfiguredXXX() 方法创建,则这些方法现在将被调用。
  12. execute() 在运行时调用。如果 target1target2 都依赖于 target3,则运行 ant target1 target2 将在 target3 中运行所有任务两次。

Ant 将为属性执行的转换

Ant 始终在将属性的值传递给相应的 setter 方法之前扩展属性。从 Ant 1.8 开始,可以 扩展 Ant 的属性处理,以便非字符串对象可能是包含单个属性引用的字符串的评估结果。这些将通过匹配类型的 setter 方法直接分配。由于需要一些超出基本操作的干预才能启用此行为,因此最好标记旨在允许此使用模式的属性。

编写属性 setter 的最常见方法是使用 java.lang.String 参数。在这种情况下,Ant 将将字面值(在属性扩展之后)传递给您的任务。但还有更多!如果您的 setter 方法的参数是

如果为给定属性存在多个 setter 方法会发生什么?接受 String 参数的方法将始终输给更具体的方法。如果仍然存在 Ant 可以选择的 setter,则只会调用其中一个,但我们不知道是哪个,这取决于您的 Java 虚拟机的实现。

支持嵌套元素

假设您的任务应支持名为 inner 的嵌套元素。首先,您需要一个表示此嵌套元素的类。通常,您只需使用 Ant 的类之一,如 org.apache.tools.ant.types.FileSet,来支持嵌套的 fileset 元素。

嵌套元素或其嵌套子元素的属性将使用与任务相同的机制处理(即,属性的setter 方法、addText() 用于嵌套文本以及create/add/addConfigured 方法用于子元素)。

现在您有一个名为 NestedElement 的类,它应该用于您的嵌套 <inner> 元素,您有三种选择

  1. public NestedElement createInner()
  2. public void addInner(NestedElement anInner)
  3. public void addConfiguredInner(NestedElement anInner)

有什么区别?

选项 1 使任务创建 NestedElement 的实例,对类型没有限制。对于选项 2 和 3,Ant 必须在将它传递给任务之前创建 NestedInner 的实例,这意味着 NestedInner 必须具有一个 public 无参数构造函数或一个接受 Project 类作为参数的 public 单参数构造函数。这是选项 1 和 2 之间的唯一区别。

2 和 3 之间的区别是 Ant 在将对象传递给方法之前对对象所做的操作。 addInner() 将在构造函数调用之后直接接收一个对象,而 addConfiguredInner() 将在处理此新对象的属性和嵌套子元素之后获取该对象。

如果您使用多个选项会发生什么?只会调用其中一个方法,但我们不知道是哪个,这取决于您的 JVM 的实现。

嵌套类型

如果您的任务需要嵌套使用 <typedef> 定义的任意类型,您有两个选择。

  1. public void add(Type type)
  2. public void addConfigured(Type type)

1 和 2 之间的区别与上一节中的 2 和 3 之间的区别相同。

例如,假设有人想处理类型为 org.apache.tools.ant.taskdefs.condition.Condition 的对象,他们可能有一个类

public class MyTask extends Task {
    private List conditions = new ArrayList();
    public void add(Condition c) {
        conditions.add(c);
    }
    public void execute() {
     // iterator over the conditions
    }
}

他们可以像这样定义和使用此类

<taskdef name="mytask" classname="MyTask" classpath="classes"/>
<typedef name="condition.equals"
         classname="org.apache.tools.ant.taskdefs.conditions.Equals"/>
<mytask>
    <condition.equals arg1="${debug}" arg2="true"/>
</mytask>

以下是一个更复杂的示例

public class Sample {
    public static class MyFileSelector implements FileSelector {
         public void setAttrA(int a) {}
         public void setAttrB(int b) {}
         public void add(Path path) {}
         public boolean isSelected(File basedir, String filename, File file) {
             return true;
         }
     }

    interface MyInterface {
        void setVerbose(boolean val);
    }

    public static class BuildPath extends Path {
        public BuildPath(Project project) {
            super(project);
        }

        public void add(MyInterface inter) {}
        public void setUrl(String url) {}
    }

    public static class XInterface implements MyInterface {
        public void setVerbose(boolean x) {}
        public void setCount(int c) {}
    }
}

此类定义了一些实现/扩展PathMyFileSelectorMyInterface的静态类。这些类可以按如下方式定义和使用

<typedef name="myfileselector" classname="Sample$MyFileSelector"
         classpath="classes" loaderref="classes"/>
<typedef name="buildpath" classname="Sample$BuildPath"
         classpath="classes" loaderref="classes"/>
<typedef name="xinterface" classname="Sample$XInterface"
         classpath="classes" loaderref="classes"/>

<copy todir="copy-classes">
   <fileset dir="classes">
      <myfileselector attra="10" attrB="-10">
         <buildpath path="." url="abc">
            <xinterface count="4"/>
         </buildpath>
      </myfileselector>
   </fileset>
</copy>

TaskContainer

TaskContainer包含一个名为addTask的方法,它基本上与嵌套元素的add方法相同。任务实例将在您的任务的execute方法被调用时配置(它们的属性和嵌套元素已处理),但在此之前不会配置。

当我们execute会被调用时,我们撒谎了;-)。实际上,Ant 将调用org.apache.tools.ant.Task中的perform方法,该方法反过来调用execute。此方法确保将触发构建事件。如果您执行嵌套在您的任务中的任务实例,您也应该在这些实例上调用perform而不是execute

示例

让我们编写自己的任务,它在System.out流上打印一条消息。该任务有一个名为message的属性。

package com.mydomain;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

public class MyVeryOwnTask extends Task {
    private String msg;

    // The method executing the task
    public void execute() throws BuildException {
        System.out.println(msg);
    }

    // The setter for the "message" attribute
    public void setMessage(String msg) {
        this.msg = msg;
    }
}

真的就这么简单 ;-)

将您的任务添加到系统中也很简单

  1. 确保在启动 Ant 时,实现您的任务的类位于类路径中。
  2. 将一个<taskdef>元素添加到您的项目中。这实际上将您的任务添加到系统中。
  3. 在构建文件的其余部分中使用您的任务。

示例

<?xml version="1.0"?>

<project name="OwnTaskExample" default="main" basedir=".">
  <taskdef name="mytask" classname="com.mydomain.MyVeryOwnTask"/>

  <target name="main">
    <mytask message="Hello World! MyVeryOwnTask works!"/>
  </target>
</project>

示例 2

要直接从创建它的构建文件中使用任务,请将<taskdef>声明放在目标编译之后。使用<taskdef>classpath属性指向刚刚编译的代码的位置。

<?xml version="1.0"?>

<project name="OwnTaskExample2" default="main" basedir=".">

  <target name="build" >
    <mkdir dir="build"/>
    <javac srcdir="source" destdir="build"/>
  </target>

  <target name="declare" depends="build">
    <taskdef name="mytask"
        classname="com.mydomain.MyVeryOwnTask"
        classpath="build"/>
  </target>

  <target name="main" depends="declare">
    <mytask message="Hello World! MyVeryOwnTask works!"/>
  </target>
</project>

另一种添加任务(更永久地)的方法是将任务名称和实现类名称添加到org.apache.tools.ant.taskdefs包中的default.properties文件中。然后,您可以像使用内置任务一样使用它。


构建事件

Ant 能够在执行构建项目所需的必要任务时生成构建事件。监听器可以附加到 Ant 以接收这些事件。例如,此功能可用于将 Ant 连接到 GUI 或将 Ant 集成到 IDE 中。

要使用构建事件,您需要创建一个 ant Project对象。然后,您可以调用addBuildListener方法将您的监听器添加到项目中。您的监听器必须实现org.apache.tools.antBuildListener接口。监听器将为以下事件接收构建事件

如果构建文件通过<ant><subant>或使用<antcall>调用另一个构建文件,您将创建一个新的 Ant “项目”,该项目将发送自己的目标和任务级事件,但永远不会发送构建开始/完成事件。从 Ant 1.6.2 开始BuildListener接口有一个名为SubBuildListener的扩展,它将接收两个新的事件,用于

如果您对这些事件感兴趣,您只需实现新的接口而不是BuildListener(当然还要注册监听器)。

如果您希望从命令行附加监听器,您可以使用-listener选项。例如

ant -listener org.apache.tools.ant.XmlLogger

将使用一个监听器运行 Ant,该监听器生成构建进度的 XML 表示。此监听器包含在 Ant 中,默认监听器也是如此,它生成到标准输出的日志记录。

注意:监听器不得直接访问System.outSystem.err,因为 Ant 的核心将这些流上的输出重定向到构建事件系统。访问这些流会导致 Ant 中出现无限循环。根据 Ant 的版本,这将导致构建终止或 JVM 耗尽堆栈空间。同样,记录器也不得直接访问System.outSystem.err。它必须使用已配置的流。

注意:除了“构建开始”和“构建完成”事件之外,BuildListener的所有方法都可能在多个线程上同时发生——例如,当 Ant 正在执行<parallel>任务时。

示例

编写一个适配器到您最喜欢的日志库非常容易。只需实现BuildListener接口,实例化您的记录器并将消息委托给该实例。

在启动构建时,将您的适配器类和日志库提供给构建类路径,并通过-listener选项激活您的记录器,如上所述。

public class MyLogAdapter implements BuildListener {

    private MyLogger getLogger() {
        final MyLogger log = MyLoggerFactory.getLogger(Project.class.getName());
        return log;
    }

    @Override
    public void buildStarted(final BuildEvent event) {
        final MyLogger log = getLogger();
        log.info("Build started.");
    }

    @Override
    public void buildFinished(final BuildEvent event) {
        final MyLogger logger = getLogger();
        MyLogLevelEnum loglevel = ... // map event.getPriority() to enum via Project.MSG_* constants
        boolean allOK = event.getException() == null;
        String logmessage = ... // create log message using data of the event and the message invoked
        logger.log(loglevel, logmessage);
    }

    // implement all methods in that way
}

源代码集成

通过 Java 扩展 Ant 的另一种方法是对现有任务进行更改,这非常鼓励。对现有源代码和新任务的更改都可以合并回 Ant 代码库,这有利于所有用户并分散维护工作量。

请参阅 Apache 网站上的参与页面,了解有关如何获取最新源代码以及如何提交更改以重新合并到源代码树中的详细信息。

Ant 还有一些任务指南,为开发和测试任务的人员提供了一些建议。即使您打算将任务保密,您也应该阅读它,因为它应该是有益的。