Everybody hates build.xml (code reuse in Ant)

Andy Balaam

2013-01-18

Contents

What is Ant?

It's like a Makefile:

target: dep1 dep2   # Declarative
    action1         # Procedural
    action2

The declarative language

The declarative language is a directed graph of targets and dependencies

<target name="A"/>
<target name="B" depends="A"/>
<target name="C" depends="B"/>
<target name="D" depends="A"/>

The procedural language

The procedural language is a list of tasks

<target ...>
    <javac ...>
    <copy ...>
    <zip ...>
    <junit ...>
</target>

Avoiding repeated code

Two similar targets:

<target name="A">
    <javac
        srcdir="a/src" destdir="a/bin"
        classpath="myutil.jar" debug="false"
    />
</target>

<target name="B">
    <javac
        srcdir="b/code" destdir="b/int"
        classpath="myutil.jar" debug="false"
    />
</target>

How to share the classpath and debug information?

(Imagine it's too complex to be just properties.)

The Wrong Way: antcall

<target name="compile">
    <javac
        srcdir="${srcdir}" destdir="${destdir}"
        classpath="myutil.jar" debug="false"
    />
</target>

<target name="A">
    <antcall target="compile">
        <param name="srcdir" value="a/src"/>
        <param name="destdir" value="a/bin"/>
    </antcall>
</target>

<target name="B">
    <antcall target="compile">
    ...

Why not antcall?

The Horrific Way: custom tasks

Java:

public class MyCompile extends Task {
    public void execute() throws BuildException
    {
        Project p = getProject();

        Javac javac = new Javac();

        javac.setSrcdir(  new Path( p, p.getUserProperty( "srcdir" ) ) );
        javac.setDestdir( new File( p.getUserProperty( "destdir" ) ) );
        javac.setClasspath( new Path( p, "myutil.jar" ) );
        javac.setDebug( false );
        javac.execute();
    }
}

The Horrific Way: custom tasks

Ant:

<target name="first">
    <javac srcdir="mycompile"/>
    <taskdef name="mycompile" classname="MyCompile"
        classpath="mycompile"/>
</target>

<target name="A" depends="first">
    <mycompile/>
</target>

<target name="B" depends="first">
    <mycompile/>
</target>

The Relatively OK Way: macrodef

<macrodef name="mycompile">
    <attribute name="srcdir"/>
    <attribute name="destdir"/>
    <sequential>
        <javac
            srcdir="@{srcdir}" destdir="@{destdir}"
            classpath="myutil.jar" debug="false"
        />
    </sequential>
</macrodef>

<target name="A">
    <mycompile srcdir="a/src" destdir="a/bin"/>
</target>

<target name="B">
    <mycompile srcdir="b/code" destdir="b/int"/>
</target>

Avoiding repeated dependencies?

<target name="everyoneneedsme"...

<target name="A" depends="everyoneneedsme"...
<target name="B" depends="everyoneneedsme"...
<target name="C" depends="everyoneneedsme"...
<target name="D" depends="everyoneneedsme"...

In Ant, I don't know how to share this.

Avoiding repeated dependencies?

Use Gradle:

task everyoneneedsme

tasks.whenTaskAdded { task ->
    task.dependsOn everyoneneedsme
}

task A
task B
task C
task D

(Disclaimer: I haven't run this.)

Discussion