Plugin Tutorial

This tutorial will guide you through the first steps to create a MavenPlugin.

Requirements:

Now, let's build a MavenPlugin together !

What do we want that plugin to do ? This is the first question that should come to your mind ! ;-)

We will make a plugin named "copy" that copies the Project's ProjectObjectModel? to a directory for further processing.

Now, let's see how we will do that:

Goals

First, we have to choose some goals.

The workflow of plugins is organized in goals. To invoke a goal, simply type "maven ${goal name}", and Maven will run it ! To see the list of goals available to you, type "maven -g".

Each goal has a name, a description, and prerequisites.

  • The name is the "key" that you will type to invoke it.
  • The description is what will be shown when typing "maven -g".
  • The prerequisites are a comma-separated list of goals that Maven must run before running the goal.

The naming convention for goals is:

  • The default goal has the same name as the plugin (we omit the "plugin" part, ie not "fooplugin").
  • Each other goal is named ${plugin name}:xxx (eg: foo:rule-the-world)
  • The plugin name and default goal name must be one block, containing only lower-case characters (ie neither foo-the-plugin nor Foo)
  • Goals that the user will never use directly (ie, goals that check a username, prepare a directory structure, etc) don't have descriptions.

Following those conventions, we will have a single goal, named "copy". It hasn't got any prerequisites yet. Thus, our plugin looks like:


<?xml version="1.0" encoding="ISO-8859-1"?>
<project>
  <goal name="copy"
           description="Copies the ProjectObjectModel to a directory"/>
  </goal>    
</project>

It does nothing yet !

Properties

Each plugin has access to its own set of properties.

Those properties are set in the file plugin.properties, in the plugin's dir.

The naming convention for plugins properties is "maven.${plugin name}.${property name, with additional "." if you like}".

So, for instance, this plugin could have properties like :

  • "maven.copy.target = mynewpom.xml".
  • "maven.copy.target.dir = ${maven.build.dir}/copy"
 (This is not a convention yet, but I think that the plugins should have their output set to ${maven.build.dir}/${plugin name},
 so that we know easily where the plugin outputs stuff.)

In this case, we set the plugin to copy the file to ${maven.copy.target.dir}/${maven.copy.target}.

If the user wants the plugin to copy the file to "target/foo" for instance, he would set "maven.copy.target.dir = ${maven.build.dir}/foo".

As the project's properties file is loaded after the plugin's one, the properties are overriden.

Content

Now, our plugin is ready to actually do something !

JakartaMaven and JakartaJelly? combined allow us to use Ant stuff directly within the application.

So, our plugin now looks like:


<?xml version="1.0" encoding="ISO-8859-1"?>
<project>
  <goal name="copy"
           description="Copies the ProjectObjectModel to a directory"/>

    <!-- This is Ant stuff -->
    <copy file="${basedir}/project.xml" tofile="${maven.copy.target.dir}/${maven.copy.target}"/>

  </goal>    
</project>

So, now when you type "maven copy", the file project.xml is copied in target/copy ! Woohoo !

This is fine, but useless.

Add features

Now, we will make a useful plugin out of that. First, let's set a property that sets the source file to copy.

In our plugin.properties file, we set:

 maven.copy.source = ${basedir}/project.xml
We default that to ${basedir}/project.xml for backward compatibility). Our copy task now looks like:
 <copy file="${maven.copy.source}" tofile="${maven.copy.target.dir}/${maven.copy.target}"/>

Now, we can copy whatever file we want to whatever dir and file. Cool.

Still we have to set that in the project's project.properties file, which is not really handy ...

Let's ask the user which file the user wants to copy, and where. To do that, we add a new "ask" goal (we don't want to mess up our first goal, do we ?). This goal is useless to the end user, so we don't set the description, and to follow the naming convention, we will name it "copy:ask". The "ask" JellyTag? is in the "interaction" JellyTagLibrary?, so we need to add a namespace for it.

We also need a namespace for JakartaJelly?'s core taglib, as it not the default in JakartaMaven. Let's bind it to "j".


