教程:使用属性、文件集和路径的任务

在阅读有关编写任务 [1]的教程后,本教程将解释如何获取和设置属性,以及如何使用嵌套文件集和路径。最后,它将解释如何将任务贡献给 Apache Ant。

内容

目标

目标是编写一个任务,该任务在路径中搜索文件并将该文件的路径保存到属性中。

构建环境

我们可以使用另一个教程中的构建文件并对其进行一些修改。这就是使用属性的优势 - 我们可以重复使用几乎整个脚本。 :-)

<?xml version="1.0" encoding="UTF-8"?>
<project name="FindTask" basedir="." default="test">
    ...
    <target name="use.init" description="Taskdef's the Find-Task" depends="jar">
        <taskdef name="find" classname="Find" classpath="${ant.project.name}.jar"/>
    </target>

    <!-- the other use.* targets are deleted -->
    ...
</project>

构建文件位于存档tutorial-tasks-filesets-properties.zip [2]中的/build.xml.01-propertyaccess(未来版本保存为 *.02...,最终版本保存为 build.xml;源代码也是如此)。

属性访问

我们的第一步是将属性设置为一个值并打印该属性的值。因此,我们的场景将是

    <find property="test" value="test-value"/>
    <find print="test"/>

好的,它可以用核心任务重写

    <property name="test" value="test-value"/>
    <echo message="${test}"/>

但我必须从已知的基础开始 :-)

那么该怎么做呢?处理三个属性(propertyvalueprint)和一个执行方法。因为这只是一个介绍性示例,所以我不会进行太多检查

import org.apache.tools.ant.BuildException;

public class Find extends Task {

    private String property;
    private String value;
    private String print;

    public void setProperty(String property) {
        this.property = property;
    }

    // setter for value and print

    public void execute() {
        if (print != null) {
            String propValue = getProject().getProperty(print);
            log(propValue);
        } else {
            if (property == null) throw new BuildException("property not set");
            if (value    == null) throw new BuildException("value not set");
            getProject().setNewProperty(property, value);
        }
    }
}

如另一个教程中所述,属性访问是通过 Project 实例完成的。我们通过公共 getProject() 方法获取此实例,该方法继承自 Task(更准确地说,继承自 ProjectComponent)。读取属性是通过 getProperty(propertyname) 完成的(非常简单,不是吗?)。此属性将值作为 String 返回,如果未设置则返回 null
设置属性... 并不难,但有多个 setter。您可以使用 setProperty() 方法,该方法将按预期执行。但 Ant 中有一个黄金法则:属性是不可变的。此方法将属性设置为指定的值 - 无论它之前是否有值。因此,我们使用另一种方法。 setNewProperty() 仅在没有同名属性时才设置属性。否则,将记录一条消息。

(顺便说一下,简要解释一下 Ant 的“命名空间” - 不要与 XML 命名空间混淆:<antcall> 为属性名称创建一个新的空间。所有来自调用者的属性都传递给被调用者,但被调用者可以在没有调用者注意的情况下设置自己的属性。)

还有一些其他 setter(但我没有使用它们,所以对此我无话可说,抱歉 :-)

将上面的两行示例放入名为 use.simple 的目标后,我们可以从测试用例中调用它

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.apache.tools.ant.BuildFileRule;

public class FindTest {

    @Rule
    public final BuildFileRule buildRule = new BuildFileRule();

    @Before
    public void setUp() {
        configureProject("build.xml");
    }

    @Test
    public void testSimple() {
        buildRule.executeTarget("useSimple");
        Assert.assertEquals("test-value", buildRule.getLog());
    }
}

一切正常。

使用文件集

Ant 提供了一种捆绑文件的通用方法:文件集。因为您正在阅读本教程,我认为您了解它们,我不必花更多时间解释它们在构建文件中的用法。我们的目标是在路径中搜索文件。在此步骤中,路径只是一个文件集(或更准确地说:文件集的集合)。因此,我们的用法将是

    <find file="ant.jar" location="location.ant-jar">
        <fileset dir="${ant.home}" includes="**/*.jar"/>
    </find>

