Section 7. Useful tasks and techniques
Useful tasks and techniques introduction
Before looking at custom tasks, we’ll first cover some useful functionality that we haven’t already encountered. There is a great deal of functionality that comes standard with Ant, so we can only pick out a few of the most useful pieces here. Pattern matching and file selectors are powerful mechanisms that greatly enhance the capabilities of some of the tasks we’ve already seen, and chaining builds together and working with CVS repositories are two areas that have been found to be of great practical use.
When we looked at filesystem tasks earlier, we used only individually named files and directories. However, it is often useful to perform those operations on a group of files at once — on all files in a given directory that end with . java, for instance. Just as the equivalent DOS and UNIX commands provide this sort of functionality, so does Ant. This is done using wildcard characters: *, which matches zero or more characters, and ? , which matches exactly one character. The pattern to match all files ending with .java would therefore be simply *.java.
Matching is also performed on directories. For example, the pattern src*/*.java would match all Java files in any directories with a prefix of src. There is one additional pattern construct: **, which matches any number of directories. For example, the pattern **/*.java would match all Java files under the current directory structure.
You can make use of patterns with filesystem tasks in a fairly consistent way, such as with a nested fileset element. Previously, we copied a single file with this task:
If we wanted to use a pattern instead, we could replace the file attribute with a fileset element, like this:
The fileset would by default consist of all the files under the specified src directory, so to select just the Java files, we use the include element with a pattern. In a similar way, we could add an exclude element with another pattern, which would potentially remove matches from the include specification. We could even specify multiple include and exclude elements; this would result in a set of files and directories consisting of all the matches from the include patterns joined together, with all of the matches from the exclude elements removed.
Note that there is one feature of filesets that is usually very helpful, but occasionally leads to confusion for those who are not aware of it. This feature is known as default excludes: a built-in list of patterns that are automatically excluded from the contents of filesets. The list includes entries to match directories called CVS, and files ending with the ~ character, which may be backup files. You often don’t want to include these sorts of files and directories in filesystem operations, so that is the default behavior. However, if you really want to select all files and directories without exception, you can set the defaultexcludes attribute of your fileset to no.
As we’ve seen, a fileset is used to specify a group of files, and the contents of this group can be defined using include and exclude patterns. You can also use special elements called selectors in conjunction with the include and exclude to select files. Here is a list of the core selectors available with Ant:
size: This is used to select files based on their size in bytes (unless the units attribute is used to specify a different unit). The when attribute is used to set the nature of the comparison — less, more, or equal — and the value attribute defines the target size against which the size of each file is compared.
contains: Only files containing the given text string (specified by the text attribute) match this selector. By default, the search is case sensitive; adding casesensitive=”no” changes this.
filename: The name attribute specifies the pattern to match filenames against. This is essentially the same as the include element, and the same as the exclude element when negate=”yes” is specified.
present: Selects those files from the current directory structure that have the same name and relative directory structure as those in a specified targetdir directory.
depend: This selector has the same effect as the present selector, except that the matching files are restricted to those that have been modified more recently than the corresponding files in the targetdir location.
date: This selects files based on the date on which they were last modified. The when attribute specifies whether the comparison is before, after, or equal, and the datetime attribute specifies the date and time to compare against, given in a fixed format of MM/DD/YYYY HH:MM AM_or_PM. Note that on Windows platforms there is a built-in margin of 2 seconds either way to allow for inaccuracies in the underlying filesystem — this may cause more files to match than otherwise expected. The amount of leeway allowed can be changed with the granularity attribute (specified in milliseconds).
depth: This selector looks at the number of levels in the directory structure of each file. Attributes for min and/or max are used to select files with a desired number of directory levels.
Selectors can also be combined together by nesting one or more selectors inside a selector container. The most common selector container, and, selects only those files that are selected by all of the selectors it encloses. Other selector containers include or, not, none, and majority.
Here is an example of a fileset that selects only those files that are larger than 512 bytes and also contain the string “hello”:
Chaining builds together
There are two different approaches to building large projects. One is to have a single monolithic build file that does everything; the other is to split the build up into a number of smaller pieces by having high-level build files that call out to other build files to perform specific tasks.
It is easy to call one Ant build from another using the ant task. In a simple case, you can specify just the build file to use, using the antfile attribute, and Ant will build the default target in that build file. For example:
Any properties defined in the parent build file will by default be passed into the sub-build, although this can be avoided by specifying inheritAll=”false”. It is also possible to pass in explicit properties by using a nested property element — these still apply even if inheritAll is set to false. This facility is good for passing in parameters to sub-builds.
Let’s consider an example. Here is a build file that we wish to call:
(We haven’t come across the echo task before — it simply outputs the given message.)
And here is a second build file that calls out to the first, passing in the message property:
The output from running the second build file is as follows:
[echo] Message=Hello from parent build
Total time: 0 seconds
Working with CVS repositories
CVS is an acronym for concurrent versions system. It is a source code control system that is designed to keep track of changes made by a number of different developers. It is extremely popular, particularly with open source projects. Ant provides close integration with CVS. This is very useful for automated build environments, as a single build file can extract one or more modules from the source code repository, build the project, and even generate patch files based on the changes that have been made since the previous build.
Note that in order to make use of the cvs task in Ant, you need to have the cvs command installed on your machine and available from the command line. This command is included in most Linux distributions; it is also available for Windows in several forms, — as part of the invaluable Cygwin environment, for instance. (See Resources for more on Cygwin.)
Here is an example build file that extracts a module from a CVS repository:
The main attribute to the cvs task is cvsRoot, which is the complete reference to the CVS repository, including connection method and user details. The format of this parameter is:
In the above example, we connect to the central repository for the Eclipse project as an anonymous user. The other attributes then specify the module or package we wish to extract and the destination for the extracted files. Extraction is the default operation for the CVS task; other operations can be specified using the command attribute.