<project xmlns:i="jelly:interaction" xmlns:j="jelly:core">
 <goal name="copy:ask">
    <i:ask question="Which file do you want to copy?" answer="maven.copy.source" default="${maven.copy.source}"/>
    <i:ask question="Where do you want to copy it?" answer="maven.copy.target.dir" default="${maven.copy.target.dir}"/>
    <!-- 
      The default name of the copied filewill be the filename of the source file, so we need to know if there was a dir
       specified in the source file name. 
     -->
    <j:set var="index" value="${maven.copy.source.indexOf('file.separator')}"/>
    <j:choose>
      <j:when test="${index != '-1'}">
        <j:set var="length" value="${maven.copy.source.length()}"/>
        <j:set var="maven.copy.source.filename" value="${maven.copy.source.substring(index, length)}"/>
      </j:when>
      <j:otherwise>
        <j:set var="maven.copy.source.filename" value="${maven.copy.source}"/>
      </j:otherwise>
    </j:choose>
    <i:ask question="What name do you want to give it?" answer="maven.copy.target" default="${maven.copy.source.filename}"/>
 </goal>
  ...
</project>

Ok, we can ask the user which file he wants and where to copy it. But we don't want to have to type "maven copy:ask copy" to have the plugin ask us.

Using prerequisites

In order for the plugin to do that automatically, we have two solutions:

  1. set the prerequisites attribute of the "copy" goal (eg: <goal name="copy" prereqs="copy:foo,copy:bar"/>)
  2. add a <attainGoal name="copy:ask"/> tag before we copy the file.
I prefer the second solution, because it outputs the goals in the right order. It would output something like:

 copy:
      copy:ask:
      ...

instead of

 copy:ask:
 copy:

This is mostly a cosmetic preference, and both solutions work exactly the same way. Note that you could also set the copy:ask goal as a preGoal of "copy" (<preGoal name="copy"/>) so that it is executed before as well. So, now our "copy" goal looks like:


  <goal name="copy"
           description="Copy a file to a directory">
    <attainGoal name="copy:ask"/>
    <copy file="${maven.copy.source}" tofile="${maven.copy.target.dir}/${maven.copy.target}"/>
  </goal>

Final plugin

Let's have a look at the whole plugin:


<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
  This plugin copies a file to a directory specified by the user.
-->
<project xmlns:i="jelly:interaction"
             xmlns:j="jelly:core">
  <!--==================================================================-->
  <!-- Ask the user which file to copy, and where                       -->
  <!--==================================================================-->

  <goal name="copy:ask">
    <i:ask question="Which file do you want to copy?" 
             answer="maven.copy.source" 
             default="${maven.copy.source}"/>
    <i:ask question="Where do you want to copy it?" 
             answer="maven.copy.target.dir" 
             default="${maven.copy.target.dir}"/>
    <!-- 
      The default name of the copied filewill be the filename of the source file, so we need to know if there was a dir
       specified in the source file name. 
     -->
    <j:set var="index" value="${maven.copy.source.indexOf('file.separator')}"/>
    <j:choose>
      <j:when test="${index != '-1'}">
        <j:set var="length" value="${maven.copy.source.length()}"/>
        <j:set var="maven.copy.source.filename" value="${maven.copy.source.substring(index, length)}"/>
      </j:when>
      <j:otherwise>
        <j:set var="maven.copy.source.filename" value="${maven.copy.source}"/>
      </j:otherwise>
    </j:choose>
    <i:ask question="What name do you want to give it?" 
             answer="maven.copy.target" 
             default="${maven.copy.source.filename}"/>
 </goal>

  <!--==================================================================-->
  <!-- Now, let's copy the file !                                       -->
  <!--==================================================================-->

  <goal name="copy"
           description="Copy a file to a directory">
    <attainGoal name="copy:ask"/>
    <copy file="${maven.copy.source}" tofile="${maven.copy.target.dir}/${maven.copy.target}"/>
  </goal>

</project>

That's all Folks !

So, what do you think about our little plugin ?

Cool, isn't it ?

To practice the plugin development, I propose you to extend this plugin to:

  • check the existence of the file, and ask the user for another file if it not valid ("copy:check")
  • check whether the source specified is a file or a dir and adapt "copy:ask" and "copy" in consequence
  • check whether the target dir is available and create it if it doesn't
  • check if the target file exists and ask the user to confirm if he wants to override it.

Further experiments could be:

  • if it is a directory, ask the user if he wants to copy the whole dir. If not, show the file names and ask the user to choose between them.
  • ask the user for files to include/exclude
  • enable/disable the interactive mode ("copy:set-interactive")

Here you are ! I hope that you enjoyed it.

Feel free to show me your results here: SamplePluginAddons?.

I'll be glad to answer your questions along the road.

Other plugins developers will sure help you too ! <hint, hint ...>

--StephaneMor


 We need to talk about the plugin's ProjectObjectModel?, the plugin's dependencies, the classloader stuff ... --StephaneMor
Edit text of this page | View other revisions
Last Edited 7 September 2002 1:48 pm (diff)
Search:
© 2002 - SourceForge