Sonatype

Previous: Chapter 4
Next: Chapter 5

Complexity is a symptom of confusion, not a cause. -- Jeff Hawkins

This chapter follows an example project. You may wish to download maven-zip-plugin and follow along.

A Structure for Goal Execution

As mentioned in the chapter on Project Relationships, Maven builds are based upon two concepts: the idea that projects are best conceptualized and managed as atomic, inter-relating objects - and that eveything else is an action taken upon them. Just as Maven manages the project-as-object concept via a pom.xml, and relationships via the dependency elements, inheritence and multi-modules, Maven actions are also managed in a well-defined list called the build lifecycle.

For the person building a project, this is a great improvement over a system like Ant, where there are innumerable possible names for the tasks that may be performed, varying per project. It is often very difficult for a new person to manage any significantly complex build mechanism without either extensive training as to the nature of certain tasks, or to read the build.xml files. A Maven user need only learn a small set of commands to build any Maven project, and the POM will ensure they get the results they desired.

As mentioned in a previous chapter, a build lifecycle is an organized sequence of phases that exist to give order to a set of goals. Those goals are chosen and bound by the packaging type of the project being acted upon. There are three standard lifecycles in Maven: clean, default (sometimes called build) and site.

clean

Clean is the simplest lifecycle with only three phases:

  • pre-clean
  • clean
  • post-clean

Most times you will only use the "clean" phase, which is bound by the clean:clean goal. All this plugin does is delete the files under the build directory which is defaulted to "target" by the SuperPOM. However, you should not execute the clean:clean goal directory. The reason being that there are three phases to the clean lifecycle for a reason. It is entirely acceptible to bind other goals to the pre-clean or post-clean phases, which some Maven POMs may take advantage of.

For example, suppose you wanted to trigger an antrun:run goal task to echo a notification on pre-clean. Simply running the clean:clean goal will not execute the lifecycle at all, but running clean will first run pre-clean. The killerapp-model POM binds the antrun:run goal to the pre-clean phase, which contains tasks alerting the user if the project artifact is about to be deleted or not. As an aside, the maven-antrun-plugin is a useful mechanism for executing simple tasks. However, a sharable Maven goal is always preferable to an embeded task - less copy-n-pasting in the long-run.

<project>
  ...
  <build>
    <plugins>
      ...
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <id>file-exists</id>
            <phase>pre-clean</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <tasks>
                <!-- adds the ant-contrib tasks (if/then/else used below) -->
                <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>

                <available
                    file="${project.build.directory}/${project.build.finalName}.jar"
                    property="file.exists" value="true" />
                <if>
                  <not><isset property="file.exists" /></not>
                  <then><echo>No ${project.build.finalName}.${project.packaging} to delete</echo></then>
                  <else><echo>Deleting ${project.build.finalName}.${project.packaging}</echo></else>
                </if>
              </tasks>
            </configuration>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>ant-contrib</groupId>
            <artifactId>ant-contrib</artifactId>
            <version>1.0b2</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
  ...
</project>

