blob: 1eb50b91ac3920dd6e8c380ef417217bcc213329 [file] [log] [blame]
-----
Guide to Developing Ant Plugins
---
John Casey
-----
09 January 2006
-----
~~ 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.
~~ NOTE: For help with the syntax of this file, see:
~~ http://maven.apache.org/doxia/references/apt-format.html
~~ NOTE: This guide has been adapted from "Guide to Developing Java Plugins"
~~ by Bob Allison.
Developing Ant Plugins for Maven 2.x
*WARNING
<<The documentation below assumes that you have updated your locally cached
cached copy of the maven-plugin-plugin. To update your copy, you will need to
include the -U option when you build your plugin project:>>
+---+
mvn -U clean install
+---+
The maven-plugin-plugin is responsible for reading plugin metadata in its
various forms and writing a standard Maven plugin descriptor based on the input.
It was designed to accommodate multiple plugin languages side by side, but its
initial design was slightly flawed for plugin languages that don't include the
metadata inline with the source (within the same file). Since the 2.0.1 release
of Maven, the maven-plugin-plugin has contained revisions to handle this scenario.
Since the API has changed (in a backward-compatible way), and since the Ant
plugin support requires these changes be in place, you will see an <<<AbstractMethodError>>>
if you try to build an Ant-based plugin using the old maven-plugin-plugin.
*Introduction
The intent of this document is to help users learn to develop Maven plugins
using Ant.
As of the 2.0.1 release, Maven supports Ant-driven plugins. These plugins
allow the invocation of Ant targets (specified in scripts embedded in the
plugin jar) at specific points in the build lifecycle. They can also inject
parameter values into the Ant project instances when a target is called.
*Conventions
In this guide, we'll use the standard Maven directory structure for projects,
to keep our POMs as simple as possible. It's important to note that this is
only a standard layout, not a requirement. The important locations for our
discussion are the following:
+---+
/<project-root>
|
+- pom.xml
|
+- /src
| |
| +- /main
| | |
| | +- /scripts (source location for script-driven plugins)
| | | |
...
+---+
*Getting Started
We'll start with the simplest of all possible plugins. This plugin takes no
parameters, and will simply print a message out to the screen when invoked.
This should familiarize the reader with the basics of mapping Ant build scripts
to the Maven plugin framework. From there, we will gradually increase the
complexity of our plugin, adding parameters, interacting with standard project
locations, and binding to lifecycle phases. Finally, we'll see how a single Ant
build script can be mapped to multiple Maven mojos within the same plugin.
**Hello, World
Our first plugin will simply print "Hello, World" to the console.
***The Build Script
The elemental Ant-driven mojo consists of a simple Ant build script, a mapping
metadata file, and of course the plugin's POM. If our goal is to print "Hello, World",
we might use an Ant build script that looks something like this:
+---+
hello.build.xml:
--------------------------------
<project>
<target name="hello">
<echo>Hello, World</echo>
</target>
</project>
+---+
***The Mapping Document
Once we've created this build script, we need to tell Maven how to use it as a
plugin. This involves creating a mapping document. Note that where the build
script was named <<hello.build.xml>>, the mapping document is named
<<hello.mojos.xml>>. The naming of these files is very important, as this is how
the plugin parser matches mapping documents to build scripts. It has the general
form:
* <basename><<.build.xml>> - The Ant build script.
* <basename><<.mojos.xml>> - The corresponding mapping document.
[]
A simple mapping document used to wire the above build script into Maven's
plugin framework might look as follows:
+---+
hello.mojos.xml:
------------------------------
<pluginMetadata>
<mojos>
<mojo>
<goal>hello</goal>
<!-- this element refers to the Ant target we'll invoke -->
<call>hello</call>
<description>
Say Hello, World.
</description>
</mojo>
</mojos>
</pluginMetadata>
+---+
***The POM
Now that we have the build script and mapping document, we're ready to build
this plugin. However, in order to build, we need to provide a POM for our new
plugin. As it turns out, the POM required for an Ant-driven plugin is fairly
complex. This is because we have to configure the maven-plugin-plugin to use
the Ant plugin parsing tools in addition to the defaults (such as the Java
parsing tools). Our POM might look something like this:
+---+
pom.xml:
------------------------------
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.myproject.plugins</groupId>
<artifactId>hello-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>Hello Plugin</name>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-script-ant</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- NOTE: We don't need groupId if the plugin's groupId is
org.apache.maven.plugins OR org.codehaus.mojo.
-->
<artifactId>maven-plugin-plugin</artifactId>
<version>2.3</version>
<!-- Add the Ant plugin tools -->
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-tools-ant</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<!-- Tell the plugin-plugin which prefix we will use.
Later, we'll configure Maven to allow us to invoke this
plugin using the "prefix:mojo" shorthand.
-->
<configuration>
<goalPrefix>hello</goalPrefix>
</configuration>
</plugin>
</plugins>
</build>
</project>
+---+
***Build It and Run It
Once we have a POM for our new plugin, we can install it into the local
repository just as we would any other Maven project:
+---+
mvn install
+---+
and invoke it like this:
+---+
mvn org.myproject.plugins:hello-plugin:hello
+---+
This should output the following:
+---+
[echo] Hello, World
+---+
*Using <<prefix:mojo>> Invocation Syntax
Our new plugin works, but look at that command line... The next thing is to
configure Maven so we can use the familiar <<prefix:mojo>> invocation syntax,
and leave that verbose, fully-qualified mess behind.
As you know, Maven maps plugins to user-friendly prefixes. However, these
prefixes might overlap; that is, multiple plugins may try to use the same
prefix inadvertently. To avoid the obvious ambiguity associated with such
a collision, Maven will search a predetermined list of plugin groupIds for
a given prefix, with the first match winning. So, if we want to add our new
plugin to this search, we need to configure the list of plugin groupIds.
**Configuring Plugin-Prefix Searching
In order to reference our new plugin by prefix, we need to add its groupId to
the <<<\<pluginGroups/\>>>> list in the <<<settings.xml>>> file. As you probably
know, this file is usually found under $HOME/.m2. The added section to make our
plugin's groupId searchable should look like this:
+---+
~/.m2/settings.xml:
------------------------------
<settings>
.
.
.
<pluginGroups>
<pluginGroup>org.myproject.plugins</pluginGroup>
</pluginGroups>
.
.
.
</settings>
+---+
**Run It
We can check that this worked by invoking our new mojo once again, this time
using the prefix syntax:
+---+
mvn hello:hello
+---+
*Adding Plugin Parameters
Now, suppose it's not enough that our plugin display static text to the console.
Suppose we need it to display a greeting that is a little more personalized.
We can easily add support for this by adding a <<name>> parameter. For good measure,
we'll output the current project's name as well.
**Change the Ant Script
The build script will have to change to output the new information:
+---+
hello.build.xml:
--------------------------------
<project>
<target name="hello">
<echo>Hello, ${name}. You're building project: ${projectName}</echo>
</target>
</project>
+---+
**Change the Mapping Document
Now that we have a build script which requires two new parameters, we have to
tell the mapping document about them, so they will be injected into the Ant
Project instance.
+---+
hello.mojos.xml:
------------------------------
<pluginMetadata>
<mojos>
<mojo>
<goal>hello</goal>
<!-- this element refers to the Ant target we'll invoke -->
<call>hello</call>
<requiresProject>true</requiresProject>
<description>
Say Hello, including the user's name, and print the project name to the console.
</description>
<parameters>
<parameter>
<name>name</name>
<property>name</property>
<required>true</required>
<expression>${name}</expression>
<type>java.lang.String</type>
<description>The name of the user to greet.</description>
</parameter>
<parameter>
<name>projectName</name>
<property>projectName</property>
<required>true</required>
<readonly>true</readonly>
<defaultValue>${project.name}</defaultValue>
<type>java.lang.String</type>
<description>The name of the project currently being built.</description>
</parameter>
</parameters>
</mojo>
</mojos>
</pluginMetadata>
+---+
You'll notice several differences from the old version of the mapping document.
First, we've added <<requiresProject="true">> to the mojo declaration. This tells
Maven that our mojo requires a valid project before it can execute...in our case,
we need a project so we can determine the correct <<projectName>> to use. Next,
we've added two parameter declarations to our mojo mapping; one for <<name>> and
another for <<projectName>>.
The <<name>> parameter declaration provides an expression attribute...this
allows the user to specify <<<-Dname=somename>>> on the command line. Otherwise,
the only way to configure this parameter would be through a <<<\<configuration/\>>>>
element within the plugin specification in the user's POM. Note that this
parameter is required to have a value before our mojo can execute.
The <<projectName>> parameter declaration provides two other interesting items.
First, it specifies a defaultValue attribute, which specifies an expression to
be evaluated against Maven's current build state in order to extract the parameter's
value. Second, it specifies a readonly attribute, which means the user cannot
directly configure this parameter - either via command line or configuration
within the POM. It can only be modified by modifying the build state referenced
in the defaultValue...in our case, the name element of the POM. Also note that
this parameter is declared to be required before our mojo can execute.
**Rebuild It and Run It
Now that we've modified our plugin, we have to rebuild it before running it again.
+---+
mvn clean install
+---+
Next, we should run the plugin again to verify that it's doing what we expect.
However, before we can run it, we have some requirements to satisfy. First, we
have to be sure we're executing in the context of a valid Maven POM...runnning
in the plugin's own project directory should satisfy that requirement. Then,
we have to satisfy the name requirement. We can do this directly through the
command line. So, the resulting invocation of our plugin will look like this:
+---+
mvn -Dname=<your-name-here> hello:hello
+---+
or, in my case:
+---+
mvn -Dname=John hello:hello
+---+
This should output the following:
+---+
[echo] Hello, John. You're building project: Hello Plugin
+---+
*Defining Multiple Mojos from One Build Script
If you're familiar with Ant, you're probably familiar with the common usage
pattern of defining multiple build types within a single build script. For
instance, you might have a build type for cleaning the project, another for
producing the application jar file, and yet another for producing the full
distribution including javadocs, etc.
The concept is pretty simple. Discrete chunks of the build process are separated
into targets within the script. These targets can reference one another in
order to make reuse within the build script possible.
These same concepts map pretty well to Maven, actually. However, instead of
targets directly referencing one another, they would be bound to the appropriate
phases of the build lifecycle. In this way, multiple Ant targets from the same
build script can be reused piecemeal at different points in multiple build
lifecycles (clean, site, and the main lifecycle are three examples).
This section will describe how to map multiple logical mojos onto different
targets within the same Ant build script. It's also possible to reference
targets from multiple build scripts, but we'll cover this later.
**Two Targets, One Script
To test this, we'll split our echo statement into two targets. Then,
we'll reference each as separate mojos in the build. The new script
looks like this:
+---+
one-two.build.xml:
--------------------------------
<project>
<target name="one">
<echo>Hello, ${name}.</echo>
</target>
<target name="two">
<echo>You're building project: ${projectName}</echo>
</target>
</project>
+---+
**Map the Mojos
Next, we'll modify our original mapping document to map these two new mojos
instead of the old one:
+---+
one-two.mojos.xml:
------------------------------
<pluginMetadata>
<mojos>
<mojo>
<goal>one</goal>
<!-- this element refers to the Ant target we'll invoke -->
<call>one</call>
<description>
Say Hello. Include the user's name.
</description>
<parameters>
<parameter>
<name>name</name>
<property>name</property>
<required>true</required>
<expression>${name}</expression>
<type>java.lang.String</type>
<description>The name of the user to greet.</description>
</parameter>
</parameters>
</mojo>
<mojo>
<goal>two</goal>
<!-- this element refers to the Ant target we'll invoke -->
<call>two</call>
<requiresProject>true</requiresProject>
<description>
Write the project name to the console.
</description>
<parameters>
<parameter>
<name>projectName</name>
<property>projectName</property>
<required>true</required>
<readonly>true</readonly>
<defaultValue>${project.name}</defaultValue>
<type>java.lang.String</type>
<description>The name of the project currently being built.</description>
</parameter>
</parameters>
</mojo>
</mojos>
</pluginMetadata>
+---+
Now that we've split the old functionality into two distinct mojos, there are
some interesting consequences. Aside from the obvious, mojo <<one>> no longer
requires a valid project instance in order to execute, since we only require
the user's name in order to greet him.
**Build It, Run It
**Rebuild It and Run It
Since we've modified our plugin, we have to rebuild it again before re-running it.
+---+
mvn clean install
+---+
Now that we have two separate mojos, we can execute them singly, or in any order
we choose. We can bind them to phases of the lifecycle using plugin configuration
inside the build element of a POM. We can execute them like this:
+---+
mvn -Dname=John hello:one
RETURNS:
[echo] Hello, John.
mvn hello:two (executed in the plugin's project directory)
RETURNS:
[echo] You're building project: Hello Plugin
+---+
Alternatively, you could build a POM like this:
+---+
test-project/pom.xml:
----------------------------
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.myproject.tests</groupId>
<artifactId>hello-plugin-tests</artifactId>
<version>1.0</version>
<name>Test Project</name>
<build>
<plugins>
<plugin>
<groupId>org.myproject.plugins</groupId>
<artifactId>hello-plugin</artifactId>
<version>1.0</version>
<configuration>
<name>John</name>
</configuration>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>one</goal>
<goal>two</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+---+
Then, simply call Maven on this new POM:
+---+
cd test-project
mvn validate
+---+
You should see the following output:
+---+
[echo] Hello, John.
...
[echo] You're building project: Test Project
+---+
**A Note on Multiple Build Scripts
It's worth mentioning that Ant-driven plugins can just as easily contain
multiple Ant build scripts. Simply follow the naming rules - naming each
A.build.xml, B.build.xml, C.build.xml, etc. for example - and be sure to
provide a mapping document to correspond to each build script that contains
a mojo (other build scripts may be contained in the plugin, and referenced
by one of these; they don't need mapping documents). So, for the above examples
(assuming they all contained mojo targets), you'd need: A.mojos.xml, B.mojos.xml,
and C.mojos.xml. If C.build.xml was referenced by A and B, but didn't contain
mojo targets, then you don't need a C.mojos.xml for obvious reasons.
*Advanced Usage
Below are some tips on some of the more advanced options related to Ant mojos.
**Component References
If your plugin needs a reference to a Plexus component, it will have to define
something similar to the following in the mapping document:
+---+
<pluginMetadata>
<mojos>
<mojo>
.
.
.
<components>
<component>
<role>org.apache.maven.project.MavenProjectBuilder</role>
<hint>default</hint> <!-- This is optional -->
</component>
</components>
.
.
.
</mojo>
</mojos>
</pluginMetadata>
+---+
**Forking New Lifecycles
In case your plugin needs to fork a new lifecycle, you can include the
following in the mapping document:
+---+
<pluginMetadata>
<mojos>
<mojo>
.
.
.
<execute>
<lifecycle>my-custom-lifecycle</lifecycle>
<phase>package</phase>
<!-- OR -->
<goal>some:goal</goal>
</execute>
.
.
.
</mojo>
</mojos>
</pluginMetadata>
+---+
**Deprecation
As time goes on, you will likely have to deprecate part of your plugin.
Whether it's a mojo parameter, or even an entire mojo, Maven can support
it, and remind your users that the mojo or configuration they're using is
deprecated, and print a message directing them to adjust their usage.
To deprecate a mojo parameter, simply add this:
+---+
<pluginMetadata>
<mojos>
<mojo>
.
.
.
<parameters>
<parameter>
.
.
.
<deprecated>Use this other parameter instead.</deprecated>
.
.
.
</parameter>
</parameters>
.
.
.
</mojo>
</mojos>
</pluginMetadata>
+---+
To deprecate an entire mojo, add this:
+---+
<pluginMetadata>
<mojos>
<mojo>
.
.
.
<deprecated>Use this other mojo instead.</deprecated>
.
.
.
</mojo>
</mojos>
</pluginMetadata>
+---+