我们需要什么?一个具有两个属性(filelocation)和嵌套文件集的任务。因为我们已经在上面的示例中解释了属性处理,并且在另一个教程中描述了嵌套元素的处理,所以代码应该非常容易

public class Find extends Task {

    private String file;
    private String location;
    private List<FileSet> filesets = new ArrayList<>();

    public void setFile(String file) {
        this.file = file;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public void addFileset(FileSet fileset) {
        filesets.add(fileset);
    }

    public void execute() {
    }
}

好的 - 该任务不会做太多事情,但我们可以在没有错误的情况下以描述的方式使用它。在下一步中,我们必须实现 execute 方法。在此之前,我们将实现相应的测试用例(TDD - 测试驱动开发)。

在另一个教程中,我们重复使用了构建文件已经编写的目标。现在,我们将通过 Java 代码配置大多数测试用例(有时编写目标比通过 Java 代码编写更容易)。可以测试什么?

也许您会找到更多测试用例。但现在这些就足够了。
对于每个点,我们创建一个 testXX 方法。

public class FindTest {

    @Rule
    public final BuildFileRule buildRule = new BuildFileRule();

    @Rule
    public ExpectedException tried = ExpectedException.none();

    ... // constructor, setUp as above

    @Test
    public void testMissingFile() {
        tried.expect(BuildException.class);
        tried.expectMessage("file not set");
        Find find = new Find();
        find.execute();
    }

    @Test
    public void testMissingLocation() {
        tried.expect(BuildException.class);
        tried.expectMessage("location not set");
        Find find = new Find();
        find.setFile("ant.jar");
        find.execute();
    }

    @Test
    public void testMissingFileset() {
        tried.expect(BuildException.class);
        tried.expectMessage("fileset not set");
        Find find = new Find();
        find.setFile("ant.jar");
        find.setLocation("location.ant-jar");
    }

    @Test
    public void testFileNotPresent() {
        buildRule.executeTarget("testFileNotPresent");
        String result = buildRule.getProject().getProperty("location.ant-jar");
        assertNull("Property set to wrong value.", result);
    }

    @Test
    public void testFilePresent() {
        buildRule.executeTarget("testFilePresent");
        String result = buildRule.getProject().getProperty("location.ant-jar");
        assertNotNull("Property not set.", result);
        assertTrue("Wrong file found.", result.endsWith("ant.jar"));
    }
}

如果我们运行此测试类,所有测试用例(除了 testFileNotPresent)都会失败。现在,我们可以实现我们的任务,以便这些测试用例通过。

    protected void validate() {
        if (file == null) throw new BuildException("file not set");
        if (location == null) throw new BuildException("location not set");
        if (filesets.size() < 1) throw new BuildException("fileset not set");
    }

    public void execute() {
        validate();                                                             // 1
        String foundLocation = null;
        for (FileSet fs : filesets) {                                           // 2
            DirectoryScanner ds = fs.getDirectoryScanner(getProject());         // 3
            for (String includedFile : ds.getIncludedFiles()) {
                String filename = includedFile.replace('\\','/');               // 4
                filename = filename.substring(filename.lastIndexOf("/") + 1);
                if (foundLocation == null && file.equals(filename)) {
                    File base  = ds.getBasedir();                               // 5
                    File found = new File(base, includedFile);
                    foundLocation = found.getAbsolutePath();
                }
            }
        }
        if (foundLocation != null)                                              // 6
            getProject().setNewProperty(location, foundLocation);
    }

//1 上,我们检查任务的前提条件。在 validate() 方法中执行此操作是一种常见方法,因为我们将前提条件与实际工作分开。在 //2 上,我们遍历所有嵌套文件集。如果我们不想处理多个文件集,则 addFileset() 方法必须拒绝进一步的调用。我们可以通过其 DirectoryScanner 获取文件集的结果,如 //3 中所示。之后,我们创建文件路径的平台无关字符串表示(//4,当然可以用其他方法完成)。我们必须执行 replace(),因为我们使用的是简单的字符串比较。Ant 本身是平台无关的,因此可以在使用斜杠 (/,例如 Linux) 或反斜杠 (\,例如 Windows) 作为路径分隔符的文件系统上运行。因此,我们必须统一这一点。如果我们找到我们的文件,我们将在 //5 上创建一个绝对路径表示,以便我们可以在不知道 basedir 的情况下使用这些信息。(这在使用多个文件集时非常重要,因为它们可能具有不同的 basedir,并且目录扫描器的返回值相对于其 basedir。)最后,如果我们找到一个文件,我们将文件的路径存储为属性(//6)。

好的,在这个简单的情况下,更容易的做法是将 file 添加为所有文件集的附加 include 元素。但我希望展示如何处理复杂情况而不变得复杂 :-)

