チュートリアル: プロパティ、ファイルセット、パスを使用したタスク

タスクの作成に関するチュートリアル[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"/>

OK、コアタスクで書き直すことができます。

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

しかし、私は既知の場所から始めなければなりません :-)。

では、何をすべきでしょうか?3つの属性(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インスタンスを介して行われます。このインスタンスは、Task(より正確にはProjectComponent)から継承した公開のgetProject()メソッドを介して取得します。プロパティの読み取りは、getProperty(propertyname)を介して行われます(非常に簡単ですね?)。このプロパティは、値をStringとして返すか、設定されていない場合はnullを返します。
プロパティの設定は...それほど難しくはありませんが、複数のセッターがあります。setProperty()メソッドを使用すると、期待どおりにジョブを実行できます。しかし、Antには黄金律があります。「プロパティは不変である」。そして、このメソッドは、プロパティに以前に値があったかどうかに関係なく、指定された値に設定します。したがって、別の方法を使用します。setNewProperty()は、同じ名前のプロパティがない場合にのみプロパティを設定します。それ以外の場合は、メッセージがログに記録されます。

(ちなみに、Antの「名前空間」についての簡単な説明です。XML名前空間と混同しないでください。<antcall>は、プロパティ名の新しい空間を作成します。呼び出し元のすべてのプロパティが呼び出し先に渡されますが、呼び出し先は呼び出し元に通知することなく独自のプロパティを設定できます。)

他のセッターもいくつかありますが(私はそれらを使用したことがないので、何も言うことはできません、ごめんなさい:-))

上記の2行の例を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>

何が必要でしょうか?2つの属性(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() {
    }
}

OK—そのタスクではあまり多くのことはできませんが、失敗することなく、説明した方法で使用できます。次のステップでは、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()メソッドが後続の呼び出しを拒否する必要があります。//3で行われているように、ファイルセットの結果をDirectoryScannerを介して取得できます。その後、ファイルパスのプラットフォームに依存しない文字列表現を作成します(//4、もちろん他の方法でも実行できます)。単純な文字列比較で作業するため、replace()を実行する必要があります。Ant自体はプラットフォームに依存しないため、パス区切り文字としてスラッシュ(/、例:Linux)またはバックスラッシュ(\、例:Windows)を使用するファイルシステムで実行できます。したがって、それを統一する必要があります。ファイルが見つかった場合は、//5で絶対パス表現を作成し、basedirを知らなくてもその情報を使用できるようにします。(これは複数のファイルセットで使用する場合に非常に重要です。なぜなら、それらは異なるbasedirを持つ可能性があり、ディレクトリ スキャナーの戻り値はbasedirに対して相対的であるためです。)最後に、ファイルが見つかった場合は、ファイルの場所をプロパティとして保存します(//6)。

OK、この単純なケースでは、fileを追加のinclude要素としてすべてのファイルセットに追加する方がはるかに簡単でしょう。しかし、複雑な状況を複雑にせずに処理する方法を示したかったのです :-)

テストケースでは、Antプロパティant.homeを参照として使用します。このプロパティは、Antを起動するLauncherクラスによって設定されます。このプロパティは、ビルドファイルで組み込みプロパティ[3]として使用できます。しかし、新しいAnt環境を作成する場合は、その値を独自に設定する必要があります。そして、<junit>タスクをforkモードで使用します。したがって、ビルドファイルを変更する必要があります。

    <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>です。ファイルがすべて共通のベースディレクトリにある場合は、ファイルセットが簡単です。しかし、そうでない場合は、問題が発生します。もう1つの欠点は、その速度です。巨大なディレクトリ構造にファイルが少ししかない場合は、代わりに<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()の名前を変更します(実際にはmust-beではありませんが、それが行うことのように名前を付ける方がよいです)。また、そのメソッドでexpected-Stringを更新します(ここでは、パスが設定されていませんというメッセージが期待されています)。より複雑なテストケースは、ビルドスクリプトに基づいています。したがって、ターゲットtestFileNotPresenttestFilePresentは、上記の方法で変更する必要があります。

テストは完了しました。次に、タスクの実装を調整する必要があります。最も簡単な変更は、validate()メソッドで、最後の行をif (paths.size()<1) throw new BuildException("path not set");に変更することです。execute()メソッドでは、もう少し作業が必要です。... mmmh ...実際には作業量が少なくなります。なぜなら、PathクラスがすべてのDirectoryScannerの処理と絶対パスの作成を代わりに実行してくれるからです。したがって、executeメソッドは次のようになります。

    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);
    }

もちろん、//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で最後の項目に末尾の区切り文字がないようにします。

OK、最初にすべてのファイルを検索し、次に最初のファイルのみを返します...独自のパフォーマンスを調整できます :-)

