blob: b52bc64ffefb8bdd4a59697cb2b94ebdf49fff92 [file] [log] [blame]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">
<script type="text/javascript">var xookiConfig = {level: 1};</script>
<script type="text/javascript" src="../xooki/xooki.js"></script>
</head>
<body>
<textarea id="xooki-source">
<h1>How To write a plugin or a buildtype for easyant</h1>
<p>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.</p>
<h2>Generating plugin from a skeleton</h2>
First we need to create a plugin structure. To ease plugin development easyant came with a skeleton for plugins.
<code type="shell">> easyant skeleton:newplugin</code>
It will then ask you a few questions
<code type="shell">
[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]
</code>
That's all !
We've a ready to use plugin structure.
<!--replace me by an image -->
<code type="shell">
|-- module.ivy
`-- src
|-- main
| `-- resources
| `-- myplugin.ant
`-- test
`-- antunit
`-- myplugin-test.xml
</code>
<h2>Ant file</h2>
The skeleton has generated the plugin main file in src/main/resources/[MYPLUGIN].ant
<code type="xml">
<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>
</code>
By convention, projectname of the plugin should be formed like
<code type="xml">
[organisation]#[module]
</code>
Example:
<code type="xml">
org.mycompany#myplugin
</code>
<h3>Understanding extension-point</h3>
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.
<code type="xml">
<ea:plugin module="abstract-compile" revision="0.9"/>
<target name="myplugin:mytarget" extensionOf="abstract-compile:compile-ready">
...//your stuff here
</target>
</code>
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.
<h3>Target Naming Conventions</h3>
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 <i>public target</i> is one that makes sense for the end user to be aware of, while a <i>private target</i> should be hidden from the end user.
Conventionally,
<ul>
<li>a public target should always have an associated 'description' attribute.</li>
<li>a private target should begin with a "-"</li>
</ul>
Those conventions are not mandatory. They just ease understanding.
Example :
<code type="xml">
<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>
</code>
Whereas a private target name should begin with '-'.
Example :
<code type="xml">
<!-- 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>
</code>
<h3>Pre conditions</h3>
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 <a href="../ref/anttasks/Parametertask.html">parameter task</a>.
Example :
<code type="xml">
<target name="myplugin:init">
<ea:parameter property="username" required="false" description="the username used to display en 'hello Username' by calling :hello target"/>
</target>
</code>
<h3>What should be documented</h3>
The following elements needs to be documented
<ul>
<li>public targets / extension points descriptions</li>
<li>parameters (properties, resource collections, paths). For each parameter specify name, description, whether it is required, and optionally a default value. This should be done with <a href="../ref/anttasks/Parametertask.html">parameter task</a></li>
<li>expected environment (files in a directory, a server up and running, ...)</li>
<li>results produced</li>
</ul>
<h2>Publishing your plugin</h2>
You can easily publish your plugin to an easyant repository using the standard phases <i>publish-shared (for snapshot)</i> or <i>release</i>
<code type="shell">> easyant publish-local</code>
<code type="shell">> easyant publish-shared</code>
<code type="shell">> easyant release</code>
By default plugins are published to a repository named <i>easyant-shared-modules</i> stored in $USER_HOME/.easyant/repository/easyant-shared-modules/.
You can specify the repository name using one of the following property
<ul>
<li>release.resolver</li>
<li>snapshot.resolver</li>
</ul>
<div id="note">Note: Repository must exist in easyant ivy instance. See <a href="../ref/ConfigureEasyantIvySettings.html">configure easyant ivy instance</a> man page for more informations.</div>
<h2>Using your plugin in your project</h2>
Considering that you published your plugin in a easyant repository, you could use it in your project.
<code type="xml">
<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>
</code>
And now running
<code type="shell">> easyant -p </code>
We should see myplugin's target.
<code type="shell">
Main targets:
...
myplugin:hello display an hello to current user
myplugin:helloworld display an hello world
...
</code>
<h2>Getting further</h2>
<h3>Adding additional files to your module</h3>
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.
<code type="xml">
<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>
</code>
Here we defined that our plugin is composed of 3 files :
<ul>
<li>myplugin.ant (if name argument is not specified the module name will be used)</li>
<li>myplugin.properties</li>
<li>myfile.xsl</li>
</ul>
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 :
<code type="xml">
[organisation].[module].[artifact].[type].file
</code>
Example:
<code type="xml">
org.mycompany.myplugin.myfile.xsl.file
</code>
The '.artifact' is optional when module name and artifact name are the same.
<code type="xml">
[organisation].[module].[type].file
</code>
Example:
<code type="xml">
org.mycompany#myplugin.properties.file
</code>
So loading a property file could be easy as :
<code type="xml">
<property file="${org.mycompany#myplugin.properties.file}" />
</code>
If you want to copy / use an additional file
<code type="xml">
<copy file="${org.mycompany.myplugin.myfile.xsl.file}" tofile="..."/>
</code>
<h3>Using third party libraries</h3>
Most of the time when we write plugins we want to use third party ant tasks.
<h4>Declaring dependencies in module.ivy</h4>
First we need to declare the dependency in the plugin module.ivy.
<code type="xml">
<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>
</code>
Here we depend on amazingAntTask and myOtherAntTask provided by foobar organisation.
<h4>Using dependency in your plugin ant script?</h4>
Easyant automatically creates a classpath specific for each plugin, this classpath contains all the required dependency <i>.jars</i>.
The classpath is named
<code tyep="xml">
[organisation]#[module].classpath
</code>
Example:
<code type="xml">
org.mycompany#myplugin.classpath
</code>
Since this classpath is auto-created you can use it to reference your taskdef.
<code type="xml">
<target name="myplugin:init">
...
<taskdef resource="amazingAntTask.properties" classpathref="org.mycompany#myplugin.classpath" />
<taskdef resource="anotherAntTask.properties" classpathref="org.mycompany#myplugin.classpath" />
</target>
</code>
<h3>Compatibilty with core revision</h3>
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:
<ul>
<li>static version (Example : 0.5)</li>
<li>dynamic version (Example : latest.revision) even if we do not recommand to use it</li>
<li>listed version (Example : (0.1,0.3,0.5) )</li>
<li>range version (Example : [0.5,0.8] means from 0.5 to 0.8. Example2 : [0.5,+] means all version superior to 0.5)</li>
</ul>
<code type="xml">
<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>
</code>
<h3>Writing plugin test case</h3>
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"
<code type="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>
</code>
Here we :
<ul>
<li>configure easyant for test</li>
<li>import the plugin</li>
<li>define a generic tearDown, setUp method that cleans the target and lib directories.</li>
<li>define a test case for the init target that check that the output log contains "This is the init target of myplugin"</li>
</ul>
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.
<code type="xml">
<target name="test-helloworld" depends="myplugin:helloworld">
<au:assertLogContains text="hello world !"/>
</target>
</code>
Tests can be executed by running :
<code type="shell">> easyant test</code>
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".</textarea>
<script type="text/javascript">xooki.postProcess();</script>
</body>
</html>