测试用例使用 Ant 属性 ant.home 作为参考。此属性由启动 Ant 的 Launcher 类设置。我们可以在构建文件中使用它作为内置属性 [3]。但是,如果我们创建一个新的 Ant 环境,我们必须为自己的环境设置该值。我们使用 fork 模式下的 <junit> 任务。因此,我们必须修改构建文件

    <target name="junit" description="Runs the unit tests" depends="jar">
        <delete dir="${junit.out.dir.xml}"/>
        <mkdir  dir="${junit.out.dir.xml}"/>
        <junit printsummary="yes" haltonfailure="no">
            <classpath refid="classpath.test"/>
            <sysproperty key="ant.home" value="${ant.home}"/>
            <formatter type="xml"/>
            <batchtest fork="yes" todir="${junit.out.dir.xml}">
                <fileset dir="${src.dir}" includes="**/*Test.java"/>
            </batchtest>
        </junit>
    </target>

使用嵌套路径

提供对文件集的支持的任务非常方便。但还有另一种捆绑文件的方法:<path>。如果所有文件都在一个公共基目录下,则文件集很容易。但如果不是这种情况,您就会遇到问题。另一个缺点是它的速度:如果您在一个巨大的目录结构中只有几个文件,为什么不使用 <filelist> 而不是?<path> 以这种方式组合这些数据类型,即路径包含其他路径、文件集、目录集和文件列表。这就是为什么Ant-Contrib [4] <foreach> 任务被修改为支持路径而不是文件集。所以我们也想要这样。

从文件集更改为路径支持非常容易

将 Java 代码从
    private List<FileSet> filesets = new ArrayList<>();
    public void addFileset(FileSet fileset) {
        filesets.add(fileset);
    }
更改为
    private List<Path> paths = new ArrayList<>();             *1
    public void addPath(Path path) {                          *2
        paths.add(path);
    }
并将构建文件从
    <find file="ant.jar" location="location.ant-jar">
        <fileset dir="${ant.home}" includes="**/*.jar"/>
    </find>
更改为
    <find file="ant.jar" location="location.ant-jar">
        <path>                                                *3
            <fileset dir="${ant.home}" includes="**/*.jar"/>
        </path>
    </find>

更改为

*1 上,我们只重命名列表。这只是为了更好地阅读源代码。在 *2 上,我们必须提供正确的方法:addName(Type t)。因此,在此处将文件集替换为路径。最后,我们必须修改 *3 上的构建文件,因为我们的任务不再支持嵌套文件集。因此,我们将文件集包装在路径中。

现在我们修改测试用例。哦,没什么好做的 :-) 重命名 testMissingFileset()(不是必须的,但最好将其命名为它所做的事情)并在该方法中更新 expected-String(现在期望 path not set 消息)。更复杂的测试用例基于构建脚本。因此,目标 testFileNotPresenttestFilePresent 必须以上面描述的方式进行修改。

    public void execute() {
        validate();
        String foundLocation = null;
        for (Path path : paths) {                                        // 1
            for (String includedFile : path.list()) {                    // 2
                String filename = includedFile.replace('\\','/');
                filename = filename.substring(filename.lastIndexOf("/") + 1);
                if (foundLocation == null && file.equals(filename)) {
                    foundLocation = includedFile;                        // 3
                }
            }
        }
        if (foundLocation != null)
            getProject().setNewProperty(location, foundLocation);
    }