ドキュメント

ビルドファイルをコードできるのがタスク開発者(そして、彼は次の数週間だけ :-))である場合、タスクは役に立ちません。したがって、ドキュメントも非常に重要です。どの形式でそれを行うかは、好みに依存します。しかし、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]には、それに関する追加情報が記載されています。

次に、そのガイドラインに記載されている「新しいタスクを送信する前のチェックリスト」を確認します。

パッケージ/ディレクトリ

このタスクは外部ライブラリに依存していません。したがって、これをコアタスクとして使用できます。このタスクにはクラスが1つしか含まれていません。したがって、コアタスクには標準パッケージ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はタスクまたはこのタスクが依存するクラスを見つけることができませんでした。

OK:前のステップで、Antに<find>タスクにFindクラスを使用するように指示しました(use.initターゲットの<taskdef>ステートメントを思い出してください)。しかし、ここでそのタスクをコアタスクとして導入したいと考えています。そして、誰もjavacechoなどをtaskdefしたくはありません。では、どうすればよいでしょうか?答えはsrc/main/.../taskdefs/default.propertiesです。ここに、タスク名と実装クラスのマッピングが記述されています。したがって、最後のコアタスク(# optional tasks行の直前)としてfind=org.apache.tools.ant.taskdefs.Findを追加します。もう一度試してみましょう。

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

テストは%ANT_HOME%\lib\ant.jar(より正確にはクラスパス)にあるプロパティファイルを探すため、Antを再ビルドする必要があります(//1)。ソースパスのみを変更したため、そのjarを再ビルドする必要があります。しかし、これで全てのテストに合格し、クラスが他のテストを壊していないか確認します。

ANTREPO> ant test -Dtest.haltonfailure=false

多くのテストがあるため、このステップには少し時間がかかります。そのため、開発中はrun-single-testを使用し、最後にのみ(開発中も時々)testを実行します。ここで-Dtest.haltonfailure=falseを使用するのは、他のテストが失敗する可能性があり、それらを調べる必要があるためです。

このテスト実行では、2つのことが確認されるはずです。テストが実行され、失敗したテストの数が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変数をクリアし、buildbootstrap、およびdistディレクトリを削除し、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ディレクトリを削除し、2回目のcheckstyle実行を行います。これで、タスクが一覧表示されなくなりました。それは正常です :-)

タスクを公開します

最後に、そのアーカイブを公開します。Antタスクガイドライン[7]に記述されているように、開発者メーリングリストでアナウンスし、BugZillaエントリを作成し、GitHubプルリクエストを開くことができます。どちらにも、いくつかの情報が必要です。

件名 簡単な説明 パス内のファイルを検索するタスク
本文 パスに関する詳細 この新しいタスクは、ネストされた<path/>内でファイルの出現を調べ、すべての場所をプロパティとして格納します。詳細については、付属のマニュアルを参照してください。
プルリクエスト参照 GitHubプルリクエストURL https://github.com/apache/ant/pull/0

この情報をメールで送信するのは非常に簡単であり、説明する必要はないと思います。BugZillaは少し難しいですが、エントリが忘れられないという利点があります(レポートは週末ごとに1回生成されます)。したがって、そのプロセスについて説明します。

まず、BugZillaアカウントが必要です。BugZillaメインページ[11]を開き、新しいBugzillaアカウントを開く[12]リンクをクリックし、まだアカウントを持っていない場合はそこに記載されている手順に従ってください。

  1. BugZillaメインページから、新しいバグレポートを入力[13]を選択します。
  2. 製品として「Ant」を選択します
  3. バージョンは最新の「アルファ(ナイトリー)」(現時点では1.10)です。
  4. コンポーネントは「コアタスク」です。
  5. プラットフォームと重要度は「その他」と「通常」で問題ありません
  6. 初期状態は「新規」で問題ありません
  7. 「割り当て先」が空の場合も同様です
  8. 自分自身をCCに追加する必要はありません。レポート作成者であるため、変更について通知されます。
  9. URL:GitHubプルリクエストURL
  10. 概要:表から件名を追加します
  11. 説明:表から本文を追加します
  12. 次に、「コミット」を押します

これで、新しいタスクがバグデータベースに登録されました。

リソース

  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.dokyumento.jp/ant_task_guidelines.html
  7. https://github.com/apache/ant
  8. https://www.oracle.com/technetwork/java/archive-139210.html
  9. (OracleによるJavaコード規約)
  10. (Checkstyleの公式サイト)
  11. (Apache Bugzillaのトップページ)
  12. (Apache Bugzillaのアカウント作成ページ)
  13. (Apache Bugzillaのバグ報告ページ)