How To write a plugin or a buildtype for easyant

A build module in Easyant is a logical unit that provides additional pluggable functionality to your build set up. You may choose to use or ignore such a plugin when running the build. A build module is composed, in the least, of a ant file associated with a ivy specs file.
So let's write a Hello World plugin.

Generating plugin from a skeleton

First we need to create a plugin structure. To ease plugin development easyant came with a skeleton for plugins.
> easyant skeleton:newplugin
It will then ask you a few questions
    [input] The path where the skeleton project will be unzipped [.]

[input] Organisation name of YOUR project [org.apache.easyant.plugins]
org.mycompany
[input] Module name of YOUR project
myplugin
[input] Revision number of YOUR project [0.1]
That's all !
We've a ready to use plugin structure.
|-- module.ivy
`-- src
|-- main
| `-- resources
| `-- myplugin.ant
`-- test
`-- antunit
`-- myplugin-test.xml

Ant file

The skeleton has generated the plugin main file in src/main/resources/[MYPLUGIN].ant
<project name="org.mycompany;myplugin" 
xmlns:ivy="antlib:org.apache.ivy.ant"
xmlns:ea="antlib:org.apache.easyant">

<!-- Force compliance with easyant-core to 0.7 or higher -->
<!-- <ea:core-version requiredrevision="[0.7,+]" /> -->

<!-- Sample init target -->
<target name="myplugin:init">
<!-- you should remove this echo message -->
<echo level="debug">This is the init target of myplugin</echo>
</target>
</project>
By convention, projectname of the plugin should be formed like
[organisation]#[module]
Example:
org.mycompany#myplugin

Understanding extension-point

Extension-points are plugins hooks. Plugins typically add low-level tasks to one or more extension-points. For example, a plugin can contribute to processing sources before compilation, you will in that case plug your own target to "abstract-compile:compile-ready" extension-point". This plugin hooks is defined in abstract-compile plugin".

So we need to import this plugin and plug our own target on it.
<ea:plugin module="abstract-compile" revision="0.9"/>
<target name="myplugin:mytarget" extensionOf="abstract-compile:compile-ready">
...//your stuff here
</target>
Less typically, a plugin can also define new extension-points for other plugins to use. We highly recommend in that case to create an "abstract" plugin defining common stuff and extension-points to limit coupling between plugins and make them more flexible.

In standard build types the project-lifecycle is defined by a plugin named phases-std. This plugin loads the default lifecycle containing a set of high level extensionPoints like compile,package.
It's build types responsability to import this plugin and and do the binding between targets and extension-points from this lifecycle.

Target Naming Conventions

By default, all targets should be prefix by the plugin name. In our example "init" target is prefixed by "myplugin".

There is a conventional difference in the way public and private targets are named in Easyant. A public target is one that makes sense for the end user to be aware of, while a private target should be hidden from the end user.

Conventionally, Those conventions are not mandatory. They just ease understanding.

Example :
<target name="myglugin:helloworld" depends="myplugin:init" description="display an hello world">
<echo>hello world !</echo>
</target>

<target name="myplugin:hello" depends="myplugin:init,-check-username" description="display an hello to current user">
<echo mess="Hello ${username}"/>
</target>
Whereas a private target name should begin with '-'.

Example :
<!-- this target initialize username property if it's not already set -->
<target name="-myplugin:check-username" unless="username">
<echo>You can also add a "-Dusername=YOU" on the commandline to display a more personal hello message</echo>
<property name="username" value="${user.name}"/>
</target>

Pre conditions

A build module should always check that a set of pre conditions is met.
This can be done at the root level of your plugin or in a target. We encourage you to use a target for initialisation as you can control when it should be executed. If intialisation is done at the root level it will be executed when the plugin is loaded.

By convention, the initialisation target should be named ":init".

Pre conditions, including for example - checking the existence of a file or a directory, could be performed inside this target. Additionally, this target is a great place to do global initializations that are needed for the rest of the build. This could include a taskdef initialization.
Pre conditions can be performed by using parameter task.
Example :
<target name="myplugin:init">
<ea:parameter property="username" required="false" description="the username used to display en 'hello Username' by calling :hello target"/>
</target>

What should be documented

The following elements needs to be documented

Publishing your plugin

You can easily publish your plugin to an easyant repository using the standard phases publish-shared (for snapshot) or release
>  easyant publish-local
>  easyant publish-shared
>  easyant release
By default plugins are published to a repository named easyant-shared-modules stored in $USER_HOME/.easyant/repository/easyant-shared-modules/.

You can specify the repository name using one of the following property
Note: Repository must exist in easyant ivy instance. See configure easyant ivy instance man page for more informations.

Using your plugin in your project

Considering that you published your plugin in a easyant repository, you could use it in your project.
<ivy-module version="2.0" xmlns:ea="http://www.easyant.org"> 
<info organisation="org.mycompany" module="myproject"
status="integration" revision="0.1">
<ea:build module="build-std-java" revision="0.2">
<ea:plugin organisation="org.mycompany" module="myplugin" revision="0.1"/>
</ea:build>
</info>
<publications>
<artifact name="myplugin" type="ant"/>
</publications>
</ivy-module>
And now running
> easyant -p 
We should see myplugin's target.
Main targets:
...
myplugin:hello display an hello to current user
myplugin:helloworld display an hello world
...

Getting further

Adding additional files to your module

Sometimes, we need to have a .properties files related to a given plugin.
Sometimes it could be an additional file (an .xsl file for example).