测试已完成。现在我们必须调整任务实现。最简单的修改是在 validate() 方法中,我们将最后一行更改为 if (paths.size()<1) throw new BuildException("path not set");。在 execute() 方法中,我们还有更多工作要做。... 嗯... 实际上工作量更少,因为 Path 类为我们完成了所有 DirectoryScanner 处理和创建绝对路径的工作。因此,execute 方法变为

返回列表

当然,我们必须在 //1 上遍历路径。在 //2//3 上,我们看到 Path 类为我们完成了工作:没有 DirectoryScanner(在 2 处)并且没有创建绝对路径(在 3 处)。
到目前为止,一切都很好。但是,文件可能在路径中的多个位置吗?- 当然。

获取所有这些文件会好吗?- 这取决于...

在本节中,我们将扩展该任务以支持返回所有文件的列表。Ant 本身不支持将列表作为属性值。因此,我们必须看看其他任务如何使用列表。使用列表的最著名任务是 Ant-Contrib 的 <foreach>。所有列表元素都连接在一起,并用可自定义的分隔符(默认值为 ,)分隔。

<find ... delimiter=""/> ... </find>

因此,我们执行以下操作

如果设置了分隔符,我们将以该分隔符返回所有找到的文件作为列表。

返回该列表

因此,我们添加以下测试用例
    <target name="test.init">
        <mkdir dir="test1/dir11/dir111"/>                             *1
        <mkdir dir="test1/dir11/dir112"/>
        ...
        <touch file="test1/dir11/dir111/test"/>
        <touch file="test1/dir11/dir111/not"/>
        ...
        <touch file="test1/dir13/dir131/not2"/>
        <touch file="test1/dir13/dir132/test"/>
        <touch file="test1/dir13/dir132/not"/>
        <touch file="test1/dir13/dir132/not2"/>
        <mkdir dir="test2"/>
        <copy todir="test2">                                          *2
            <fileset dir="test1"/>
        </copy>
    </target>

    <target name="testMultipleFiles" depends="use.init,test.init">    *3
        <find file="test" location="location.test" delimiter=";">
            <path>
                <fileset dir="test1"/>
                <fileset dir="test2"/>
            </path>
        </find>
        <delete>                                                      *4
            <fileset dir="test1"/>
            <fileset dir="test2"/>
        </delete>
    </target>
在构建文件中
    public void testMultipleFiles() {
        executeTarget("testMultipleFiles");
        String result = getProject().getProperty("location.test");
        assertNotNull("Property not set.", result);
        assertTrue("Only one file found.", result.indexOf(";") > -1);
    }

在测试类中

现在,我们需要一个目录结构,我们可以在其中找到不同目录中具有相同名称的文件。因为我们不能确定是否有一个,所以我们在 *1*2 上创建一个。当然,我们在 *4 上清理它。可以在我们的测试目标中或在单独的目标中创建它,这对于以后的重复使用会更好(*3)。

    private List<String> foundFiles = new ArrayList<>();
    ...
    private String delimiter = null;
    ...
    public void setDelimiter(String delim) {
        delimiter = delim;
    }
    ...
    public void execute() {
        validate();
        // find all files
        for (Path path : paths) {
            for (File includedFile : path.list()) {
                String filename = includedFile.replace('\\','/');
                filename = filename.substring(filename.lastIndexOf("/")+1);
                if (file.equals(filename) && !foundFiles.contains(includedFile)) {  // 1
                    foundFiles.add(includedFile);
                }
            }
        }

        // create the return value (list/single)
        String rv = null;
        if (!foundFiles.isEmpty()) {                                                // 2
            if (delimiter == null) {
                // only the first
                rv = foundFiles.get(0);
            } else {
                // create list
                StringBuilder list = new StringBuilder();
                for (String file : foundFiles) {                                    // 3
                    list.append(it.next());
                    if (list.length() > 0) list.append(delimiter);                  // 4
                 }
                rv = list.toString();
            }
        }

        // create the property
        if (rv != null)
            getProject().setNewProperty(location, rv);
    }

任务实现如下修改

该算法执行以下操作:查找所有文件,根据用户的意愿创建返回值,将值作为属性返回。在 //1 上,我们消除了重复项。//2 确保我们仅在找到一个文件时才创建返回值。在 //3 上,我们遍历所有找到的文件,并且 //4 确保最后一个条目没有尾随分隔符。

