教學:使用屬性、檔案集和路徑的任務

在閱讀關於 撰寫任務 [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) 讀取屬性(非常簡單,不是嗎?)。此屬性會傳回值為 Stringnull(如果未設定)。
設定屬性... 其實並不困難,但有一個以上的設定器。您可以使用 setProperty() 方法,它會如預期般執行工作。但在 Ant 中有一個黃金法則:屬性是不可變的。而且此方法會將屬性設定為指定值,無論之前是否有值。因此,我們使用另一種方式。 setNewProperty() 僅在沒有名稱相同的屬性時設定屬性。否則,會記錄訊息。

(順便一提,簡短說明 Ant 的「命名空間」,不要與 XML 命名空間混淆:<antcall> 會為屬性名稱建立新的空間。呼叫者的所有屬性都會傳遞給被呼叫者,但被呼叫者可以在呼叫者不知情的情況下設定自己的屬性。)

還有一些其他設定器(但我沒有使用過,所以無法說明,抱歉 :-)

將上面我們的兩行範例放入名稱為 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 編碼撰寫目標比透過 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)。

*1 上,我們只重新命名清單。這只是為了更佳閱讀來源。在 *2 上,我們必須提供正確的方法:addName(Type t)。因此,在此處將檔案集合替換為路徑。最後,我們必須在 *3 上修改我們的建置檔案,因為我們的任務不再支援巢狀檔案集合。因此,我們將檔案集合包裝在路徑內。

測試案例使用 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>

變更為

現在我們修改測試案例。喔,沒什麼好做的 :-) 將 testMissingFileset() 重新命名(並非一定要做,但最好以其功能命名)並更新該方法中的 expected-String(現在預期會出現 路徑未設定 訊息)。較複雜的測試案例會根據建置指令碼。因此,必須以上述方式修改目標 testFileNotPresenttestFilePresent

測試已完成。現在我們必須調整工作實作。最容易修改的是 validate() 方法,我們將最後一行變更為 if (paths.size()<1) throw new BuildException("path not set");。在 execute() 方法中,我們的工作稍多一些。... 嗯... 實際上工作更少,因為 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 確保最後一個項目沒有尾隨分隔符號。

好,首先搜尋所有檔案,然後只回傳第一個檔案...您可以自行調整效能 :-)

文件

如果只有建置檔開發人員(而且他只有接下來的幾週時間 :-)才能編寫建置檔,那麼這項任務就毫無用處。因此文件也很重要。您要使用哪種形式的文件,取決於您的喜好。不過在 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 clone,而且通常會貢獻我的成果,所以我從一開始就處理本地副本。優點:如果您修改現有的來源,這個步驟不是必要的,而且可以節省大量工作 :-)

現在,我們的修改已完成,我們將重新測試它

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> 陳述式)。但現在我們想要將該任務作為核心任務來引入。而且沒有人想要對 javacecho、... 執行 taskdef。那該怎麼辦?答案是 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。

根據訊息整理程式碼後,我們刪除報告目錄並執行第二次 checkstyle 執行。現在我們的任務沒有列出。這很好 :-)

發布任務

最後我們發布該檔案。如 Ant 任務指南 [7] 中所述,我們可以在開發人員郵件清單上公告,建立 BugZilla 條目並開啟 GitHub pull 要求。對於這兩者,我們需要一些資訊

主旨 簡短說明 在路徑中尋找檔案的任務
本文 路徑的更多詳細資料 這個新任務會在嵌套的 <path/> 中尋找檔案的出現,並將所有位置儲存為屬性。有關詳細資料,請參閱隨附的手冊。
pull 要求參考 GitHub pull 要求 URL https://github.com/apache/ant/pull/0

傳送包含此資訊的電子郵件非常容易,我想我不用說明。BugZilla 稍微困難一些。但好處是條目不會被遺忘(每週六會產生一份報告)。因此,我將說明這個流程。

首先,您必須擁有 BugZilla 帳戶。因此,開啟 BugZilla 主頁 [11],並追蹤連結 開啟新的 Bugzilla 帳戶 [12],如果您沒有帳戶,請執行其中所述的步驟。

  1. 從 BugZilla 主頁選擇 輸入新的錯誤報告 [13]
  2. 選擇「Ant」作為產品
  3. 版本是最後一個「Alpha(夜間)」版本(目前為 1.10)
  4. 元件是「核心任務」
  5. 平台和嚴重性使用「其他」和「一般」即可
  6. 初始狀態使用「新」即可
  7. 與空白的「指派給」相同
  8. 不需要將自己新增為副本收件者,因為您是報告者,因此會收到變更通知
  9. URL:GitHub pull 要求 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.dev.org.tw/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