Before using it we must declare the new file in the plugin module descriptor.
Open the module.ivy at the root level of plugin structure.
<ivy-module version="2.0" xmlns:ea="http://www.easyant.org"> 
<info organisation="org.mycompany" module="myplugin"
status="integration" revision="0.1">
<!-- here we use build-std-ant-plugin build type that provide everything we need for plugin development -->
<ea:build module="build-std-ant-plugin" revision="0.9"/>
</info>
<configurations>
<conf name="default" description="runtime dependencies artifact can be used with this conf"/>
<conf name="test" description="this scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases."/>
<conf name="provided" description="this is much like compile, but indicates you expect the JDK or a container to provide it. It is only available on the compilation classpath, and is not transitive."/>
</configurations>
<publications>
<!--Defines the plugin main script -->
<artifact name="myplugin" type="ant"/>
<!--Defines a property file -->
<artifact name="myplugin" type="properties"/>
<artifact name="myfile" type="xsl"/>
</publications>
</ivy-module>
Here we defined that our plugin is composed of 3 files : Now we will see how we can use those files in our plugin script.
Considering that a plugin must be generic and can be retrieved from different repository (filesystem, url, ftp, etc...) we should take care of how we reference those additional files in our script.
To avoid any problems due to repository layout configuration, easyant gives you gives you access to properties containing the absolute path of a declared artifact. Those properties are composed with the following syntax :
[organisation].[module].[artifact].[type].file
Example:
org.mycompany.myplugin.myfile.xsl.file
The '.artifact' is optional when module name and artifact name are the same.
[organisation].[module].[type].file
Example:
org.mycompany#myplugin.properties.file
So loading a property file could be easy as :
<property file="${org.mycompany#myplugin.properties.file}" />
If you want to copy / use an additional file
<copy file="${org.mycompany.myplugin.myfile.xsl.file}" tofile="..."/>

Using third party libraries

Most of the time when we write plugins we want to use third party ant tasks.

Declaring dependencies in module.ivy

First we need to declare the dependency in the plugin module.ivy.
<ivy-module version="2.0" xmlns:ea="http://www.easyant.org"> 
<info organisation="org.mycompany" module="myplugin"
status="integration" revision="0.1">
<ea:build module="build-std-ant-plugin" revision="0.1"/>
</info>
<configurations>
<conf name="default" description="runtime dependencies artifact can be used with this conf"/>
<conf name="test" description="this scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases."/>
<conf name="provided" description="this is much like compile, but indicates you expect the JDK or a container to provide it. It is only available on the compilation classpath, and is not transitive."/>
</configurations>
<publications>
<artifact name="myplugin" type="ant"/>
</publications>

<dependencies>
<!-- your plugin dependencies goes here -->
<dependency org="foobar" name="amazingAntTask" rev="4.4" conf="default->default" />
<dependency org="foobar" name="myOtherAntTask" rev="4.4" conf="default->default" />
</dependencies>
</ivy-module>
Here we depend on amazingAntTask and myOtherAntTask provided by foobar organisation.

Using dependency in your plugin ant script?

Easyant automatically creates a classpath specific for each plugin, this classpath contains all the required dependency .jars.

The classpath is named
[organisation]#[module].classpath
Example:
org.mycompany#myplugin.classpath
Since this classpath is auto-created you can use it to reference your taskdef.
<target name="myplugin:init">
...
<taskdef resource="amazingAntTask.properties" classpathref="org.mycompany#myplugin.classpath" />
<taskdef resource="anotherAntTask.properties" classpathref="org.mycompany#myplugin.classpath" />
</target>

Compatibilty with core revision

A module can be dependent on features available in Easyant core. As such, it is possible for a module to be functional with particular versions of Easyant only.
Easyant provides a way for modules to explicitly specify their dependency on core revisions.
A module may use the ea:core-version task to specify such a dependency.
A task may depend on:
<project name="org.mycompany;myplugin" 
xmlns:ivy="antlib:org.apache.ivy.ant"
xmlns:ea="antlib:org.apache.easyant">

<!-- Force compliance with easyant-core to 0.7 or higher -->
<ea:core-version requiredrevision="[0.7,+]" />

<!-- Sample init target -->
<target name=":init">
<!-- you should remove this echo message -->
<echo level="debug">This is the init target of myplugin</echo>
</target>
</project>

Writing plugin test case

By default the skeleton has generated a antunit test file in src/test/antunit/[module]-test.ant.

So in our case let's open "src/test/antunit/myplugin-test.xml"
<project name="org.mycompany;myplugin-test" 
xmlns:au="antlib:org.apache.ant.antunit"
xmlns:ivy="antlib:org.apache.ivy.ant"
xmlns:ea="antlib:org.apache.easyant">

<!-- Import your plugin -->
<property name="target" value="target/test-antunit"/>
<!-- configure easyant in current project -->
<ea:configure-easyant-ivy-instance />
<!-- import our local plugin -->
<ea:import-test-module moduleIvy="../../../module.ivy" sourceDirectory="../../main/resources"/>

<!-- Defines a setUp / tearDown (before each test) that cleans the environment -->
<target name="clean" description="remove stale build artifacts before / after each test">
<delete dir="${basedir}" includeemptydirs="true">
<include name="**/target/**"/>
<include name="**/lib/**"/>
</delete>
</target>

<target name="setUp" depends="clean"/>
<target name="tearDown" depends="clean"/>

<!-- init test case -->
<target name="test-myplugin:init" depends="myplugin:init">
<au:assertLogContains level="debug" text="This is the init target of myplugin"/>
</target>

</project>
Here we : All targets prefixed by "test" will be executed as a test case (similar to junit 3 behavior).

Now we will write a test case for our "myplugin:helloworld" target.
<target name="test-helloworld" depends="myplugin:helloworld">
<au:assertLogContains text="hello world !"/>
</target>
Tests can be executed by running :
> easyant test
You can access test-report at "target/antunit/html/index.html" or if you prefer the brut result "target/antunit/xml/TEST-src.test.antunit.myplugin-test_xml.xml".