文档

如果只有任务开发者能够编写构建文件(而且他只有在接下来的几周内才能做到 :-)),那么这个任务就毫无用处。因此,文档也很重要。你用什么形式来做取决于你的喜好。但在 Ant 内部有一个通用格式,使用它有很多好处:所有任务用户都知道这种格式,如果你决定贡献你的任务,就会要求使用这种格式。所以我们将以这种形式来记录我们的任务。

如果你看一下 Java 任务 [5] 的手册页,你会发现它

作为模板,我们有

<!DOCTYPE html>
<html lang="en">

<head>
<title>Taskname Task</title>
</head>

<body>

<h2 id="taskname">Taskname</h2>
<h3>Description</h3>
<p>Describe the task.</p>

<h3>Parameters</h3>
<table class="attr">
  <tr>
    <th scope="col">Attribute</th>
    <th scope="col">Description</th>
    <th scope="col">Required</th>
  </tr>

  do this html row for each attribute (including inherited attributes)
  <tr>
    <td>classname</td>
    <td>the Java class to execute.</td>
    <td>Either jar or classname</td>
  </tr>

</table>

<h3>Parameters specified as nested elements</h3>

Describe each nested element (including inherited)
<h4>your nested element</h4>
<p>description</p>
<p><em>since Ant 1.6</em>.</p>

<h3>Examples</h3>
<pre>
    A code sample; don't forget to escape the < of the tags with &lt;
</pre>
What should that example do?

</body>
</html>

这是一个关于我们任务的示例文档页面

<!DOCTYPE html>
<html lang="en">

<head>
<title>Find Task</title>
</head>

<body>

<h2 id="find">Find</h2>
<h3>Description</h3>
<p>Searches in a given path for a file and returns the absolute to it as property.
If delimiter is set this task returns all found locations.</p>

<h3>Parameters</h3>
<table class="attr">
  <tr>
    <th scope="col">Attribute</th>
    <th scope="col">Description</th>
    <th scope="col">Required</th>
  </tr>
  <tr>
    <td>file</td>
    <td>The name of the file to search.</td>
    <td>yes</td>
  </tr>
  <tr>
    <td>location</td>
    <td>The name of the property where to store the location</td>
    <td>yes</td>
  </tr>
  <tr>
    <td>delimiter</td>
    <td>A delimiter to use when returning the list</td>
    <td>only if the list is required</td>
  </tr>
</table>

<h3>Parameters specified as nested elements</h3>

<h4>path</h4>
<p>The path where to search the file.</p>

<h3>Examples</h3>
<pre>
<find file="ant.jar" location="loc">
    <path>
        <fileset dir="${ant.home}"/>
    <path>
</find></pre>
Searches in Ant's home directory for a file <samp>ant.jar</samp> and stores its location in
property <code>loc</code> (should be <samp>ANT_HOME/bin/ant.jar</samp>).

<pre>
<find file="ant.jar" location="loc" delimiter=";">
    <path>
        <fileset dir="C:/"/>
    <path>
</find>
<echo>ant.jar found in: ${loc}</echo></pre>
Searches in Windows C: drive for all <samp>ant.jar</samp> and stores their locations in
property <code>loc</code> delimited with <q>;</q>. (should need a long time :-)
After that it prints out the result (e.g. <samp>C:/ant-1.5.4/bin/ant.jar;C:/ant-1.6/bin/ant.jar</samp>).

</body>
</html>

贡献新任务

如果我们决定贡献我们的任务,我们应该做一些事情

有关此方面的更多信息,请参阅 Ant 任务指南 [6]

现在我们将检查该指南中描述的“提交新任务前的清单”。

包/目录

此任务不依赖于任何外部库。因此,我们可以将其用作核心任务。此任务只包含一个类。因此,我们可以使用核心任务的标准包:org.apache.tools.ant.taskdefs。实现位于 src/main 目录中,测试位于 src/testcases 目录中,测试的构建文件位于 src/etc/testcases 目录中。

