source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/docs/manual/tutorial-tasks-filesets-properties.html@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

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