If you run the clean phase, you will see something similar to the following execute (you may see more information print out if Maven must retrieve the antrun plugin and its dependencies from Maven's central repository before execution):

[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building Killer App Model
[INFO]    task-segment: [clean]
[INFO] ----------------------------------------------------------------------------
[INFO] [antrun:run {execution: file-exists}]
[INFO] Executing tasks
     [echo] Deleting killerapp-model-1.0-SNAPSHOT.jar
[INFO] Executed tasks
[INFO] [clean:clean]
[INFO] Deleting directory ~/killerapp/killerapp-model/target
[INFO] Deleting directory ~/killerapp/killerapp-model/target/classes
[INFO] Deleting directory ~/killerapp/killerapp-model/target/test-classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 second
[INFO] Finished at: Wed Nov 08 11:46:26 CST 2006
[INFO] Final Memory: 2M/5M
[INFO] ------------------------------------------------------------------------

One last note about the clean plugin - you are not constrained to the default behavior (delete everything in the build directory). You can configure the plugin to remove specific files in a fileSet. The example below configures clean to remove all .class files in a directory named othertarget using standard Ant file wildcards, * and **.

<project>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-clean-plugin</artifactId>
        <configuration>
          <filesets>
            <fileset>
              <directory>othertarget</directory>
              <includes>
                <include>*.class</include>
              </includes>
            </fileset>
          </filesets>
        </configuration>
      </plugin>

You can also create a set of excludes files.

default

This lifecycle is really the meat of Maven's action framework. As mentioned in chapter 2, the default lifecycle is made up of a sequence of phases which, upon execution of a chosen phase, Maven first executes every previous phase in order beginning with the first. The phase sequence is based upon an abstraction of a standard build lifecycle. For example the first phase is validate, where any bound goal is expected to validate that the project conforms to some basic expectations - like project structure or the existence of certain files. From there source files are generated or manipulated then compiled. After that tests are compiled and executed. Barring any failures the compiled files and resources are packaged, integration tested, verified and installed. Finally, the POM and artifact are deployed to a remote repository. Note that this entire lifecycle will not be executed every time. Indeed, the majority of the time - for example during development - you only desire to run up to the compile phase, or test.

  • validate - validate the project is correct and all necessary information is available
  • generate-sources - generate any source code for inclusion in compilation
  • process-sources - process the source code, for example to filter any values
  • generate-resources - generate resources for inclusion in the package
  • process-resources - copy and process the resources into the destination directory, ready for packaging
  • compile - compile the source code of the project
  • process-classes - post-process the generated files from compilation, for example to do bytecode enhancement on Java classes
  • generate-test-sources - generate any test source code for inclusion in compilation
  • process-test-sources - process the test source code, for example to filter any values
  • generate-test-resources - create resources for testing
  • process-test-resources - copy and process the resources into the test destination directory
  • test-compile - compile the test source code into the test destination directory
  • test - run tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  • prepare-package - perform any operations necessary to prepare a package before the actual packaging. This often results in an unpacked, processed version of the package (coming in Maven 2.1+)
  • package - take the compiled code and package it in its distributable format, such as a JAR
  • pre-integration-test - perform actions required before integration tests are executed. This may involve things such as setting up the required environment
  • integration-test - process and deploy the package if necessary into an environment where integration tests can be run
  • post-integration-test - perform actions required after integration tests have been executed. This may including cleaning up the environment
  • verify - run any checks to verify the package is valid and meets quality criteria
  • install - install the package into the local repository, for use as a dependency in other projects locally
  • deploy - done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects

The specific goals bound to each phase default to a set of values declared by the project's packaging type. Although the killerapp-stores pom packaging will still execute the same set of phases in the same order as the killerapp-api jar packaging, the goals that are bound to those phases will be different. Where the package phase in a pom project will execute the site:attach-descriptor goal, a jar project will execute the jar:jar goal instead.

The following are the built-in packaging types in Maven, and the goals that are bound to their lifecycles by default.

jar

Jar is the default packaging type, the most common, and thus the most commonly encountered lifecycle configuration.

  • process-resources - resources:resources
  • compile - compiler:compile
  • process-test-resources - resources:testResources
  • test-compile - compiler:testCompile
  • test - surefire:test
  • package - jar:jar
  • install - install:install
  • deploy - deploy:deploy

pom

POM is the simplest packaging type. The artifact that it generates is itself only, rather than a jar or ejb package.

  • package - site:attach-descriptor
  • install - install:install
  • deploy - deploy:deploy

maven-plugin

You will notice that this packaging type is similar to jar with three additions: plugin:descriptor, plugin:addPluginArtifactMetadata, and plugin:updateRegistry. These goals generate a descriptor file and perform some modifications to the repository data. We cover Maven plugins in more detail in the Using Plugins Chapter. For now just know that every one of the goals bound to the lifecycle phase are themselves packaged in plugins that were built with the maven-plugin lifecycle implementation. How's that for bootstrapping?

  • generate-resources - plugin:descriptor
  • process-resources - resources:resources
  • compile - compiler:compile
  • process-test-resources - resources:testResources
  • test-compile - compiler:testCompile
  • test - surefire:test
  • package - jar:jar, plugin:addPluginArtifactMetadata
  • install - install:install, plugin:updateRegistry
  • deploy - deploy:deploy

ejb

EJBs, or Enterprise Java Beans, are a common data access mechanism for model-driven development in JavaEE, and Maven provides support for EJB 2 and 3. Though you must configure the ejb plugin to specifically package for EJB3, else the plugin defaults to 2.1 and demands certain EJB configuation files.

  • process-resources - resources:resources
  • compile - compiler:compile
  • process-test-resources - resources:testResources
  • test-compile - compiler:testCompile
  • test - surefire:test
  • package - ejb:ejb
  • install - install:install
  • deploy - deploy:deploy

war

The war packaging type is similar to the jar and ejb types (notice a pattern here?). The exception being the package goal of war:war. Note that the war:war plugin requires a web.xml configuration in your src/main/webapp/WEB-INF/ directory. More on quirks like these later.

  • process-resources - resources:resources
  • compile - compiler:compile
  • process-test-resources - resources:testResources
  • test-compile - compiler:testCompile
  • test - surefire:test
  • package - war:war
  • install - install:install
  • deploy - deploy:deploy

ear

EARs are probably the simplest Java EE constructs, consisting primarily of the deployment descriptor (application.xml) file, some resources and some modules. The ear plugin offers a nice addition to the build process in the ear:generate-application-xml goal. It generates the application.xml based upon the configuration in the EAR project's POM, although this is not required since you may provide your own file if so desired, which the ear:ear goal will use when packaging.

  • generate-resources - ear:generate-application-xml
  • process-resources - resources:resources
  • package - ear:ear
  • install - install:install
  • deploy - deploy:deploy

par

The par type may be a little esoteric for some people.

  • process-resources - resources:resources
  • compile - compiler:compile
  • process-test-resources - resources:testResources
  • test-compile - compiler:testCompile
  • test - surefire:test
  • package - par:par
  • install - install:install
  • deploy - deploy:deploy

Note that this list is not exhaustive of every packaging type available for Maven. There are more available through external projects, such as the NAR (native archive) packaging type. They require two things, however: access to their remote repository (by default the only repository available is Maven Central, check the SuperPOM), and the packaging plugin must be added as a build extension. The build extension is merely a mechanism to allow you to add a project's artifact into Maven's execution classpath... much simpler and more portable than manually adding "-cp /path/to/project.jar" to the mvn execution script.

As an example on how this is done, let us use the Codehaus Mojo project's jboss-sar packaging type - to create a deployable JMX Service Archive (SAR) project for JBoss.

<project>
  ...
  <package>jboss-sar</package>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jboss-packaging-maven-plugin</artifactId>
        <version>2.0-SNAPSHOT</version>
        <extensions>true</extensions>
      <plugin>
    </plugins>
  </build>
  ...
  <repositories>
    <repository>
      <id>Codehaus Snapshots</id>
      <url>http://snapshots.repository.codehaus.org/</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>Codehaus Snapshots</id>
      <url>http://snapshots.repository.codehaus.org/</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>
  ...
</project>

We will cover presicely how to create your own packaging type later on in this chapter, but this example should give you an idea on what is required to use it.

This should give a basic understanding of the default build lifecycle, how to use it, extend it, and how to use the packaging type in the POM for altering the functions of the build phases.

Redundancies

You may notice that many of the packaging type lifecycles were similar in every respect except for the packaging phase's bound goal. This makes sense, since moving resource files, or compiling java to class files, are going to be the same reguardless of whether those files are packaged in a JAR, EAR or WAR. In order to get you comfortable operating in the Maven lifecycle requires us to spend some time pointing out some behaviors that, although are still dependant on the underlying goals, are so embedded into the Maven process its easy to forget and treat it as simply a function of that phase.

Resources

The first is the process-resources phase, implemented by the resources:resources goal. As mentioned above, the process-resources phase processes the resources to the destination directory. What does this mean to "process" resources? In the default case, this means simply to copy the files from the src/main/resources directory to target/classes (the ${project.build.outputDirectory}) - but there is a more advanced behavior makes this seemingly benign phase interesting: filters. Filters are used by the resources plugin to replace the values in text files with properties in the standard ${...} form. It has been said (seriously) that people come to Maven for the dependency management, but stay for the filtering.

For example, let us say that you have a project with an xml file src/main/resources/META-INF/service.xml. Rather than create one file per distribution, you create a skeleton file, replacing actual values with \${...} delimited properties.

<service>
  <!-- This URL was set by project version ${project.version} -->
  <url>${jdbc.url}</url>
  <user>${jdbc.username}</user>
  <password>${jdbc.password}</password>
</service>

There is also a file src/main/filters/default.properties. These types of "filter files" contains name=value pairs which will replace \${name} strings within filtered resources.

jdbc.url=jdbc:hsqldb:mem:mydb
jdbc.username=sa
jdbc.password=

In the project's POM we specify two items, a list of files that contain properties to filter by, and a flag to Maven that the resources directory is to be filtered (by default it is not, and will merely be copied to the target directory).

<build>
  <filters>
    <filter>src/main/filters/default.properties</filter>
  </filters>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
</build>

Note: The resources directory is not required to be under src/main/resources, this is simply the default that we adhere to for this example.

Filtering resources do not require using filter files since you can filter with any Maven properties (like we did with \${project.version}, a default POM property). What good is this? Did we not just move the configuration from the resource file to the POM? Yes, in this limited example case. Let's take a look at the KillerApp. In the killerapp-cli project we use a custom resources directory and filters to match the jar's final name.

        <resources>
          <resource>
            <filtering>true</filtering>
            <directory>${basedir}/src/main/command</directory>
            <includes>
              <include>killerapp.bat</include>
            </includes>
            <targetPath>..</targetPath>
          </resource>
        </resources>

The command-line bat file generated in turn, converts the killerapp.bat file from this:

@echo off
java -jar ${project.build.finalName}-jar-with-dependencies.jar %*

to this:

@echo off
java -jar killerapp-cli-1.0-SNAPSHOT-jar-with-dependencies.jar %*

Compile

The compile phase is straightforward enough. All you usually need to know in normal operations is that the compiler:compile goal assumes you are compiling JDK 1.3 complient code. If you wish to use a different version, you must configure.

<project>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
    ...
  </build>
  ...
</project>

Notice we are configuring the plugin maven-compiler-plugin, and not the specific goal compile:compile (in other words, the configuration element is under the plugin element, and not under execution element as we have used it before). When you configure the plugin, the settings are propogated to all of a plugin's executed goals, therefore, the compile:testCompile goal bound to the test-compile phase will be set with a JDK 1.5 source and target as well.

Test Resources

process-test-resources is similar to process-resources. The glaring difference being that you define test resources in the POM under the element testResources with a set of children testResource, and they are copied to target/test-classes (the ${project.build.testOutputDirectory}). All other things are equal.

Test

The test phase is bound by surefire:test. Surefire executes files ending with *Test.java by default, though other values may be set (see Appendix B for more information). JUnit tests are executed by default, although others like TestNG are available. You can add TestNG as a dependency to your test project, just like you would with JUnit (remember chapter 2?) but with the extra classifier element.

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>4.7</version>
      <scope>test</scope>
      <classifier>jdk14</classifier>
    </dependency>

If you wish to use Java 5 annotations rather than JDK 1.4 javadoc annotations, change the classifier to jdk15.

Installation and deployment to local and remote repositories will be covered in a later chapter.

site

Up until now we have focused clearly on the concept of building and placing artifacts to the repositories. But Maven can do more than simply generate code, it is also well equiped to generate documentation and reports about the project, or a collection of projects (aggregation). This is such an important task of Maven that "site" generation has its own lifecycle. The site lifecycle contains four phases:

  • pre-site
  • site - generate the site and reports
  • post-site
  • site-deploy - deploys the site to a remote server

The default goals bound to the site lifecycle is:

  • site - site:site
  • site-deploy - site:deploy

Unlike the default build lifecycle - but similar to clean - the packaging type does not tend to alter this lifecycle since packaging types are concerned primarily with artifact creation, not with the type of site generated. This is actually a good thing as sites of all types will get a similar look-and-feel.

The site plugin is a powerful thing in Maven. It kicks off the execution of Doxia document generation and other report generation plugins.

mvn site

We will cover site and report generation in depth in Site Generation and the Reporting chapters, respectively.

Plugins and the Lifecyle

The last item on the agenda is to mention that there is are two more ways for manipulating the build lifecycle. One is to bind a goal to a phase by default using the @phase annotation in the Mojo definition. The last is to create your own packaging type and write your own implementation of the phases. So far we have yet to cover plugins in depth, so it seems a good place to start. In the next chapter we will go over plugins in detail, but for now we will write a simple mojo and bind it to the phase.

Mojos are a play-on-words of the Java term POJO (Plain Old Java Object) - but for Maven. A Mojo is an object that implements a goal that can be called. An important aspect of a Mojo is that it is self-contained. Unlike Ant tasks, Mojos cannot contain other Mojos. This is why Maven prefers to call its actions "goals" rather than "tasks". It may require several tasks to complete a purpose you have in mind, but a goal is merely a declaration of a purpose. For example when executing the Maven goal jar:jar, I am declaring that I would like a jar created based upon the class files that are in the pre-designated location (by default in the SuperPOM, that is target/classes). Now, if you wish to execute two goals, say, as mvn compile:compile jar:jar then they work together quite nicely. compile:compile generates *.class files from your *.java files and places them in the target/classes directory, then jar:jar packages them into a nice *.jar artifact. Chances are you will always want to execute both of those goals, in that order. This is why the build lifecycle exists. To bind those goals to this default order, so you, the user, don't have to worry about the details.

But back to mojos. Mojos are the code written that defines what a goal does. When you execute a goal, you are actually asking Maven to execute a mojo for you. The following is our simple mojo definition (if you have yet to download the sample project, get it from here to follow along):

/**
 * Zips up the output directory.
 * @goal zip
 * @phase package
 */
public class ZipMojo extends AbstractMojo
{
    /**
     * The Zip archiver.
     * @component role="org.codehaus.plexus.archiver.Archiver" role-hint="zip"
     */
    private ZipArchiver zipArchiver;

    /**
     * Directory containing the build files.
     * @parameter expression="${project.build.directory}"
     */
    private File buildDirectory;

    public void execute()
        throws MojoExecutionException
    {
        try {
            zipArchiver.addDirectory( buildDirectory, new String[]{"**/**"}, new String[]{"**/output.zip"} );
            zipArchiver.setDestFile( new File( buildDirectory, "ouput.zip" ) );
            zipArchiver.createArchive();
        } catch( Exception e ) {
            throw new MojoExecutionException( "Could not zip", e );
        }
    }
}

note: If you are familiar with Java 5 you may wonder, "why not just use the built in annotations?" This is because, by default, Maven executes on JDK 1.3. This allows the widest audience for your plugins. Maven 2.1 will add support for writing plugins for Java 5, but for now, javadoc annotations will have to do.

The above class is part of the maven-zip-plugin project which you can install with - you guessed it - mvn install. You may then zip up a project's build directory by executing your goal on the command line:

mvn com.training.plugins:maven-zip-plugin:zip

We will go into more details over the mojo structure in the next chapter. For now draw your attention to the @phase annotation in the class javadoc. This is the third method for binding a goal to a phase. Where in the antrun:run goal in the beginning of this chapter we manually bound the goal to the pre-clean phase, in the above example we may use the "zip" goal (name defined by the @goal annotation) without specifying the phase, we need only add the zip:zip goal to the POM.

<project>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <groupId>com.training.plugins</groupId>
        <artifactId>maven-zip-plugin</artifactId>
        <executions>
          <execution>
            <!-- no phase element! -->
            <goals>
              <goal>zip</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

Now you may run mvn package and the zip goal will execute at that phase.

But what if the goal is not meant to be part of the standard lifecycle phase - for example as an occasional backup? You would want to run mvn com.training.plugins:maven-zip-plugin:zip manually, but the goal will fail without an existing output directory. If you remember in chapter 3 we mentioned that Maven parameters can be accessed via "dot" notation and \${...} delimited. So the buildDirectory field is populated with the contents of the \${project.build.directory} property - set by default (thanks to the SuperPOM) to the target directory. If no goal, such as compile:compile has yet to populate the target directory, the zip plugin has no content to zip up, so we therefore must first force the execution of some goal, phase or lifecycle before the zip goal.

Forking your own Lifecycle

The custom lifecycle must be packaged in the plugin under the META-INF/maven/lifecycle.xml file. The straight-forward way to accomplish this is used in the example under src/main/resources/META-INF/maven/lifecycle.xml. This configuration declares a lifecycle named "zipcycle" that contains only the zip goal in the package phase.

<lifecycles>
  <lifecycle>
    <id>zipcycle</id>
    <phases>
      <phase>
        <id>package</id>
        <executions>
          <execution>
            <goals>
              <goal>zip</goal>
            </goals>
          </execution>
        </executions>
      </phase>
    </phases>
  </lifecycle>
</lifecycles>

In the zip-fork goal's mojo, the zipcycle is the lifecycle executed up to the package phase (which is bound by zip:zip). The ZipForkMojo doesn't actually do anything itself.

/**
 * Forks a zip lifecycle.
 * @goal zip-fork
 * @execute lifecycle="zipcycle" phase="package"
 */
public class ZipForkMojo extends AbstractMojo
{
  public void execute()
    throws MojoExecutionException
  {
    getLog().info( "doing nothing here" );
  }
}

Now running the zip-fork goal will fork the lifecycle (try it in the zip-lifecycle-test under maven-zip-plugin/src/projects - you will find other integration tests there as well).

mvn zip:zip-fork

As you can see, running zip:zip-fork will first execute all phases up to the zipcycle's package phase - which is only zip:zip.

[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'zip'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Zip Forked Lifecycle Test
[INFO]    task-segment: [zip:zip-fork]
[INFO] ----------------------------------------------------------------------------
[INFO] Preparing zip:zip-fork
[INFO] [site:attach-descriptor]
[INFO] [zip:zip]
[INFO] Building zip: ~/maven-zip-plugin/src/projects/zip-lifecycle-test/target/output.zip
[INFO] [zip:zip-fork]
[INFO] doing nothing here
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 second
[INFO] Finished at: Sun Apr 29 16:10:06 CDT 2007
[INFO] Final Memory: 3M/7M
[INFO] ------------------------------------------------------------------------

Overriding the default Lifecycle

Finally, you can creating your own packaging type with its own lifecycle mapping. You do this by overriding Maven's component mechanism implemented by another project called Plexus. Without getting into the details of Plexus, you achieve this by creating a file META-INF/plexus/components.xml, and set the name of the packaging type under role-hint, and the set of phases containing the versionless coordinates of the goals to bind. Note that not every phase must be bound, and that multipled goals may be executed per phase, defined as a comma-seperated list.

<component-set>
  <components>
    <component>
      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
      <role-hint>zip</role-hint>
      <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
      <configuration>
        <phases>
          <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
          <compile>org.apache.maven.plugins:maven-compiler-plugin:compile</compile>
          <package>com.training.plugins:maven-zip-plugin:zip</package>
        </phases>
      </configuration>
    </component>
  </components>
</component-set>

Since the packaging type you have created is not, by default, accessible to Maven's build system, you need to add the plugin that contains the packaging definition with the extensions element set to true. This lets Maven know that this plugin is not simply executed, but that it is added to Maven's execution classpath.

<project>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <groupId>com.training.plugins</groupId>
        <artifactId>maven-zip-plugin</artifactId>
        <extensions>true</extensions>
      </plugin>
    </plugins>
  </build>
</project>

Finally, you may add the packaging type to the POM:

  <packaging>zip</packaging>

If you run mvn package on the maven-zip-plugin-test project you will see:

[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Zip Plugin Test
[INFO]    task-segment: [package]
[INFO] ----------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] No sources to compile
[INFO] [zip:zip]
[INFO] Building zip: ~/maven-zip-plugin/src/projects/zip-packaging-test/target/output.zip
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Thu Nov 09 17:41:21 CST 2006
[INFO] Final Memory: 3M/6M
[INFO] ------------------------------------------------------------------------

You have just created your own Maven plugin goal as a mojo, manipulated the build lifecycle, and created your own packaging type. We have begun to dig deeper into more advanced levels of Maven usage, a trend that we will continue in the next chapter with advanced plugin usage and a few tips and tricks.

If the details of manipulating the build lifecycle has piqued your interest in writing custom plugins, please, read at least the next chapter before jumping over to the details of writing them.

Tips and Tricks

Clean to the Rescue

It is an unfortunate case that - despite all best and valiant efforts for encapsulating actions - something will go wrong when creating builds mixing non-standard goal and phase executions, or testing configurations. For example, if you run the dependency:copy goal into a directory, then configure war:explode, to exclude such jars, it's possible they will end up in your build because they are already there. This is why the clean phase exists - and until you become more proficient at using Maven, I would recommend to prefix all executions with clean. Rather than run:

mvn install

Try running:

mvn clean install

This will remove all output generated by previous executions, giving you a nice clean feeling.

Summary

In this chapter we looked at what the lifecycle is, its structure, and some of its defaults based upon a project's packaging type. We also investigated 5 ways to manipulate build lifecycle:

  1. Change project package to use the lifecycle of a default package definition, jar or ear for example.
  2. Manually bind a goal to a phase in the POM.
  3. Bind in the goal definition in the mojo, so adding the goal to the POM will default to a phase.
  4. Create a forked lifecycle and execute it in a Mojo.
  5. Create your own packaging type with a custom default lifecycle.

In the next chapter we will build upon what was covered here by drilling down even further in the action stack, to manipulating the plugins themselves.


Previous: Chapter 4
Next: Chapter 5