现在我们将我们的工作集成到 Ant 发行版中。因此,首先我们要更新我们的 Git 树。如果还没有完成,你应该在 GitHub[7] 上克隆 Ant 仓库,然后创建一个本地克隆

git clone https://github.com/your-sig/ant.git

现在我们将构建我们的 Ant 发行版并进行测试。这样我们就可以看到我们的机器上是否有任何测试失败。(我们可以在后面的步骤中忽略这些失败的测试;这里使用的是 Windows 语法——如果需要,请将其转换为 UNIX 语法)

ANTREPO> build                                                    // 1
ANTREPO> set ANT_HOME=%CD%\dist                                   // 2
ANTREPO> ant test -Dtest.haltonfailure=false                      // 3

首先,我们必须构建我们的 Ant 发行版(//1)。在 //2 上,我们将 ANT_HOME 环境变量设置为新创建的发行版所在的目录(%CD% 在 Windows 2000 及更高版本上扩展为当前目录)。在 //3 上,我们让 Ant 执行所有测试(这会强制编译所有测试),而不会在第一次失败时停止。

接下来,我们将我们的工作应用到 Ant 源代码上。因为我们没有修改任何内容,所以这是一个相对简单的步骤。(因为我有一个 Ant 的本地 Git 克隆,并且通常会贡献我的工作,所以我从一开始就在本地副本上工作。优点是:如果你修改了现有的源代码,则不需要此步骤,并且可以节省大量工作 :-))

现在我们的修改已经完成,我们将重新测试它

ANTREPO> build
ANTREPO> ant run-single-test                                      // 1
             -Dtestcase=org.apache.tools.ant.taskdefs.FindTest    // 2
             -Dtest.haltonfailure=false

