I'm really confused with the proper (most modern, best practices, etc.) way of defining taskdefs for Ant plugins. Below is a snippet of code that I pieced together taken from one of our in-house Java app's build.xml
. Please note that all of the following Ant targets work perfect 100% (even though I've cut-n-pasted them together, and stripped out most of their contents for brevity's sake):
<project name="MyApp" default="package" basedir="." xmlns:jacoco="antlib:org.jacoco.ant">
<path id="cobertura.path">
<!-- ... -->
</path>
<taskdef name="jacoco-coverage" classname="org.jacoco.ant.CoverageTask"/>
<taskdef name="jacoco-report" classname="org.jacoco.ant.ReportTask"/>
<taskdef classpathref="cobertura.path" resource="tasks.properties" />
<taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask"/>
<target name="run-coco" depends="doOtherStuff">
<jacoco:coverage>
<!-- ... -->
</jacoco:covergae>
<jacoco-report>
<!-- ... -->
</jacoco-report>
</target>
<target name="findbugs">
<antcall target="compile" />
<findbugs home="${findbugs.home}" output="xml:withMessages" outputFile="findbugs.xml">
<!-- ... -->
</findbugs>
</target>
</project>
jacoco-coverage
and javacoco-report
taskdefs use a hyphen ("-") in their name, the corresponding tasks are called jacoco:coverage
and jacoco-report
respectively...why the colon (":") for coverage, and how does that even work (it does, trust me!)?Thanks in advance!
Let's take this question one step at a time:
You can define a task in one of two ways:
In your case for JaCoCo, you did the first:
<taskdef name="jacoco-coverage"
classname="org.jacoco.ant.CoverageTask"/>
<taskdef name="jacoco-report"
classname="org.jacoco.ant.ReportTask"/>
However, you could do it the second way too:
<taskdef resource="org/jacoco/ant/antlib.xml"/>
If you open up the jacoco jar file and drill down in that directory, you will see a file called antlib.xml
. If you look at that file, you will see this:
<antlib>
<taskdef name="coverage" classname="org.jacoco.ant.CoverageTask"/>
<taskdef name="agent" classname="org.jacoco.ant.AgentTask"/>
<taskdef name="report" classname="org.jacoco.ant.ReportTask"/>
<taskdef name="merge" classname="org.jacoco.ant.MergeTask"/>
<taskdef name="dump" classname="org.jacoco.ant.DumpTask"/>
</antlib>
So, if there is a resource file located in the jar, you can define all of your tasks with a single <taskdef>
. Note that what you called jacoco-coverage
is simply called coverage
here and what you called jacoco-report
is called report
here.
Where you used <jacoco-coverage/>
as an Ant task, I would use <coverage/>
.
In the above, I defined the task as:
<taskdef resource="org/jacoco/ant/antlib.xml">
Somewhere, Ant has to find that path in its classpath, but where? If you put the JaCoCo jar into your $ANT_HOME/lib
folder, it will automatically included in your classpath. It makes defining the task very easy to do. Unfortunately, it also means that if someone else wants to run your Ant script, they'll have to download that JaCoCo jar and put it in their $ANT_HOME/lib
folder. Not very portable.
Fortunately, you can specify where that JaCoCo jar is located. Since your project is usually under version control, the best place to put it is under your project's directory just below where the build.xml
is located. When someone checks out your project, and the build.xml
, they'll get the JaCoCo.jar too.
My preference is to create a directory called antlib
and then create a subdirectory in that antlib
directory for each and every set of tasks. In this case, I would have a directory called antlib/jacoco
.
Once my jacoco.jar file is safely ensconced. in that directory, I can add a <classpath>
sub-entity to my <taskdef>
saying where to find jacoco.jar
:
Before Classpath:
<taskdef resource="org/jacoco/ant/antlib.xml"/>
After Classpath:
<taskdef resource="org/jacoco/ant/antlib.xml">
<classpath>
<fileset dir="${basedir}/antlib/jacoco"/>
</classpath>
</taskdef>
Note by using <fileset/>
, I don't care whether my JaCoCo jar file is called jacoco.jar
or jacoco-2.3.jar
. If you know the jar's exact name, you could do this:
<taskdef resource="org/jacoco/ant/antlib.xml">
<classpath path="${basedir}/antlib/jacoco/jacoco.jar"/>
</taskdef>
And save a couple of lines.
As of this point, I don't need jacoco:
prefixed to my task names.
I could simply do this:
<project name="MyApp" default="package" basedir=".">
<taskdef resource="org/jacoco/ant/antlib.xml">
<classpath path="${basedir}/antlib/jacoco/jacoco.jar"/>
</taskdef>
<target name="run-coco" depends="doOtherStuff">
<coverage>
<!-- ... -->
<coverage>
<report>
<!-- ... -->
<report>
</target>
</project>
And, you could leave it at that. No problems at all. Simple and clean.
However, what if you are using two different Ant task sets, one called Foo
and one called Bar
, and both happen to have a <munge/>
task defined in them?
<taskdef resource="org/foo/ant/antlib.xml">
<classpath path="${basedir}/antlib/foo.jar/>
</taskdef>
<taskdef resource="org/bar/ant/antlib.xml">
<classpath path="${basedir}/antlib/bar.jar/>
</taskdef>
<target name="munge-this">
<!-- Is this foo.jar's or bar.jar's munge task? -->
<munge vorbix="fester"/>
</target>
Which munge
task is being executed? Is it the one in foo.jar
or the one in bar.jar
?
To get around this, Ant allows you to define an XML Namespace for each set of tasks. You can define such a namespace in the very top <project>
entity, inside a task itself, or in a <target>
name. 99% of the time, it's done in the <project>
entity.
<project name="demo" default="package" basename="."
xmlns:foo="I-will-have-fries-with-that"
xmlns:bar="Never-on-a-first-date">
<taskdef uri="I-will-have-fries-with-that"
resource="org/foo/ant/antlib.xml">
<classpath path="${basedir}/antlib/foo.jar/>
</taskdef>
<taskdef uri="Never-on-a-first-date"
resource="org/bar/ant/antlib.xml">
<classpath path="${basedir}/antlib/bar.jar/>
</taskdef>
<target name="munge-this">
<!-- Look the 'foo:' XMLNS prefix! It's foo.jar's much task! -->
<foo:munge vorbix="fester"/>
</target>
Now, I know that this is the <munge/>
task from the Foo task list!
The xmlns
defines an XML namespace. The xmlns:foo
defines a namespace for the Foo set of taks and the xmlns:bar
defines it for the bar set of task. When I use a task from foo.jar
, I prefix it with the foo:
namespace. Note that this prefix is the one right after xmlns:
.
The uri
is merely a string that matches the namespace string. I used the strings I-will-have-fries-with-that
and Never-on-a-first-date
to show you that the strings themselves aren't important. What is important is that this string matches the uri
parameter of the <taskdef>
task. This way, tasks being defined know what namespace they should use.
Now, normally the URI string is a URI definition. There are two ways to go with this:
antlib:
URI that is somewhat standardized, and uses that reverse naming for the namespace: antlib:org.jacoco.ant
.http://www.eclemma.org/jacoco/
.I prefer the latter because it'll point to the documentation. However it looks like the first is more popular.
So, let's look at how Jacoco will work:
<project name="MyApp" default="package" basedir="."
xmlns:jacoco="antlib:org.jacoco.ant">
<taskdef uri="antlib:org.jacoco.ant"
resource="org/jacoco/ant/antlib.xml">
<classpath path="${basedir}/antlib/jacoco/jacoco.jar"/>
</taskdef>
<target name="run-coco" depends="doOtherStuff">
<jacoco:coverage>
<!-- ... -->
<jacoco:coverage>
<jacoco:report>
<!-- ... -->
<jacoco:report>
</target>
</project>
Note that the uri
in the JaCoCo <taskdef/>
is the same as the xmlns:jacoco
string.
That's all there is to task definitions. I hope it explains where the jacoco:
prefix comes and how you can define tasks by pointing to an actual classname that contains that class, or to a resource file that points to both the task name and the class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With