From 753a6c60f47f3ac4f270005b65e9d6481de8eb68 Mon Sep 17 00:00:00 2001
From: Ashlee Young After reading the tutorial about writing
+tasks [1] this tutorial explains how to get and set properties and how to use
+nested filesets and paths. Finally it explains how to contribute tasks to Apache Ant.Tutorial: Tasks using Properties, Filesets & Paths
+
+Content
+
The goal is to write a task, which searchs in a path for a file and saves the +location of that file in a property.
+ + +We can use the buildfile from the other tutorial and modify it a little bit. +That's the advantage of using properties - we can reuse nearly the whole script. :-)
++<?xml version="1.0" encoding="ISO-8859-1"?> +<project name="FindTask" basedir="." default="test"> + ... + <target name="use.init" description="Taskdef's the Find-Task" depends="jar"> + <taskdef name="find" classname="Find" classpath="${ant.project.name}.jar"/> + </target> + + <!-- the other use.* targets are deleted --> + ... +</project> ++ +
The buildfile is in the archive +tutorial-tasks-filesets-properties.zip [2] in /build.xml.01-propertyaccess +(future version saved as *.02..., final version as build.xml; same for sources).
+ + +Our first step is to set a property to a value and print the value of that property. +So our scenario would be +
+ <find property="test" value="test-value"/> + <find print="test"/> ++ok, can be rewritten with the core tasks +
+ <property name="test" value="test-value"/> + <echo message="${test}"/> ++but I have to start on known ground :-) +
So what to do? Handling three attributes (property, value, print) and an execute method. +Because this is only an introduction example I don't do much checking: + +
+import org.apache.tools.ant.BuildException; + +public class Find extends Task { + + private String property; + private String value; + private String print; + + public void setProperty(String property) { + this.property = property; + } + + // setter for value and print + + public void execute() { + if (print != null) { + String propValue = getProject().getProperty(print); + log(propValue); + } else { + if (property == null) throw new BuildException("property not set"); + if (value == null) throw new BuildException("value not set"); + getProject().setNewProperty(property, value); + } + } +} ++ +As said in the other tutorial, the property access is done via Project instance. +We get this instance via the public getProject() method which we inherit from +Task (more precise from ProjectComponent). Reading a property is done via +getProperty(propertyname) (very simple, isn't it?). This property returns +the value as String or null if not set.
(by the way: a short word to ants "namespaces" (don't
+be confused with xml namespaces:
+an <antcall>
creates a new space for property names. All properties from the caller
+are passed to the callee, but the callee can set its own properties without notice by the
+caller.)
There are some other setter, too (but I haven't used them, so I can't say something +to them, sorry :-)
+ +After putting our two line example from above into a target names use.simple +we can call that from our testcase: + +
+import org.junit.Rule; +import org.junit.Test; +import org.junit.Before; +import org.junit.Assert; +import org.apache.tools.ant.BuildFileRule; + + +public class FindTest { + + @Rule + public final BuildFileRule buildRule = new BuildFileRule(); + + + @Before + public void setUp() { + configureProject("build.xml"); + } + + @Test + public void testSimple() { + buildRule.executeTarget("useSimgle"); + Assert.assertEquals("test-value", buildRule.getLog()); + } +} ++ +and all works fine. + + + +
Ant provides a common way of bundling files: the fileset. Because you are reading +this tutorial I think you know them and I don't have to spend more explanations about +their usage in buildfiles. Our goal is to search a file in path. And on this step the +path is simply a fileset (or more precise: a collection of filesets). So our usage +would be +
+ <find file="ant.jar" location="location.ant-jar"> + <fileset dir="${ant.home}" includes="**/*.jar"/> + </find> ++ + +
What do we need? A task with two attributes (file, location) and nested +filesets. Because we had attribute handling already explained in the example above and the +handling of nested elements is described in the other tutorial the code should be very easy: +
+public class Find extends Task { + + private String file; + private String location; + private Vector filesets = new Vector(); + + public void setFile(String file) { + this.file = file; + } + + public void setLocation(String location) { + this.location = location; + } + + public void addFileset(FileSet fileset) { + filesets.add(fileset); + } + + public void execute() { + } +} ++Ok - that task wouldn't do very much, but we can use it in the described manner without +failure. On next step we have to implement the execute method. And before that we will +implement the appropriate testcases (TDD - test driven development). + +
In the other tutorial we have reused the already written targets of our buildfile. +Now we will configure most of the testcases via java code (sometimes it's much easier +to write a target than doing it via java coding). What can be tested?
+public class FindTest { + + @Rule + public final BuildFileRule buildRule = new BuildFileRule(); + + ... // constructor, setUp as above + + @Test + public void testMissingFile() { + Find find = new Find(); + try { + find.execute(); + fail("No 'no-file'-exception thrown."); + } catch (Exception e) { + // exception expected + String expected = "file not set"; + assertEquals("Wrong exception message.", expected, e.getMessage()); + } + } + + @Test + public void testMissingLocation() { + Find find = new Find(); + find.setFile("ant.jar"); + try { + find.execute(); + fail("No 'no-location'-exception thrown."); + } catch (Exception e) { + ... // similar to testMissingFile() + } + } + + @Test + public void testMissingFileset() { + Find find = new Find(); + find.setFile("ant.jar"); + find.setLocation("location.ant-jar"); + try { + find.execute(); + fail("No 'no-fileset'-exception thrown."); + } catch (Exception e) { + ... // similar to testMissingFile() + } + } + + @Test + public void testFileNotPresent() { + buildRule.executeTarget("testFileNotPresent"); + String result = buildRule.getProject().getProperty("location.ant-jar"); + assertNull("Property set to wrong value.", result); + } + + @Test + public void testFilePresent() { + buildRule.executeTarget("testFilePresent"); + String result = buildRule.getProject().getProperty("location.ant-jar"); + assertNotNull("Property not set.", result); + assertTrue("Wrong file found.", result.endsWith("ant.jar")); + } +} ++ +
If we run this test class all test cases (except testFileNotPresent) fail. Now we +can implement our task, so that these test cases will pass.
+ ++ protected void validate() { + if (file==null) throw new BuildException("file not set"); + if (location==null) throw new BuildException("location not set"); + if (filesets.size()<1) throw new BuildException("fileset not set"); + } + + public void execute() { + validate(); // 1 + String foundLocation = null; + for(Iterator itFSets = filesets.iterator(); itFSets.hasNext(); ) { // 2 + FileSet fs = (FileSet)itFSets.next(); + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); // 3 + String[] includedFiles = ds.getIncludedFiles(); + for(int i=0; i<includedFiles.length; i++) { + String filename = includedFiles[i].replace('\\','/'); // 4 + filename = filename.substring(filename.lastIndexOf("/")+1); + if (foundLocation==null && file.equals(filename)) { + File base = ds.getBasedir(); // 5 + File found = new File(base, includedFiles[i]); + foundLocation = found.getAbsolutePath(); + } + } + } + if (foundLocation!=null) // 6 + getProject().setNewProperty(location, foundLocation); + } ++ +
On //1 we check the prerequisites for our task. Doing that in a validate-method +is a common way, because we separate the prerequisites from the real work. On //2 we iterate +over all nested filesets. If we don't want to handle multiple filesets, the addFileset() +method has to reject the further calls. We can get the result of a fileset via its DirectoryScanner +like done in //3. After that we create a platform independent String representation of +the file path (//4, can be done in other ways of course). We have to do the replace(), +because we work with a simple string comparison. Ant itself is platform independent and can +therefore run on filesystems with slash (/, e.g. Linux) or backslash (\, e.g. Windows) as +path separator. Therefore we have to unify that. If we found our file we create an absolute +path representation on //5, so that we can use that information without knowing the basedir. +(This is very important on use with multiple filesets, because they can have different basedirs +and the return value of the directory scanner is relative to its basedir.) Finally we store the +location of the file as property, if we had found one (//6).
+ +Ok, much more easier in this simple case would be to add the file as additional +include element to all filesets. But I wanted to show how to handle complex situations +without being complex :-)
+ +The test case uses the ant property ant.home as reference. This property is set by the
+Launcher class which starts ant. We can use that property in our buildfiles as a
+build-in property [3]. But if we create a new ant
+environment we have to set that value for our own. And we use the <junit>
task in fork-mode.
+Therefore we have do modify our buildfile:
+
+ <target name="junit" description="Runs the unit tests" depends="jar"> + <delete dir="${junit.out.dir.xml}"/> + <mkdir dir="${junit.out.dir.xml}"/> + <junit printsummary="yes" haltonfailure="no"> + <classpath refid="classpath.test"/> + <sysproperty key="ant.home" value="${ant.home}"/> + <formatter type="xml"/> + <batchtest fork="yes" todir="${junit.out.dir.xml}"> + <fileset dir="${src.dir}" includes="**/*Test.java"/> + </batchtest> + </junit> + </target> ++ + +
A task providing support for filesets is a very comfortable one. But there is another
+possibility of bundling files: the <path>
. Fileset are easy if the files are all under
+a common base directory. But if this is not the case you have a problem. Another disadvantage
+is its speed: if you have only a few files in a huge directory structure, why not use a
+<filelist>
instead? <path>
s combines these datatypes in that way that a path contains
+other paths, filesets, dirsets and filelists. This is why
+Ant-Contribs [4] <foreach>
task is modified to support paths instead of filesets. So we want that,
+too.
Changing from fileset to path support is very easy:
++Change java code from: + private Vector filesets = new Vector(); + public void addFileset(FileSet fileset) { + filesets.add(fileset); + } +to: + private Vector paths = new Vector(); *1 + public void addPath(Path path) { *2 + paths.add(path); + } +and build file from: + <find file="ant.jar" location="location.ant-jar"> + <fileset dir="${ant.home}" includes="**/*.jar"/> + </find> +to: + <find file="ant.jar" location="location.ant-jar"> + <path> *3 + <fileset dir="${ant.home}" includes="**/*.jar"/> + </path> + </find> ++
On *1 we rename only the vector. It�s just for better reading the source. On *2 +we have to provide the right method: an addName(Type t). Therefore replace the +fileset with path here. Finally we have to modify our buildfile on *3 because our task +doesn�t support nested filesets any longer. So we wrap the fileset inside a path.
+ +And now we modify the testcase. Oh, not very much to do :-) Renaming the testMissingFileset() +(not really a must-be but better it�s named like the think it does) and update the +expected-String in that method (now a path not set message is expected). The more complex +test cases base on the buildscript. So the targets testFileNotPresent and testFilePresent have to be +modified in the manner described above.
+ +The test are finished. Now we have to adapt the task implementation. The easiest modification is +in the validate() method where we change le last line to if (paths.size()<1) throw new +BuildException("path not set");. In the execute() method we have a little more work. +... mmmh ... in reality it's lesser work, because the Path class does the whole DirectoryScanner-handling +and creating-absolute-paths stuff for us. So the execute method is just:
+ ++ public void execute() { + validate(); + String foundLocation = null; + for(Iterator itPaths = paths.iterator(); itPaths.hasNext(); ) { + Path path = (Path)itPaths.next(); // 1 + String[] includedFiles = path.list(); // 2 + for(int i=0; i<includedFiles.length; i++) { + String filename = includedFiles[i].replace('\\','/'); + filename = filename.substring(filename.lastIndexOf("/")+1); + if (foundLocation==null && file.equals(filename)) { + foundLocation = includedFiles[i]; // 3 + } + } + } + if (foundLocation!=null) + getProject().setNewProperty(location, foundLocation); + } ++ +
Of course we have to do the typecase to Path on //1. On //2 and //3 +we see that the Path class does the work for us: no DirectoryScanner (was at 2) and no +creating of the absolute path (was at 3).
+ + + +So far so good. But could a file be on more than one place in the path? - Of course.
+And would it be good to get all of them? - It depends on ...
+ +
In this section we will extend that task to support returning a list of all files.
+Lists as property values are not supported by Ant natively. So we have to see how other
+tasks use lists. The most famous task using lists is Ant-Contribs <foreach>
. All list
+elements are concatenated and separated with a customizable separator (default ',').
So we do the following:
+ ++ <find ... delimiter=""/> ... </find> ++ +
If the delimiter is set we will return all found files as list with that delimiter.
+ +Therefore we have to
So we add as testcase:
++in the buildfile: + <target name="test.init"> + <mkdir dir="test1/dir11/dir111"/> *1 + <mkdir dir="test1/dir11/dir112"/> + ... + <touch file="test1/dir11/dir111/test"/> + <touch file="test1/dir11/dir111/not"/> + ... + <touch file="test1/dir13/dir131/not2"/> + <touch file="test1/dir13/dir132/test"/> + <touch file="test1/dir13/dir132/not"/> + <touch file="test1/dir13/dir132/not2"/> + <mkdir dir="test2"/> + <copy todir="test2"> *2 + <fileset dir="test1"/> + </copy> + </target> + + <target name="testMultipleFiles" depends="use.init,test.init"> *3 + <find file="test" location="location.test" delimiter=";"> + <path> + <fileset dir="test1"/> + <fileset dir="test2"/> + </path> + </find> + <delete> *4 + <fileset dir="test1"/> + <fileset dir="test2"/> + </delete> + </target> + +in the test class: + public void testMultipleFiles() { + executeTarget("testMultipleFiles"); + String result = getProject().getProperty("location.test"); + assertNotNull("Property not set.", result); + assertTrue("Only one file found.", result.indexOf(";") > -1); + } ++ +
Now we need a directory structure where we CAN find files with the same +name in different directories. Because we can't sure to have one we create +one on *1 and *2. And of course we clean up that on *4. The creation +can be done inside our test target or in a separate one, which will be better +for reuse later (*3). + +
The task implementation is modified as followed:
+ ++ private Vector foundFiles = new Vector(); + ... + private String delimiter = null; + ... + public void setDelimiter(String delim) { + delimiter = delim; + } + ... + public void execute() { + validate(); + // find all files + for(Iterator itPaths = paths.iterator(); itPaths.hasNext(); ) { + Path path = (Path)itPaths.next(); + String[] includedFiles = path.list(); + for(int i=0; i<includedFiles.length; i++) { + String filename = includedFiles[i].replace('\\','/'); + filename = filename.substring(filename.lastIndexOf("/")+1); + if (file.equals(filename) && !foundFiles.contains(includedFiles[i])) { // 1 + foundFiles.add(includedFiles[i]); + } + } + } + + // create the return value (list/single) + String rv = null; + if (foundFiles.size() > 0) { // 2 + if (delimiter==null) { + // only the first + rv = (String)foundFiles.elementAt(0); + } else { + // create list + StringBuffer list = new StringBuffer(); + for(Iterator it=foundFiles.iterator(); it.hasNext(); ) { // 3 + list.append(it.next()); + if (it.hasNext()) list.append(delimiter); // 4 + } + rv = list.toString(); + } + } + + // create the property + if (rv!=null) + getProject().setNewProperty(location, rv); + } ++ +
The algorithm does: finding all files, creating the return value depending on the users +wish, returning the value as property. On //1 we eliminates the duplicates. //2 +ensures that we create the return value only if we have found one file. On //3 we +iterate over all found files and //4 ensures that the last entry has no trailing +delimiter.
+ +Ok, first searching for all files and then returning only the first one ... You can +tune the performance of your own :-)
+ + +A task is useless if the only who is able to code the buildfile is the task developer +(and he only the next few weeks :-). So documentation is also very important. In which +form you do that depends on your favourite. But inside Ant there is a common format and +it has advantages if you use that: all task users know that form, this form is requested if +you decide to contribute your task. So we will doc our task in that form.
+ +If you have a look at the manual page of the Java task [5] + you will see that it:
+<html> + +<head> +<meta http-equiv="Content-Language" content="en-us"> +<title>Taskname Task</title> +</head> + +<body> + +<h2><a name="taskname">Taskname</a></h2> +<h3>Description</h3> +<p> Describe the task.</p> + +<h3>Parameters</h3> +<table border="1" cellpadding="2" cellspacing="0"> + <tr> + <td valign="top"><b>Attribute</b></td> + <td valign="top"><b>Description</b></td> + <td align="center" valign="top"><b>Required</b></td> + </tr> + + do this html row for each attribute (including inherited attributes) + <tr> + <td valign="top">classname</td> + <td valign="top">the Java class to execute.</td> + <td align="center" valign="top">Either jar or classname</td> + </tr> + +</table> + +<h3>Parameters specified as nested elements</h3> + +Describe each nested element (including inherited) +<h4>your nested element</h4> +<p>description</p> +<p><em>since Ant 1.6</em>.</p> + +<h3>Examples</h3> +<pre> + A code sample; don't forget to escape the < of the tags with < +</pre> +What should that example do? + +</body> +</html> ++ +
Here is an example documentation page for our task:
++<html> + +<head> +<meta http-equiv="Content-Language" content="en-us"> +<title>Find Task</title> +</head> + +<body> + +<h2><a name="find">Find</a></h2> +<h3>Description</h3> +<p>Searchs in a given path for a file and returns the absolute to it as property. +If delimiter is set this task returns all found locations.</p> + +<h3>Parameters</h3> +<table border="1" cellpadding="2" cellspacing="0"> + <tr> + <td valign="top"><b>Attribute</b></td> + <td valign="top"><b>Description</b></td> + <td align="center" valign="top"><b>Required</b></td> + </tr> + <tr> + <td valign="top">file</td> + <td valign="top">The name of the file to search.</td> + <td align="center" valign="top">yes</td> + </tr> + <tr> + <td valign="top">location</td> + <td valign="top">The name of the property where to store the location</td> + <td align="center" valign="top">yes</td> + </tr> + <tr> + <td valign="top">delimiter</td> + <td valign="top">A delimiter to use when returning the list</td> + <td align="center" valign="top">only if the list is required</td> + </tr> +</table> + +<h3>Parameters specified as nested elements</h3> + +<h4>path</h4> +<p>The path where to search the file.</p> + +<h3>Examples</h3> +<pre> + <find file="ant.jar" location="loc"> + <path> + <fileset dir="${ant.home}"/> + <path> + </find> +</pre> +Searches in Ants home directory for a file <i>ant.jar</i> and stores its location in +property <i>loc</i> (should be ANT_HOME/bin/ant.jar). + +<pre> + <find file="ant.jar" location="loc" delimiter=";"> + <path> + <fileset dir="C:/"/> + <path> + </find> + <echo>ant.jar found in: ${loc}</echo> +</pre> +Searches in Windows C: drive for all <i>ant.jar</i> and stores their locations in +property <i>loc</i> delimited with <i>';'</i>. (should need a long time :-) +After that it prints out the result (e.g. C:/ant-1.5.4/bin/ant.jar;C:/ant-1.6/bin/ant.jar). + +</body> +</html> ++ + +
Now we will check the "Checklist before submitting a new task" described in that guideline. +
This task does not depend on any external library. Therefore we can use this as +a core task. This task contains only one class. So we can use the standard package +for core tasks: org.apache.tools.ant.taskdefs. Implementations are in the +directory src/main, tests in src/testcases and buildfiles for +tests in src/etc/testcases.
+ +Now we integrate our work into Ants distribution. So first we do an update of our +cvs tree. If not done yet, you have to checkout the ant module from Apaches cvs server +as described in Access the Source Tree (AnonCVS) +[7] (password is anoncvs):
+cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic login //1 +cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout ant //2 ++If you have a local copy of Ants sources just do an update +
+cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic login +cd ant //3 +cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic update //4 ++ +
We use the -d flag on //1 to specify the cvs directory. You can +specify the environment variable CVSROOT with that value and after that you haven�t +to use that flag any more. On //2 we get the whole cvs tree of ant. (Sorry, +but that uses a lot of time ... 10 up to 30 minutes are not unusual ... but this has +to be done only once :-). A cvs update doesn't use a modulename but you have to be +inside the directory. Therefore we go into that on //3 and do the update +on //4.
+ +Now we will build our Ant distribution and do a test. So we can see if there +are any tests failing on our machine. (We can ignore these failing tests on later +steps; windows syntax used here- translate to xNIX if needed): +
+ANTHOME> build // 1 +ANTHOME> set ANT_HOME=%CD%\dist // 2 +ANTHOME> ant test -Dtest.haltonfailure=false // 3 ++ +First we have to build our Ant distribution (//1). On //2 we set the ANT_HOME +environment variable to the directory where the new created distribution is stored +(%CD% is expanded to the current directory on Windows 2000 and XP, on 9x and NT +write it out). On //3 we let Ant do all the tests (which enforced a compile +of all tests) without stopping on first failure. + +
Next we apply our work onto Ants sources. Because we haven't modified any, this is +a relative simple step. (Because I have a local copy of Ant and usually contribute my +work, I work on the local copy just from the beginning. The advantage: this step isn't +necessary and saves a lot of work if you modify existing source :-). + +
+ANTHOME> build +ANTHOME> ant run-single-test // 1 + -Dtestcase=org.apache.tools.ant.taskdefs.FindTest // 2 + -Dtest.haltonfailure=false ++Because we only want to test our new class, we use the target for single tests, specify +the test to use and configure not to halt on the first failure - we want to see all +failures of our own test (//1 + 2). + +
And ... oh, all tests fail: Ant could not find the task or a class this task relies upon.
+ +Ok: in the earlier steps we told Ant to use the Find class for the <find>
task (remember the
+<taskdef>
statement in the "use.init" target). But now we want to introduce that task as
+a core task. And nobody wants to taskdef the javac, echo, ... So what to do? The answer is the
+src/main/.../taskdefs/default.properties. Here is the mapping between taskname and implementing
+class done. So we add a find=org.apache.tools.ant.taskdefs.Find as the last core
+task (just before the # optional tasks line). Now a second try:
+
+ANTHOME> build // 1 +ANTHOME> ant run-single-test + -Dtestcase=org.apache.tools.ant.taskdefs.FindTest + -Dtest.haltonfailure=false ++We have to rebuild (//1) Ant because the test look in the %ANT_HOME%\lib\ant.jar +(more precise: on the classpath) for the properties file. And we have only modified it in the +source path. So we have to rebuild that jar. But now all tests pass and we check whether our class +breaks some other tests. +
+ANTHOME> ant test -Dtest.haltonfailure=false ++Because there are a lot of tests this step requires a little bit of time. So use the run-single-test +during development and do the test only at the end (maybe sometimes during development too). +We use the -Dtest.haltonfailure=false here because there could be other tests fail and we have +to look into them. + +
This test run should show us two things: our test will run and the number of failing tests +is the same as directly after the cvs update (without our modifications).
+ + + +Simply copy the license text from one the other source from the Ant source tree.
+ + +Until version 1.5 Ant must be able to run on a JDK 1.1. With version 1.6 this is not a +requisite any more. But JDK 1.2 is a must-to-work-with. So we have to test that. You can download older +JDKs from Oracle [8].
+ +Clean the ANT_HOME variable, delete the build, bootstrap and dist directory +and point JAVA_HOME to the JDK 1.2 home directory. Then do the build, set ANT_HOME +and run ant test (like above).
+ +Our test should pass.
+ + + +There are many things we have to ensure. Indentation with 4 spaces, blanks here and there, ... +(all described in the Ant Task Guidelines [6] which +includes the Sun code style +[9]). Because there are so many things we would be happy to have a tool for do the checks. +There is one: checkstyle. Checkstyle is available at +Sourceforge [10] and Ant provides with the check.xml a buildfile which will do the job +for us.
+ +Download it and put the checkstyle-*-all.jar into your %USERPROFILE%\.ant\lib directory. +All jar's stored there are available to Ant so you haven't to add it to you %ANT_HOME%\lib +directory (this feature was added with Ant 1.6).
+ +So we will run the tests with +
+ANTHOME> ant -f check.xml checkstyle htmlreport ++I prefer the HTML report because there are lots of messages and we can navigate faster. +Open the ANTHOME/build/reports/checkstyle/html/index.html and navigate to the Find.java. Now we +see that there are some errors: missing whitespaces, unused imports, missing javadocs. So we have +to do that. + +
Hint: start at the buttom of the file so the line numbers in the report will keep +up to date and you will find the next error place much more easier without redoing the checkstyle.
+ +After cleaning up the code according to the messages we delete the reports directory and +do a second checkstyle run. Now our task isn't listed. That's fine :-)
+ + + + + + +Finally we publish that archive. As described in the +Ant Task Guidelines [7] we can post it on the developer mailinglist or we create a BugZilla +entry. For both we need some information:
+ +subject | +short description | +Task for finding files in a path | +
---|---|---|
body | +more details about the path | +This new task looks inside a nested <path/> for occurrences of a file and stores
+ all locations as a property. See the included manual for details. |
+
attachments | +all files needed to apply the path | +Archive containing a patch with the new and modified resources | +
Sending an email with these information is very easy and I think I haven't to show that. +The other way - BugZilla - is slightly more difficult. But it has the advantage that entries +will not be forgotten (once per week a report is generated). So I will show this way.
+ +You must have a BugZilla account for that. So open the +BugZilla Main Page [11] and follow the link +Open a new Bugzilla account [12] +and the steps described there if you haven't one.
+ +