Running Programs
|
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 every example was run with it on JDK 26 on macOS on 2026-10-16.
The examples are small complete build files for the tidepool scenario used across this section. Each one runs with
ant -f <file> <target> from the project directory, and the Java class they start is a stand-in for the tidepool
command-line tool whose source is shown below. The console output is real, with three kinds of lines left out: the
Buildfile: line, blank lines and the Total time: line. The project directory is shortened to /work/tidepool.
A build runs other programs for many reasons: to start the program it has just built (a smoke test of the command-line
tool), to run a code generator or a Java-based tool that has no Ant task, to call a native command such as git or
tar, and to run a tool once for each of many files. Ant has three tasks for this, one for each need.
| Task | Runs | Typical use |
|---|---|---|
|
a Java class, a JAR, a module or a single source file, in Ant’s JVM or in a new one |
a smoke test of the application, a code generator written in Java |
|
one native command |
|
|
a native command over the files of a resource collection, once per file or in batches |
an image converter, a checksum tool |
The redirector type, which connects the standard input, output and error of the program to files, properties and filter
chains, is shared by all three.
The program used in the examples
The Java stand-in for the tidepool tool prints its arguments, and its first argument selects a behaviour: info prints
the system property tidepool.units, the maximum heap size, the working directory and whether assertions are on;
exit <n> writes a message to standard error and calls System.exit; sleep <ms> waits; stdin echoes its input.
package org.tidepool;
import java.nio.file.Files;
import java.nio.file.Path;
/** A tiny stand-in for the tidepool CLI, used to exercise Ant's execution tasks. */
public final class Main {
private Main() { }
public static void main(String[] args) throws Exception {
System.out.println("args=" + String.join(",", args));
if (args.length == 0) {
return;
}
switch (args[0]) {
case "info" -> {
boolean assertions = false;
assert assertions = true;
System.out.println("tidepool.units=" + System.getProperty("tidepool.units", "<unset>"));
System.out.println("maxMemory=" + Runtime.getRuntime().maxMemory() / (1024 * 1024) + "M");
System.out.println("cwd=" + Path.of("").toAbsolutePath().getFileName());
System.out.println("assertions=" + assertions);
}
case "exit" -> {
System.err.println("exiting with status " + args[1]);
System.exit(Integer.parseInt(args[1]));
}
case "sleep" -> Thread.sleep(Long.parseLong(args[1]));
case "stdin" -> System.out.println("stdin=" + new String(System.in.readAllBytes()).trim());
case "stations" -> System.out.println("stations=" + (Files.readAllLines(Path.of(args[1])).size() - 1));
default -> { }
}
}
}
The files in this section are this page’s own setup for the examples that follow, not taken from the documentation.
Running Java with java
The java task runs a Java class "within the running (Apache Ant) JVM or forks another JVM if specified". Exactly one of
four attributes says what to run.
| Attribute | Runs | Requires |
|---|---|---|
|
the |
nothing; may be combined with |
|
the |
|
|
the main module (its |
|
|
a |
|
In the Ant JVM and in a forked JVM
Without fork, the class runs inside Ant’s own JVM, with Ant’s JVM settings, in Ant’s class loader hierarchy and in the
working directory in which Ant was started. That is cheap, since there is no process to start, but it has drawbacks: JVM options such as -Xmx cannot
be set, a program that calls System.exit ends Ant itself, a static variable survives into the next task, and
assertions are ignored. fork="true" runs a new JVM that starts a fresh copy of the program: JVM arguments
(jvmarg, maxmemory), a different working directory (dir), a different Java installation (jvm), an input stream, a
timeout and a clean exit status are all available there. The ant manual’s advice is "if odd things go wrong when you run
this task, set fork = true".
| Attribute | Meaning | Default |
|---|---|---|
|
what to run, as above |
none (exactly one of |
|
the class path, as an attribute or by reference; a nested |
Ant’s class path |
|
the module path; nested |
none |
|
run the class in a new JVM |
|
|
start a process that outlives Ant; needs |
|
|
the command that starts the forked JVM |
|
|
deprecated attributes: use nested |
none |
|
the maximum heap of the forked JVM ( |
JVM default |
|
the working directory of the forked JVM |
the project’s |
|
fail the build if the exit status is not 0 |
|
|
a property to receive the exit status; useful with |
none |
|
kill the program after this many milliseconds; use it only with |
none |
|
where standard output and standard error go; see the redirector below |
the Ant log |
|
standard input from a file or a string; mutually exclusive |
the console, unless |
|
do not pass Ant’s environment variables to the JVM when |
|
|
give the forked JVM the system properties and the boot class path of Ant’s JVM |
|
|
throw standard output or error away (since 1.10.10) |
|
The nested elements are <arg> and <jvmarg> for the program’s and the JVM’s command line, <sysproperty> and
<syspropertyset> for system properties, <classpath>, <bootclasspath>, <modulepath>, <upgrademodulepath>,
<env> (environment variables for a forked JVM; the description is under exec below), <assertions>, <permissions>
and <redirector>.
The first build runs the class in Ant’s JVM and then forks it. Two points to see in the output: <arg value="station list"/>
is one argument that contains a space, while <arg line="--units metric"/> is split into two; and the forked JVM
has its own heap limit and working directory, with assertions enabled by the -ea option.
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-java" default="compile" basedir=".">
<target name="compile">
<mkdir dir="build/classes"/>
<javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
</target>
<!-- 1. in the Ant JVM (fork="false", the default) -->
<target name="in-process" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes">
<arg value="info"/>
<arg value="station list"/>
<arg line="--units metric"/>
<sysproperty key="tidepool.units" value="metric"/>
</java>
</target>
<!-- 2. in a forked JVM, with JVM options and a working directory -->
<target name="forked" depends="compile">
<mkdir dir="build/run"/>
<java classname="org.tidepool.Main" classpath="${basedir}/build/classes"
fork="true" dir="build/run" maxmemory="64m">
<jvmarg value="-Xss512k"/>
<jvmarg value="-ea"/>
<sysproperty key="tidepool.units" value="imperial"/>
<arg value="info"/>
</java>
</target>
</project>
ant -f run-java.xml in-process
ant -f run-java.xml forked
compile:
[mkdir] Created dir: /work/tidepool/build/classes
[javac] Compiling 1 source file to /work/tidepool/build/classes
in-process:
[java] args=info,station list,--units,metric
[java] tidepool.units=metric
[java] maxMemory=8192M
[java] cwd=tidepool
[java] assertions=false
BUILD SUCCESSFUL
compile:
forked:
[mkdir] Created dir: /work/tidepool/build/run
[java] args=info
[java] tidepool.units=imperial
[java] maxMemory=64M
[java] cwd=run
[java] assertions=true
BUILD SUCCESSFUL
The in-process run reports maxMemory=8192M, which is Ant’s heap, and cwd=tidepool, which is where Ant started. The forked
run reports 64M and cwd=run, the dir that was set.
Source: Ant manual: java
Running a JAR
jar runs the Main-Class named in the JAR’s manifest, as java -jar does, so the JVM ignores the classpath; put a
Class-Path manifest attribute in the JAR instead. The jar attribute is a File, so a relative value is resolved against
the project’s basedir, not against the directory the task runs in when dir is set. If you need a path relative to dir,
build the full path yourself.
<target name="jar" depends="compile">
<java jar="build/lib/tidepool-cli.jar" fork="true" failonerror="true">
<arg value="info"/>
</java>
</target>
The complete build file, with the compile target that makes the JAR, is listed with the next example.
Exit codes, output and timeouts
By default the exit status of a forked program is ignored, which surprises people: a java task whose program fails does
not fail the build. With failonerror="true" any non-zero status stops the build. resultproperty stores the status in
a property, which is how to decide what to do next. The task also logs Java Result: n for a non-zero result when
the build continues. A task that does not fork behaves differently: the class runs in the build’s own JVM, so there is no process exit
status to inspect, and a program that calls System.exit ends Ant itself (see below).
outputproperty and errorproperty capture the streams in properties. If only outputproperty is set, error output is
merged into it; here both are set, so they are kept apart. timeout kills a forked JVM that runs too long, and the
task fails when failonerror is true. inputstring sends text to the standard input of the forked program.
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-java2" default="all" basedir=".">
<target name="compile">
<mkdir dir="build/classes"/>
<javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
<jar destfile="build/lib/tidepool-cli.jar" basedir="build/classes">
<manifest>
<attribute name="Main-Class" value="org.tidepool.Main"/>
</manifest>
</jar>
</target>
<target name="jar" depends="compile">
<java jar="build/lib/tidepool-cli.jar" fork="true" failonerror="true">
<arg value="info"/>
</java>
</target>
<target name="exit-codes" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" fork="true"
resultproperty="rc" failonerror="false">
<arg line="exit 3"/>
</java>
<echo message="exit status stored in rc: ${rc}"/>
<java classname="org.tidepool.Main" classpath="build/classes" fork="true" failonerror="true">
<arg line="exit 3"/>
</java>
</target>
<target name="capture" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" fork="true"
outputproperty="out" errorproperty="err" resultproperty="rc2">
<arg line="exit 2"/>
</java>
<echo message="rc2=${rc2}"/>
<echo message="stdout was:${line.separator}${out}"/>
<echo message="stderr was: ${err}"/>
</target>
<target name="timeout" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" fork="true"
timeout="1500" failonerror="true">
<arg line="sleep 10000"/>
</java>
</target>
<target name="stdin" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" fork="true"
inputstring="station BRE" outputproperty="stdin.out">
<arg value="stdin"/>
</java>
<echo message="${stdin.out}"/>
</target>
<target name="all" depends="jar"/>
</project>
ant -f run-java2.xml exit-codes
ant -f run-java2.xml capture
ant -f run-java2.xml timeout
ant -f run-java2.xml stdin
compile:
[jar] Building jar: /work/tidepool/build/lib/tidepool-cli.jar
jar:
[java] args=info
[java] tidepool.units=<unset>
[java] maxMemory=8192M
[java] cwd=tidepool
[java] assertions=false
BUILD SUCCESSFUL
compile:
exit-codes:
[java] args=exit,3
[java] exiting with status 3
[java] Java Result: 3
[echo] exit status stored in rc: 3
[java] args=exit,3
[java] exiting with status 3
BUILD FAILED
/work/tidepool/run-java2.xml:25: Java returned: 3
The first build gave exit status 3 once with failonerror="false" (the status is stored in rc) and once with
failonerror="true", which ended the build with Java returned: 3. The next builds capture streams, hit a timeout and send input:
compile:
capture:
[java] Java Result: 2
[echo] rc2=2
[echo] stdout was:
[echo] args=exit,2
[echo] stderr was: exiting with status 2
BUILD SUCCESSFUL
compile:
timeout:
[java] args=sleep,10000
BUILD FAILED
/work/tidepool/run-java2.xml:42: Timeout: killed the sub-process
compile:
stdin:
[echo] args=stdin
[echo] stdin=station BRE
BUILD SUCCESSFUL
The standard error text exiting with status 2 went to errorproperty, not into outputproperty, and the timeout produced
Timeout: killed the sub-process.
Source: Ant manual: Redirector
Programs that call System.exit()
A Java program that calls System.exit stops the JVM it is running in. When the java task does not fork, that JVM is
Ant, so the build stops in the middle of the task with no BUILD FAILED or BUILD SUCCESSFUL message and the exit
status of the program becomes the exit status of Ant. Older Ant versions guarded against this by installing a Java
SecurityManager that intercepted the call, which is what the <permissions> element and the default permissions
did. The Java platform has removed that mechanism: JEP 411 (Java 17) deprecated the SecurityManager for removal, and
JEP 486 (Java 24) permanently disabled it. Ant 1.10.14 follows: "when using Java 18 or higher, Ant will no longer use
Java SecurityManager", and a task that calls System.exit() and does not run in a forked JVM "will now kill the entire
Ant build process", in the words of the release notes. The remedy is to fork.
The two builds below run the same program, which exits with status 7 in the first and 0 in the second. In the first the JVM is Ant’s:
<target name="exit-in-process" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" failonerror="true">
<arg line="exit 7"/>
</java>
<echo message="this line is never reached"/>
</target>
<target name="exit-forked" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" fork="true" failonerror="true">
<arg line="exit 0"/>
</java>
<echo message="the build goes on"/>
</target>
The status.sh wrapper prints the exit status of the ant command after the build output:
compile:
exit-in-process:
[java] args=exit,7
[java] exiting with status 7
ant exit status: 7
compile:
exit-forked:
[java] args=exit,0
[java] exiting with status 0
[echo] the build goes on
BUILD SUCCESSFUL
In the first run Ant exits with status 7, the program’s status, without printing a final message: a CI server sees a failed
step but the log has no summary. The second run, forked, prints the build goes on.
The same applies to tasks that call System.exit internally, such as some code-generation or test-runner entry points
invoked through java. Another consequence is that the other tasks (junit, junitlauncher) have their own fork
options for the same reason; see
Testing with JUnit & junitlauncher.
Source: Ant manual: java
The permissions type on Java 18 and later
<permissions> grants and revokes Java security permissions while the class runs, through nested <grant> and <revoke>
elements (class, name and actions). It requires a SecurityManager, which is why it cannot work any more: on Java 18
and later, using <permissions> throws a BuildException, and the manual recommends no longer using the type.
The element is ignored when the task forks. Setting the system or Ant property ant.securitymanager.usage.warn to
true replaces the exception with a logged warning, but no security checks are performed; it is a compatibility switch
for old build files, not a feature.
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-permissions" default="restricted" basedir=".">
<target name="compile">
<mkdir dir="build/classes"/>
<javac srcdir="src" destdir="build/classes" release="21" includeantruntime="false"/>
</target>
<target name="restricted" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" failonerror="true">
<permissions>
<grant class="java.security.AllPermission"/>
<revoke class="java.lang.RuntimePermission" name="exitVM"/>
</permissions>
<arg line="exit 0"/>
</java>
<echo message="reached after the java task"/>
</target>
</project>
compile:
restricted:
BUILD FAILED
/work/tidepool/run-permissions.xml:9: Use of <permissions> or org.apache.tools.ant.types.Permissions is disallowed in current Java runtime version
With the property set on the command line the build continues, the security checks do nothing, and the program calls
System.exit and ends Ant:
ant -f run-permissions.xml -Dant.securitymanager.usage.warn=true
compile:
restricted:
Security checks are disabled - Use of <permissions> or org.apache.tools.ant.types.Permissions is disallowed in current Java runtime version
[java] args=exit,0
[java] exiting with status 0
The warning is printed once; [java] args=exit,0 shows the program ran, and the reached after the java task message is
missing, because the JVM was Ant’s.
Source: Ant manual: Permissions
Assertions
The nested <assertions> element enables or disables Java assertions for the program. <enable> and <disable> take a
class or a package (a package includes its subpackages; ... means the anonymous package), and without either they
apply to all user code. enableSystemAssertions controls the JDK’s own classes. The element is a data type, so it can be
defined with an id and reused with refid. As the manual says, "assertion statements are currently ignored in
non-forked mode", so use it with fork="true". The Main class prints assertions=true only when they are enabled.
compile:
assertions:
[java] args=info
[java] tidepool.units=<unset>
[java] maxMemory=8192M
[java] cwd=tidepool
[java] assertions=true
BUILD SUCCESSFUL
Source: Ant manual: Assertions
Modules
For a modular application, module names the main module and modulepath (an attribute, or a nested <modulepath>
path-like element) says where modules are found. When classname is also given, it names the main class inside the
module. A modular program must be forked. The example compiles a one-class module with javac and runs it.
module tidepool.hello {
exports org.tidepool.hello;
}
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-module" default="run" basedir=".">
<target name="compile">
<mkdir dir="build/mods/tidepool.hello"/>
<javac srcdir="modsrc" destdir="build/mods/tidepool.hello" release="21" includeantruntime="false"/>
</target>
<target name="run" depends="compile">
<!-- module + classname: run a class of a module found on the module path -->
<java fork="true" failonerror="true" module="tidepool.hello" classname="org.tidepool.hello.Hello">
<modulepath>
<pathelement location="build/mods"/>
</modulepath>
<arg value="Brest"/>
</java>
</target>
<target name="no-fork" depends="compile">
<java module="tidepool.hello" classname="org.tidepool.hello.Hello" modulepath="build/mods"/>
</target>
</project>
compile:
[mkdir] Created dir: /work/tidepool/build/mods/tidepool.hello
[javac] Compiling 2 source files to /work/tidepool/build/mods/tidepool.hello
run:
[java] module=tidepool.hello named=true args=Brest
BUILD SUCCESSFUL
compile:
no-fork:
BUILD FAILED
/work/tidepool/run-module.xml:19: Cannot execute a module in non-forked mode. Please set fork='true'.
The module=tidepool.hello named=true line proves the class was loaded from a named module. The second build shows the
message when fork is missing. Module-aware compilation with javac is on
Compiling Java.
Source: Ant manual: java, modulepath
Single-file source programs
Since Ant 1.10.5 the sourcefile attribute runs a .java file straight away through the Java 11 launcher (JEP 330), without
a separate compile step. It also accepts a file with a shebang line. It forks, like jar and module, and the Java version
that runs it must support the feature.
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, " + args[0] + " (java.version " + System.getProperty("java.specification.version") + ")");
}
}
<target name="sourcefile">
<java sourcefile="single/Hello.java" fork="true" failonerror="true">
<arg value="Brest"/>
</java>
</target>
sourcefile:
[java] Hello, Brest (java.version 26)
BUILD SUCCESSFUL
System properties and clonevm
A forked JVM does not inherit the system properties of Ant’s JVM. sysproperty and syspropertyset pass the ones the
program needs. clonevm="true" instead makes the forked JVM’s system properties and boot class path the same as Ant’s,
which is useful when Ant is started with options such as -Dhttp.proxyHost=... through ANT_OPTS and the program should
see them. The system property ant.build.clonevm does the same for every forked JVM in the build; it must be a real system
property, so it cannot be set on the Ant command line (-D sets an Ant property): put it in ANT_OPTS.
The build file has two targets, one with and one without clonevm. Both run with
ANT_OPTS="-Dtidepool.units=metric" in the environment:
<target name="no-clonevm" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" fork="true">
<arg value="info"/>
</java>
</target>
<target name="clonevm" depends="compile">
<java classname="org.tidepool.Main" classpath="build/classes" fork="true" clonevm="true">
<arg value="info"/>
</java>
</target>
ANT_OPTS="-Dtidepool.units=metric" ant -f run-java3.xml no-clonevm
ANT_OPTS="-Dtidepool.units=metric" ant -f run-java3.xml clonevm
Filtered to the relevant line, the two runs print:
[java] tidepool.units=<unset>
[java] tidepool.units=metric
Source: Ant manual: ant.build.clonevm
Starting a background process with spawn
spawn="true" (with fork="true") starts a process that outlives the build, for example a server that later tests talk to.
The task does not wait for it, does not capture its output and cannot time it out. exec has the same attribute, and
the example uses exec to show the effect. The build starts a shell that sleeps for two seconds and then writes a
file; when the build checks, the file is not there yet, and it appears after Ant has ended.
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-spawn" default="spawn" basedir=".">
<target name="spawn">
<delete file="build/spawned.txt"/>
<exec executable="sh" spawn="true">
<arg value="-c"/>
<arg value="sleep 2; echo finished > build/spawned.txt"/>
</exec>
<available file="build/spawned.txt" property="done.already"/>
<echo message="when Ant gets here, spawned.txt exists: ${done.already}"/>
</target>
</project>
spawn:
[echo] when Ant gets here, spawned.txt exists: ${done.already}
BUILD SUCCESSFUL
ls: build/spawned.txt: No such file or directory
finished
When Ant itself runs as a background process on a Unix-like system (ant &), a forked java, exec or apply task that does
not set spawn tries to read the terminal and suspends Ant. Give it explicit input — inputstring or input — or set
spawn="true".
Running native programs with exec
exec runs one native command. Name the program in executable and give its arguments as nested <arg> elements. The
command attribute, which takes the whole command line as a string, is deprecated, and Ant prints a deprecation message
when it is used.
| Attribute | Meaning | Default |
|---|---|---|
|
the program, without arguments |
required (or |
|
the working directory |
the current directory when |
|
run only on the named operating systems ( |
any |
|
fail the build if the exit status signals failure |
|
|
fail the build if the program cannot be started |
|
|
a property to receive the exit status |
none |
|
kill the program after this many milliseconds; the status is then -1 |
none |
|
start the program and leave it running when Ant ends |
|
|
redirect the streams |
the Ant log |
|
standard input |
EOF: a program reading its input sees end of file since Ant 1.6 |
|
do not pass Ant’s environment variables when |
|
|
start the process through the JVM; if |
|
|
resolve |
|
|
search the |
|
|
throw the output or error stream away (since 1.10.10) |
|
Nested elements are <arg>, <env> and <redirector>. Each <env> sets one environment variable for the child process
through key and exactly one of value, path (a PATH-like value; : and ; are converted) or file (the absolute
path of a file). Environment variables of the Ant process are available to the build as ${env.NAME} after
<property environment="env"/>, as described on
Properties.
Arguments
The nested <arg> has value (one argument, passed as is, however many spaces it holds), line (a command-line string
split at spaces), file (the absolute path of a file), path (a path-like value converted to the platform’s
separators) and pathref, plus prefix and suffix. The mistake the manual calls out is <arg value="-l -a"/>: it
passes one argument that contains a space, and most programs reject it. Write two <arg> elements, or use line, which
the manual says to avoid when possible, because Ant splits it much as a Unix shell would and the result can differ from what
you expect.
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-exec" default="exec-basic" basedir=".">
<property environment="env"/>
<target name="exec-basic">
<exec executable="uname" outputproperty="os.kernel" failonerror="true">
<arg value="-s"/>
</exec>
<echo message="kernel: ${os.kernel}"/>
<!-- one argument containing spaces: use value, not line -->
<exec executable="echo">
<arg value="station list"/>
<arg line="--units metric"/>
</exec>
<!-- environment variable for the child process only -->
<exec executable="sh" outputproperty="env.out">
<env key="TIDEPOOL_HOME" value="${basedir}/data"/>
<arg value="-c"/>
<arg value="echo home=$TIDEPOOL_HOME"/>
</exec>
<echo message="${env.out}"/>
</target>
<target name="exec-result">
<exec executable="sh" resultproperty="rc" failonerror="false">
<arg value="-c"/>
<arg value="echo about to fail 1>&2; exit 4"/>
</exec>
<echo message="rc=${rc}"/>
<exec executable="sh" failonerror="true">
<arg value="-c"/>
<arg value="exit 4"/>
</exec>
</target>
<target name="exec-missing">
<exec executable="no-such-program" failifexecutionfails="false" resultproperty="rc.missing"/>
<echo message="rc.missing=${rc.missing}"/>
<exec executable="no-such-program"/>
</target>
<target name="exec-os">
<exec executable="cmd.exe" osfamily="windows" failonerror="true">
<arg line="/c echo only on Windows"/>
</exec>
<exec executable="echo" osfamily="unix">
<arg value="only on Unix"/>
</exec>
</target>
<target name="exec-timeout">
<exec executable="sleep" timeout="1500" failonerror="true">
<arg value="30"/>
</exec>
</target>
<target name="exec-input">
<exec executable="tr" outputproperty="shout">
<arg value="a-z"/>
<arg value="A-Z"/>
<redirector inputstring="brest sydney" />
</exec>
<echo message="${shout}"/>
<exec executable="cat">
<redirector outputproperty="cat.out" errorproperty="cat.err"
inputstring="blah before blah" alwayslog="true">
<inputfilterchain>
<replacestring from="before" to="after"/>
</inputfilterchain>
</redirector>
</exec>
<echo message="property cat.out = ${cat.out}"/>
</target>
</project>
exec-basic:
[echo] kernel: Darwin
[exec] station list --units metric
[echo] home=/work/tidepool/data
BUILD SUCCESSFUL
The echo line cannot show how value and line differ: both print as one line. The difference matters to the program
that receives them, which gets one argument for value and one for each word of line.
Exit status, missing programs and timeouts
Like java, exec ignores the exit status unless failonerror="true". With resultproperty the status is available
to the build. A command that cannot be started at all — a missing executable, a wrong directory — fails the build
unless failifexecutionfails="false", in which case Ant logs the problem and carries on; the result property is then not set.
The default timeout result is -1.
exec-result:
[exec] about to fail
[exec] Result: 4
[echo] rc=4
BUILD FAILED
/work/tidepool/run-exec.xml:30: exec returned: 4
exec-missing:
[exec] Execute failed: java.io.IOException: Cannot run program "no-such-program" (in directory "/work/tidepool"): Exec failed, error: 2 (No such file or directory)
[echo] rc.missing=${rc.missing}
BUILD FAILED
/work/tidepool/run-exec.xml:39: Execute failed: java.io.IOException: Cannot run program "no-such-program" (in directory "/work/tidepool"): Exec failed, error: 2 (No such file or directory)
exec-timeout:
BUILD FAILED
/work/tidepool/run-exec.xml:52: Timeout: killed the sub-process
In the exec-missing output the Java stack trace that Ant prints for the failed start (the at ... frames and the cause) was
removed. The first failing call is the one with failifexecutionfails="false": its message is logged and the build goes on, but
rc.missing is not set, which is why the second echo prints it literally. The second call is the one that fails.
Restricting a command to a platform uses osfamily:
exec-os:
[exec] only on Unix
BUILD SUCCESSFUL
Because exec does not use a shell, a build for Windows cannot run a .bat file directly; the Ant manual’s workaround is
to run cmd with /c and the batch file as arguments. Running ant.bat -p that way is its example. The cmd.exe call above
was skipped on macOS because of osfamily="windows", and this page did not run a Windows build.
Source: Ant manual: exec
Running a program per file with apply
apply (formerly execon, a name that still works) runs a command with the files of its resource collections added
to the command line. By default it runs the command once per file; with parallel="true" it runs it once with all
the files. Its attributes include everything exec has, plus the following.
| Attribute | Meaning | Default |
|---|---|---|
|
the command, without arguments |
required |
|
one invocation with all the files, rather than one invocation per file |
|
|
in parallel mode, pass at most this many files per invocation (use it when the command line gets too long) |
unlimited |
|
pass names relative to the fileset’s directory, or to |
|
|
use forward slashes in names even on systems that use backslashes |
|
|
|
|
|
add the source file names at the end of the command line; turn it off to place them yourself with |
|
|
do not run the command when no file was found or all are up to date; despite the name, it also covers filelists |
|
|
the directory in which the command is expected to create target files, used with a |
none |
|
ignore timestamps of target files (since 1.6.3) |
|
|
ignore files named in a |
|
|
print a summary |
|
The nested elements are <fileset>, <filelist>, <dirset> and any other resource collection (since 1.7), a <mapper> for
dependency checking, <arg>, <env>, <redirector> and two position markers: <srcfile> places the source file names
between the arguments, and <targetfile> does the same for the target names that the mapper produces. Both accept prefix
and suffix (since 1.8.0), and <targetfile> is allowed only with a mapper.
With a mapper, apply compares each source with its mapped target and skips files whose target is up to date. That makes
it a small incremental build step. The example below does three things: counts the lines of each file (one invocation per
file), prints all the file names in one invocation (with <srcfile/> between two arguments), and sorts each CSV file
into a new file only when the target is out of date, using sh -c so the redirection works.
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-apply" default="demo" basedir=".">
<target name="clean">
<delete dir="build/apply"/>
</target>
<target name="demo">
<mkdir dir="build/apply"/>
<!-- 1. one invocation per file (parallel="false", the default), file name at the end -->
<apply executable="wc" parallel="false" relative="true" dir="tide-data">
<arg value="-l"/>
<fileset dir="tide-data" includes="*.csv"/>
</apply>
<!-- 2. one invocation for all files; place the file names with srcfile -->
<apply executable="echo" parallel="true" dir="tide-data" relative="true">
<arg value="csv files:"/>
<srcfile/>
<arg value="(end)"/>
<fileset dir="tide-data" includes="*.csv"/>
</apply>
<!-- 3. dependency checking with a mapper: only files newer than their target are processed -->
<apply executable="sh" dest="build/apply" parallel="false" verbose="true" skipemptyfilesets="true">
<arg value="-c"/>
<arg value="sort -t, -k2 -n -r $0 > $1"/>
<srcfile/>
<targetfile/>
<fileset dir="tide-data" includes="*.csv"/>
<mapper type="glob" from="*.csv" to="*.sorted"/>
</apply>
</target>
</project>
The first run creates the sorted files, and the second finds them up to date:
demo:
[mkdir] Created dir: /work/tidepool/build/apply
[apply] 2 brest.csv
[apply] 1 sydney.csv
[apply] csv files: brest.csv sydney.csv (end)
[apply] Applied sh to 2 files and 0 directories.
BUILD SUCCESSFUL
demo:
[apply] 2 brest.csv
[apply] 1 sydney.csv
[apply] csv files: brest.csv sydney.csv (end)
[apply] Skipping fileset for directory /work/tidepool/tide-data. It is up to date.
BUILD SUCCESSFUL
In the second run wc and echo ran again, because they have no mapper, but the third apply printed
Skipping fileset ... It is up to date. instead of running.
Source: Ant manual: apply
The redirector type
<redirector> is the shared way of connecting standard input, output and error. exec, apply and java accept one as a
nested element. The attributes of the three tasks that have the same name — output, error, input, outputproperty,
errorproperty, inputstring, append, logError — behave the same when put on the redirector. The redirector adds
more.
| Attribute or element | Meaning | Default |
|---|---|---|
|
send the output to the Ant log as well as to its other destination (since 1.6.3) |
|
|
create output and error files even when empty |
|
|
character sets of the streams |
the JVM default |
|
log the value of |
|
|
do not split the output into lines (since 1.9.4) |
|
|
throw the stream away (since 1.10.10) |
|
|
a mapper that chooses the file for each source file; useful with |
none |
|
a filter chain applied to the stream |
none |
The example sends a string to tr and captures the result, then sends a string to cat through an input filter chain that
rewrites before to after, capturing the output and error in properties. alwayslog logs the output as well.
exec-input:
[echo] BREST SYDNEY
[exec] blah after blah
[echo] property cat.out = blah after blah
BUILD SUCCESSFUL
The exec-input target is part of the run-exec.xml file listed above.
Source: Ant manual: Redirector
The same jobs in Maven and Gradle
| Job | Maven 3.10.0 | Gradle 9.8.1 |
|---|---|---|
run a Java class |
|
a |
run a native command |
|
an |
run a command from a build script |
no equivalent; a plugin goal bound to a phase does it |
|
run a command per file |
no equivalent |
a loop over a file collection in a task action that uses |
Maven has no model for running arbitrary programs: its plugins are the programs, and the exec plugin is the usual
escape hatch. Gradle has two task types for it. Ant’s java task is available from both: in Maven through
maven-antrun-plugin, and in Gradle through the ant object, described on
Ant Integration. The Gradle task model is on
Tasks, and writing a Maven plugin that does the work in
process, instead of starting a program, is on
Writing Plugins.
Source: exec-maven-plugin
Source: Gradle DSL: JavaExec
Source: Gradle DSL: Exec
Errors you will meet
The failing builds below come from run-errors.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project name="run-errors" default="none" basedir=".">
<target name="none"/>
<target name="no-class-in-process">
<java classname="org.tidepool.Mian" classpath="build/classes" failonerror="true"/>
</target>
<target name="no-class-forked">
<java classname="org.tidepool.Mian" classpath="build/classes" fork="true" failonerror="true"/>
</target>
<target name="jar-no-fork">
<java jar="build/lib/tidepool-cli.jar"/>
</target>
<target name="two-ways">
<java classname="org.tidepool.Main" jar="build/lib/tidepool-cli.jar" fork="true"/>
</target>
<target name="neither">
<java fork="true"/>
</target>
<target name="exec-two">
<exec executable="echo" command="echo hi"/>
</target>
<target name="wrong-dir">
<exec executable="echo" dir="no/such/dir"/>
</target>
</project>
no-class-in-process:
BUILD FAILED
/work/tidepool/run-errors.xml:5: Could not find org.tidepool.Mian. Make sure you have it in your classpath
no-class-forked:
[java] Error: Could not find or load main class org.tidepool.Mian
[java] Caused by: java.lang.ClassNotFoundException: org.tidepool.Mian
BUILD FAILED
/work/tidepool/run-errors.xml:8: Java returned: 1
jar-no-fork:
BUILD FAILED
/work/tidepool/run-errors.xml:11: Cannot execute a jar in non-forked mode. Please set fork='true'.
two-ways:
BUILD FAILED
/work/tidepool/run-errors.xml:14: Cannot use combination of 'jar', 'sourcefile', 'classname', 'module' attributes in same command
neither:
BUILD FAILED
/work/tidepool/run-errors.xml:17: Classname must not be null.
wrong-dir:
BUILD FAILED
/work/tidepool/run-errors.xml:23: The directory /work/tidepool/no/such/dir does not exist
The exec-two target, which sets both executable and command, still works and logs The command attribute is deprecated.
and Please use the executable attribute and nested arg elements.
| Message | Cause |
|---|---|
|
a wrong class name or missing |
|
the same mistake in a forked JVM, where the message comes from the JVM |
|
|
|
more than one of the four ways to say what to run |
|
none of them given |
|
|
|
the program ran longer than |
Source: Apache Ant manual: Java
Where older books are out of date
Books written for Ant 1.5, such as the 2002 Java Development with Ant, whose chapter 5 covers running programs, predate several changes:
-
They target JDK 1.3 and 1.4. Ant 1.10 needs Java 8 or later, and the manual now says that Microsoft’s
jview.execannot be used as thejvmof ajavatask (it takes different parameters) and must be started fromexecinstead. -
In-process programs relied on the security manager to stop
System.exit. On Java 18 and later and Ant 1.10.14 and later that protection is gone,<permissions>throws, and the answer isfork="true". -
Many attributes written in old build files, such as
argsandjvmargsonjavaandcommandonexec, are deprecated in favour of nested elements. -
Modules (
module,modulepath, since Ant 1.9.7), single-file programs (sourcefile, 1.10.5) and thediscardOutputattributes (1.10.10) did not exist. -
execon Windows has not changed in substance: batch files still needcmd /c.
Source: Ant manual: java
Where to go next
-
File Operations for the tasks that prepare the files a program works on.
-
Testing with JUnit & junitlauncher for forking test JVMs.
-
Filters & Mappers for the filter chains and mappers used with redirectors.
-
Paths, Filesets & Patterns for the resource collections
applytakes. -
Conditions & Control Flow for the
oscondition behindosfamily. -
Running Ant for
ANT_OPTSand the other ways to start Ant.