| ----- |
| 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> |
| +---+ |