Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the variations on Ant plugin taskdefs?

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>
  1. Why do I need to define an XML namespace for JaCoCo but not for Findbugs?
  2. What is antlib? Sometimes I see it inside the XML namespace definition, and sometimes it's not there.
  3. Although both 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!

like image 455
IAmYourFaja Avatar asked Dec 26 '22 05:12

IAmYourFaja


1 Answers

Let's take this question one step at a time:

Step #1: Defining your task

You can define a task in one of two ways:

  1. You can point to a class file, and then give the task that classfile defines a name.
  2. You can point to resource file inside the jar that has the task names and the classfile that those task names use.

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/>.

Step #2: Where's the JAR file?

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.

Step #3: XML Namespace

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:

  • Use an antlib: URI that is somewhat standardized, and uses that reverse naming for the namespace: antlib:org.jacoco.ant.
  • Use the URL that points to the project: 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.

like image 181
David W. Avatar answered Jan 26 '23 00:01

David W.