|
- <!DOCTYPE html>
- <!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
- <html lang="en">
- <head>
- <link rel="stylesheet" type="text/css" href="stylesheets/style.css">
- <title>Tutorial: Tasks using Properties, Filesets & Paths</title>
- </head>
- <body>
- <h1>Tutorial: Tasks using Properties, Filesets & Paths</h1>
-
- <p>After reading the tutorial about <a href="tutorial-writing-tasks.html">writing tasks [1]</a> this tutorial explains
- how to get and set properties and how to use nested filesets and paths. Finally it explains how to contribute tasks to
- Apache Ant.</p>
-
- <h2>Content</h2>
- <ul>
- <li><a href="#goal">The goal</a></li>
- <li><a href="#buildenvironment">Build environment</a></li>
- <li><a href="#propertyaccess">Property access</a></li>
- <li><a href="#filesets">Using filesets</a></li>
- <li><a href="#path">Using nested paths</a></li>
- <li><a href="#returning-list">Returning a list</a></li>
- <li><a href="#documentation">Documentation</a></li>
- <li><a href="#contribute">Contribute the new task</a></li>
- <li><a href="#resources">Resources</a></li>
- </ul>
-
- <h2 id="goal">The goal</h2>
- <p>The goal is to write a task, which searches in a path for a file and saves the location of that file in a
- property.</p>
-
- <h2 id="buildenvironment">Build environment</h2>
- <p>We can use the buildfile from the other tutorial and modify it a little bit. That's the advantage of using
- properties—we can reuse nearly the whole script. :-)</p>
- <pre>
- <?xml version="1.0" encoding="UTF-8"?>
- <project name="<b>FindTask</b>" basedir="." default="test">
- ...
- <target name="use.init" description="Taskdef's the <b>Find</b>-Task" depends="jar">
- <taskdef name="<b>find</b>" classname="<b>Find</b>" classpath="${ant.project.name}.jar"/>
- </target>
-
- <b><!-- the other use.* targets are deleted --></b>
- ...
- </project></pre>
-
- <p>The buildfile is in the
- archive <a href="tutorial-tasks-filesets-properties.zip">tutorial-tasks-filesets-properties.zip [2]</a>
- in <samp>/build.xml.01-propertyaccess</samp> (future version saved as *.02..., final version as <samp>build.xml</samp>;
- same for sources).</p>
-
- <h2 id="propertyaccess">Property access</h2>
- <p>Our first step is to set a property to a value and print the value of that property. So
- our scenario would be</p>
- <pre>
- <find property="test" value="test-value"/>
- <find print="test"/></pre>
- <p>Ok, it can be rewritten with the core tasks</p>
- <pre>
- <property name="test" value="test-value"/>
- <echo message="${test}"/></pre>
- <p>but I have to start on known ground :-)</p>
- <p>So what to do? Handling three attributes (<var>property</var>, <var>value</var>, <var>print</var>) and an execute
- method. Because this is only an introduction example I don't do much checking:</p>
-
- <pre>
- 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 = <b>getProject().getProperty(print)</b>;
- log(propValue);
- } else {
- if (property == null) throw new BuildException("property not set");
- if (value == null) throw new BuildException("value not set");
- <b>getProject().setNewProperty(property, value)</b>;
- }
- }
- }</pre>
-
- <p>As said in the other tutorial, the property access is done via <code class="code">Project</code> instance. We get
- this instance via the public <code class="code">getProject()</code> method which we inherit
- from <code class="code">Task</code> (more precisely from <code class="code">ProjectComponent</code>). Reading a property
- is done via <code class="code">getProperty(<i>propertyname</i>)</code> (very simple, isn't it?). This property returns
- the value as <code>String</code> or <code>null</code> if not set.<br/> Setting a property is ... not really difficult,
- but there is more than one setter. You can use the <code class="code">setProperty()</code> method which will do the job
- as expected. But there is a golden rule in Ant: <em>properties are immutable</em>. And this method sets the property to
- the specified value—whether it has a value before that or not. So we use another
- way. <code class="code">setNewProperty()</code> sets the property only if there is no property with that name. Otherwise
- a message is logged.</p>
-
- <p><em>(By the way, a short explanation of Ant's "namespaces"—not to be confused with XML namespaces:
- an <code><antcall></code> creates a new space for property names. All properties from the caller are passed to the
- callee, but the callee can set its own properties without notice by the caller.)</em></p>
-
- <p>There are some other setters, too (but I haven't used them, so I can't say something to them, sorry :-)</p>
-
- <p>After putting our two line example from above into a target names <code>use.simple</code> we can call that from our
- test case:</p>
-
- <pre>
- 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");
- <b>Assert.assertEquals("test-value", buildRule.getLog());</b>
- }
- }</pre>
-
- <p>and all works fine.</p>
-
- <h2 id="filesets">Using filesets</h2>
- <p>Ant provides a common way of bundling files: the fileset. Because you are reading this tutorial I think you know them
- and I don't have to spend more explanations about their usage in buildfiles. Our goal is to search for a file in
- path. And in this step the path is simply a fileset (or more precise: a collection of filesets). So our usage would
- be</p>
- <pre>
- <find file="ant.jar" location="location.ant-jar">
- <fileset dir="${ant.home}" includes="**/*.jar"/>
- </find></pre>
-
- <p>What do we need? A task with two attributes (<var>file</var>, <var>location</var>) and nested filesets. Because we
- had attribute handling already explained in the example above and the handling of nested elements is described in the
- other tutorial, the code should be very easy:</p>
- <pre>
- 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() {
- }
- }</pre>
- <p>Ok—that task wouldn't do very much, but we can use it in the described manner without failure. In the next step
- we have to implement the execute method. And before that we will implement the appropriate test cases (TDD—test
- driven development).</p>
-
- <p>In the other tutorial we have reused the already written targets of our buildfile. Now we will configure most of the
- test cases via Java code (sometimes it's much easier to write a target than doing it via Java coding). What can be
- tested?</p>
- <ul>
- <li>invalid configuration of the task (missing file, missing location, missing fileset)</li>
- <li>don't find a present file</li>
- <li>behaviour if file can't be found</li>
- </ul>
- <p>Maybe you find some more test cases. But this is enough for now.<br/> For each of these points we create
- a <code class="code">testXX</code> method.</p>
-
- <pre>
- 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");
- <b>Find find = new Find();</b>
- <b>find.execute();</b>
- }
-
- @Test
- public void testMissingLocation() {
- tried.expect(BuildException.class);
- tried.expectMessage("location not set");
- Find find = new Find();
- <b>find.setFile("ant.jar");</b>
- 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"));
- }
- }</pre>
-
- <p>If we run this test class all test cases (except <code class="code">testFileNotPresent</code>) fail. Now we can
- implement our task, so that these test cases will pass.</p>
-
- <pre>
- 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);
- }</pre>
-
- <p>On <strong>//1</strong> we check the prerequisites for our task. Doing that in a <code class="code">validate()</code>
- method is a common way, because we separate the prerequisites from the real work. On <strong>//2</strong> we iterate
- over all nested filesets. If we don't want to handle multiple filesets, the <code class="code">addFileset()</code>
- method has to reject the further calls. We can get the result of a fileset via
- its <code class="code">DirectoryScanner</code> like done in <strong>//3</strong>. After that we create a platform
- independent String representation of the file path (<strong>//4</strong>, can be done in other ways of course). We have
- to do the <code class="code">replace()</code>, because we work with a simple string comparison. Ant itself is platform
- independent and can therefore run on filesystems with slash (<q>/</q>, e.g. Linux) or backslash (<q>\</q>, e.g. Windows)
- as path separator. Therefore we have to unify that. If we find our file, we create an absolute path representation
- on <strong>//5</strong>, so that we can use that information without knowing the <var>basedir</var>. (This is very
- important on use with multiple filesets, because they can have different <var>basedir</var>s and the return value of the
- directory scanner is relative to its <var>basedir</var>.) Finally we store the location of the file as property, if we
- had found one (<strong>//6</strong>).</p>
-
- <p>Ok, much more easier in this simple case would be to add the <var>file</var> as additional <code>include</code>
- element to all filesets. But I wanted to show how to handle complex situations without being complex :-)</p>
-
- <p>The test case uses the Ant property <code>ant.home</code> as reference. This property is set by
- the <code class="code">Launcher</code> class which starts ant. We can use that property in our buildfiles as
- a <a href="properties.html#built-in-props">build-in property [3]</a>. But if we create a new Ant environment we have to
- set that value for our own. And we use the <code><junit></code> task in <var>fork</var> mode. Therefore we have
- do modify our buildfile:</p>
- <pre>
- <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"/>
- <b><sysproperty key="ant.home" value="${ant.home}"/></b>
- <formatter type="xml"/>
- <batchtest fork="yes" todir="${junit.out.dir.xml}">
- <fileset dir="${src.dir}" includes="**/*Test.java"/>
- </batchtest>
- </junit>
- </target></pre>
-
- <h2 id="path">Using nested paths</h2>
- <p>A task providing support for filesets is a very comfortable one. But there is another possibility of bundling files:
- the <code><path></code>. Filesets are easy if the files are all under a common base directory. But if this is not
- the case, you have a problem. Another disadvantage is its speed: if you have only a few files in a huge directory
- structure, why not use a <code><filelist></code> instead? <code><path></code>s combines these datatypes in
- that way that a path contains other paths, filesets, dirsets and filelists. This is
- why <a href="http://ant-contrib.sourceforge.net/" target="_top">Ant-Contrib [4]</a> <code><foreach></code> task is
- modified to support paths instead of filesets. So we want that, too.</p>
-
- <p>Changing from fileset to path support is very easy:</p>
- <em><strong>Change Java code from:</strong></em>
- <pre>
- private List<FileSet> filesets = new ArrayList<>();
- public void addFileset(FileSet fileset) {
- filesets.add(fileset);
- }</pre>
- <em><strong>to:</strong></em>
- <pre>
- private List<Path> paths = new ArrayList<>(); *1
- public void add<b>Path</b>(<b>Path</b> path) { *2
- paths.add(path);
- }</pre>
- <em><strong>and build file from:</strong></em>
- <pre>
- <find file="ant.jar" location="location.ant-jar">
- <fileset dir="${ant.home}" includes="**/*.jar"/>
- </find></pre>
- <em><strong>to:</strong></em>
- <pre>
- <find file="ant.jar" location="location.ant-jar">
- <b><path></b> *3
- <fileset dir="${ant.home}" includes="**/*.jar"/>
- </path>
- </find></pre>
- <p>On <strong>*1</strong> we rename only the list. It's just for better reading the source. On <strong>*2</strong> we
- have to provide the right method: an <code>add<i>Name</i>(<i>Type</i> t)</code>. Therefore replace the fileset with path
- here. Finally we have to modify our buildfile on <strong>*3</strong> because our task doesn't support nested filesets
- any longer. So we wrap the fileset inside a path.</p>
-
- <p>And now we modify the test case. Oh, not very much to do :-) Renaming
- the <code class="code">testMissingFileset()</code> (not really a <em>must-be</em> but better it's named like the thing
- it does) and update the <var>expected</var>-String in that method (now a <samp>path not set</samp> message is
- expected). The more complex test cases base on the build script. So the targets <var>testFileNotPresent</var>
- and <var>testFilePresent</var> have to be modified in the manner described above.</p>
-
- <p>The test are finished. Now we have to adapt the task implementation. The easiest modification is in
- the <code class="code">validate()</code> method where we change the last line to <code class="code">if
- (paths.size()<1) throw new BuildException("path not set");</code>. In the <code class="code">execute()</code> method
- we have a little more work. ... mmmh ... in reality it's less work, because the <code class="code">Path</code> class
- does the whole <code class="code">DirectoryScanner</code>-handling and creating-absolute-paths stuff for us. So the
- execute method becomes just:</p>
-
- <pre>
- public void execute() {
- validate();
- String foundLocation = null;
- for (Path path : paths) { // 1
- for (String includedFile : <b>path.list()</b>) { // 2
- String filename = includedFile.replace('\\','/');
- filename = filename.substring(filename.lastIndexOf("/") + 1);
- if (foundLocation == null && file.equals(filename)) {
- <b>foundLocation = includedFile;</b> // 3
- }
- }
- }
- if (foundLocation != null)
- getProject().setNewProperty(location, foundLocation);
- }
- </pre>
-
- <p>Of course we have to iterate through paths on <strong>//1</strong>. On <strong>//2</strong> and <strong>//3</strong>
- we see that the Path class does the work for us: no DirectoryScanner (was at 2) and no creating of the absolute path
- (was at 3).</p>
-
- <h2 id="returning-list">Returning a list</h2>
- <p>So far so good. But could a file be on more than one place in the path?—Of course.<br/>
- And would it be good to get all of them?—It depends ...<p>
-
- <p>In this section we will extend that task to support returning a list of all files. Lists as property values are not
- supported by Ant natively. So we have to see how other tasks use lists. The most famous task using lists is
- Ant-Contrib's <code><foreach></code>. All list elements are concatenated and separated with a customizable
- separator (default <q>,</q>).</p>
-
- <p>So we do the following:</p>
-
- <pre><find ... <b>delimiter=""</b>/> ... </find></pre>
-
- <p>if the delimiter is set, we will return all found files as list with that delimiter.</p>
-
- <p>Therefore we have to</p>
- <ul>
- <li>provide a new attribute</li>
- <li>collect more than the first file</li>
- <li>delete duplicates</li>
- <li>create the list if necessary</li>
- <li>return that list</li>
- </ul>
-
- <p>So we add as test case:</p>
- <strong><em>in the buildfile:</em></strong>
- <pre>
- <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,<b>test.init</b>"> *3
- <find file="test" location="location.test" <b>delimiter=";"</b>>
- <path>
- <fileset dir="test1"/>
- <fileset dir="test2"/>
- </path>
- </find>
- <delete> *4
- <fileset dir="test1"/>
- <fileset dir="test2"/>
- </delete>
- </target></pre>
- <strong><em>in the test class:</em></strong>
- <pre>
- public void testMultipleFiles() {
- executeTarget("testMultipleFiles");
- String result = getProject().getProperty("location.test");
- assertNotNull("Property not set.", result);
- assertTrue("Only one file found.", result.indexOf(";") > -1);
- }</pre>
-
- <p>Now we need a directory structure where we CAN find files with the same name in different directories. Because we
- can't sure to have one we create one on <strong>*1</strong> and <strong>*2</strong>. And of course we clean up that
- on <strong>*4</strong>. The creation can be done inside our test target or in a separate one, which will be better for
- reuse later (<strong>*3</strong>).
-
- <p>The task implementation is modified as followed:</p>
-
- <pre>
- 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) && <b>!foundFiles.contains(includedFile)</b>) { // 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 (<b>list.length() > 0</b>) list.append(delimiter); // 4
- }
- rv = list.toString();
- }
- }
-
- // create the property
- if (rv != null)
- getProject().setNewProperty(location, rv);
- }</pre>
-
- <p>The algorithm does: finding all files, creating the return value depending on the users wish, returning the value as
- property. On <strong>//1</strong> we eliminates the duplicates. <strong>//2</strong> ensures that we create the return
- value only if we have found one file. On <strong>//3</strong> we iterate over all found files and <strong>//4</strong>
- ensures that the last entry has no trailing delimiter.</p>
-
- <p>Ok, first searching for all files and then returning only the first one ... You can tune the performance of your own
- :-)</p>
-
- <h2 id="documentation">Documentation</h2>
- <p>A task is useless if the only who is able to code the buildfile is the task developer (and he only the next few weeks
- :-). So documentation is also very important. In which form you do that depends on your favourite. But inside Ant there
- is a common format and it has advantages if you use that: all task users know that form, this form is requested if you
- decide to contribute your task. So we will doc our task in that form.</p>
-
- <p>If you have a look at the manual page of the <a href="Tasks/java.html">Java task [5]</a> you will see that it:</p>
- <ul>
- <li>is plain html</li>
- <li>starts with the name</li>
- <li>has sections: description, parameters, nested elements, (maybe return codes) and (most important :-) examples</li>
- <li>parameters are listed in a table with columns for attribute name, its description and whether it's required (if you
- add a feature after an Ant release, provide a <em>since Ant xx</em> statement when it's introduced)</li>
- <li>describe the nested elements (since-statement if necessary)</li>
- <li>provide one or more useful examples; first code, then description.</li>
- </ul>
- <p>As a template we have:</p>
-
- <pre>
- <!DOCTYPE html>
- <html lang="en">
-
- <head>
- <title><b>Taskname</b> Task</title>
- </head>
-
- <body>
-
- <h2 id="<b>taskname</b>"><b>Taskname</b></h2>
- <h3>Description</h3>
- <p><b>Describe the task.</b></p>
-
- <h3>Parameters</h3>
- <table class="attr">
- <tr>
- <th scope="col">Attribute</th>
- <th scope="col">Description</th>
- <th scope="col">Required</th>
- </tr>
-
- <b>do this html row for each attribute (including inherited attributes)</b>
- <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>
-
- <b>Describe each nested element (including inherited)</b>
- <h4><b>your nested element</b></h4>
- <p><b>description</b></p>
- <p><em>since Ant 1.6</em>.</p>
-
- <h3>Examples</h3>
- <pre>
- <b>A code sample; don't forget to escape the < of the tags with &lt;</b>
- </pre>
- <b>What should that example do?</b>
-
- </body>
- </html></pre>
-
- <p>Here is an example documentation page for our task:</p>
- <pre>
- <!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></pre>
-
- <h2 id="contribute">Contribute the new task</h2>
- <p>If we decide to contribute our task, we should do some things:</p>
- <ul>
- <li>is our task welcome? :-) Simply ask on the user list</li>
- <li>is the right package used?</li>
- <li>does the code conform to the styleguide?</li>
- <li>do all tests pass?</li>
- <li>does the code compile on JDK 5 (and passes all tests there)?</li>
- <li>code under Apache license</li>
- <li>create a patch file</li>
- <li>publishing that patch file</li>
- </ul>
- <p>The <a href="https://ant.apache.org/ant_task_guidelines.html" target="_top">Ant Task Guidelines [6]</a> support
- additional information on that.</p>
-
- <p>Now we will check the "Checklist before submitting a new task" described in that guideline.</p>
- <ul>
- <li>Java file begins with Apache license statement. <strong><em>must do that</em></strong></li>
- <li>Task does not depend on GPL or LGPL code. <strong><em>ok</em></strong></li>
- <li>Source code complies with style guidelines <strong><em>have to check (checkstyle)</em></strong></li>
- <li>Code compiles and runs on Java 5 <strong><em>have to try</em></strong></li>
- <li>Member variables are private, and provide public accessor methods if access is actually needed. <strong><em>have to
- check (checkstyle)</em></strong></li>
- <li><em>Maybe</em> Task has <var>failonerror</var> attribute to control failure
- behaviour <strong><em>hasn't</em></strong></li>
- <li>New test cases written and succeed <strong><em>passed on JDK 8, have to try on JDK 5</em></strong></li>
- <li>Documentation page written <strong><em>ok</em></strong></li>
- <li>Example task declarations in the documentation tested. <strong><em>ok (used in tests)</em></strong></li>
- <li>Message to dev contains [SUBMIT] and task name in subject <strong><em>to do</em></strong></li>
- <li>Message body contains a rationale for the task <strong><em>to do</em></strong></li>
- <li>Message body contains the URL to GitHub pull request. <strong><em>to do</em></strong></li>
- </ul>
-
- <h3>Package / Directories</h3>
- <p>This task does not depend on any external library. Therefore we can use this as a core task. This task contains only
- one class. So we can use the standard package for core
- tasks: <code class="code">org.apache.tools.ant.taskdefs</code>. Implementations are in the
- directory <samp>src/main</samp>, tests in <samp>src/testcases</samp> and buildfiles for tests
- in <samp>src/etc/testcases</samp>.</p>
-
- <p>Now we integrate our work into Ant distribution. So first we do an update of our Git tree. If not done yet, you
- should clone the Ant repository on GitHub[7], then create a local clone:</p>
- <pre class="input">git clone https://github.com/<em>your-sig</em>/ant.git</pre>
-
- <p>Now we will build our Ant distribution and do a test. So we can see if there are any tests failing on our
- machine. (We can ignore these failing tests on later steps; Windows syntax used here—translate to UNIX if
- needed):</p>
- <pre class="input">
- ANTREPO> build // 1
- ANTREPO> set ANT_HOME=%CD%\dist // 2
- ANTREPO> ant test -Dtest.haltonfailure=false // 3</pre>
-
- <p>First we have to build our Ant distribution (<strong>//1</strong>). On <strong>//2</strong> we set
- the <code>ANT_HOME</code> environment variable to the directory where the new created distribution is stored
- (<code>%CD%</code> is expanded to the current directory on Windows 2000 and later). On <strong>//3</strong> we let Ant
- do all the tests (which enforced a compile of all tests) without stopping on first failure.</p>
-
- <p>Next we apply our work onto Ant sources. Because we haven't modified any, this is a relatively simple
- step. <em>(Because I have a local Git clone of Ant and usually contribute my work, I work on the local copy just from
- the beginning. The advantage: this step isn't necessary and saves a lot of work if you modify existing sources :-)</em>.
-
- <ul>
- <li>move the <samp>Find.java</samp> to <samp>ANTREPO/src/main/org/apache/tools/ant/taskdefs/Find.java</samp></li>
- <li>move the <samp>FindTest.java</samp> to <samp>ANTREPO/src/testcases/org/apache/tools/ant/taskdefs/FindTest.java</samp></li>
- <li>move the <samp>build.xml</samp> to <samp>ANTREPO/src/etc/testcases/taskdefs/<strong>find.xml</strong></samp> (!!! renamed !!!)</li>
- <li>add a <code>package org.apache.tools.ant.taskdefs;</code> at the beginning of the two java files</li>
- <li>delete all stuff from <samp>find.xml</samp> keeping the
- targets <q>testFileNotPresent</q>, <q>testFilePresent</q>, <q>test.init</q> and <q>testMultipleFiles</q></li>
- <li>delete the dependency to <q>use.init</q> in the <samp>find.xml</samp></li>
- <li>in <samp>FindTest.java</samp> change the line <code>configureProject("build.xml");</code>
- to <code>configureProject("src/etc/testcases/taskdefs/find.xml");</code></li>
- <li>move the <samp>find.html</samp> to <samp>ANTREPO/docs/manual/Tasks/find.html</samp></li>
- <li>add a <code><a href="Tasks/find.html">Find</a><br></code> in
- the <samp>ANTREPO/docs/manual/tasklist.html</samp></li>
- </ul>
-
- <p>Now our modifications are done and we will retest it:</p>
- <pre class="input">
- ANTREPO> build
- ANTREPO> ant run-single-test // 1
- -Dtestcase=org.apache.tools.ant.taskdefs.FindTest // 2
- -Dtest.haltonfailure=false</pre>
- <p>Because we only want to test our new class, we use the target for single tests, specify the test to use and configure
- not to halt on the first failure—we want to see all failures of our own test (<strong>//1 + 2</strong>).</p>
-
- <p>And ... oh, all tests fail: <em>Ant could not find the task or a class this task relies upon.</em></p>
-
- <p>Ok: in the earlier steps we told Ant to use the Find class for the <code><find></code> task (remember
- the <code><taskdef></code> statement in the <q>use.init</q> target). But now we want to introduce that task as a
- core task. And nobody wants to <code>taskdef</code> the <code>javac</code>, <code>echo</code>, ... So what to do? The
- answer is the <samp>src/main/.../taskdefs/default.properties</samp>. Here is the mapping between taskname and
- implementing class done. So we add a <code>find=org.apache.tools.ant.taskdefs.Find</code> as the last core task (just
- before the <code># optional tasks</code> line). Now a second try:</p>
- <pre class="input">
- ANTREPO> build // 1
- ANTREPO> ant run-single-test
- -Dtestcase=org.apache.tools.ant.taskdefs.FindTest
- -Dtest.haltonfailure=false</pre>
- <p>We have to rebuild (<strong>//1</strong>) Ant because the test look in the <samp>%ANT_HOME%\lib\ant.jar</samp> (more
- precise: on the classpath) for the properties file. And we have only modified it in the source path. So we have to
- rebuild that jar. But now all tests pass and we check whether our class breaks some other tests.</p>
- <pre class="input">ANTREPO> ant test -Dtest.haltonfailure=false</pre>
- <p>Because there are a lot of tests this step requires a little bit of time. So use the <q>run-single-test</q> during
- development and do the <q>test</q> only at the end (maybe sometimes during development too). We use
- the <kbd>-Dtest.haltonfailure=false</kbd> here because there could be other tests fail and we have to look into
- them.</p>
-
- <p>This test run should show us two things: our test will run and the number of failing tests is the same as directly
- after <code>git clone</code> (without our modifications).</p>
-
- <h3>Apache license statement</h3>
- <p>Simply copy the license text from one the other source from the Ant source tree.</p>
-
- <h3>Test on JDK 5</h3>
- <p>Ant 1.10 uses Java 8 for development, but Ant 1.9 is actively maintained, too. That means that Ant code must be able
- to run on a JDK 5. So we have to test that. You can download older JDKs
- from <a href="https://www.oracle.com/technetwork/java/archive-139210.html" target="_top">Oracle [8]</a>.</p>
-
- <p>Clean the <code>ANT_HOME</code> variable, delete the <samp>build</samp>, <samp>bootstrap</samp> and <samp>dist</samp>
- directories, and point <code>JAVA_HOME</code> to the JDK 5 home directory. Then create the patch with your commit,
- checkout 1.9.x branch in Git, apply your patch and do the <code>build</code>, set <code>ANT_HOME</code> and
- run <kbd>ant test</kbd> (like above).</p>
-
- <p>Our test should pass.</p>
-
- <h3>Checkstyle</h3>
- <p>There are many things we have to ensure. Indentation with 4 spaces, blanks here and there, ... (all described in
- the <a href="https://ant.apache.org/ant_task_guidelines.html" target="_top">Ant Task Guidelines [6]</a> which includes
- the <a href="https://www.oracle.com/technetwork/java/codeconvtoc-136057.html" target="_top">Sun code style
- [9]</a>). Because there are so many things we would be happy to have a tool for do the checks. There is one:
- checkstyle. Checkstyle is available at <a href="http://checkstyle.sourceforge.net/" target="_top">Sourceforge [10]</a>
- and Ant provides with the <samp>check.xml</samp> a buildfile which will do the job for us.</p>
-
- <p>Download it and put the <samp>checkstyle-*-all.jar</samp> into your <samp>%USERPROFILE%\.ant\lib</samp> directory.
- All jar's stored there are available to Ant so you haven't to add it to you <samp>%ANT_HOME%\lib</samp> directory (this
- feature is available <em>since Ant 1.6</em>).</p>
-
- <p>So we will run the tests with</p>
- <pre class="input">ANTREPO> ant -f check.xml checkstyle htmlreport</pre>
- <p>I prefer the HTML report because there are lots of messages and we can navigate faster. Open
- the <samp>ANTREPO/build/reports/checkstyle/html/index.html</samp> and navigate to the <samp>Find.java</samp>. Now we see
- that there are some errors: missing whitespaces, unused imports, missing javadocs. So we have to do that.</p>
-
- <p>Hint: start at the <strong>bottom</strong> of the file so the line numbers in the report will keep up to date and you
- will find the next error place much more easier without redoing the checkstyle.</p>
-
- <p>After cleaning up the code according to the messages we delete the reports directory and do a second checkstyle
- run. Now our task isn't listed. That's fine :-)</p>
-
- <h3>Publish the task</h3>
- <p>Finally we publish that archive. As described in the <a href="https://ant.apache.org/ant_task_guidelines.html"
- target="_top">Ant Task Guidelines [7]</a> we can announce it on the developer mailing list, create a BugZilla entry and
- open a GitHub pull request. For both we need some information:</p>
-
- <table>
- <tr>
- <th scope="row">subject</th>
- <td><em>short description</em></td>
- <td>Task for finding files in a path</td>
- </tr>
- <tr>
- <th scope="row">body</th>
- <td><em>more details about the path</em></td>
- <td>This new task looks inside a nested <code><path/></code> for occurrences of a file and stores all locations
- as a property. See the included manual for details.</td>
- </tr>
- <tr>
- <th scope="row">pull request reference</th>
- <td><em>GitHub pull request URL</em></td>
- <td>https://github.com/apache/ant/pull/0</td>
- </tr>
- </table>
-
- <p>Sending an email with this information is very easy and I think I haven't to describe that. BugZilla is slightly
- more difficult. But the advantage is that entries will not be forgotten (a report is generated once every weekend). So
- I will describe the process.</p>
-
- <p>First, you must have a BugZilla account. So open the <a href="https://issues.apache.org/bugzilla/"
- target="_top">BugZilla Main Page [11]</a> and follow the
- link <a href="https://issues.apache.org/bugzilla/createaccount.cgi" target="_top">Open a new Bugzilla account [12]</a>
- and the steps described there if you haven't one.</p>
-
- <ol>
- <li>From the BugZilla main page choose <a href="https://issues.apache.org/bugzilla/enter_bug.cgi" target="_top">Enter a
- new bug report [13]</a></li>
- <li>Choose "Ant" as product</li>
- <li>Version is the last "Alpha (nightly)" (at this time 1.10)</li>
- <li>Component is "Core tasks"</li>
- <li>Platform and Severity are ok with "Other" and "Normal"</li>
- <li>Initial State is ok with "New"</li>
- <li>Same with the empty "Assigned to"</li>
- <li>It is not required to add yourself as CC, because you are the reporter and therefore will be informed on
- changes</li>
- <li>URL: GitHub pull request URL</li>
- <li>Summary: add the <var>subject</var> from the table</li>
- <li>Description: add the <var>body</var> from the table</li>
- <li>Then press "Commit"</li>
- </ol>
-
- <p>Now the new task is registered in the bug database.</p>
-
- <h2 id="resources">Resources</h2>
- <ol class="refs">
- <li><a href="tutorial-writing-tasks.html">tutorial-writing-tasks.html</a></li>
- <li><a href="tutorial-tasks-filesets-properties.zip">tutorial-tasks-filesets-properties.zip</a></li>
- <li><a href="properties.html#built-in-props">properties.html#built-in-props</a></li>
- <li><a href="http://ant-contrib.sourceforge.net/" target="_top">http://ant-contrib.sourceforge.net/</a></li>
- <li><a href="Tasks/java.html">Tasks/java.html</a></li>
- <li><a href="https://ant.apache.org/ant_task_guidelines.html"
- target="_top">https://ant.apache.org/ant_task_guidelines.html</a></li>
- <li><a href="https://github.com/apache/ant" target="_top">https://github.com/apache/ant</a></li>
- <li><a href="https://www.oracle.com/technetwork/java/archive-139210.html"
- target="_top">https://www.oracle.com/technetwork/java/archive-139210.html</a></li>
- <li><a href="https://www.oracle.com/technetwork/java/codeconvtoc-136057.html"
- target="_top">https://www.oracle.com/technetwork/java/codeconvtoc-136057.html</a></li>
- <li><a href="http://checkstyle.sourceforge.net/" target="_top">http://checkstyle.sourceforge.net/</a></li>
- <li><a href="https://issues.apache.org/bugzilla/" target="_top">https://issues.apache.org/bugzilla/</a></li>
- <li><a href="https://issues.apache.org/bugzilla/createaccount.cgi"
- target="_top">https://issues.apache.org/bugzilla/createaccount.cgi</a></li>
- <li><a href="https://issues.apache.org/bugzilla/enter_bug.cgi"
- target="_top">https://issues.apache.org/bugzilla/enter_bug.cgi</a></li>
- </ol>
-
- </body>
- </html>
|