source: release-kits/lirk3/bin/ant-installer/web/manual1.7.0/manual/tutorial-tasks-filesets-properties.html@ 14982

Last change on this file since 14982 was 14982, checked in by oranfry, 16 years ago

initial import of LiRK3

File size: 43.1 KB
Line 
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
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16-->
17<html>
18<head>
19
20<title>Tutorial: Tasks using Properties, Filesets &amp; Paths</title>
21 <meta name="author" content="Jan Mat&egrave;rne">
22 <link rel="stylesheet" type="text/css" href="stylesheets/style.css">
23 <style type="text/css">
24 <!--
25 .code { background: #EFEFEF; margin-top: }
26 .output { color: #FFFFFF; background: #837A67; }
27 -->
28 </style>
29</head>
30<body>
31<h1>Tutorial: Tasks using Properties, Filesets &amp; Paths</h1>
32
33<p>After reading the tutorial about <a href="tutorial-writing-tasks.html">writing
34tasks [1]</a> this tutorial explains how to get and set properties and how to use
35nested filesets and paths. Finally it explains how to contribute tasks to Ant.</p>
36
37<h2>Content</h2>
38<p><ul>
39<li><a href="#goal">The goal</a></li>
40<li><a href="#buildenvironment">Build environment</a></li>
41<li><a href="#propertyaccess">Property access</a></li>
42<li><a href="#filesets">Using filesets</a></li>
43<li><a href="#path">Using nested paths</a></li>
44<li><a href="#returning-list">Returning a list</a></li>
45<li><a href="#documentation">Documentation</a></li>
46<li><a href="#contribute">Contribute the new task</a></li>
47<li><a href="#resources">Resources</a></li>
48</ul></p>
49
50
51<h2><a name="goal">The goal</a></h2>
52<p>The goal is to write a task, which searchs in a path for a file and saves the
53location of that file in a property.</p>
54
55
56<h2><a name="buildenvironment">Build environment</a></h2>
57<p>We can use the buildfile from the other tutorial and modify it a little bit.
58That's the advantage of using properties - we can reuse nearly the whole script. :-)</p>
59<pre class="code">
60&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;
61&lt;project name="<b>FindTask</b>" basedir="." default="test"&gt;
62 ...
63 &lt;target name="use.init" description="Taskdef's the <b>Find</b>-Task" depends="jar"&gt;
64 &lt;taskdef name="<b>find</b>" classname="<b>Find</b>" classpath="${ant.project.name}.jar"/&gt;
65 &lt;/target&gt;
66
67 <b>&lt;!-- the other use.* targets are deleted --&gt;</b>
68 ...
69&lt;/project&gt;
70</pre>
71
72<p>The buildfile is in the archive <a href="tutorial-tasks-filesets-properties.zip">
73tutorial-tasks-filesets-properties.zip [2]</a> in <tt>/build.xml.01-propertyaccess</tt>
74(future version saved as *.02..., final version as build.xml; same for sources).</p>
75
76
77<h2><a name="propertyaccess">Property access</a></h2>
78<p>Our first step is to set a property to a value and print the value of that property.
79So our scenario would be
80<pre class="code">
81 &lt;find property="test" value="test-value"/&gt;
82 &lt;find print="test"/&gt;
83</pre>
84ok, can be rewritten with the core tasks
85<pre class="code">
86 &lt;property name="test" value="test-value"/&gt;
87 &lt;echo message="${test}"/&gt;
88</pre>
89but I have to start on known ground :-)</p>
90<p>So what to do? Handling three attributes (property, value, print) and an execute method.
91Because this is only an introduction example I don't do much checking:
92
93<pre class="code">
94import org.apache.tools.ant.BuildException;
95
96public class Find extends Task {
97
98 private String property;
99 private String value;
100 private String print;
101
102 public void setProperty(String property) {
103 this.property = property;
104 }
105
106 // setter for value and print
107
108 public void execute() {
109 if (print != null) {
110 String propValue = <b>getProject().getProperty(print)</b>;
111 log(propValue);
112 } else {
113 if (property == null) throw new BuildException("property not set");
114 if (value == null) throw new BuildException("value not set");
115 <b>getProject().setNewProperty(property, value)</b>;
116 }
117 }
118}
119</pre>
120
121As said in the other tutorial, the property access is done via Project instance.
122We get this instance via the public <tt>getProject()</tt> method which we inherit from
123<tt>Task</tt> (more precise from ProjectComponent). Reading a property is done via
124<tt>getProperty(<i>propertyname</i>)</tt> (very simple, isn't it?). This property returns
125the value as String or <i>null</i> if not set.<br>
126Setting a property is ... not really difficult, but there is more than one setter. You can
127use the <tt>setProperty()</tt> method which will do the job like expected. But there is
128a golden rule in Ant: <i>properties are immutable</i>. And this method sets the property
129to the specified value - whether it has a value before that or not. So we use another
130way. <tt>setNewProperty()</tt> sets the property only if there is no property with that
131name. Otherwise a message is logged.</p>
132
133<p><i>(by the way: a short word to ants "namespaces" (don't
134be confused with xml namespaces:
135an <code>&lt;antcall&gt;</code> creates a new space for property names. All properties from the caller
136are passed to the callee, but the callee can set its own properties without notice by the
137caller.)</i></p>
138
139<p>There are some other setter, too (but I haven't used them, so I can't say something
140to them, sorry :-)</p>
141
142<p>After putting our two line example from above into a target names <tt>use.simple</tt>
143we can call that from our testcase:
144
145<pre class="code">
146import org.apache.tools.ant.BuildFileTest;
147
148public class FindTest extends BuildFileTest {
149
150 public FindTest(String name) {
151 super(name);
152 }
153
154 public void setUp() {
155 configureProject("build.xml");
156 }
157
158 public void testSimple() {
159 <b>expectLog("use.simple", "test-value");</b>
160 }
161}
162</pre>
163
164and all works fine.</p>
165
166
167
168<h2><a name="filesets">Using filesets</a></h2>
169<p>Ant provides a common way of bundling files: the fileset. Because you are reading
170this tutorial I think you know them and I don't have to spend more explanations about
171their usage in buildfiles. Our goal is to search a file in path. And on this step the
172path is simply a fileset (or more precise: a collection of filesets). So our usage
173would be
174<pre class="code">
175 &lt;find file="ant.jar" location="location.ant-jar"&gt;
176 &lt;fileset dir="${ant.home}" includes="**/*.jar"/&gt;
177 &lt;/find&gt;
178</pre>
179</p>
180
181<p>What do we need? A task with two attributes (file, location) and nested
182filesets. Because we had attribute handling already explained in the example above and the
183handling of nested elements is described in the other tutorial the code should be very easy:
184<pre class="code">
185public class Find extends Task {
186
187 private String file;
188 private String location;
189 private Vector filesets = new Vector();
190
191 public void setFile(String file) {
192 this.file = file;
193 }
194
195 public void setLocation(String location) {
196 this.location = location;
197 }
198
199 public void addFileset(FileSet fileset) {
200 filesets.add(fileset);
201 }
202
203 public void execute() {
204 }
205}
206</pre>
207Ok - that task wouldn't do very much, but we can use it in the described manner without
208failure. On next step we have to implement the execute method. And before that we will
209implement the appropriate testcases (TDD - test driven development).</p>
210
211<p>In the other tutorial we have reused the already written targets of our buildfile.
212Now we will configure most of the testcases via java code (sometimes it's much easier
213to write a target than doing it via java coding). What can be tested?<ul>
214<li>not valid configured task (missing file, missing location, missing fileset)</li>
215<li>don't find a present file</li>
216<li>behaviour if file can't be found</li>
217</ul>
218Maybe you find some more testcases. But this is enough for now.<br>
219For each of these points we create a <tt>testXX</tt> method.</p>
220
221<pre class="code">
222public class FindTest extends BuildFileTest {
223
224 ... // constructor, setUp as above
225
226 public void testMissingFile() {
227 <b>Find find = new Find();</b>
228 try {
229 <b>find.execute();</b>
230 fail("No 'no-file'-exception thrown.");
231 } catch (Exception e) {
232 // exception expected
233 String expected = "file not set";
234 assertEquals("Wrong exception message.", expected, e.getMessage());
235 }
236 }
237
238 public void testMissingLocation() {
239 Find find = new Find();
240 <b>find.setFile("ant.jar");</b>
241 try {
242 find.execute();
243 fail("No 'no-location'-exception thrown.");
244 } catch (Exception e) {
245 ... // similar to testMissingFile()
246 }
247 }
248
249 public void testMissingFileset() {
250 Find find = new Find();
251 find.setFile("ant.jar");
252 find.setLocation("location.ant-jar");
253 try {
254 find.execute();
255 fail("No 'no-fileset'-exception thrown.");
256 } catch (Exception e) {
257 ... // similar to testMissingFile()
258 }
259 }
260
261 public void testFileNotPresent() {
262 executeTarget("testFileNotPresent");
263 String result = getProject().getProperty("location.ant-jar");
264 assertNull("Property set to wrong value.", result);
265 }
266
267 public void testFilePresent() {
268 executeTarget("testFilePresent");
269 String result = getProject().getProperty("location.ant-jar");
270 assertNotNull("Property not set.", result);
271 assertTrue("Wrong file found.", result.endsWith("ant.jar"));
272 }
273}
274</pre>
275
276<p>If we run this test class all test cases (except <i>testFileNotPresent</i>) fail. Now we
277can implement our task, so that these test cases will pass.</p>
278
279<pre class="code">
280 protected void validate() {
281 if (file==null) throw new BuildException("file not set");
282 if (location==null) throw new BuildException("location not set");
283 if (filesets.size()&lt;1) throw new BuildException("fileset not set");
284 }
285
286 public void execute() {
287 validate(); // 1
288 String foundLocation = null;
289 for(Iterator itFSets = filesets.iterator(); itFSets.hasNext(); ) { // 2
290 FileSet fs = (FileSet)itFSets.next();
291 DirectoryScanner ds = fs.getDirectoryScanner(getProject()); // 3
292 String[] includedFiles = ds.getIncludedFiles();
293 for(int i=0; i&lt;includedFiles.length; i++) {
294 String filename = includedFiles[i].replace('\\','/'); // 4
295 filename = filename.substring(filename.lastIndexOf("/")+1);
296 if (foundLocation==null &amp;&amp; file.equals(filename)) {
297 File base = ds.getBasedir(); // 5
298 File found = new File(base, includedFiles[i]);
299 foundLocation = found.getAbsolutePath();
300 }
301 }
302 }
303 if (foundLocation!=null) // 6
304 getProject().setNewProperty(location, foundLocation);
305 }
306</pre>
307
308<p>On <b>//1</b> we check the prerequisites for our task. Doing that in a <tt>validate</tt>-method
309is a common way, because we separate the prerequisites from the real work. On <b>//2</b> we iterate
310over all nested filesets. If we don't want to handle multiple filesets, the <tt>addFileset()</tt>
311method has to reject the further calls. We can get the result of a fileset via its DirectoryScanner
312like done in <b>//3</b>. After that we create a plattform independend String representation of
313the file path (<b>//4</b>, can be done in other ways of course). We have to do the <tt>replace()</tt>,
314because we work with a simple string comparison. Ant itself is platform independant and can
315therefore run on filesystems with slash (/, e.g. Linux) or backslash (\, e.g. Windows) as
316path separator. Therefore we have to unify that. If we found our file we create an absolute
317path representation on <b>//5</b>, so that we can use that information without knowing the basedir.
318(This is very important on use with multiple filesets, because they can have different basedirs
319and the return value of the directory scanner is relative to its basedir.) Finally we store the
320location of the file as property, if we had found one (<b>//6</b>).</p>
321
322<p>Ok, much more easier in this simple case would be to add the <i>file</i> as additional
323<i>include</i> element to all filesets. But I wanted to show how to handle complex situations
324whithout being complex :-)</p>
325
326<p>The test case uses the ant property <i>ant.home</i> as reference. This property is set by the
327<tt>Launcher</tt> class which starts ant. We can use that property in our buildfiles as a
328<a href="using.html#built-in-props">build-in property [3]</a>. But if we create a new ant
329environment we have to set that value for our own. And we use the <code>&lt;junit&gt;</code> task in fork-mode.
330Therefore we have do modify our buildfile:
331<pre class="code">
332 &lt;target name="junit" description="Runs the unit tests" depends="jar"&gt;
333 &lt;delete dir="${junit.out.dir.xml}"/&gt;
334 &lt;mkdir dir="${junit.out.dir.xml}"/&gt;
335 &lt;junit printsummary="yes" haltonfailure="no"&gt;
336 &lt;classpath refid="classpath.test"/&gt;
337 <b>&lt;sysproperty key="ant.home" value="${ant.home}"/&gt;</b>
338 &lt;formatter type="xml"/&gt;
339 &lt;batchtest fork="yes" todir="${junit.out.dir.xml}"&gt;
340 &lt;fileset dir="${src.dir}" includes="**/*Test.java"/&gt;
341 &lt;/batchtest&gt;
342 &lt;/junit&gt;
343 &lt;/target&gt;
344</pre>
345
346
347<h2><a name="path">Using nested paths</a></h2>
348<p>A task providing support for filesets is a very comfortable one. But there is another
349possibility of bundling files: the <code>&lt;path&gt;</code>. Fileset are easy if the files are all under
350a common base directory. But if this is not the case you have a problem. Another disadvantage
351is its speed: if you have only a few files in a huge directory structure, why not use a
352<code>&lt;filelist&gt;</code> instead? <code>&lt;path&gt;</code>s combines these datatypes in that way that a path contains
353other paths, filesets, dirsets and filelists. This is why <a href="http://ant-contrib.sourceforge.net/">
354Ant-Contribs [4]</a> <code>&lt;foreach&gt;</code> task is modified to support paths instead of filesets. So we want that,
355too.</p>
356
357<p>Changing from fileset to path support is very easy:</p>
358<pre class="code">
359<i><b>Change java code from:</b></i>
360 private Vector filesets = new Vector();
361 public void addFileset(FileSet fileset) {
362 filesets.add(fileset);
363 }
364<i><b>to:</b></i>
365 private Vector paths = new Vector(); *1
366 public void add<b>Path</b>(<b>Path</b> path) { *2
367 paths.add(path);
368 }
369<i><b>and build file from:</b></i>
370 &lt;find file="ant.jar" location="location.ant-jar"&gt;
371 &lt;fileset dir="${ant.home}" includes="**/*.jar"/&gt;
372 &lt;/find&gt;
373<i><b>to:</b></i>
374 &lt;find file="ant.jar" location="location.ant-jar"&gt;
375 <b>&lt;path&gt;</b> *3
376 &lt;fileset dir="${ant.home}" includes="**/*.jar"/&gt;
377 &lt;/path&gt;
378 &lt;/find&gt;
379</pre>
380<p>On <b>*1</b> we rename only the vector. Itï¿œs just for better reading the source. On <b>*2</b>
381we have to provide the right method: an add<i>Name</i>(<i>Type</i> t). Therefore replace the
382fileset with path here. Finally we have to modify our buildfile on <b>*3</b> because our task
383doesnï¿œt support nested filesets any longer. So we wrap the fileset inside a path.</p>
384
385<p>And now we modify the testcase. Oh, not very much to do :-) Renaming the <tt>testMissingFileset()</tt>
386(not really a <i>must-be</i> but better itï¿œs named like the think it does) and update the
387<i>expected</i>-String in that method (now a <tt>path not set</tt> message is expected). The more complex
388test cases base on the buildscript. So the targets <tt>testFileNotPresent</tt> and <tt>testFilePresent</tt> have to be
389modified in the manner described above.</p>
390
391<p>The test are finished. Now we have to adapt the task implementation. The easiest modification is
392in the <tt>validate()</tt> method where we change le last line to <tt>if (paths.size()&lt;1) throw new
393BuildException("path not set");</tt>. In the <tt>execute()</tt> method we have a little more work.
394... mmmh ... in reality it's lesser work, because the Path class does the whole DirectoryScanner-handling
395and creating-absolute-paths stuff for us. So the execute method is just:</p>
396
397<pre class="code">
398 public void execute() {
399 validate();
400 String foundLocation = null;
401 for(Iterator itPaths = paths.iterator(); itPaths.hasNext(); ) {
402 Path path = (<b>Path</b>)itPaths.next(); // 1
403 String[] includedFiles = <b>path.list()</b>; // 2
404 for(int i=0; i&lt;includedFiles.length; i++) {
405 String filename = includedFiles[i].replace('\\','/');
406 filename = filename.substring(filename.lastIndexOf("/")+1);
407 if (foundLocation==null &amp;&amp; file.equals(filename)) {
408 <b>foundLocation = includedFiles[i];</b> // 3
409 }
410 }
411 }
412 if (foundLocation!=null)
413 getProject().setNewProperty(location, foundLocation);
414 }
415</pre>
416
417<p>Of course we have to do the typecase to Path on <b>//1</b>. On <b>//2</b> and <b>//3</b>
418we see that the Path class does the work for us: no DirectoryScanner (was at 2) and no
419creating of the absolute path (was at 3).</p>
420
421
422
423<h2><a name="returning-list">Returning a list</a></h2>
424<p>So far so good. But could a file be on more than one place in the path? - Of course.<br>
425And would it be good to get all of them? - It depends on ...<p>
426
427<p>In this section we will extend that task to support returning a list of all files.
428Lists as property values are not supported by Ant natively. So we have to see how other
429tasks use lists. The most famous task using lists is Ant-Contribs <code>&lt;foreach&gt;</code>. All list
430elements are concatenated and separated with a customizable separator (default ',').</p>
431
432<p>So we do the following:</p>
433
434<pre class="code">
435 &lt;find ... <b>delimiter=""</b>/&gt; ... &lt;/find&gt;
436</pre>
437
438<p>If the delimiter is set we will return all found files as list with that delimiter.</p>
439
440<p>Therefore we have to<ul>
441<li>provide a new attribute</li>
442<li>collect more than the first file</li>
443<li>delete duplicates</li>
444<li>create the list if necessary</li>
445<li>return that list</li>
446</ul></p>
447
448<p>So we add as testcase:</p>
449<pre class="code">
450<b><i>in the buildfile:</i></b>
451 &lt;target name="test.init"&gt;
452 &lt;mkdir dir="test1/dir11/dir111"/&gt; *1
453 &lt;mkdir dir="test1/dir11/dir112"/&gt;
454 ...
455 &lt;touch file="test1/dir11/dir111/test"/&gt;
456 &lt;touch file="test1/dir11/dir111/not"/&gt;
457 ...
458 &lt;touch file="test1/dir13/dir131/not2"/&gt;
459 &lt;touch file="test1/dir13/dir132/test"/&gt;
460 &lt;touch file="test1/dir13/dir132/not"/&gt;
461 &lt;touch file="test1/dir13/dir132/not2"/&gt;
462 &lt;mkdir dir="test2"/&gt;
463 &lt;copy todir="test2"&gt; *2
464 &lt;fileset dir="test1"/&gt;
465 &lt;/copy&gt;
466 &lt;/target&gt;
467
468 &lt;target name="testMultipleFiles" depends="use.init,<b>test.init</b>"&gt; *3
469 &lt;find file="test" location="location.test" <b>delimiter=";"</b>&gt;
470 &lt;path&gt;
471 &lt;fileset dir="test1"/&gt;
472 &lt;fileset dir="test2"/&gt;
473 &lt;/path&gt;
474 &lt;/find&gt;
475 &lt;delete&gt; *4
476 &lt;fileset dir="test1"/&gt;
477 &lt;fileset dir="test2"/&gt;
478 &lt;/delete&gt;
479 &lt;/target&gt;
480
481<b><i>in the test class:</i></b>
482 public void testMultipleFiles() {
483 executeTarget("testMultipleFiles");
484 String result = getProject().getProperty("location.test");
485 assertNotNull("Property not set.", result);
486 assertTrue("Only one file found.", result.indexOf(";") &gt; -1);
487 }
488</pre>
489
490<p>Now we need a directory structure where we CAN find files with the same
491name in different directories. Because we can't sure to have one we create
492one on <b>*1</b> and <b>*2</b>. And of course we clean up that on <b>*4</b>. The creation
493can be done inside our test target or in a separate one, which will be better
494for reuse later (<b>*3</b>).
495
496<p>The task implementation is modified as followed:</p>
497
498<pre class="code">
499 private Vector foundFiles = new Vector();
500 ...
501 private String delimiter = null;
502 ...
503 public void setDelimiter(String delim) {
504 delimiter = delim;
505 }
506 ...
507 public void execute() {
508 validate();
509 // find all files
510 for(Iterator itPaths = paths.iterator(); itPaths.hasNext(); ) {
511 Path path = (Path)itPaths.next();
512 String[] includedFiles = path.list();
513 for(int i=0; i&lt;includedFiles.length; i++) {
514 String filename = includedFiles[i].replace('\\','/');
515 filename = filename.substring(filename.lastIndexOf("/")+1);
516 if (file.equals(filename) &amp;&amp; <b>!foundFiles.contains(includedFiles[i]</b>)) { // 1
517 foundFiles.add(includedFiles[i]);
518 }
519 }
520 }
521
522 // create the return value (list/single)
523 String rv = null;
524 if (foundFiles.size() &gt; 0) { // 2
525 if (delimiter==null) {
526 // only the first
527 rv = (String)foundFiles.elementAt(0);
528 } else {
529 // create list
530 StringBuffer list = new StringBuffer();
531 for(Iterator it=foundFiles.iterator(); it.hasNext(); ) { // 3
532 list.append(it.next());
533 if (<b>it.hasNext()</b>) list.append(delimiter); // 4
534 }
535 rv = list.toString();
536 }
537 }
538
539 // create the property
540 if (rv!=null)
541 getProject().setNewProperty(location, rv);
542 }
543</pre>
544
545<p>The algorithm does: finding all files, creating the return value depending on the users
546wish, returning the value as property. On <b>//1</b> we eliminates the duplicates. <b>//2</b>
547ensures that we create the return value only if we have found one file. On <b>//3</b> we
548iterate over all found files and <b>//4</b> ensures that the last entry has no trailing
549delimiter.</p>
550
551<p>Ok, first searching for all files and then returning only the first one ... You can
552tune the performance of your own :-)</p>
553
554
555<h2><a name="documentation">Documentation</a></h2>
556<p>A task is useless if the only who is able to code the buildfile is the task developer
557(and he only the next few weeks :-). So documentation is also very important. In which
558form you do that depends on your favourite. But inside Ant there is a common format and
559it has advantages if you use that: all task users know that form, this form is requested if
560you decide to contribute your task. So we will doc our task in that form.</p>
561
562<p>If you have a look at the manual page of the <a href="CoreTasks/java.html">java [5]</a>
563 task you will see<ul>
564<li>it is plain html</li>
565<li>starts with the name</li>
566<li>has sections: description, parameters, nested elements, (maybe return codes) and (most
567important :-) examples</li>
568<li>parameters are listed in a table with columns for attribute name, its description and whether
569 it's required (if you add a feature after an Ant release, provide a <tt>since Ant xx</tt>
570 statement when it's introduced)</li>
571<li>describe the nested elements (since-statement if necessary)</li>
572<li>provide one or more useful examples; first code then description</li>
573</ul>
574As a template we have:
575
576<pre class="code">
577&lt;html&gt;
578
579&lt;head&gt;
580&lt;meta http-equiv="Content-Language" content="en-us"&gt;
581&lt;title&gt; <b>Taskname</b> Task&lt;/title&gt;
582&lt;/head&gt;
583
584&lt;body&gt;
585
586&lt;h2&gt;&lt;a name="<i>taskname</i>"&gt;<b>Taskname</b>&lt;/a&gt;&lt;/h2&gt;
587&lt;h3&gt;Description&lt;/h3&gt;
588&lt;p&gt; <b>Describe the task.</b>&lt;/p&gt;
589
590&lt;h3&gt;Parameters&lt;/h3&gt;
591&lt;table border="1" cellpadding="2" cellspacing="0"&gt;
592 &lt;tr&gt;
593 &lt;td valign="top"&gt;&lt;b&gt;Attribute&lt;/b&gt;&lt;/td&gt;
594 &lt;td valign="top"&gt;&lt;b&gt;Description&lt;/b&gt;&lt;/td&gt;
595 &lt;td align="center" valign="top"&gt;&lt;b&gt;Required&lt;/b&gt;&lt;/td&gt;
596 &lt;/tr&gt;
597
598 <b>do this html row for each attribute (including inherited attributes)</b>
599 &lt;tr&gt;
600 &lt;td valign="top"&gt;classname&lt;/td&gt;
601 &lt;td valign="top"&gt;the Java class to execute.&lt;/td&gt;
602 &lt;td align="center" valign="top"&gt;Either jar or classname&lt;/td&gt;
603 &lt;/tr&gt;
604
605&lt;/table&gt;
606
607&lt;h3&gt;Parameters specified as nested elements&lt;/h3&gt;
608
609<b>Describe each nested element (including inherited)</b>
610&lt;h4&gt;your nested element</b>&lt;/h4&gt;
611&lt;p&gt; <b>description</b> &lt;/p&gt;
612&lt;p&gt;&lt;em&gt;since Ant 1.6&lt;/em&gt;.&lt;/p&gt;
613
614&lt;h3&gt;Examples&lt;/h3&gt;
615&lt;pre&gt;
616 <b>A code sample; don't forget to escape the &lt; of the tags with &amp;lt;</b>
617&lt;/pre&gt;
618<b>what should that example do?</b>
619
620&lt;/body&gt;
621&lt;/html&gt;
622</pre>
623
624<p>For our task we have <a href="CoreTasks/find.html">that [6]</a>:</p>
625<pre class="code">
626&lt;html&gt;
627
628&lt;head&gt;
629&lt;meta http-equiv="Content-Language" content="en-us"&gt;
630&lt;title&gt; Find Task&lt;/title&gt;
631&lt;/head&gt;
632
633&lt;body&gt;
634
635&lt;h2&gt;&lt;a name="find"&gt;Find&lt;/a&gt;&lt;/h2&gt;
636&lt;h3&gt;Description&lt;/h3&gt;
637&lt;p&gt;Searchs in a given path for a file and returns the absolute to it as property.
638If delimiter is set this task returns all found locations.&lt;/p&gt;
639
640&lt;h3&gt;Parameters&lt;/h3&gt;
641&lt;table border="1" cellpadding="2" cellspacing="0"&gt;
642 &lt;tr&gt;
643 &lt;td valign="top"&gt;&lt;b&gt;Attribute&lt;/b&gt;&lt;/td&gt;
644 &lt;td valign="top"&gt;&lt;b&gt;Description&lt;/b&gt;&lt;/td&gt;
645 &lt;td align="center" valign="top"&gt;&lt;b&gt;Required&lt;/b&gt;&lt;/td&gt;
646 &lt;/tr&gt;
647 &lt;tr&gt;
648 &lt;td valign="top"&gt;file&lt;/td&gt;
649 &lt;td valign="top"&gt;The name of the file to search.&lt;/td&gt;
650 &lt;td align="center" valign="top"&gt;yes&lt;/td&gt;
651 &lt;/tr&gt;
652 &lt;tr&gt;
653 &lt;td valign="top"&gt;location&lt;/td&gt;
654 &lt;td valign="top"&gt;The name of the property where to store the location&lt;/td&gt;
655 &lt;td align="center" valign="top"&gt;yes&lt;/td&gt;
656 &lt;/tr&gt;
657 &lt;tr&gt;
658 &lt;td valign="top"&gt;delimiter&lt;/td&gt;
659 &lt;td valign="top"&gt;A delimiter to use when returning the list&lt;/td&gt;
660 &lt;td align="center" valign="top"&gt;only if the list is required&lt;/td&gt;
661 &lt;/tr&gt;
662&lt;/table&gt;
663
664&lt;h3&gt;Parameters specified as nested elements&lt;/h3&gt;
665
666&lt;h4&gt;path&lt;/h4&gt;
667&lt;p&gt;The path where to search the file.&lt;/p&gt;
668
669&lt;h3&gt;Examples&lt;/h3&gt;
670&lt;pre&gt;
671 &lt;find file="ant.jar" location="loc"&gt;
672 &lt;path&gt;
673 &lt;fileset dir="${ant.home}"/&gt;
674 &lt;path&gt;
675 &lt;/find&gt;
676&lt;/pre&gt;
677Searches in Ants home directory for a file &lt;i&gt;ant.jar&lt;/i&gt; and stores its location in
678property &lt;i&gt;loc&lt;/i&gt; (should be ANT_HOME/bin/ant.jar).
679
680&lt;pre&gt;
681 &lt;find file="ant.jar" location="loc" delimiter=";"&gt;
682 &lt;path&gt;
683 &lt;fileset dir="C:/"/&gt;
684 &lt;path&gt;
685 &lt;/find&gt;
686 &lt;echo&gt;ant.jar found in: ${loc}&lt;/echo&gt;
687&lt;/pre&gt;
688Searches in Windows C: drive for all &lt;i&gt;ant.jar&lt;/i&gt; and stores their locations in
689property &lt;i&gt;loc&lt;/i&gt; delimited with &lt;i&gt;';'&lt;/i&gt;. (should need a long time :-)
690After that it prints out the result (e.g. C:/ant-1.5.4/bin/ant.jar;C:/ant-1.6/bin/ant.jar).
691
692&lt;/body&gt;
693&lt;/html&gt;
694</pre>
695
696
697<h2><a name="contribute">Contribute the new task</a></h2>
698If we decide to contribute our task, we should do some things:<ul>
699<li>is our task welcome? :-) Simply ask on the user list</li>
700<li>is the right package used? </li>
701<li>is the code conform to the styleguide?</li>
702<li>do all tests pass? </li>
703<li>does the code compile on JDK 1.2 (and passes all tests there)?</li>
704<li>code under Apache license</li>
705<li>create a patch file</li>
706<li>publishing that patch file</li>
707</ul>
708The <a href="../ant_task_guidelines.html">Ant Task Guidelines [7]</a> support additional
709information on that.</p>
710
711<p>Now we will check the "Checklist before submitting a new task" described in that guideline.
712<ul>
713<li>Java file begins with Apache license statement. <b><i>must do that</i></b></li>
714<li>Task does not depend on GPL or LGPL code. <b><i>ok</i></b></li>
715<li>Source code complies with style guidelines <b><i>have to check (checkstyle)</i></b></li>
716<li>Code compiles and runs on Java1.2 <b><i>have to try</i></b></li>
717<li>Member variables are private, and provide public accessor methods
718 if access is actually needed. <b><i>have to check (checkstyle)</i></b></li>
719<li><i>Maybe</i> Task has failonerror attribute to control failure behaviour <b><i>hasn't</i></b></li>
720<li>New test cases written and succeed <b><i>passed on JDK 1.4, have to try on JDK 1.2</i></b></li>
721<li>Documentation page written <b><i>ok</i></b></li>
722<li>Example task declarations in the documentation tested. <b><i>ok (used in tests)</i></b></li>
723<li>Patch files generated using cvs diff -u <b><i>to do</i></b></li>
724<li>patch files include a patch to defaults.properties to register the
725tasks <b><i>to do</i></b></li>
726<li>patch files include a patch to coretasklist.html or
727optionaltasklist.html to link to the new task page <b><i>to do</i></b></li>
728<li>Message to dev contains [SUBMIT] and task name in subject <b><i>to do</i></b></li>
729<li>Message body contains a rationale for the task <b><i>to do</i></b></li>
730<li>Message attachments contain the required files -source, documentation,
731test and patches zipped up to escape the HTML filter. <b><i>to do</i></b></li>
732</ul>
733
734
735<h3>Package / Directories</h3>
736<p>This task does not depend on any external library. Therefore we can use this as
737a core task. This task contains only one class. So we can use the standard package
738for core tasks: <tt>org.apache.tools.ant.taskdefs</tt>. Implementations are in the
739directory <tt>src/main</tt>, tests in <tt>src/testcases</tt> and buildfiles for
740tests in <tt>src/etc/testcases</tt>.</p>
741
742<p>Now we integrate our work into Ants distribution. So first we do an update of our
743cvs tree. If not done yet, you have to checkout the ant module from Apaches cvs server
744as described in <a href="http://ant.apache.org/cvs.html">Access the Source Tree (AnonCVS)
745[8]</a> (password is <i>anoncvs</i>):<pre class="output">
746cvs -d :pserver:[email protected]:/home/cvspublic login //1
747cvs -d :pserver:[email protected]:/home/cvspublic checkout ant //2
748</pre>
749If you have a local copy of Ants sources just do an update
750<pre class="output">
751cvs -d :pserver:[email protected]:/home/cvspublic login
752cd ant //3
753cvs -d :pserver:[email protected]:/home/cvspublic update //4
754</pre></p>
755
756<p>We use the <i>-d</i> flag on <b>//1</b> to specifiy the cvs directory. You can
757specify the environment variable CVSROOT with that value and after that you havenï¿œt
758to use that flag any more. On <b>//2</b> we get the whole cvs tree of ant. (Sorry,
759but that uses a lot of time ... 10 up to 30 minutes are not unusual ... but this has
760to be done only once :-). A cvs update doesn't use a modulename but you have to be
761inside the directory. Therefore we go into that on <b>//3</b> and do the update
762on <b>//4</b>.</p>
763
764<p>Now we will build our Ant distribution and do a test. So we can see if there
765are any tests failing on our machine. (We can ignore these failing tests on later
766steps; windows syntax used here- translate to xNIX if needed):
767<pre class="output">
768ANTHOME&gt; build // 1
769ANTHOME&gt; set ANT_HOME=%CD%\dist // 2
770ANTHOME&gt; ant test -Dtest.haltonfailure=false // 3
771</pre>
772
773First we have to build our Ant distribution (<b>//1</b>). On <b>//2</b> we set the ANT_HOME
774environment variable to the directory where the new created distribution is stored
775(%CD% is expanded to the current directory on Windows 2000 and XP, on 9x and NT
776write it out). On <b>//3</b> we let Ant do all the tests (which enforced a compile
777of all tests) without stopping on first failure.</p>
778
779<p>Next we apply our work onto Ants sources. Because we haven't modified any, this is
780a relative simple step. <i>(Because I have a local copy of Ant and usually contribute my
781work, I work on the local copy just from the beginning. The advantage: this step isn't
782necessary and saves a lot of work if you modify existing source :-)</i>.
783
784<ul>
785<li>move the Find.java to ANTHOME/src/main/org/apache/tools/ant/taskdefs/Find.java </li>
786<li>move the FindTest.java to ANTHOME/src/testcases/org/apache/tools/ant/taskdefs/FindTest.java </li>
787<li>move the build.xml to ANTHOME/src/etc/testcases/taskdefs/<b>find.xml</b> (!!! renamed !!!)</li>
788<li>add a <tt>package org.apache.tools.ant.taskdefs;</tt> at the beginning of the two java files </li>
789<li>delete all stuff from find.xml keeping the targets "testFileNotPresent", "testFilePresent",
790 "test.init" and "testMultipleFiles" </li>
791<li>delete the dependency to "use.init" in the find.xml </li>
792<li>in FindTest.java change the line <tt>configureProject("build.xml");</tt> to
793 <tt>configureProject("src/etc/testcases/taskdefs/find.xml");</tt> </li>
794<li>move the find.html to ANTHOME/docs/manual/CoreTasks/find.html </li>
795<li>add a <tt>&lt;a href="CoreTasks/find.html"&gt;Find&lt;/a&gt;&lt;br&gt;</tt>
796 in the ANTHOME/docs/manual/coretasklist.html </li>
797</ul>
798
799Now our modifications are done and we will retest it:
800<pre class="output">
801ANTHOME&gt; build
802ANTHOME&gt; ant run-single-test // 1
803 -Dtestcase=org.apache.tools.ant.taskdefs.FindTest // 2
804 -Dtest.haltonfailure=false
805</pre>
806Because we only want to test our new class, we use the target for single tests, specify
807the test to use and configure not to halt on the first failure - we want to see all
808failures of our own test (<b>//1 + 2</b>).</p>
809
810<p>And ... oh, all tests fail: <i>Ant could not find the task or a class this task relies upon.</i></p>
811
812<p>Ok: in the earlier steps we told Ant to use the Find class for the <code>&lt;find&gt;</code> task (remember the
813<code>&lt;taskdef&gt;</code> statement in the "use.init" target). But now we want to introduce that task as
814a core task. And nobody wants to taskdef the javac, echo, ... So what to do? The answer is the
815src/main/.../taskdefs/default.properties. Here is the mapping between taskname and implementing
816class done. So we add a <tt>find=org.apache.tools.ant.taskdefs.Find</tt> as the last core
817task (just before the <tt># optional tasks</tt> line). Now a second try:
818<pre class="output">
819ANTHOME&gt; build // 1
820ANTHOME&gt; ant run-single-test
821 -Dtestcase=org.apache.tools.ant.taskdefs.FindTest
822 -Dtest.haltonfailure=false
823</pre>
824We have to rebuild (<b>//1</b>) Ant because the test look in the %ANT_HOME%\lib\ant.jar
825(more precise: on the classpath) for the properties file. And we have only modified it in the
826source path. So we have to rebuild that jar. But now all tests pass and we check whether our class
827breaks some other tests.
828<pre class="output">
829ANTHOME&gt; ant test -Dtest.haltonfailure=false
830</pre>
831Because there are a lot of tests this step requires a little bit of time. So use the <i>run-single-test</i>
832during development and do the <i>test</i> only at the end (maybe sometimes during development too).
833We use the <i>-Dtest.haltonfailure=false</i> here because there could be other tests fail and we have
834to look into them.</p>
835
836<p>This test run should show us two things: our test will run and the number of failing tests
837is the same as directly after the cvs update (without our modifications).</p>
838
839
840
841<h3>Apache license statement</h3>
842<p>Simply copy the license text from one the other source from the Ant source tree.</p>
843
844
845<h3>Test on JDK 1.2</h3>
846<p>Until version 1.5 Ant must be able to run on a JDK 1.1. With version 1.6 this is not a
847requisite any more. But JDK 1.2 is a must-to-work-with. So we have to test that. You can download older
848JDKs from <a href="http://java.sun.com/products/archive/index.html">Sun [9]</a>.</p>
849
850<p>Clean the ANT_HOME variable, delete the <i>build, bootstrap</i> and <i>dist</i> directory
851and point JAVA_HOME to the JDK 1.2 home directory. Then do the <tt>build</tt>, set ANT_HOME
852and run <tt>ant test</tt> (like above).</p>
853
854<p>Our test should pass.</p>
855
856
857
858<h3>Checkstyle</h3>
859<p>There are many things we have to ensure. Indentation with 4 spaces, blanks here and there, ...
860(all described in the <a href="../ant_task_guidelines.html">Ant Task Guidelines [7]</a> which
861includes the <a href="http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html">Sun code style
862[10]</a>). Because there are so many things we would be happy to have a tool for do the checks.
863There is one: checkstyle. Checkstyle is available at <a href="http://checkstyle.sourceforge.net/">
864Sourceforge [11]</a> and Ant provides with the <tt>check.xml</tt> a buildfile which will do the job
865for us.</p>
866
867<p>Download it and put the checkstyle-*-all.jar into your %USERPROFILE%\.ant\lib directory.
868All jar's stored there are available to Ant so you haven't to add it to you %ANT_HOME%\lib
869directory (this feature was added with Ant 1.6).</p>
870
871<p>So we will run the tests with
872<pre class="output">
873ANTHOME&gt; ant -f check.xml checkstyle htmlreport
874</pre>
875I prefer the HTML report because there are lots of messages and we can navigate faster.
876Open the ANTHOME/build/reports/checkstyle/html/index.html and navigate to the Find.java. Now we
877see that there are some errors: missing whitespaces, unused imports, missing javadocs. So we have
878to do that.</p>
879
880<p>Hint: start at the <b>buttom</b> of the file so the line numbers in the report will keep
881up to date and you will find the next error place much more easier without redoing the checkstyle.</p>
882
883<p>After cleaning up the code according to the messages we delete the reports directory and
884do a second checkstyle run. Now our task isn't listed. That's fine :-)</p>
885
886
887
888<!--
889 Couldnt create the diff that way for myself, but that should be documented.
890 But on the other hand this tutorial should not be forgotten any longer so I
891 comment that out. JHM
892<h3>Creating the diff</h3>
893<p>Creating a diff for Ant is very easy: just start <tt>ant -f patch.xml</tt> and all is done
894automatically. This step requires a cvs executable in your path and internet access (more precise:
895cvs access). As a result we get a file <i> XXX </i>.</p>
896-->
897
898
899<h3>Publish the task</h3>
900<p>Finally we publish that archive. As described in the <a href="../ant_task_guidelines.html">
901Ant Task Guidelines [7]</a> we can post it on the developer mailinglist or we create a BugZilla
902entry. For both we need some information:</p>
903
904<table border="1" cellpadding="2" cellspacing="0">
905<tr>
906 <th>subject</th>
907 <td><i>short description</i></td>
908 <td>Task for finding files in a path</td>
909</tr>
910<tr>
911 <th>body</th>
912 <td><i>more details about the path</i></td>
913 <td>This new task looks inside a nested <code>&lt;path/&gt;</code> for occurrences of a file and stores
914 all locations as a property. See the included manual for details.</td>
915</tr>
916<tr>
917 <th>attachements</th>
918 <td><i>all files needed to apply the path</td>
919 <td>Archive containing a patch with the new and modified resources</td>
920</tr>
921</table>
922
923<p>Sending an email with these information is very easy and I think I haven't to show that.
924The other way - BugZilla - is slightly more difficult. But it has the advantage that entries
925will not be forgotten (once per week a report is generated). So I will show this way.</p>
926
927<p>You must have a BugZilla account for that. So open the <a href="http://issues.apache.org/bugzilla/">
928BugZilla Main Page [12]</a> and follow the link
929<a href="http://issues.apache.org/bugzilla/createaccount.cgi">Open a new Bugzilla account [13]</a>
930and the steps described there if you haven't one.</p>
931
932<ol>
933<li>From the BugZilla main page choose <a href="http://issues.apache.org/bugzilla/enter_bug.cgi">Enter
934 a new bug report [14]</a></li>
935<li>Choose "Ant" as product </li>
936<li>Version is the last "Alpha (nightly)" (at this time 1.7)</li>
937<li>Component is "Core tasks"</li>
938<li>Plattform and Severity are ok with "Other" and "Normal"</li>
939<li>Initial State is ok with "New"</li>
940<li>Same with the empy "Assigned to"</li>
941<li>It is not required to add yourself as CC, because you are the reporter and therefore will be
942 informed on changes</li>
943<li>URL: no url required</li>
944<li>Summary: add the <i>subject</i> from the table</li>
945<li>Description: add the <i>body</i> from the table</li>
946<li>Then press "Commit"</li>
947<li>After redirecting to the new created bug entry click "Create a New Attachment"</li>
948<li>Enter the path to your local path file into "File" or choose it via the "File"'s
949 button.</li>
950<li>Enter a short description into "Description", so that you could guess, what the
951 path file includes. Here we could add "Initial Patch".</li>
952<li>The "Content Type" is "auto-detect". You could use the "patch" type, if you only
953 provide a single path file, but we want do upload more that one, included in our
954 patch.zip.</li>
955<li>Then press "Commit"</li>
956</ol>
957Now the new task is uploaded into the bug database.
958
959
960<h2><a name="resources">Resources</a></h2>
961&nbsp;&nbsp;[1] <a href="tutorial-writing-tasks.html">tutorial-writing-tasks.html</a><br>
962&nbsp;&nbsp;[2] <a href="tutorial-tasks-filesets-properties.zip">tutorial-tasks-filesets-properties.zip</a><br>
963&nbsp;&nbsp;[3] <a href="using.html#built-in-props">using.html#built-in-props</a><br>
964&nbsp;&nbsp;[4] <a href="http://ant-contrib.sourceforge.net/">http://ant-contrib.sourceforge.net/</a><br>
965&nbsp;&nbsp;[5] <a href="CoreTasks/java.html">CoreTasks/java.html</a><br>
966&nbsp;&nbsp;[6] <a href="CoreTasks/find.html">CoreTasks/find.html</a><br>
967&nbsp;&nbsp;[7] <a href="../ant_task_guidelines.html">../ant_task_guidelines.html</a><br>
968&nbsp;&nbsp;[8] <a href="http://ant.apache.org/cvs.html">http://ant.apache.org/cvs.html</a><br>
969&nbsp;&nbsp;[9] <a href="http://java.sun.com/products/archive/index.html">http://java.sun.com/products/archive/index.html</a><br>
970&nbsp;&nbsp;[10] <a href="http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html">http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html</a><br>
971&nbsp;&nbsp;[11] <a href="http://checkstyle.sourceforge.net/">http://checkstyle.sourceforge.net/</a><br>
972&nbsp;&nbsp;[12] <a href="http://issues.apache.org/bugzilla/">http://issues.apache.org/bugzilla/</a><br>
973&nbsp;&nbsp;[13] <a href="http://issues.apache.org/bugzilla/createaccount.cgi">http://issues.apache.org/bugzilla/createaccount.cgi</a><br>
974&nbsp;&nbsp;[14] <a href="http://issues.apache.org/bugzilla/enter_bug.cgi">http://issues.apache.org/bugzilla/enter_bug.cgi</a><br>
975
976
977<!--
978 TODO:
979 - how to create a path (path.xml / command line)
980-->
981
982
983
984
985
986</body>
987</html>
Note: See TracBrowser for help on using the repository browser.