Paths, Filesets & Patterns
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Apache Ant 1.10.18 and JDK 26. Every example was run on 2026-10-16 in a scratch copy of the
tidepool source tree (the three modules tidepool-model, tidepool-engine and tidepool-cli, the data file
data/stations.csv and a lib directory with commons-csv, commons-io, commons-codec and picocli). The build
files are run from the root of that tree. Paths in the console output were shortened to /work/tidepool, and the
Buildfile:, BUILD SUCCESSFUL and Total time: lines are left out of the listings unless they matter. Maven 3.10.0
and Gradle 9.8.1 are named where they do the same job.
Almost everything an Ant build does is "take these files and do something with them", so the way Ant names a set of
files is the most reused part of the language. This page covers the whole family: the path-like structures that carry
class paths, the patterns that select files, and the resource collections built from them (fileset, dirset,
filelist, zipfileset, tarfileset, multirootfileset and classfileset). The selectors that refine a fileset are on
Selectors, the other kinds of resource collection are on
Resource Collections, and the filters and
mappers that transform the selected files are on
Filters & Mappers. The way properties such as
${basedir} are resolved is on Properties, and where
id and refid fit in a build file is on
Build File Structure.
The family at a glance
Ant has no single "file set" class. It has a handful of types that all answer the question "which files?" in a different
way, and a handful of tasks that consume the answer. A path-like structure is an ordered list of file system locations
(a PATH, a CLASSPATH); a fileset is a group of files found by scanning a directory tree and matching patterns; a
dirset is the same for directories; a filelist names files explicitly, whether they exist or not.
Two ideas run through the whole page. First, a set of files is described once, with an id, and used many times with
a refid; the description is evaluated again each time it is used, so the result always reflects the files that exist
at that moment. Second, patterns are relative to a base directory and are matched one directory level at a time, which
explains almost every surprise a newcomer meets.
Path-like structures
A path-like structure is Ant’s value for "a list of locations separated by the platform’s path separator": the
CLASSPATH of javac and java, the module path, the PATH an exec task needs, the list of source directories. The
manual’s phrase is that wherever a path-like value is needed, a nested element can be used. Three elements take the
same content:
-
<path>is a stand-alone type. It can sit under<project>or<target>, carries anid, and can be referenced. -
<classpath>(and its siblings<bootclasspath>,<modulepath>,<sourcepath>,<srcdir>and so on) is the same structure nested in a task that needs one. -
<pathelement>is one entry of either of them.
pathelement, location and path
<pathelement> has two mutually exclusive attributes. location names a single file or directory, relative to the
project’s base directory or absolute. path takes a list of locations separated by : or ;, and Ant converts the
separators to the ones of the platform it runs on. The manual advises using path only to splice in a predefined path
(such as the value of a property that is itself a class path) and preferring several location elements in every other
case, which is also what keeps a build file free of platform separators.
Beyond pathelement, a path may contain any resource collection that consists of file-type resources:
| Nested element | What it adds to the path |
|---|---|
|
one file or directory (since Ant 1.8.2 the last name of a |
|
a list of locations; both separators are accepted |
|
the matching files or directories, in an order that Ant does not define for filesets, dirsets and |
|
every entry of another path, which is how a test class path is built on top of a compile class path |
The attributes of a stand-alone <path> are small:
| Attribute | Meaning |
|---|---|
|
the name under which the path is registered so that |
|
shortcuts for a single nested |
|
makes this path a reference to a path defined elsewhere; no other attribute or nested element may be given |
|
since Ant 1.8.0: when |
The cache attribute is worth setting on a path that contains a large fileset and is used by many tasks, such as a
test class path, because the default re-scans the file system for every use. It is wrong for a path that must see files
created during the build, such as the output directory of an earlier target, for the same reason.
Building paths and looking at them
The build file below defines the class paths of the tidepool build and prints them. ${toString:ref} is a property
expansion that turns a path (or any resource collection) reference into a string with the platform separator;
pathconvert is described in the next section. The file is paths.xml:
<project name="tidepool-paths" default="show" basedir=".">
<property name="engine.classes" location="tidepool-engine/build/classes"/>
<path id="compile.path">
<pathelement location="${engine.classes}"/>
<fileset dir="lib" includes="*.jar"/>
</path>
<path id="test.path" cache="true">
<path refid="compile.path"/>
<pathelement path="tidepool-model/build/classes:tidepool-model/build/test-classes"/>
</path>
<path id="shortcut.path" path="a.jar;b.jar:c.jar"/>
<target name="show">
<echo>compile.path = ${toString:compile.path}</echo>
<echo>test.path = ${toString:test.path}</echo>
<echo>shortcut = ${toString:shortcut.path}</echo>
<pathconvert property="win.path" refid="test.path" targetos="windows">
<map from="${basedir}" to="C:\work\tidepool"/>
</pathconvert>
<echo>windows = ${win.path}</echo>
<pathconvert property="jar.list" pathsep=", ">
<fileset dir="lib" includes="*.jar"/>
<mapper type="flatten"/>
</pathconvert>
<echo>jars = ${jar.list}</echo>
</target>
</project>
$ ant -f paths.xml
show:
[echo] compile.path = /work/tidepool/tidepool-engine/build/classes:/work/tidepool/lib/commons-csv-1.14.1.jar:/work/tidepool/lib/picocli-4.7.7.jar
[echo] test.path = /work/tidepool/tidepool-engine/build/classes:/work/tidepool/lib/commons-csv-1.14.1.jar:/work/tidepool/lib/picocli-4.7.7.jar:/work/tidepool/tidepool-model/build/classes:/work/tidepool/tidepool-model/build/test-classes
[echo] shortcut = /work/tidepool/a.jar:/work/tidepool/b.jar:/work/tidepool/c.jar
[echo] windows = C:\work\tidepool\tidepool-engine\build\classes;C:\work\tidepool\lib\commons-csv-1.14.1.jar;C:\work\tidepool\lib\picocli-4.7.7.jar;C:\work\tidepool\tidepool-model\build\classes;C:\work\tidepool\tidepool-model\build\test-classes
[echo] jars = commons-csv-1.14.1.jar, picocli-4.7.7.jar
Four things can be read from the output. Every relative location was resolved against basedir, so the path holds
absolute names. The engine.classes directory is in the path although it does not exist yet: a pathelement is not
checked, which is why a typo in a class path path shows up later as "package does not exist" and not as a path error.
The mixed ; and : separators of shortcut.path came out as the platform separator. The lib directory was
expanded to the jar files it holds on the day the path was used, not on the day it was defined.
Source: Apache Ant manual: Path Shortcut
Wildcards in a pathelement
The wildcard form of location exists for the Java 6 lib/* class path syntax. Ant does not look at the file system for
it. The path below keeps the star as written, and only a JVM that understands the syntax expands it:
<path id="w"><pathelement location="lib/*"/></path>
<echo>${toString:w}</echo>
[echo] /work/tidepool/lib/*
A <fileset dir="lib" includes="*.jar"/> inside the path is the portable choice, because the list is expanded by Ant
and works for tasks that do not hand the path to a JVM as a class path (for example copy).
pathconvert
<pathconvert> turns a path, or any resource collection, into a string, and either stores it in a property, writes it
to a resource or logs it. Its two jobs are producing a platform-specific path for another program (a script, a
manifest, a shell command) and producing a list with a separator of your choice.
| Attribute | Meaning | Required |
|---|---|---|
|
what to convert: a reference to a |
yes, unless a nested resource collection is given |
|
the property that receives the result |
no; the result is logged if neither |
|
since Ant 1.10.13: a resource (a file by default) that receives the result |
no |
|
|
no |
|
the separator between entries; defaults to the one of the running JVM |
no |
|
the separator between directory names; defaults to the one of the running JVM |
no |
|
whether to set the property when the result is empty; default |
no |
|
since Ant 1.8: keep duplicate resources; default |
no |
The nested elements are <map from="..." to="..."/> (replace a leading prefix; the first matching map wins, so list
the longest prefix first; the match is case-insensitive on Windows), a nested <mapper> (since Ant 1.6.2) for any other
file-name transformation, and the resource collections themselves when refid is not used. The Windows result in the
output above came from targetos="windows" and one map; the second pathconvert used a flatten mapper to keep the
file names only and a comma as separator.
An empty result is where the defaults bite. With setonempty="true" (the default) the property exists and is empty, so
a later unless="..." test sees it as set; with setonempty="false" the property is not defined:
<pathconvert property="p" setonempty="false"><fileset dir="data" includes="*.xml"/></pathconvert>
<echo>p=${p}</echo>
<pathconvert property="q" setonempty="true"><fileset dir="data" includes="*.xml"/></pathconvert>
<echo>q=[${q}]</echo>
[echo] p=${p}
[echo] q=[]
The unexpanded ${p} in the first line is how Ant shows a property that does not exist. Calling pathconvert with
nothing to convert is an error:
BUILD FAILED
/work/tidepool/misc.xml:19: You must specify a path to convert
Source: Apache Ant manual: PathConvert
manifestclasspath
A runnable JAR that needs libraries has them listed in its manifest’s Class-Path attribute, as file names relative to
the JAR. The <manifestclasspath> task (since Ant 1.7) computes that string from an Ant path, so that the manifest never
contains an absolute name of the machine that built it. It is also the standard workaround for the Windows limit on the
length of a command line, because a launcher JAR with a long Class-Path replaces a class path of hundreds of entries.
| Attribute or element | Meaning | Required |
|---|---|---|
|
the property to set; it must not already be set |
yes |
|
the JAR that will carry the manifest; it need not exist, but its parent directory must |
yes |
|
the maximum number of |
no |
nested |
the path to convert; it must not be empty |
yes |
The build.xml below compiles all three modules with one javac (whose <src> is a path built from a dirset),
copies the libraries next to the JAR, computes the Class-Path, builds tidepool-cli.jar and runs it:
<project name="tidepool" default="run" basedir=".">
<property name="build.dir" location="build"/>
<property name="classes.dir" location="${build.dir}/classes"/>
<property name="dist.dir" location="${build.dir}/dist"/>
<path id="compile.path">
<fileset dir="lib" includes="*.jar"/>
</path>
<path id="main.sources">
<dirset dir=".">
<include name="*/src/main/java"/>
</dirset>
</path>
<target name="compile">
<mkdir dir="${classes.dir}"/>
<javac destdir="${classes.dir}" release="21" includeantruntime="false">
<src refid="main.sources"/>
<classpath refid="compile.path"/>
</javac>
</target>
<target name="jar" depends="compile">
<mkdir dir="${dist.dir}/lib"/>
<copy todir="${dist.dir}/lib" flatten="true">
<path refid="compile.path"/>
</copy>
<manifestclasspath property="jar.classpath" jarfile="${dist.dir}/tidepool-cli.jar">
<classpath>
<fileset dir="${dist.dir}/lib" includes="*.jar"/>
</classpath>
</manifestclasspath>
<echo>Class-Path value: ${jar.classpath}</echo>
<jar destfile="${dist.dir}/tidepool-cli.jar" basedir="${classes.dir}">
<manifest>
<attribute name="Main-Class" value="io.tidepool.cli.Main"/>
<attribute name="Class-Path" value="${jar.classpath}"/>
</manifest>
</jar>
</target>
<target name="run" depends="jar">
<java jar="${dist.dir}/tidepool-cli.jar" fork="true" failonerror="true">
<arg value="data/stations.csv"/>
<arg value="BOS"/>
</java>
</target>
</project>
$ ant
compile:
[mkdir] Created dir: /work/tidepool/build/classes
[javac] Compiling 7 source files to /work/tidepool/build/classes
jar:
[mkdir] Created dir: /work/tidepool/build/dist/lib
[copy] Copying 4 files to /work/tidepool/build/dist/lib
[echo] Class-Path value: lib/commons-codec-1.19.0.jar lib/commons-csv-1.14.1.jar lib/commons-io-2.20.0.jar lib/picocli-4.7.7.jar
[jar] Building jar: /work/tidepool/build/dist/tidepool-cli.jar
run:
[java] Boston: -0,662 m
(the decimal comma is the locale of the machine that ran the example) and the manifest the jar task wrote, read with unzip -p build/dist/tidepool-cli.jar META-INF/MANIFEST.MF, shows the
property re-wrapped at 72 characters as the JAR specification requires:
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.10.18
Created-By: 26+35-2893 (Oracle Corporation)
Main-Class: io.tidepool.cli.Main
Class-Path: lib/commons-codec-1.19.0.jar lib/commons-csv-1.14.1.jar li
b/commons-io-2.20.0.jar lib/picocli-4.7.7.jar
Two failure modes are worth knowing. The first is a library that is on the compile path but was not copied next to the
JAR: the Class-Path is built from the files that exist, so the missing JAR is simply not listed, and the program fails
at run time. The first run of this example had commons-io missing from lib (it is a dependency of commons-csv), and
Class-Path listed only two libraries:
[echo] Class-Path value: lib/commons-csv-1.14.1.jar lib/picocli-4.7.7.jar
...
[java] Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/io/output/AppendableOutputStream
[java] at io.tidepool.engine.StationReader.read(Unknown Source)
The JVM ignores a Class-Path entry whose file is missing without any message, so this failure is the only sign. The
second failure mode is an entry that is more than maxParentLevels directories away from the JAR. With a limit of 1,
the library directory two levels up is rejected:
BUILD FAILED
/work/tidepool/mcp.xml:3: No suitable relative path from /work/tidepool/build/dist to /work/tidepool/lib/picocli-4.7.7.jar
The relative layout that manifestclasspath assumes must be the layout of the deployed files, which is why the example
copies the libraries into build/dist/lib first and computes the value from that directory. Maven does the same job with
the addClasspath manifest setting of the maven-jar-plugin and Gradle with a Class-Path attribute computed from
runtimeClasspath; both are on the Maven packaging page
and the Gradle Java projects page. Packing the
libraries inside one JAR is the subject of
Packaging Archives.
The system class path: includeantruntime and build.sysclasspath
Every class path Ant hands to a compiler or a forked JVM can be combined with the system class path, the one in
effect when Ant itself runs. The build.sysclasspath property chooses how:
| Value | Meaning |
|---|---|
|
only the system class path is used; the class paths of the build file are ignored (the person who runs the build knows the environment better than the author) |
|
the system class path is ignored; this is the reverse, and the mode the manual recommends for portable scripts |
|
the build file’s class path first, the system class path appended |
|
the system class path first, then the build file’s class path |
The default differs from task to task, and the property also affects the bootclasspath settings since Ant 1.7 (when
the property is unset it defaults to ignore there). In day-to-day builds the property is rarely set. What matters more
is its consequence for javac: the includeantruntime attribute says whether the Ant libraries themselves are put on the
compile class path, and it defaults to "yes, unless build.sysclasspath is set". That default is why a javac without
the attribute prints a warning on every build, and why a build that works on one machine can fail on another where Ant is
installed differently:
<javac srcdir="tidepool-model/src/main/java" destdir="build/a" release="21"/>
[javac] /work/tidepool/javac.xml:4: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[javac] Compiling 4 source files to /work/tidepool/build/a
Setting includeantruntime="false" removes the warning and makes the compile class path exactly what the build file
says. A project whose sources really use Ant classes (a custom task, see
Writing Custom Tasks) must either set it to
true or name the Ant jars. The probe below is a class that imports org.apache.tools.ant.Main; it fails to compile
with includeantruntime="false", compiles with true, and compiles with an explicit class path only when both
ant.jar and ant-launcher.jar are named:
package demo;
import org.apache.tools.ant.Main;
public class Probe {
public static String version() throws Exception {
return Main.getAntVersion();
}
}
<target name="cp">
<mkdir dir="build/probe"/>
<javac srcdir="src-ant" destdir="build/probe" release="21" includeantruntime="false">
<classpath>
<pathelement location="${ant.core.lib}"/>
<pathelement location="${ant.library.dir}/ant-launcher.jar"/>
</classpath>
</javac>
</target>
$ ant -f probe.xml without
[javac] /work/tidepool/src-ant/demo/Probe.java:3: error: package org.apache.tools.ant does not exist
[javac] import org.apache.tools.ant.Main;
[javac] ^
...
BUILD FAILED
/work/tidepool/probe.xml:4: Compile failed; see the compiler error output for details.
$ ant -f probe.xml with
[javac] Compiling 1 source file to /work/tidepool/build/probe
BUILD SUCCESSFUL
With only ${ant.core.lib} (the property that points at ant.jar) on the class path, javac stopped with
error: cannot access AntMain and class file for org.apache.tools.ant.launch.AntMain not found, because Main is
declared in terms of a class that lives in ant-launcher.jar; adding ${ant.library.dir}/ant-launcher.jar fixed it.
ant.core.lib, ant.library.dir and ant.home are among the built-in properties listed on
Properties. The full treatment of javac is on
Compiling Java.
Source: Apache Ant manual: Javac
Directory-based tasks and implicit filesets
Many tasks work on a directory tree: javac compiles the .java files under srcdir, delete removes files below a
directory, copy copies a subset. For those tasks, the task element is a fileset. The manual’s list of tasks that can
act as an implicit fileset includes checksum, delete, dependset, fixcrlf, javac, replace, rmic, xslt,
tar and zip, and the deprecated copydir, jlink and jspc. On such a task the attributes includes,
excludes, includesfile and excludesfile and the nested elements include, exclude, includesfile and
excludesfile describe the subset, exactly as on a fileset.
<project name="implicit" default="all" basedir=".">
<target name="all">
<mkdir dir="build/implicit"/>
<javac srcdir="tidepool-model/src" destdir="build/implicit" release="21" includeantruntime="false"
includes="main/**" excludes="**/TideEvent.java"/>
<copy todir="build/implicit/data" verbose="true">
<fileset dir="data" includes="*.csv"/>
</copy>
<delete verbose="true">
<fileset dir="build/implicit" includes="**/*.class" excludes="**/Height.class"/>
</delete>
</target>
</project>
all:
[mkdir] Created dir: /work/tidepool/build/implicit
[javac] Compiling 3 source files to /work/tidepool/build/implicit
[copy] Copying 1 file to /work/tidepool/build/implicit/data
[copy] Copying /work/tidepool/data/stations.csv to /work/tidepool/build/implicit/data/stations.csv
[delete] Deleting /work/tidepool/build/implicit/io/tidepool/model/Constituent.class
[delete] Deleting /work/tidepool/build/implicit/io/tidepool/model/Station.class
javac compiled three of the four model sources, because TideEvent.java was excluded; the explicit <fileset> in
copy and delete is the other way to write the same thing, and the only way when a task needs more than one set (the
explicit form is the only one copy accepts, because it is not in the manual’s list of implicit-fileset tasks). When a task is an implicit fileset, the
selectors of Selectors can be nested directly inside it.
Patterns
A pattern selects files by their path relative to the base directory of the set, and only files below that directory
are ever scanned. A pattern such as ../foo.java is syntactically legal and never matches anything, because the parent
of the base directory is not scanned.
The matching rules are few:
-
*matches zero or more characters within one name, and?matches exactly one character. -
Matching is done one directory level at a time: the first directory of the pattern is matched against the first directory of the path, then the second, and so on.
-
**, when it is the whole name of a directory, matches zero or more directories. That is the only way to cross directory levels. -
A pattern that ends with
/or\has**appended:mypackage/test/meansmypackage/test/**. -
Patterns are case sensitive unless the set has
casesensitive="false". -
A set with no include pattern includes everything (with the exception of the default excludes below); a file is selected when it matches at least one include pattern and no exclude pattern.
The manual’s own table of examples is reproduced in spirit by the next listing, run for real. The list macro prints
the files of the set it is given, with their paths relative to the project directory (the macrodef is explained on
Macros & Presets). The file is patterns.xml:
<project name="tidepool-patterns" default="all" basedir=".">
<macrodef name="list">
<attribute name="title"/>
<attribute name="refid"/>
<sequential>
<pathconvert property="out.@{refid}" refid="@{refid}" pathsep="${line.separator} ">
<map from="${basedir}/" to=""/>
</pathconvert>
<echo>@{title}${line.separator} ${out.@{refid}}</echo>
</sequential>
</macrodef>
<fileset id="all.java" dir="." includes="**/*.java"/>
<fileset id="main.java" dir="." includes="**/src/main/**/*.java"/>
<fileset id="tests.only" dir="." includes="**/*Test.java"/>
<fileset id="six.letters" dir="tidepool-model/src/main/java/io/tidepool/model" includes="??????.java"/>
<fileset id="not.tests" dir="." includes="**/*.java" excludes="**/*Test*"/>
<fileset id="slash.dir" dir="tidepool-engine" includes="src/test/"/>
<fileset id="mixed.case" dir="tidepool-model" includes="**/HEIGHT.JAVA" casesensitive="false"/>
<fileset id="data.default" dir="data"/>
<fileset id="data.all" dir="data" defaultexcludes="false"/>
<target name="all">
<list title="**/*.java" refid="all.java"/>
<list title="**/src/main/**/*.java" refid="main.java"/>
<list title="**/*Test.java" refid="tests.only"/>
<list title="six-letter names (??????.java)" refid="six.letters"/>
<list title="excludes **/*Test*" refid="not.tests"/>
<list title="src/test/ (trailing slash)" refid="slash.dir"/>
<list title="HEIGHT.JAVA, casesensitive=false" refid="mixed.case"/>
<list title="data with default excludes" refid="data.default"/>
<list title="data, defaultexcludes=false" refid="data.all"/>
</target>
</project>
$ ant -f patterns.xml
all:
[echo] **/*.java
[echo] tidepool-cli/src/main/java/io/tidepool/cli/Main.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/StationReader.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/TideEngine.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/StationReaderTest.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/TideEngineTest.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Station.java
[echo] tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
[echo] tidepool-model/src/test/java/io/tidepool/model/HeightTest.java
[echo] **/src/main/**/*.java
[echo] tidepool-cli/src/main/java/io/tidepool/cli/Main.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/StationReader.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/TideEngine.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Station.java
[echo] tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
[echo] **/*Test.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/StationReaderTest.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/TideEngineTest.java
[echo] tidepool-model/src/test/java/io/tidepool/model/HeightTest.java
[echo] six-letter names (??????.java)
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] excludes **/*Test*
[echo] tidepool-cli/src/main/java/io/tidepool/cli/Main.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/StationReader.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/TideEngine.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Station.java
[echo] tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
[echo] src/test/ (trailing slash)
[echo] tidepool-engine/src/test/java/io/tidepool/engine/StationReaderTest.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/TideEngineTest.java
[echo] HEIGHT.JAVA, casesensitive=false
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] data with default excludes
[echo] data/stations.csv
[echo] data, defaultexcludes=false
[echo] data/.DS_Store
[echo] data/.gitignore
[echo] data/stations.csv
[echo] data/stations.csv~
What the listing shows, pattern by pattern:
-
**/*.javamatched sources at every depth, because**also matches zero directories. -
**/src/main/**/*.javaselected the production sources of all three modules and none of the tests: thesrc/mainpart is matched as two directory names and the second**crossesjava/io/tidepool/.... -
??????.javamatchedHeight.javaand nothing else in the directory: six letters and the extension. -
**/*Test*as an exclude removed the three test classes from the**/*.javaset. -
src/test/(trailing slash) behaved assrc/test/**and selected the two test classes of the engine. -
casesensitive="false"letHEIGHT.JAVAmatchHeight.java. The default istrueon every platform, including the file systems of macOS and Windows that are case-insensitive, so a pattern that works because the disk ignores case breaks on Linux. -
The last two sets differ by the default excludes, described next.
A * inside a name never matches a /, so *.java selects only the files directly in the base
directory, and a frequent beginner error is to expect it to find the sources of a package tree. The mirror error is a
fileset whose base directory is already the package directory but whose pattern still starts with src/.
Source: Apache Ant manual: Patterns
Default excludes
Directory-based tasks skip a fixed list of files that are rarely meant to be part of a build: the temporary files of editors and the metadata of version control systems. The list in Ant 1.10.18 is:
| Patterns | Origin |
|---|---|
|
editor backups, lock and temporary files ( |
|
CVS |
|
SCCS and Visual SourceSafe |
|
Subversion |
|
macOS Finder |
|
Git (added in Ant 1.8.2) |
|
Mercurial (added in Ant 1.8.2) |
|
Bazaar (added in Ant 1.8.2) |
Both runs of data in the previous listing show the effect: .DS_Store, .gitignore and stations.csv~ are in the
directory, but only stations.csv is part of the default set. A single set can switch the list off with
defaultexcludes="false". The list for a whole build can be changed with the <defaultexcludes> task, which alters the
default excludes for all subsequent processing:
| Attribute | Meaning |
|---|---|
|
print the current list; |
|
add a pattern to the list |
|
remove a pattern from the list |
|
go back to the hard-wired list |
<target name="excludes">
<defaultexcludes add="**/*.bak"/>
<defaultexcludes remove="**/*~"/>
<fileset id="data" dir="data"/>
<list title="after add **/*.bak and remove **/*~" refid="data"/>
<defaultexcludes default="true"/>
</target>
$ touch data/x.bak
$ ant -f sets.xml excludes
excludes:
[echo] after add **/*.bak and remove **/*~
[echo] data/stations.csv
[echo] data/stations.csv~
The backup file stations.csv~ came back into the set, and x.bak stayed out. The task is global and sticky for the
rest of the build (and of the JVM, if Ant is embedded), so a build file that changes the list restores it with
default="true". With echo="true" the task prints the current list (in the order of a hash set, not the order of the
table above); in 1.10.18 its first line carries a literal %n where a newline was meant:
[defaultexcludes] Current Default Excludes:%n **/%*%
[defaultexcludes] **/.git/**
[defaultexcludes] **/SCCS
[defaultexcludes] **/.bzr
[defaultexcludes] **/.hg/**
...
A frequent reason to touch the list is the Subversion _svn convention: remove **/.svn and **/.svn/** and add
**/_svn and **/_svn/**, as the manual shows. In practice, the commonest default-excludes surprise has the
opposite cause: a file that should be packaged, such as a .gitignore shipped inside a project template, is left out
because it is in the list. The fix is defaultexcludes="false" on that one fileset, not a global change.
Gradle uses the same Ant DirectoryScanner underneath its file trees and therefore the same default excludes; it lets
a settings script change them with DirectoryScanner.removeDefaultExclude(...) and does not allow changing them
during the execution phase.
fileset
A <fileset> is a group of files found in a directory tree below a base directory and matched by patterns and
selectors. It holds an implicit patternset, so it takes the patternset’s attributes and nested elements directly. It
also acts as an <and> selector container, so any nested selector must select a file for it to belong to the set.
| Attribute | Meaning | Required |
|---|---|---|
|
the root of the directory tree |
exactly one of |
|
shortcut for a single-file set |
exactly one of |
|
comma- or space-separated patterns |
no; defaults to all files |
|
a file whose every line is a pattern; an empty |
no |
|
whether the default excludes apply ( |
no; |
|
whether patterns are case sensitive |
no; |
|
whether symbolic links and (since Ant 1.10.16) Windows junctions are followed |
no; |
|
since Ant 1.7.1: what happens when |
no; |
|
register the set, or make this element a reference to a set defined elsewhere |
no |
erroronmissingdir defaults to true "for backward compatibility", so the first thing a clean checkout does with a
javac that points at a not-yet-created generated-sources directory is fail. Setting it to false on the sets that
describe optional directories, for example a src/generated directory that only exists after a code-generation step,
keeps the build file valid before and after:
BUILD FAILED
/work/tidepool/errs.xml:3: /work/tidepool/no-such-dir does not exist.
Symbolic links deserve a sentence of their own. followsymlinks="false" excludes symbolic links and junctions
completely, so a link that points at a directory of sources makes the whole subtree vanish from the set; true lets
selectors with their own followsymlinks attribute decide whether to look at the link or its target. Before Ant 1.10.2
"symbolic link" meant any file whose canonical path differs from its path, which gave false results on some platforms;
Ant 1.10.2 and later use Files#isSymbolicLink. With a link data/model-link to ../tidepool-model the difference is
the whole module:
<fileset id="f1" dir="data"><include name="**/*.java"/></fileset>
<fileset id="f2" dir="data" followsymlinks="false"><include name="**/*.java"/></fileset>
<resourcecount property="n1"><fileset refid="f1"/></resourcecount>
<resourcecount property="n2"><fileset refid="f2"/></resourcecount>
<echo>followsymlinks=true: ${n1} files, false: ${n2} files</echo>
[echo] followsymlinks=true: 5 files, false: 0 files
Many errors come from the way the element is written, and Ant’s messages are precise about them:
| Message | Cause |
|---|---|
|
|
|
a fileset with neither |
|
both given |
|
a |
|
|
|
|
Source: Apache Ant manual: FileSet
patternset
A <patternset> groups patterns so that they can be named and shared. It may appear nested in a fileset or in a
directory-based task, and also stand-alone at the same level as <target> (a child of <project> or of <target>).
| Attribute or element | Meaning |
|---|---|
|
comma- or space-separated patterns |
|
a file of patterns, one per line; the nested |
nested |
exactly one pattern each, with optional |
nested |
adds the nested patterns to the parent patternset |
nested |
since Ant 1.7.1: a nested patternset that selects everything except what it would select |
|
a reference to a patternset defined elsewhere |
The comma-or-space convenience of the attributes does not apply to the nested element: a nested include takes one
pattern. The if and unless attributes of include and exclude test whether a named property is set (not its
value), which is the cheap way to make a source list depend on the command line. In the file sets.xml:
<patternset id="sources">
<include name="*/src/main/java/**/*.java"/>
<include name="*/src/test/java/**/*.java" if="with.tests"/>
<exclude name="tidepool-cli/**" unless="with.cli"/>
</patternset>
<fileset id="selected" dir=".">
<patternset refid="sources"/>
</fileset>
$ ant -q -f sets.xml
[echo] selected sources
[echo] tidepool-engine/src/main/java/io/tidepool/engine/StationReader.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/TideEngine.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Station.java
[echo] tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
$ ant -q -f sets.xml -Dwith.cli=true -Dwith.tests=true
[echo] selected sources
[echo] tidepool-cli/src/main/java/io/tidepool/cli/Main.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/StationReader.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/TideEngine.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/StationReaderTest.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/TideEngineTest.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Station.java
[echo] tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
[echo] tidepool-model/src/test/java/io/tidepool/model/HeightTest.java
A patternset also reads patterns from a file. The file extra.patterns below has a comment line, and the example shows
that the file has no comment syntax: every line is a pattern, so the comment became a pattern that matches nothing
(harmless here, but a source of confusion). The pattern *.csv matched nothing under . either, because it is relative
to the base directory and * does not cross directories:
*.csv
# comment line
**/*Test.java
<fileset id="from.file" dir="." includesfile="extra.patterns"/>
[echo] includesfile extra.patterns
[echo] tidepool-engine/src/test/java/io/tidepool/engine/StationReaderTest.java
[echo] tidepool-engine/src/test/java/io/tidepool/engine/TideEngineTest.java
[echo] tidepool-model/src/test/java/io/tidepool/model/HeightTest.java
The <invert> element is only accepted inside a patternset, not directly in a fileset (a direct nesting fails with
fileset doesn't support the nested "invert" element):
<fileset id="inverted" dir="tidepool-model/src/main/java/io/tidepool/model">
<patternset>
<invert>
<patternset includes="H*.java,S*.java"/>
</invert>
</patternset>
</fileset>
[echo] invert
[echo] tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[echo] tidepool-model/src/main/java/io/tidepool/model/TideEvent.java
Gradle’s counterpart is PatternFilterable (the include, exclude, setIncludes family used by fileTree, copy
specs and the source sets), with the same Ant-style patterns; Maven’s counterpart is the <includes> and <excludes>
list of a resource directory or a plugin configuration.
Source: Apache Ant manual: PatternSet
dirset
A <dirset> is the directory counterpart of a fileset: a group of directories matched by patterns and selectors below a
base directory. It takes the same attributes and nested elements as a fileset except file (its dir is required), and
it is an <and> selector container as well. A dirset contains the directories only; the files inside them are not part of
the set, which is why it is most often seen nested in a path (a source path, a list of class directories):
<dirset id="packages" dir=".">
<include name="*/src/main/java/io/tidepool/*"/>
</dirset>
[echo] dirset packages
[echo] tidepool-cli/src/main/java/io/tidepool/cli
[echo] tidepool-engine/src/main/java/io/tidepool/engine
[echo] tidepool-model/src/main/java/io/tidepool/model
The build file in the manifestclasspath section used a dirset for the opposite purpose: its <path id="main.sources">
wraps the dirset of */src/main/java, which javac took as a <src refid>. A bare <dirset id> cannot be passed as a
refid where a path is expected; the type error is
BUILD FAILED
/work/tidepool/build.xml:16: main.sources doesn't denote a path
and the fix is to wrap it in a <path>, as the example does. The manual’s last dirset example, a present selector
with a glob mapper, selects every directory that contains a marker file, which is also the way to find every module
directory in a multi-module tree; selectors are on Selectors.
Source: Apache Ant manual: DirSet
filelist
A <filelist> is an explicit list of files relative to a directory. Where a fileset filters what exists, a filelist
lists names whether or not the files exist, and does not expand wildcards (a * in a name is kept as written). That makes
it the right type for ordered lists (the files of a documentation set, the chapters of a book, the modules a script
must run in order), and for the expected outputs of a build that has not run yet.
| Attribute or element | Meaning | Required |
|---|---|---|
|
the base directory |
yes |
|
names separated by whitespace or commas |
yes, unless nested |
nested |
since Ant 1.6.2: one name; allows spaces and commas in the name |
no |
|
register the list, or refer to one |
no |
<filelist id="release.files" dir=".">
<file name="README.txt"/>
<file name="build/dist/tidepool-cli.jar"/>
<file name="docs/user guide.pdf"/>
</filelist>
[echo] filelist release.files
[echo] README.txt
[echo] build/dist/tidepool-cli.jar
[echo] docs/user guide.pdf
docs/user guide.pdf does not exist, and it is in the list anyway, with a space in its name that the files attribute
could not have carried. This is useful with pathconvert, as in the manual’s example that converts a filelist into a
Unix path with a map, and with the available and uptodate tasks that check whether the listed files exist.
Source: Apache Ant manual: FileList
zipfileset and tarfileset
<zipfileset> and <tarfileset> are filesets with extra attributes for building archives. Each has two modes: with
dir it is populated with files from the file system; with src (or, since Ant 1.7, a nested resource) it is
populated with the entries of an archive, which is how one archive is merged into another. They take every attribute
of <fileset> plus:
| Attribute | Meaning | Applies to |
|---|---|---|
|
every file is placed under this path in the archive |
both |
|
the single file of the set is placed at exactly this path; cannot be combined with |
both |
|
a zip (or tar) file whose entries are included |
both |
|
three-digit octal permissions of plain files; default 644 |
both (zipfileset since Ant 1.5.2) |
|
three-digit octal permissions of directories; default 755 |
both (zipfileset since Ant 1.5.2) |
|
encoding of the entry names ( |
both |
|
since Ant 1.8.0: whether a missing |
both |
|
owner of the tar entries |
|
Only the tar and zip tasks use the permission attributes, and only tar uses the ownership attributes. A zipfileset can be defined once with an id and used in several archives with refid
(since Ant 1.6; for tarfileset since Ant 1.7). The file archives.xml:
<project name="archives" default="all" basedir=".">
<property name="out" location="build/dist"/>
<zipfileset id="libs" dir="lib" includes="*.jar" prefix="app/lib" filemode="640"/>
<target name="all">
<mkdir dir="${out}"/>
<zip destfile="${out}/bundle.zip">
<zipfileset dir="data" prefix="app/data"/>
<zipfileset refid="libs"/>
<zipfileset dir="." includes="README.txt" fullpath="app/docs/READ-ME.txt"/>
<zipfileset src="${out}/tidepool-cli.jar" includes="io/tidepool/model/**" prefix="app/classes"/>
</zip>
<tar destfile="${out}/bundle.tar" longfile="posix">
<tarfileset dir="data" prefix="app/data" filemode="600" username="tide" group="pool" uid="1001" gid="1002"/>
<tarfileset dir="." includes="README.txt" fullpath="app/bin/run" filemode="755"/>
</tar>
</target>
</project>
$ ant -f archives.xml
all:
[zip] Building zip: /work/tidepool/build/dist/bundle.zip
[tar] Building tar: /work/tidepool/build/dist/bundle.tar
$ unzip -Z build/dist/bundle.zip (directories and the summary line left out)
-rw-r--r-- app/data/stations.csv
-rw-r----- app/lib/commons-codec-1.19.0.jar
-rw-r----- app/lib/commons-csv-1.14.1.jar
-rw-r----- app/lib/commons-io-2.20.0.jar
-rw-r----- app/lib/picocli-4.7.7.jar
-rw-r--r-- app/docs/READ-ME.txt
-rw-r--r-- app/classes/io/tidepool/model/Constituent.class
-rw-r--r-- app/classes/io/tidepool/model/Height.class
-rw-r--r-- app/classes/io/tidepool/model/Station.class
-rw-r--r-- app/classes/io/tidepool/model/TideEvent.class
$ tar tvf build/dist/bundle.tar
-rw------- 0 tide pool 239 Oct 16 11:33 app/data/stations.csv
-rwxr-xr-x 0 0 0 6 Oct 16 11:36 app/bin/run
The reading guide: prefix moved data and lib under app/; fullpath renamed README.txt to
app/docs/READ-ME.txt; the src form copied only the model classes out of tidepool-cli.jar (the build of the
earlier section); filemode="640" shows as -rw-r----- and the tar entry for stations.csv carries mode 600 and
the owner names. The default-excluded stations.csv~ is not in either archive. Two messages catch the usual
mistakes:
/work/tidepool/errs2.xml:10: Cannot set both fullpath and prefix attributes
/work/tidepool/errs2.xml:13: fullpath attribute may only be specified for filesets that specify a single file.
The jar, war and ear tasks accept the same nested sets, plus zipgroupfileset to merge the contents of many
archives, and are the subject of Packaging Archives.
Merging is also how Ant builds an uber JAR; Maven uses the Shade or Assembly plugins and Gradle a Jar task with
from(zipTree(...)) (see Uber JARs with Shade & Assembly).
Source: Apache Ant manual: ZipFileSet
Source: Apache Ant manual: TarFileSet
multirootfileset
A <multirootfileset> (since Ant 1.9.4) is a union of filesets and dirsets that share the same patterns and selectors:
the same include and exclude rules applied to several base directories. It takes everything a fileset takes except dir,
and adds:
| Attribute or element | Meaning |
|---|---|
|
a comma-separated list of root directories |
nested |
one root each; merged with |
|
|
|
whether to cache the result; the default is |
<multirootfileset id="roots" basedirs="tidepool-model/src,tidepool-engine/src" type="file">
<include name="main/**/*.java"/>
<exclude name="**/Tide*"/>
</multirootfileset>
[echo] multirootfileset roots
[echo] tidepool-model/src/main/java/io/tidepool/model/Constituent.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Height.java
[echo] tidepool-model/src/main/java/io/tidepool/model/Station.java
[echo] tidepool-engine/src/main/java/io/tidepool/engine/StationReader.java
The result lists the model sources first and the engine sources next, the order of basedirs, and the Tide* exclude
dropped TideEvent.java and TideEngine.java. The two-module union would otherwise need two filesets and the same
patterns twice (or a shared patternset). Gradle’s equivalent is a files(...) collection of several fileTree objects,
and Maven spreads the same filter over <sourceDirectory> and the build-helper plugin’s add-source goal.
classfileset
A <classfileset> is a fileset computed from the dependencies of some root classes: given a directory of class files and
one or more roots, it includes every class file the roots need. It is typically used to build a JAR containing only what
an application requires. It needs the BCEL library, which Ant does not ship; the optional ant-apache-bcel.jar in
ANT_HOME/lib is the glue, and the BCEL jar (and its own dependencies) must be on Ant’s class path.
| Attribute or element | Meaning |
|---|---|
|
the directory of class files to draw from |
|
a single root class name |
nested |
one more root, by fully-qualified name |
nested |
roots given as a fileset of class files; the class name is derived from the relative path |
<classfileset id="needed" dir="build/classes">
<root classname="io.tidepool.engine.StationReader"/>
</classfileset>
The first run failed because BCEL is not installed in Ant 1.10.18 (the stack trace is long; this is its first line):
BUILD FAILED
/work/tidepool/classfs.xml:7: java.lang.NoClassDefFoundError: org/apache/bcel/classfile/Visitor
BCEL 6.12.0 (the version in Ant 1.10.18’s libraries.properties) was fetched from Maven Central into a directory
extlib, which is passed to Ant with -lib. That run failed again, with NoClassDefFoundError: org/apache/commons/io/IOUtils,
because BCEL 6.12.0 itself needs commons-lang3 (3.20.0) and commons-io (2.21.0). With the three jars in extlib:
$ ant -lib extlib -f classfs.xml
all:
[echo]
[echo] io/tidepool/model/Station.class
[echo] io/tidepool/engine/StationReader.class
[echo] io/tidepool/model/Constituent.class
[jar] Building jar: /work/tidepool/build/dist/minimal.jar
The root StationReader needs Station and, through it, Constituent; the other classes of the model (Height,
TideEvent) and of the engine were left out of minimal.jar. Static analysis of the byte code cannot see classes loaded
by reflection or by name, so a classfileset is an optimisation for small applications, not a replacement for a
dependency declaration. Gradle and Maven reach the same goal with the Shade plugin’s minimizeJar and ProGuard-style
tools, not with a core feature.
Source: Apache Ant manual: ClassFileSet
References: id and refid
Any element of a project can be given an id, and an element of the same type can then be a reference to it with
refid. The mechanism is not specific to sets and paths, but they are its main use, and a build file that defines
compile.path once and refers to it from javac, java and junitlauncher is more than tidy: it cannot drift.
Rules that matter in practice:
-
The reference is by type.
<classpath refid="x"/>needs apath; handing it adirsetends withx doesn't denote a path, as above. -
An element with
refidcan have no other attributes and no nested elements (the two messages are in the table in thefilesetsection). -
A reference to something that does not exist fails with
Reference nope not found., and the order in which the file is read is not the order in which targets run. An element directly under<project>is processed when the file is parsed, so every target sees it. An element inside a<target>is registered only when that target executes, so a path defined in one target and used by another that did not depend on it is not there. With only theusetarget the expansion stays literal:<target name="define"><path id="late"><pathelement location="lib"/></path></target> <target name="use"><echo>${toString:late}</echo></target>$ ant -q -f misc.xml use [echo] ${toString:late} $ ant -q -f misc.xml define use [echo] /work/tidepool/lib -
The same
iddefined twice is not an error; the later definition silently replaces the earlier. Within a macro or asubantcall the references of the calling project are copied or not depending on theinheritrefsattribute (see Multi-File Builds: import, include & subant). -
A reference is not a snapshot. A fileset is rescanned and a
cache="false"path re-evaluated each time it is used, which is what keeps arefidto the output directory correct after the compile target ran.
The <arg> element of the tasks that start programs has a path form for the same purpose: <arg path="..."/> is one
argument written with either separator and converted to the platform’s, and <arg pathref="..."/> takes a path reference.
<arg line="..."/> splits on spaces and the manual recommends avoiding it. ${toString:ref} is the string form of any
reference, used in the first example.
Source: Apache Ant manual: References
The same ideas in Maven and Gradle
Neither Maven nor Gradle has a type called "path": a class path is derived from the declared dependencies. The sets of files do have counterparts:
| Ant | Maven 3.10.0 | Gradle 9.8.1 |
|---|---|---|
|
computed from the dependencies and their scopes (see Dependencies, Scopes & Mediation); |
a |
|
|
|
|
several |
|
|
not available; list the names in the plugin configuration |
|
|
assembly descriptor |
|
default excludes |
fixed by the plugin’s scanner |
the Ant |
|
the |
|
The Maven row for path is the one that most often surprises Ant users: in Maven the class path is not something one
builds, it is a consequence of the POM, and a "wrong" class path is fixed by the dependency declaration and not by a
pathelement. The Gradle page on Ant Integration shows how an Ant
build’s paths and filesets can be reused from a Gradle build, and the page
Migrating Between Tools takes the tidepool Ant build to the other two tools.
Source: Gradle User Manual: File trees
Source: Maven: POM Reference, Resources
Release history of the features on this page
The features are older than most builds, so the release numbers are mostly of interest when a build file must also run on a very old Ant:
| Release | Change |
|---|---|
1.5.2 |
|
1.6 |
|
1.7 |
resource collections; |
1.8.0 |
|
1.8.1, 1.8.2 |
the default excludes grow to the current list; the Git, Mercurial and Bazaar patterns arrive in 1.8.2, and so does the
|
1.9.4 |
|
1.10.2 |
symbolic links detected with |
1.10.4 |
|
1.10.13 |
|
1.10.16 |
Windows junctions detected like symbolic links |
Nothing on this page is deprecated in Ant 1.10.18. The nearby tasks copydir, jlink and jspc that the manual lists as
implicit-fileset users are deprecated; the migration advice is on
Deprecations & Migration.
Where the books are outdated
Java Development with Ant (written for Ant 1.5, 2002) teaches paths and filesets as the only vocabulary, because
resource collections, multirootfileset, cache on paths, erroronmissingdir and the includeantruntime warning did
not exist yet; its examples compile without includeantruntime, which prints a warning today and is
something to set to false on every javac. Its default-excludes list predates the Git, Mercurial and Bazaar
entries, so the .gitignore surprise above cannot be found in it. The path and fileset rules themselves have not
changed, and the book’s chapter on them is still a good introduction. The Maven 2 and Gradle books do not cover Ant
paths.
References
-
Java Development with Ant (Manning)