You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

tutorial-tasks-filesets-properties.html 41 KiB

7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. <!--
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. -->
  15. <html>
  16. <head>
  17. <link rel="stylesheet" type="text/css" href="stylesheets/style.css">
  18. <title>Tutorial: Tasks using Properties, Filesets &amp; Paths</title>
  19. </head>
  20. <body>
  21. <h1>Tutorial: Tasks using Properties, Filesets &amp; Paths</h1>
  22. <p>After reading the tutorial about <a href="tutorial-writing-tasks.html">writing tasks [1]</a> this tutorial explains
  23. how to get and set properties and how to use nested filesets and paths. Finally it explains how to contribute tasks to
  24. Apache Ant.</p>
  25. <h2>Content</h2>
  26. <ul>
  27. <li><a href="#goal">The goal</a></li>
  28. <li><a href="#buildenvironment">Build environment</a></li>
  29. <li><a href="#propertyaccess">Property access</a></li>
  30. <li><a href="#filesets">Using filesets</a></li>
  31. <li><a href="#path">Using nested paths</a></li>
  32. <li><a href="#returning-list">Returning a list</a></li>
  33. <li><a href="#documentation">Documentation</a></li>
  34. <li><a href="#contribute">Contribute the new task</a></li>
  35. <li><a href="#resources">Resources</a></li>
  36. </ul>
  37. <h2 id="goal">The goal</h2>
  38. <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
  39. property.</p>
  40. <h2 id="buildenvironment">Build environment</h2>
  41. <p>We can use the buildfile from the other tutorial and modify it a little bit. That's the advantage of using
  42. properties&mdash;we can reuse nearly the whole script. :-)</p>
  43. <pre class="code">
  44. &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  45. &lt;project name="<b>FindTask</b>" basedir="." default="test"&gt;
  46. ...
  47. &lt;target name="use.init" description="Taskdef's the <b>Find</b>-Task" depends="jar"&gt;
  48. &lt;taskdef name="<b>find</b>" classname="<b>Find</b>" classpath="${ant.project.name}.jar"/&gt;
  49. &lt;/target&gt;
  50. <b>&lt;!-- the other use.* targets are deleted --&gt;</b>
  51. ...
  52. &lt;/project&gt;</pre>
  53. <p>The buildfile is in the
  54. archive <a href="tutorial-tasks-filesets-properties.zip">tutorial-tasks-filesets-properties.zip [2]</a>
  55. in <samp>/build.xml.01-propertyaccess</samp> (future version saved as *.02..., final version as <samp>build.xml</samp>;
  56. same for sources).</p>
  57. <h2 id="propertyaccess">Property access</h2>
  58. <p>Our first step is to set a property to a value and print the value of that property. So
  59. our scenario would be</p>
  60. <pre class="code">
  61. &lt;find property="test" value="test-value"/&gt;
  62. &lt;find print="test"/&gt;</pre>
  63. <p>Ok, it can be rewritten with the core tasks</p>
  64. <pre class="code">
  65. &lt;property name="test" value="test-value"/&gt;
  66. &lt;echo message="${test}"/&gt;</pre>
  67. <p>but I have to start on known ground :-)</p>
  68. <p>So what to do? Handling three attributes (<var>property</var>, <var>value</var>, <var>print</var>) and an execute
  69. method. Because this is only an introduction example I don't do much checking:</p>
  70. <pre class="code">
  71. import org.apache.tools.ant.BuildException;
  72. public class Find extends Task {
  73. private String property;
  74. private String value;
  75. private String print;
  76. public void setProperty(String property) {
  77. this.property = property;
  78. }
  79. // setter for value and print
  80. public void execute() {
  81. if (print != null) {
  82. String propValue = <b>getProject().getProperty(print)</b>;
  83. log(propValue);
  84. } else {
  85. if (property == null) throw new BuildException("property not set");
  86. if (value == null) throw new BuildException("value not set");
  87. <b>getProject().setNewProperty(property, value)</b>;
  88. }
  89. }
  90. }</pre>
  91. <p>As said in the other tutorial, the property access is done via Project instance. We get this instance via the
  92. public <code>getProject()</code> method which we inherit from <code>Task</code> (more precisely
  93. from <code>ProjectComponent</code>). Reading a property is done via <code>getProperty(<i>propertyname</i>)</code> (very
  94. simple, isn't it?). This property returns the value as <samp>String</samp> or <code>null</code> if not set.<br/>
  95. Setting a property is ... not really difficult, but there is more than one setter. You can use
  96. the <code>setProperty()</code> method which will do the job as expected. But there is a golden rule in
  97. Ant: <em>properties are immutable</em>. And this method sets the property to the specified value&mdash;whether it has a
  98. value before that or not. So we use another way. <code>setNewProperty()</code> sets the property only if there is no
  99. property with that name. Otherwise a message is logged.</p>
  100. <p><em>(By the way, a short explanation of Ant's "namespaces"&mdash;not to be confused with XML namespaces:
  101. an <code>&lt;antcall&gt;</code> creates a new space for property names. All properties from the caller are passed to the
  102. callee, but the callee can set its own properties without notice by the caller.)</em></p>
  103. <p>There are some other setters, too (but I haven't used them, so I can't say something to them, sorry :-)</p>
  104. <p>After putting our two line example from above into a target names <code>use.simple</code> we can call that from our
  105. test case:</p>
  106. <pre class="code">
  107. import org.junit.Assert;
  108. import org.junit.Before;
  109. import org.junit.Rule;
  110. import org.junit.Test;
  111. import org.apache.tools.ant.BuildFileRule;
  112. public class FindTest {
  113. @Rule
  114. public final BuildFileRule buildRule = new BuildFileRule();
  115. @Before
  116. public void setUp() {
  117. configureProject("build.xml");
  118. }
  119. @Test
  120. public void testSimple() {
  121. buildRule.executeTarget("useSimple");
  122. <b>Assert.assertEquals("test-value", buildRule.getLog());</b>
  123. }
  124. }</pre>
  125. <p>and all works fine.</p>
  126. <h2 id="filesets">Using filesets</h2>
  127. <p>Ant provides a common way of bundling files: the fileset. Because you are reading this tutorial I think you know them
  128. and I don't have to spend more explanations about their usage in buildfiles. Our goal is to search for a file in
  129. path. And in this step the path is simply a fileset (or more precise: a collection of filesets). So our usage would
  130. be</p>
  131. <pre class="code">
  132. &lt;find file="ant.jar" location="location.ant-jar"&gt;
  133. &lt;fileset dir="${ant.home}" includes="**/*.jar"/&gt;
  134. &lt;/find&gt;</pre>
  135. <p>What do we need? A task with two attributes (<var>file</var>, <var>location</var>) and nested filesets. Because we
  136. had attribute handling already explained in the example above and the handling of nested elements is described in the
  137. other tutorial, the code should be very easy:</p>
  138. <pre class="code">
  139. public class Find extends Task {
  140. private String file;
  141. private String location;
  142. private List&lt;FileSet&gt; filesets = new ArrayList&lt;&gt;();
  143. public void setFile(String file) {
  144. this.file = file;
  145. }
  146. public void setLocation(String location) {
  147. this.location = location;
  148. }
  149. public void addFileset(FileSet fileset) {
  150. filesets.add(fileset);
  151. }
  152. public void execute() {
  153. }
  154. }</pre>
  155. <p>Ok&mdash;that task wouldn't do very much, but we can use it in the described manner without failure. In the next step
  156. we have to implement the execute method. And before that we will implement the appropriate test cases (TDD&mdash;test
  157. driven development).</p>
  158. <p>In the other tutorial we have reused the already written targets of our buildfile. Now we will configure most of the
  159. test cases via Java code (sometimes it's much easier to write a target than doing it via Java coding). What can be
  160. tested?</p>
  161. <ul>
  162. <li>invalid configuration of the task (missing file, missing location, missing fileset)</li>
  163. <li>don't find a present file</li>
  164. <li>behaviour if file can't be found</li>
  165. </ul>
  166. <p>Maybe you find some more test cases. But this is enough for now.<br/>
  167. For each of these points we create a <code>testXX</code> method.</p>
  168. <pre class="code">
  169. public class FindTest {
  170. @Rule
  171. public final BuildFileRule buildRule = new BuildFileRule();
  172. @Rule
  173. public ExpectedException tried = ExpectedException.none();
  174. ... // constructor, setUp as above
  175. @Test
  176. public void testMissingFile() {
  177. tried.expect(BuildException.class);
  178. tried.expectMessage("file not set");
  179. <b>Find find = new Find();</b>
  180. <b>find.execute();</b>
  181. }
  182. @Test
  183. public void testMissingLocation() {
  184. tried.expect(BuildException.class);
  185. tried.expectMessage("location not set");
  186. Find find = new Find();
  187. <b>find.setFile("ant.jar");</b>
  188. find.execute();
  189. }
  190. @Test
  191. public void testMissingFileset() {
  192. tried.expect(BuildException.class);
  193. tried.expectMessage("fileset not set");
  194. Find find = new Find();
  195. find.setFile("ant.jar");
  196. find.setLocation("location.ant-jar");
  197. }
  198. @Test
  199. public void testFileNotPresent() {
  200. buildRule.executeTarget("testFileNotPresent");
  201. String result = buildRule.getProject().getProperty("location.ant-jar");
  202. assertNull("Property set to wrong value.", result);
  203. }
  204. @Test
  205. public void testFilePresent() {
  206. buildRule.executeTarget("testFilePresent");
  207. String result = buildRule.getProject().getProperty("location.ant-jar");
  208. assertNotNull("Property not set.", result);
  209. assertTrue("Wrong file found.", result.endsWith("ant.jar"));
  210. }
  211. }</pre>
  212. <p>If we run this test class all test cases (except <var>testFileNotPresent</var>) fail. Now we can implement our task,
  213. so that these test cases will pass.</p>
  214. <pre class="code">
  215. protected void validate() {
  216. if (file == null) throw new BuildException("file not set");
  217. if (location == null) throw new BuildException("location not set");
  218. if (filesets.size() &lt; 1) throw new BuildException("fileset not set");
  219. }
  220. public void execute() {
  221. validate(); // 1
  222. String foundLocation = null;
  223. for (FileSet fs : filesets) { // 2
  224. DirectoryScanner ds = fs.getDirectoryScanner(getProject()); // 3
  225. for (String includedFile : ds.getIncludedFiles()) {
  226. String filename = includedFile.replace('\\','/'); // 4
  227. filename = filename.substring(filename.lastIndexOf("/") + 1);
  228. if (foundLocation == null &amp;&amp; file.equals(filename)) {
  229. File base = ds.getBasedir(); // 5
  230. File found = new File(base, includedFile);
  231. foundLocation = found.getAbsolutePath();
  232. }
  233. }
  234. }
  235. if (foundLocation != null) // 6
  236. getProject().setNewProperty(location, foundLocation);
  237. }</pre>
  238. <p>On <strong>//1</strong> we check the prerequisites for our task. Doing that in a <code>validate</code>-method is a
  239. common way, because we separate the prerequisites from the real work. On <strong>//2</strong> we iterate over all nested
  240. filesets. If we don't want to handle multiple filesets, the <code>addFileset()</code> method has to reject the further
  241. calls. We can get the result of a fileset via its DirectoryScanner like done in <strong>//3</strong>. After that we
  242. create a platform independent String representation of the file path (<strong>//4</strong>, can be done in other ways of
  243. course). We have to do the <code>replace()</code>, because we work with a simple string comparison. Ant itself is
  244. platform independent and can therefore run on filesystems with slash (<q>/</q>, e.g. Linux) or backslash (<q>\</q>,
  245. e.g. Windows) as path separator. Therefore we have to unify that. If we find our file, we create an absolute path
  246. representation on <strong>//5</strong>, so that we can use that information without knowing the <var>basedir</var>.
  247. (This is very important on use with multiple filesets, because they can have different <var>basedir</var>s and the
  248. return value of the directory scanner is relative to its <var>basedir</var>.) Finally we store the location of the file
  249. as property, if we had found one (<strong>//6</strong>).</p>
  250. <p>Ok, much more easier in this simple case would be to add the <var>file</var> as additional <code>include</code>
  251. element to all filesets. But I wanted to show how to handle complex situations without being complex :-)</p>
  252. <p>The test case uses the Ant property <code>ant.home</code> as reference. This property is set by
  253. the <code>Launcher</code> class which starts ant. We can use that property in our buildfiles as
  254. a <a href="properties.html#built-in-props">build-in property [3]</a>. But if we create a new Ant environment we have to
  255. set that value for our own. And we use the <code>&lt;junit&gt;</code> task in <var>fork</var> mode. Therefore we have
  256. do modify our buildfile:</p>
  257. <pre class="code">
  258. &lt;target name="junit" description="Runs the unit tests" depends="jar"&gt;
  259. &lt;delete dir="${junit.out.dir.xml}"/&gt;
  260. &lt;mkdir dir="${junit.out.dir.xml}"/&gt;
  261. &lt;junit printsummary="yes" haltonfailure="no"&gt;
  262. &lt;classpath refid="classpath.test"/&gt;
  263. <b>&lt;sysproperty key="ant.home" value="${ant.home}"/&gt;</b>
  264. &lt;formatter type="xml"/&gt;
  265. &lt;batchtest fork="yes" todir="${junit.out.dir.xml}"&gt;
  266. &lt;fileset dir="${src.dir}" includes="**/*Test.java"/&gt;
  267. &lt;/batchtest&gt;
  268. &lt;/junit&gt;
  269. &lt;/target&gt;</pre>
  270. <h2 id="path">Using nested paths</h2>
  271. <p>A task providing support for filesets is a very comfortable one. But there is another possibility of bundling files:
  272. the <code>&lt;path&gt;</code>. Filesets are easy if the files are all under a common base directory. But if this is not
  273. the case, you have a problem. Another disadvantage is its speed: if you have only a few files in a huge directory
  274. structure, why not use a <code>&lt;filelist&gt;</code> instead? <code>&lt;path&gt;</code>s combines these datatypes in
  275. that way that a path contains other paths, filesets, dirsets and filelists. This is
  276. why <a href="http://ant-contrib.sourceforge.net/">Ant-Contrib [4]</a> <code>&lt;foreach&gt;</code> task is modified to
  277. support paths instead of filesets. So we want that, too.</p>
  278. <p>Changing from fileset to path support is very easy:</p>
  279. <em><strong>Change Java code from:</strong></em>
  280. <pre class="code">
  281. private List&lt;FileSet&gt; filesets = new ArrayList&lt;&gt;();
  282. public void addFileset(FileSet fileset) {
  283. filesets.add(fileset);
  284. }</pre>
  285. <em><strong>to:</strong></em>
  286. <pre class="code">
  287. private List&lt;Path&gt; paths = new ArrayList&lt;&gt;(); *1
  288. public void add<b>Path</b>(<b>Path</b> path) { *2
  289. paths.add(path);
  290. }</pre>
  291. <em><strong>and build file from:</strong></em>
  292. <pre class="code">
  293. &lt;find file="ant.jar" location="location.ant-jar"&gt;
  294. &lt;fileset dir="${ant.home}" includes="**/*.jar"/&gt;
  295. &lt;/find&gt;</pre>
  296. <em><strong>to:</strong></em>
  297. <pre class="code">
  298. &lt;find file="ant.jar" location="location.ant-jar"&gt;
  299. <b>&lt;path&gt;</b> *3
  300. &lt;fileset dir="${ant.home}" includes="**/*.jar"/&gt;
  301. &lt;/path&gt;
  302. &lt;/find&gt;</pre>
  303. <p>On <strong>*1</strong> we rename only the list. It's just for better reading the source. On <strong>*2</strong> we
  304. have to provide the right method: an <code>add<i>Name</i>(<i>Type</i> t)</code>. Therefore replace the fileset with path
  305. here. Finally we have to modify our buildfile on <strong>*3</strong> because our task doesn't support nested filesets
  306. any longer. So we wrap the fileset inside a path.</p>
  307. <p>And now we modify the test case. Oh, not very much to do :-) Renaming the <code>testMissingFileset()</code> (not
  308. really a <em>must-be</em> but better it's named like the thing it does) and update the <var>expected</var>-String in
  309. that method (now a <samp>path not set</samp> message is expected). The more complex test cases base on the build
  310. script. So the targets <var>testFileNotPresent</var> and <var>testFilePresent</var> have to be modified in the manner
  311. described above.</p>
  312. <p>The test are finished. Now we have to adapt the task implementation. The easiest modification is in
  313. the <code>validate()</code> method where we change the last line to <code>if (paths.size()&lt;1) throw new
  314. BuildException("path not set");</code>. In the <code>execute()</code> method we have a little more work. ... mmmh
  315. ... in reality it's less work, because the Path class does the whole DirectoryScanner-handling and
  316. creating-absolute-paths stuff for us. So the execute method becomes just:</p>
  317. <pre class="code">
  318. public void execute() {
  319. validate();
  320. String foundLocation = null;
  321. for (Path path : paths) { // 1
  322. for (String includedFile : <b>path.list()</b>) { // 2
  323. String filename = includedFile.replace('\\','/');
  324. filename = filename.substring(filename.lastIndexOf("/") + 1);
  325. if (foundLocation == null &amp;&amp; file.equals(filename)) {
  326. <b>foundLocation = includedFile;</b> // 3
  327. }
  328. }
  329. }
  330. if (foundLocation != null)
  331. getProject().setNewProperty(location, foundLocation);
  332. }
  333. </pre>
  334. <p>Of course we have to iterate through paths on <strong>//1</strong>. On <strong>//2</strong> and <strong>//3</strong>
  335. we see that the Path class does the work for us: no DirectoryScanner (was at 2) and no creating of the absolute path
  336. (was at 3).</p>
  337. <h2 id="returning-list">Returning a list</h2>
  338. <p>So far so good. But could a file be on more than one place in the path?&mdash;Of course.<br/>
  339. And would it be good to get all of them?&mdash;It depends ...<p>
  340. <p>In this section we will extend that task to support returning a list of all files. Lists as property values are not
  341. supported by Ant natively. So we have to see how other tasks use lists. The most famous task using lists is
  342. Ant-Contribs <code>&lt;foreach&gt;</code>. All list elements are concatenated and separated with a customizable
  343. separator (default <q>,</q>).</p>
  344. <p>So we do the following:</p>
  345. <pre class="code">&lt;find ... <b>delimiter=""</b>/&gt; ... &lt;/find&gt;</pre>
  346. <p>if the delimiter is set, we will return all found files as list with that delimiter.</p>
  347. <p>Therefore we have to</p>
  348. <ul>
  349. <li>provide a new attribute</li>
  350. <li>collect more than the first file</li>
  351. <li>delete duplicates</li>
  352. <li>create the list if necessary</li>
  353. <li>return that list</li>
  354. </ul>
  355. <p>So we add as test case:</p>
  356. <strong><em>in the buildfile:</em></strong>
  357. <pre class="code">
  358. &lt;target name="test.init"&gt;
  359. &lt;mkdir dir="test1/dir11/dir111"/&gt; *1
  360. &lt;mkdir dir="test1/dir11/dir112"/&gt;
  361. ...
  362. &lt;touch file="test1/dir11/dir111/test"/&gt;
  363. &lt;touch file="test1/dir11/dir111/not"/&gt;
  364. ...
  365. &lt;touch file="test1/dir13/dir131/not2"/&gt;
  366. &lt;touch file="test1/dir13/dir132/test"/&gt;
  367. &lt;touch file="test1/dir13/dir132/not"/&gt;
  368. &lt;touch file="test1/dir13/dir132/not2"/&gt;
  369. &lt;mkdir dir="test2"/&gt;
  370. &lt;copy todir="test2"&gt; *2
  371. &lt;fileset dir="test1"/&gt;
  372. &lt;/copy&gt;
  373. &lt;/target&gt;
  374. &lt;target name="testMultipleFiles" depends="use.init,<b>test.init</b>"&gt; *3
  375. &lt;find file="test" location="location.test" <b>delimiter=";"</b>&gt;
  376. &lt;path&gt;
  377. &lt;fileset dir="test1"/&gt;
  378. &lt;fileset dir="test2"/&gt;
  379. &lt;/path&gt;
  380. &lt;/find&gt;
  381. &lt;delete&gt; *4
  382. &lt;fileset dir="test1"/&gt;
  383. &lt;fileset dir="test2"/&gt;
  384. &lt;/delete&gt;
  385. &lt;/target&gt;</pre>
  386. <strong><em>in the test class:</em></strong>
  387. <pre class="code">
  388. public void testMultipleFiles() {
  389. executeTarget("testMultipleFiles");
  390. String result = getProject().getProperty("location.test");
  391. assertNotNull("Property not set.", result);
  392. assertTrue("Only one file found.", result.indexOf(";") &gt; -1);
  393. }</pre>
  394. <p>Now we need a directory structure where we CAN find files with the same name in different directories. Because we
  395. can't sure to have one we create one on <strong>*1</strong> and <strong>*2</strong>. And of course we clean up that
  396. on <strong>*4</strong>. The creation can be done inside our test target or in a separate one, which will be better for
  397. reuse later (<strong>*3</strong>).
  398. <p>The task implementation is modified as followed:</p>
  399. <pre class="code">
  400. private List&lt;String&gt; foundFiles = new ArrayList&lt;&gt;();
  401. ...
  402. private String delimiter = null;
  403. ...
  404. public void setDelimiter(String delim) {
  405. delimiter = delim;
  406. }
  407. ...
  408. public void execute() {
  409. validate();
  410. // find all files
  411. for (Path path : paths) {
  412. for (File includedFile : path.list()) {
  413. String filename = includedFile.replace('\\','/');
  414. filename = filename.substring(filename.lastIndexOf("/")+1);
  415. if (file.equals(filename) &amp;&amp; <b>!foundFiles.contains(includedFile)</b>) { // 1
  416. foundFiles.add(includedFile);
  417. }
  418. }
  419. }
  420. // create the return value (list/single)
  421. String rv = null;
  422. if (!foundFiles.isEmpty()) { // 2
  423. if (delimiter == null) {
  424. // only the first
  425. rv = foundFiles.get(0);
  426. } else {
  427. // create list
  428. StringBuilder list = new StringBuilder();
  429. for (String file : foundFiles) { // 3
  430. list.append(it.next());
  431. if (<b>list.length() > 0</b>) list.append(delimiter); // 4
  432. }
  433. rv = list.toString();
  434. }
  435. }
  436. // create the property
  437. if (rv != null)
  438. getProject().setNewProperty(location, rv);
  439. }</pre>
  440. <p>The algorithm does: finding all files, creating the return value depending on the users wish, returning the value as
  441. property. On <strong>//1</strong> we eliminates the duplicates. <strong>//2</strong> ensures that we create the return
  442. value only if we have found one file. On <strong>//3</strong> we iterate over all found files and <strong>//4</strong>
  443. ensures that the last entry has no trailing delimiter.</p>
  444. <p>Ok, first searching for all files and then returning only the first one ... You can tune the performance of your own
  445. :-)</p>
  446. <h2 id="documentation">Documentation</h2>
  447. <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
  448. :-). So documentation is also very important. In which form you do that depends on your favourite. But inside Ant there
  449. is a common format and it has advantages if you use that: all task users know that form, this form is requested if you
  450. decide to contribute your task. So we will doc our task in that form.</p>
  451. <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>
  452. <ul>
  453. <li>is plain html</li>
  454. <li>starts with the name</li>
  455. <li>has sections: description, parameters, nested elements, (maybe return codes) and (most important :-) examples</li>
  456. <li>parameters are listed in a table with columns for attribute name, its description and whether it's required (if you
  457. add a feature after an Ant release, provide a <em>since Ant xx</em> statement when it's introduced)</li>
  458. <li>describe the nested elements (since-statement if necessary)</li>
  459. <li>provide one or more useful examples; first code, then description.</li>
  460. </ul>
  461. <p>As a template we have:</p>
  462. <pre class="code">
  463. &lt;html&gt;
  464. &lt;head&gt;
  465. &lt;meta http-equiv="Content-Language" content="en-us"&gt;
  466. &lt;title&gt;<b>Taskname</b> Task&lt;/title&gt;
  467. &lt;/head&gt;
  468. &lt;body&gt;
  469. &lt;h2 id=&quot;<b>taskname</b>&quot;&gt;<b>Taskname</b>&lt;/h2&gt;
  470. &lt;h3&gt;Description&lt;/h3&gt;
  471. &lt;p&gt;<b>Describe the task.</b>&lt;/p&gt;
  472. &lt;h3&gt;Parameters&lt;/h3&gt;
  473. &lt;table class=&quot;attr&quot;&gt;
  474. &lt;tr&gt;
  475. &lt;th&gt;Attribute&lt;/th&gt;
  476. &lt;th&gt;Description&lt;/th&gt;
  477. &lt;th&gt;Required&lt;/th&gt;
  478. &lt;/tr&gt;
  479. <b>do this html row for each attribute (including inherited attributes)</b>
  480. &lt;tr&gt;
  481. &lt;td&gt;classname&lt;/td&gt;
  482. &lt;td&gt;the Java class to execute.&lt;/td&gt;
  483. &lt;td&gt;Either jar or classname&lt;/td&gt;
  484. &lt;/tr&gt;
  485. &lt;/table&gt;
  486. &lt;h3&gt;Parameters specified as nested elements&lt;/h3&gt;
  487. <b>Describe each nested element (including inherited)</b>
  488. &lt;h4&gt;<b>your nested element</b>&lt;/h4&gt;
  489. &lt;p&gt;<b>description</b>&lt;/p&gt;
  490. &lt;p&gt;&lt;em&gt;since Ant 1.6&lt;/em&gt;.&lt;/p&gt;
  491. &lt;h3&gt;Examples&lt;/h3&gt;
  492. &lt;pre&gt;
  493. <b>A code sample; don't forget to escape the &lt; of the tags with &amp;lt;</b>
  494. &lt;/pre&gt;
  495. <b>What should that example do?</b>
  496. &lt;/body&gt;
  497. &lt;/html&gt;</pre>
  498. <p>Here is an example documentation page for our task:</p>
  499. <pre class="code">
  500. &lt;html&gt;
  501. &lt;head&gt;
  502. &lt;meta http-equiv="Content-Language" content="en-us"&gt;
  503. &lt;title&gt;Find Task&lt;/title&gt;
  504. &lt;/head&gt;
  505. &lt;body&gt;
  506. &lt;h2 id="find"&gt;Find&lt;/h2&gt;
  507. &lt;h3&gt;Description&lt;/h3&gt;
  508. &lt;p&gt;Searches in a given path for a file and returns the absolute to it as property.
  509. If delimiter is set this task returns all found locations.&lt;/p&gt;
  510. &lt;h3&gt;Parameters&lt;/h3&gt;
  511. &lt;table class=&quot;attr&quot;&gt;
  512. &lt;tr&gt;
  513. &lt;th&gt;Attribute&lt;/th&gt;
  514. &lt;th&gt;Description&lt;/th&gt;
  515. &lt;th&gt;Required&lt;/th&gt;
  516. &lt;/tr&gt;
  517. &lt;tr&gt;
  518. &lt;td&gt;file&lt;/td&gt;
  519. &lt;td&gt;The name of the file to search.&lt;/td&gt;
  520. &lt;td&gt;yes&lt;/td&gt;
  521. &lt;/tr&gt;
  522. &lt;tr&gt;
  523. &lt;td&gt;location&lt;/td&gt;
  524. &lt;td&gt;The name of the property where to store the location&lt;/td&gt;
  525. &lt;td&gt;yes&lt;/td&gt;
  526. &lt;/tr&gt;
  527. &lt;tr&gt;
  528. &lt;td&gt;delimiter&lt;/td&gt;
  529. &lt;td&gt;A delimiter to use when returning the list&lt;/td&gt;
  530. &lt;td&gt;only if the list is required&lt;/td&gt;
  531. &lt;/tr&gt;
  532. &lt;/table&gt;
  533. &lt;h3&gt;Parameters specified as nested elements&lt;/h3&gt;
  534. &lt;h4&gt;path&lt;/h4&gt;
  535. &lt;p&gt;The path where to search the file.&lt;/p&gt;
  536. &lt;h3&gt;Examples&lt;/h3&gt;
  537. &lt;pre&gt;
  538. &lt;find file="ant.jar" location="loc"&gt;
  539. &lt;path&gt;
  540. &lt;fileset dir="${ant.home}"/&gt;
  541. &lt;path&gt;
  542. &lt;/find&gt;&lt;/pre&gt;
  543. Searches in Ant's home directory for a file &lt;samp&gt;ant.jar&lt;/samp&gt; and stores its location in
  544. property &lt;code&gt;loc&lt;/code&gt; (should be &lt;samp&gt;ANT_HOME/bin/ant.jar&lt;/samp&gt;).
  545. &lt;pre&gt;
  546. &lt;find file="ant.jar" location="loc" delimiter=";"&gt;
  547. &lt;path&gt;
  548. &lt;fileset dir="C:/"/&gt;
  549. &lt;path&gt;
  550. &lt;/find&gt;
  551. &lt;echo&gt;ant.jar found in: ${loc}&lt;/echo&gt;&lt;/pre&gt;
  552. Searches in Windows C: drive for all &lt;samp&gt;ant.jar&lt;/samp&gt; and stores their locations in
  553. property &lt;code&gt;loc&lt;/code&gt; delimited with &lt;q&gt;;&lt;/q&gt;. (should need a long time :-)
  554. After that it prints out the result (e.g. &lt;samp&gt;C:/ant-1.5.4/bin/ant.jar;C:/ant-1.6/bin/ant.jar&lt;/samp&gt;).
  555. &lt;/body&gt;
  556. &lt;/html&gt;</pre>
  557. <h2 id="contribute">Contribute the new task</h2>
  558. <p>If we decide to contribute our task, we should do some things:</p>
  559. <ul>
  560. <li>is our task welcome? :-) Simply ask on the user list</li>
  561. <li>is the right package used?</li>
  562. <li>does the code conform to the styleguide?</li>
  563. <li>do all tests pass?</li>
  564. <li>does the code compile on JDK 5 (and passes all tests there)?</li>
  565. <li>code under Apache license</li>
  566. <li>create a patch file</li>
  567. <li>publishing that patch file</li>
  568. </ul>
  569. <p>The <a href="https://ant.apache.org/ant_task_guidelines.html">Ant Task Guidelines [6]</a> support additional
  570. information on that.</p>
  571. <p>Now we will check the "Checklist before submitting a new task" described in that guideline.</p>
  572. <ul>
  573. <li>Java file begins with Apache license statement. <strong><em>must do that</em></strong></li>
  574. <li>Task does not depend on GPL or LGPL code. <strong><em>ok</em></strong></li>
  575. <li>Source code complies with style guidelines <strong><em>have to check (checkstyle)</em></strong></li>
  576. <li>Code compiles and runs on Java 5 <strong><em>have to try</em></strong></li>
  577. <li>Member variables are private, and provide public accessor methods if access is actually needed. <strong><em>have to
  578. check (checkstyle)</em></strong></li>
  579. <li><em>Maybe</em> Task has <var>failonerror</var> attribute to control failure
  580. behaviour <strong><em>hasn't</em></strong></li>
  581. <li>New test cases written and succeed <strong><em>passed on JDK 8, have to try on JDK 5</em></strong></li>
  582. <li>Documentation page written <strong><em>ok</em></strong></li>
  583. <li>Example task declarations in the documentation tested. <strong><em>ok (used in tests)</em></strong></li>
  584. <li>Message to dev contains [SUBMIT] and task name in subject <strong><em>to do</em></strong></li>
  585. <li>Message body contains a rationale for the task <strong><em>to do</em></strong></li>
  586. <li>Message body contains the URL to GitHub pull request. <strong><em>to do</em></strong></li>
  587. </ul>
  588. <h3>Package / Directories</h3>
  589. <p>This task does not depend on any external library. Therefore we can use this as a core task. This task contains only
  590. one class. So we can use the standard package for core
  591. tasks: <code>org.apache.tools.ant.taskdefs</code>. Implementations are in the directory <samp>src/main</samp>, tests
  592. in <samp>src/testcases</samp> and buildfiles for tests in <samp>src/etc/testcases</samp>.</p>
  593. <p>Now we integrate our work into Ant distribution. So first we do an update of our Git tree. If not done yet, you
  594. should clone the Ant repository on GitHub[7], then create a local clone:</p>
  595. <pre class="output">git clone https://github.com/<em>your-sig</em>/ant.git</pre>
  596. <p>Now we will build our Ant distribution and do a test. So we can see if there are any tests failing on our
  597. machine. (We can ignore these failing tests on later steps; Windows syntax used here&mdash;translate to UNIX if
  598. needed):</p>
  599. <pre class="output">
  600. ANTREPO&gt; build // 1
  601. ANTREPO&gt; set ANT_HOME=%CD%\dist // 2
  602. ANTREPO&gt; ant test -Dtest.haltonfailure=false // 3</pre>
  603. <p>First we have to build our Ant distribution (<strong>//1</strong>). On <strong>//2</strong> we set
  604. the <code>ANT_HOME</code> environment variable to the directory where the new created distribution is stored
  605. (<code>%CD%</code> is expanded to the current directory on Windows 2000 and later). On <strong>//3</strong> we let Ant
  606. do all the tests (which enforced a compile of all tests) without stopping on first failure.</p>
  607. <p>Next we apply our work onto Ant sources. Because we haven't modified any, this is a relatively simple
  608. step. <em>(Because I have a local Git clone of Ant and usually contribute my work, I work on the local copy just from
  609. the beginning. The advantage: this step isn't necessary and saves a lot of work if you modify existing sources :-)</em>.
  610. <ul>
  611. <li>move the <samp>Find.java</samp> to <samp>ANTREPO/src/main/org/apache/tools/ant/taskdefs/Find.java</samp></li>
  612. <li>move the <samp>FindTest.java</samp> to <samp>ANTREPO/src/testcases/org/apache/tools/ant/taskdefs/FindTest.java</samp></li>
  613. <li>move the <samp>build.xml</samp> to <samp>ANTREPO/src/etc/testcases/taskdefs/<strong>find.xml</strong></samp> (!!! renamed !!!)</li>
  614. <li>add a <code>package org.apache.tools.ant.taskdefs;</code> at the beginning of the two java files</li>
  615. <li>delete all stuff from <samp>find.xml</samp> keeping the
  616. targets <q>testFileNotPresent</q>, <q>testFilePresent</q>, <q>test.init</q> and <q>testMultipleFiles</q></li>
  617. <li>delete the dependency to <q>use.init</q> in the <samp>find.xml</samp></li>
  618. <li>in <samp>FindTest.java</samp> change the line <code>configureProject("build.xml");</code>
  619. to <code>configureProject("src/etc/testcases/taskdefs/find.xml");</code></li>
  620. <li>move the <samp>find.html</samp> to <samp>ANTREPO/docs/manual/Tasks/find.html</samp></li>
  621. <li>add a <code>&lt;a href="Tasks/find.html"&gt;Find&lt;/a&gt;&lt;br&gt;</code> in
  622. the <samp>ANTREPO/docs/manual/tasklist.html</samp></li>
  623. </ul>
  624. <p>Now our modifications are done and we will retest it:</p>
  625. <pre class="output">
  626. ANTREPO&gt; build
  627. ANTREPO&gt; ant run-single-test // 1
  628. -Dtestcase=org.apache.tools.ant.taskdefs.FindTest // 2
  629. -Dtest.haltonfailure=false</pre>
  630. <p>Because we only want to test our new class, we use the target for single tests, specify the test to use and configure
  631. not to halt on the first failure&mdash;we want to see all failures of our own test (<strong>//1 + 2</strong>).</p>
  632. <p>And ... oh, all tests fail: <em>Ant could not find the task or a class this task relies upon.</em></p>
  633. <p>Ok: in the earlier steps we told Ant to use the Find class for the <code>&lt;find&gt;</code> task (remember
  634. the <code>&lt;taskdef&gt;</code> statement in the "use.init" target). But now we want to introduce that task as a core
  635. task. And nobody wants to taskdef the javac, echo, ... So what to do? The answer is
  636. the <samp>src/main/.../taskdefs/default.properties</samp>. Here is the mapping between taskname and implementing class
  637. done. So we add a <code>find=org.apache.tools.ant.taskdefs.Find</code> as the last core task (just before the <code>#
  638. optional tasks</code> line). Now a second try:</p>
  639. <pre class="output">
  640. ANTREPO&gt; build // 1
  641. ANTREPO&gt; ant run-single-test
  642. -Dtestcase=org.apache.tools.ant.taskdefs.FindTest
  643. -Dtest.haltonfailure=false</pre>
  644. <p>We have to rebuild (<strong>//1</strong>) Ant because the test look in the <samp>%ANT_HOME%\lib\ant.jar</samp> (more
  645. precise: on the classpath) for the properties file. And we have only modified it in the source path. So we have to
  646. rebuild that jar. But now all tests pass and we check whether our class breaks some other tests.</p>
  647. <pre class="output">ANTREPO&gt; ant test -Dtest.haltonfailure=false</pre>
  648. <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
  649. development and do the <q>test</q> only at the end (maybe sometimes during development too). We use
  650. the <code>-Dtest.haltonfailure=false</code> here because there could be other tests fail and we have to look into
  651. them.</p>
  652. <p>This test run should show us two things: our test will run and the number of failing tests is the same as directly
  653. after <code>git clone</code> (without our modifications).</p>
  654. <h3>Apache license statement</h3>
  655. <p>Simply copy the license text from one the other source from the Ant source tree.</p>
  656. <h3>Test on JDK 5</h3>
  657. <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
  658. to run on a JDK 5. So we have to test that. You can download older JDKs
  659. from <a href="https://www.oracle.com/technetwork/java/archive-139210.html">Oracle [8]</a>.</p>
  660. <p>Clean the <code>ANT_HOME</code> variable, delete the <samp>build</samp>, <samp>bootstrap</samp> and <samp>dist</samp>
  661. directories, and point <code>JAVA_HOME</code> to the JDK 5 home directory. Then create the patch with your commit,
  662. checkout 1.9.x branch in Git, apply your patch and do the <code>build</code>, set <code>ANT_HOME</code> and
  663. run <code>ant test</code> (like above).</p>
  664. <p>Our test should pass.</p>
  665. <h3>Checkstyle</h3>
  666. <p>There are many things we have to ensure. Indentation with 4 spaces, blanks here and there, ... (all described in
  667. the <a href="https://ant.apache.org/ant_task_guidelines.html">Ant Task Guidelines [6]</a> which includes
  668. the <a href="https://www.oracle.com/technetwork/java/codeconvtoc-136057.html">Sun code style [9]</a>). Because there are
  669. so many things we would be happy to have a tool for do the checks. There is one: checkstyle. Checkstyle is available
  670. at <a href="http://checkstyle.sourceforge.net/"> Sourceforge [10]</a> and Ant provides with the <samp>check.xml</samp> a
  671. buildfile which will do the job for us.</p>
  672. <p>Download it and put the <samp>checkstyle-*-all.jar</samp> into your <samp>%USERPROFILE%\.ant\lib</samp> directory.
  673. 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
  674. feature is available <em>since Ant 1.6</em>).</p>
  675. <p>So we will run the tests with</p>
  676. <pre class="output">ANTREPO&gt; ant -f check.xml checkstyle htmlreport</pre>
  677. <p>I prefer the HTML report because there are lots of messages and we can navigate faster. Open
  678. the <samp>ANTREPO/build/reports/checkstyle/html/index.html</samp> and navigate to the <samp>Find.java</samp>. Now we see
  679. that there are some errors: missing whitespaces, unused imports, missing javadocs. So we have to do that.</p>
  680. <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
  681. will find the next error place much more easier without redoing the checkstyle.</p>
  682. <p>After cleaning up the code according to the messages we delete the reports directory and do a second checkstyle
  683. run. Now our task isn't listed. That's fine :-)</p>
  684. <h3>Publish the task</h3>
  685. <p>Finally we publish that archive. As described in the <a href="https://ant.apache.org/ant_task_guidelines.html">Ant
  686. Task Guidelines [7]</a> we can announce it on the developer mailing list, create a BugZilla entry and open a GitHub pull
  687. request. For both we need some information:</p>
  688. <table>
  689. <tr>
  690. <!-- this is an empty "table head" -->
  691. </tr>
  692. <tr>
  693. <th>subject</th>
  694. <td><em>short description</em></td>
  695. <td>Task for finding files in a path</td>
  696. </tr>
  697. <tr>
  698. <th>body</th>
  699. <td><em>more details about the path</em></td>
  700. <td>This new task looks inside a nested <code>&lt;path/&gt;</code> for occurrences of a file and stores all locations
  701. as a property. See the included manual for details.</td>
  702. </tr>
  703. <tr>
  704. <th>pull request reference</th>
  705. <td><em>GitHub pull request URL</em></td>
  706. <td>https://github.com/apache/ant/pull/0</td>
  707. </tr>
  708. </table>
  709. <p>Sending an email with this information is very easy and I think I haven't to describe that. BugZilla is slightly
  710. more difficult. But the advantage is that entries will not be forgotten (a report is generated once every weekend). So
  711. I will describe the process.</p>
  712. <p>First, you must have a BugZilla account. So open the <a href="https://issues.apache.org/bugzilla/">BugZilla Main Page
  713. [11]</a> and follow the link <a href="https://issues.apache.org/bugzilla/createaccount.cgi">Open a new Bugzilla account
  714. [12]</a> and the steps described there if you haven't one.</p>
  715. <ol>
  716. <li>From the BugZilla main page choose <a href="https://issues.apache.org/bugzilla/enter_bug.cgi">Enter a new bug report
  717. [13]</a></li>
  718. <li>Choose "Ant" as product</li>
  719. <li>Version is the last "Alpha (nightly)" (at this time 1.10)</li>
  720. <li>Component is "Core tasks"</li>
  721. <li>Platform and Severity are ok with "Other" and "Normal"</li>
  722. <li>Initial State is ok with "New"</li>
  723. <li>Same with the empty "Assigned to"</li>
  724. <li>It is not required to add yourself as CC, because you are the reporter and therefore will be informed on
  725. changes</li>
  726. <li>URL: GitHub pull request URL</li>
  727. <li>Summary: add the <var>subject</var> from the table</li>
  728. <li>Description: add the <var>body</var> from the table</li>
  729. <li>Then press "Commit"</li>
  730. </ol>
  731. <p>Now the new task is registered in the bug database.</p>
  732. <h2 id="resources">Resources</h2>
  733. <ol class="refs">
  734. <li><a href="tutorial-writing-tasks.html">tutorial-writing-tasks.html</a></li>
  735. <li><a href="tutorial-tasks-filesets-properties.zip">tutorial-tasks-filesets-properties.zip</a></li>
  736. <li><a href="properties.html#built-in-props">properties.html#built-in-props</a></li>
  737. <li><a href="http://ant-contrib.sourceforge.net/">http://ant-contrib.sourceforge.net/</a></li>
  738. <li><a href="Tasks/java.html">Tasks/java.html</a></li>
  739. <li><a href="https://ant.apache.org/ant_task_guidelines.html">https://ant.apache.org/ant_task_guidelines.html</a></li>
  740. <li><a href="https://github.com/apache/ant">https://github.com/apache/ant</a></li>
  741. <li><a href="https://www.oracle.com/technetwork/java/archive-139210.html">https://www.oracle.com/technetwork/java/archive-139210.html</a></li>
  742. <li><a href="https://www.oracle.com/technetwork/java/codeconvtoc-136057.html">https://www.oracle.com/technetwork/java/codeconvtoc-136057.html</a></li>
  743. <li><a href="http://checkstyle.sourceforge.net/">http://checkstyle.sourceforge.net/</a></li>
  744. <li><a href="https://issues.apache.org/bugzilla/">https://issues.apache.org/bugzilla/</a></li>
  745. <li><a href="https://issues.apache.org/bugzilla/createaccount.cgi">https://issues.apache.org/bugzilla/createaccount.cgi</a></li>
  746. <li><a href="https://issues.apache.org/bugzilla/enter_bug.cgi">https://issues.apache.org/bugzilla/enter_bug.cgi</a></li>
  747. </ol>
  748. </body>
  749. </html>