因为我们只想测试我们的新类,所以我们使用单个测试的目标,指定要使用的测试,并配置为不在第一次失败时停止——我们希望看到我们自己的测试的所有失败(//1 + 2)。

然后……哦,所有测试都失败了:Ant 找不到任务或此任务依赖的类。

好的:在前面的步骤中,我们告诉 Ant 使用 Find 类来执行 <find> 任务(请记住 use.init 目标中的 <taskdef> 语句)。但现在我们想将该任务作为核心任务引入。没有人想要 taskdef javacecho……那么该怎么办?答案是 src/main/.../taskdefs/default.properties。这里完成了任务名称和实现类之间的映射。因此,我们将 find=org.apache.tools.ant.taskdefs.Find 添加为最后一个核心任务(就在 # optional tasks 行之前)。现在我们再试一次

ANTREPO> build                                                    // 1
ANTREPO> ant run-single-test
             -Dtestcase=org.apache.tools.ant.taskdefs.FindTest
             -Dtest.haltonfailure=false

我们必须重新构建(//1)Ant,因为测试会在 %ANT_HOME%\lib\ant.jar(更准确地说:在类路径上)中查找属性文件。而我们只在源路径中修改了它。因此,我们必须重新构建该 jar。但现在所有测试都通过了,我们将检查我们的类是否破坏了其他一些测试。

ANTREPO> ant test -Dtest.haltonfailure=false

因为有很多测试,所以这一步需要一些时间。因此,在开发过程中使用 run-single-test,并在最后(可能在开发过程中也会偶尔使用)使用 test。我们在这里使用 -Dtest.haltonfailure=false,因为可能会有其他测试失败,我们需要查看它们。

此测试运行应该向我们展示两件事:我们的测试将运行,并且失败的测试数量与直接在 git clone 之后(没有我们的修改)相同。

Apache 许可声明

只需从 Ant 源代码树中的其他源代码中复制许可证文本。

在最小 JDK 版本上测试

Ant 1.10 使用 Java 8 进行开发,但 Ant 1.9 也在积极维护。这意味着 Ant 1.9 中存在的 Ant 代码更新必须能够在 JDK 5 上运行。(对于新的任务,只针对 Ant 1.10 及更高版本进行处理是可以的。)因此,我们必须对其进行测试。你可以从 Oracle [8] 下载旧版本的 JDK。

清理 ANT_HOME 变量,删除 buildbootstrapdist 目录,并将 JAVA_HOME 指向 JDK 5 主目录。然后使用你的提交创建补丁,在 Git 中检出 1.9.x 分支,应用你的补丁并执行 build,设置 ANT_HOME 并运行 ant test(如上所述)。

我们的测试应该通过。

Checkstyle

我们必须确保很多事情。缩进使用 4 个空格,这里和那里有空格……(所有这些都在 Ant 任务指南 [6] 中描述,其中包括 Sun 代码风格 [9])。因为有很多事情,所以我们很乐意有一个工具来执行检查。有一个工具:checkstyle。Checkstyle 可在 Sourceforge [10] 上获得,Ant 提供了 check.xml 构建文件,它可以为我们完成这项工作。

下载它并将 checkstyle-*-all.jar 放入你的 %USERPROFILE%\.ant\lib 目录中。存储在那里的所有 jar 都可供 Ant 使用,因此你无需将其添加到你的 %ANT_HOME%\lib 目录中(此功能自 Ant 1.6 起可用)。

因此,我们将使用以下命令运行测试

ANTREPO> ant -f check.xml checkstyle htmlreport

我更喜欢 HTML 报告,因为有很多消息,我们可以更快地浏览。打开 ANTREPO/build/reports/checkstyle/html/index.html 并导航到 Find.java。现在我们看到有一些错误:缺少空格、未使用的导入、缺少 javadoc。因此,我们必须这样做。

提示:从文件的底部开始,这样报告中的行号将保持最新,你将更容易找到下一个错误位置,而无需重新执行 checkstyle。

根据消息清理代码后,我们删除 reports 目录并再次运行 checkstyle。现在我们的任务没有列出。这很好 :-)

发布任务

最后,我们发布该存档。如 Ant 任务指南 [7] 中所述,我们可以在开发者邮件列表中宣布它,创建一个 BugZilla 条目并打开一个 GitHub 拉取请求。对于两者,我们都需要一些信息

主题 简短描述 用于在路径中查找文件的任务
正文 有关路径的更多详细信息 此新任务在嵌套的 <path/> 中查找文件出现的位置,并将所有位置存储为属性。有关详细信息,请参阅附带的手册。
拉取请求引用 GitHub 拉取请求 URL https://github.com/apache/ant/pull/0

发送包含此信息的电子邮件非常容易,我认为我不需要描述它。BugZilla 稍微复杂一些。但优点是条目不会被遗忘(每周都会生成一次报告)。因此,我将描述该过程。

首先,您需要拥有一个 BugZilla 帐户。因此,请打开 BugZilla 主页 [11] 并按照链接 创建新的 Bugzilla 帐户 [12] 中的步骤操作,如果您还没有帐户的话。

  1. 从 BugZilla 主页选择 提交新的错误报告 [13]
  2. 选择 "Ant" 作为产品
  3. 版本为最新的 "Alpha (nightly)"(目前为 1.10)
  4. 组件为 "Core tasks"
  5. 平台和严重程度选择 "Other" 和 "Normal"
  6. 初始状态选择 "New"
  7. 空着的 "Assigned to" 也是一样
  8. 不需要添加自己为 CC,因为您是报告者,因此会收到更改通知
  9. URL:GitHub 拉取请求 URL
  10. 摘要:添加表格中的 subject
  11. 描述:添加表格中的 body
  12. 然后点击 "Commit"

现在新的任务已在错误数据库中注册。

资源

  1. tutorial-writing-tasks.html
  2. tutorial-tasks-filesets-properties.zip
  3. properties.html#built-in-props
  4. http://ant-contrib.sourceforge.net/
  5. Tasks/java.html
  6. https://ant.apache.org/ant_task_guidelines.html
  7. https://github.com/apache/ant
  8. https://www.oracle.com/technetwork/java/archive-139210.html
  9. https://www.oracle.com/technetwork/java/codeconvtoc-136057.html
  10. https://checkstyle.org/
  11. https://issues.apache.org/bugzilla/
  12. https://issues.apache.org/bugzilla/createaccount.cgi
  13. https://issues.apache.org/bugzilla/enter_bug.cgi