A game-changing Maven 2 plugin you absolutely must use

May 04, 2009

Ever since I first started using Maven 2, I envisioned having a console in which I could execute life-cycle goals without having to incur Maven's startup cost between every run. It just seemed to me such a waste for Maven to build up the project object model (POM) from the pom.xml only to run a single sequence of goals and immediately shutdown. It also gives the impression that Maven is slow. In fact, it's extremely slow when it's used in this way, especially multi-module projects. But it doesn't have to be if you take advantage of...

The maven-cli-plugin

Today my vision has come true thanks to Mr. Don (and the ease of collaboration made possible by git). Having a similar vision to mine, he wrote the maven-cli-plugin, hosted at github.com. This plugin uses the jline library to create an execution console for Maven 2 (i.e., command shell), shown here:

maven2>

At the prompt you can issue Maven 2 life-cycle phases or plugin goals. The console even supports tab completion of commands! In this entry, I'll explain how you setup and use this console to put Maven into hyperdrive.

Technically, the plugin has two consoles. One is for executing Maven 2 life-cycle phases, including all prerequisite phases, and the other is used to only run plugin goals (the life cycle is not executed). Those are the same two ways you use Maven 2 from your normal shell. In this entry, I'll be focusing on the execute-phase console.

A fork in the road

I was thrilled when I first discovered the plugin, but after giving it a shot I was disappointed to discover that it wasn't honoring profiles, which are a critical piece of the build (see issue 2). I didn't give up hope though. Since Mr. Don had done most of the legwork in creating the plugin, I figured it wouldn't be that difficult for me to figure out why the profiles weren't working. Sure enough, in under an hour I discovered the source of the problem and was able to apply a fix. That left me with the dilemma of how to distribute my changes. I wanted to be able to use the plugin in the Seam 3 examples, so I couldn't just maintain a hacked version on my computer. Github.com (and git) saved the day.

Following the process for creating a fork on github.com (for which they practically spoon feed you the instructions), I forked the code and committed my changes to a publicly accessible repository. You can access both the master tree and my forked tree. You'll need the version from my tree to use all the features I cover in this entry. Hopefully Mr. Don will merge my changes into the master tree soon, but the fact that you can get my code today is a true testament to the influence git can have on open source collaboration.

Now let's "git" on with the presentation.

Getting started

To get started, add a plugin repository where the maven-cli-plugin is hosted as a top level element in your pom.xml. I recommend using the JBoss maven repository because it hosts my forked version.

<pluginRepositories>
    <pluginRepository>
        <id>repository.jboss.org</id>
        <name>JBoss Repository</name>
        <url>http://repository.jboss.org/maven2</url>
    </pluginRepository>
</pluginRepositories>

Alternatively, you can publish the plugin to your local or company repository. Grab the source from github.com or the binaries from the JBoss Maven repository. Remember, you need my forked version until my features get merged in. Follow the project at github.com to find out when that happens.

Next, define the maven-cli-plugin inside the build > plugins element in your POM. This just allows you to use the plugin prefix from the commandline, which is cli. You're also going to use this section to add some configuration options later.

<plugin>
   <groupId>org.twdata.maven</groupId>
   <artifactId>maven-cli-plugin</artifactId>
</plugin>

With the setup out of the way, I'm going to demonstrate three reasons why this plugin is game-changing. By the end, I guarantee you'll be jumping out of your chair.

#1 - Speed

Let's face it, Maven is dog-slow. That's because it has to parse that heap of XML the Maven developers call a POM. Then it has to figure out what it's supposed to execute. Then it pings the internet for updates. Then it enforces constraints. Then it runs through all the precursor phases (likely including tests). And finally it arrives at the phase you really want it to execute.

Of course, some of those steps can be trimmed using various flags, switches, and properties. But that means having to type, and remember, a ridiculously long command that is just something no human should be expected to do. More on that later. Let's deal with this startup cost once and for all.

We'll begin by firing up the maven-cli-plugin phase executor console. You run it just like you would any other Maven plugin:

mvn cli:execute-phase

After Maven goes through it's normal loading process, you are presented with a command prompt:

maven2>

From here you can type any of Maven's life-cycle phases, such as package, or a plugin goal (more on plugins later). Give it a try:

maven2> package

The first time the life cycle executes, you'll see a noticeable improvement in speed. By the second execution, it's blazing fast! You can just feel years being added back to your life. Power up!

Now it's time to extend the build with...

#2 - Profiles

One of the most powerful features of Maven is profiles. In fact, I think Maven is pretty useless without them. That's because in Maven, you really only have one "command" you can execute, the Maven 2 life cycle. There must be a way, then, to instruct that execution pass to perform different steps along the way. That's what profiles are for. With a profile, you can hook additional plugins to a phase as a way to weave that extra behavior into the build.

But we have a dilemma. Profiles are typically activated from the commandline either explicitly using the -Pprofile flag or through an activation, typically by assigning a property such as -Dproperty=value. How can we set the profile once we are in the command console? This is where my contribution to the maven-cli-plugin comes in.

The commands typed in the console are processed by the jline ConsoleReader from the maven-cli-plugin, not by Maven. That means we can allow any command we want, including flags like -P and -D. Fortunately, Maven was designed in such a way that an execution is isolated internally from parsing the POM. So it's possible to execute a life-cycle phase with a different set of profiles and properties, or even put Maven into offline mode for a single execution, without having to start the console again.

I hacked up the maven-cli-plugin to support the following flags:

  • -P activates the profile specified immediately after the flag (no spaces); this flag can be used multiple times
  • -D assigns a property specified immediate after the flag (no spaces) in the form name=value; this flag can be used multiple times
  • -o puts Maven in offline mode for a single execution; if not specified, will inherit the setting used to start the console
  • -N instructs Maven not to recurse into projects in the reactor
  • -S skip tests, an alias for -Dmaven.test.skip=true

Here's an example of a command you can issue:

maven2> clean package -Prun-integration-tests -o

That would activate the run-integration-tests profile, run the clean plugin and the life cycle up to the package phase, all while executing Maven in offline mode (to avoid checks for missing pom files, snapshots, and plugin updates).

In addition, the maven-cli-plugin already supported specifying individual projects by artifactId. This allows you to execute a phase on a sub-project without having to descend into that project or use the -f flag.

Let's say you are working with an standard EAR project and you want to build the WAR. To package just the WAR, you would either have to change into the war directory and execute Maven or use the -f flag as follows:

mvn -f war/pom.xml package

With the maven-cli-plugin, you can accomplish the same thing using this command (note that "seam-booking-war" is the artifactId of the module):

maven2> seam-booking-war package

Ah, the simplicity! And now, for the grand finale!

#3 - Aliases

Aliases are the Holy Grail of Maven 2. When I switch people from Ant to Maven, the first thing they get annoyed about is the ridiculous commands they are required to type. To put it simply, if you want to run clean and package, there is no way to specify that with a single command. You have to type:

mvn clean package

Things get worse with plugins. All plugin goals must be namespaced. That's because Maven 2 technically supports any command in the world, as long as there is a plugin to execute it. To run Jetty on a WAR project, for instance, you have to type:

mvn run:jetty

If you want to run clean, package, and then start jetty, you have to type:

mvn clean package run:jetty

Let's say that you also need to expand the WAR in-place and you want to run offline. Then the command becomes:

mvn -o clean package war:inplace run:jetty

I think you can see where this is going. When I first setup the booking examples for Seam 3, the record for the longest commandline in the readme went to this command, which undeploys, packages, and redeploys an EAR to JBoss AS:

mvn -o -f ear/pom.xml jboss:undeploy && \
  mvn -o package && \
  mvn -o -f ear/pom.xml jboss:deploy

Uuuuugly! That's why the aliases feature of the maven-cli-plugin is absolutely game-changing (perhaps even life changing). In fact, combined with the other two features I have covered, they make Maven 2 better and faster than Ant, hands down. I'll go so far as to say that there has never been a faster, more convenient way to execute builds.

So what is an alias? Quite simply, a string of commands you would otherwise have to type in the console, aliased to a single word. You define them in the plugin configuration. Here's an alias I put together to deploy an exploded EAR archive to JBoss AS in the Seam booking example:

<plugin>
   <groupId>org.twdata.maven</groupId>
   <artifactId>maven-cli-plugin</artifactId>
   <configuration>
       <userAliases>
           <explode>package -o -Pexplode</explode>
       </userAliases>
   </configuration>
</plugin>

After starting up the console, the user only has to type one word:

maven2> explode

It's no longer even necessary to prefix commands with mvn (or ant in the old days). Just one command. One word.

The execute-phase console also supports direct execution of plugin goals. That means you can include them in the alias command. The only limitation is that when you include one in an alias, you have to specify the fully qualified name of the plugin (groupId:artifactId) before the goal rather than just it's prefix (e.g., org.codehaus.mojo:jboss-maven-plugin rather than jboss). This next alias invokes the harddeploy goal of the jboss-maven-plugin after packing the project.

<userAliases>
   <deploy>seam-booking-ear package -o org.codehaus.mojo:jboss-maven-plugin:harddeploy</deploy>
</userAliases>

You can even mix aliases with regular commands. Perhaps you want to clean first:

maven2> clean deploy

I find it nice to alias commonly used built-in Maven goals too, such as the one that lists the active profiles:

<userAliases>
   <profiles>org.apache.maven.plugins:maven-help-plugin:active-profiles -o</profiles>
</userAliases>

The maven-cli-plugin also provides a handful of built-in aliases.

If this plugin isn't game changing, I don't know what is. All I can say as hell yeah!

See the Seam 3 booking example to see this plugin in action and read additional commentary.

Update: There is also a cli client for IntelliJ IDEA that can invoke builds remotely. Of course, the plugin supports this from the commandline too.

Update: I should also mention that this is a great way to debug Maven since you get an opportunity to attach a debugger before executing a command. First, set the MAVEN_OPTS environment variable:

MAVEN_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n"

Then run cli:execute-phase, attach a debugger (port 8787 in this case) and execute a command.

Posted at 02:18 PM in Java, Programming, Usability | Permalink Icon Permalink

38 Comments from the Peanut Gallery

1 | Posted by ALR on May 04, 2009 at 04:10 PM EST

Thanks for sharing this, Dan. Looking forward to giving it a spin. Maybe I'll even get a chance to break it and make some more patches. ;)

S. ALR

2 | Posted by Bertrand Delacretaz on May 05, 2009 at 04:01 AM EST

I just tested your plugin on an Apache Sling module that I'm working on...running the test phase takes 1.5 seconds vs. 8 for "mvn -o test", how cool is that? Big thanks for sharing this!

3 | Posted by Alexander Klimetschek on May 05, 2009 at 05:49 AM EST

I tried it, but it always fails to execute commands inside the cli. The error is always like:

[INFO] Internal error in the plugin manager getting plugin 'org.apache.felix:maven-bundle-plugin': Plugin 'org.apache.felix:maven-bundle-plugin:1.4.3' has an invalid descriptor: 1) Plugin's descriptor contains the wrong group ID: org.twdata.maven 2) Plugin's descriptor contains the wrong artifact ID: maven-cli-plugin 3) Plugin's descriptor contains the wrong version: 0.6.3.CR2

I tried Maven 2.0.7, 2.0.9 and 2.1.0 + all versions of the cli plugin from 0.6 up to 0.6.3.CR2, always the same issue.

Could it be that plugin definitions in a pom without a version number trigger this issue? For example, the above mentioned maven-bundle-plugin is simply defined like this:

org.apache.felix maven-bundle-plugin // some config....

4 | Posted by Sébastien Arbogast on May 05, 2009 at 07:18 AM EST

Such a nice promise... but so far, it doesn't work. Right after setup, I try to run mvn cli:execute-phase but I get an error message saying: "The plugin 'org.apache.maven.plugins:maven-cli-plugin' does not exist or no valid version could be found"

I tried to run it without the shortcut: "mvn org.twdata.maven:maven-cli-plugin:execute-phase". It runs fine, but then when I try to run install goal, I get this message: "Caused by: org.apache.maven.plugin.PluginManagerException: Plugin 'org.sonatype.flexmojos:flexmojos-maven-plugin:3.1.0' has an invalid descriptor: 1) Plugin's descriptor contains the wrong group ID: org.twdata.maven 2) Plugin's descriptor contains the wrong artifact ID: maven-cli-plugin 3) Plugin's descriptor contains the wrong version: 0.6.3.CR2"

Any idea?

5 | Posted by Dan Allen on May 05, 2009 at 08:40 AM EST

@Sebastien, to solve your first problem, you must declare the plugin in your pom.xml. That's how you can invoke a plugin by only using its prefix (i.e., cli:execute-phase instead of org.twdata.maven:maven-cli-plugin:execute-phase). Otherwise, Maven assumes the standard groupId.

The maven-cli-plugin user guide explains another way to use the shorthand by defining an additional plugin group in your $HOME/.m2/settings.xml file.

<settings>
   <pluginGroups>
      <pluginGroup>
         org.twdata.maven
      </pluginGroup>
   </pluginGroups>
</settings>

6 | Posted by Ian Springer on May 05, 2009 at 09:11 AM EST

I'm seeing the same issue as Alexander. I also have plugins in my pom, and in all its ancestor poms, that do not explicitly specify a groupId, but it would be a pain to change all of them. Has anyone figured out a workaround or fix for the issue?

7 | Posted by Dan Allen on May 05, 2009 at 09:25 AM EST

@Ian, again, you can alter your global Maven configuration ($HOME/.m2/settings.xml) so that it checks additional groupIds when trying to locate a plugin by name. That's the only way that I know of.

8 | Posted by Dan Allen on May 05, 2009 at 09:42 AM EST

@Alex and @Sebastien, it sounds to me like something is awry with your local Maven repository. I would try to move your repository to the side and try with a clean slate. It could be that some plugin as invalid descriptor. I just tried using a new project and empty repository and everything went fine (before I was testing with my project).

9 | Posted by Alexander Klimetschek on May 05, 2009 at 10:26 AM EST

@Dan: it's not related to my local maven repo, the error also happens with a fresh, empty repo.

It seems like somehow the plugin descriptors returned for any plugin in the current execution is always the descriptor for the cli plugin. I got the error for different plugins, which are configured in my poms, always talking about the cli plugin descriptor mismatch.

The reason that it doesn't happen in a fresh, new project might be that it requires more complex pom structures. For example plugin definitions inherited from parent poms or some execution phase plugin config could be the issue. Not that I would understand how Maven exactly behaves there, it's just a guess...

10 | Posted by Dan Allen on May 05, 2009 at 10:28 AM EST

@Alex Hm, that's really strange. Hopefully someone can shed some light on your problem. I encourage you to keep digging. Maven can be a complex beast sometimes, no doubt.

11 | Posted by shaolang on May 05, 2009 at 11:19 PM EST

harlow, Dan, thanks for sharing such a great tool! I think the problem Alex and Sebastien (and I) are facing lies in the cli:execute-phase goal. When running cli:execute, everything works fine.

12 | Posted by Alexander Klimetschek on May 06, 2009 at 08:24 AM EST

@shaolang: That's it! Using mvn cli:execute works for me. Such a simple solution, but I didn't even look for other cli goals, albeit I was wondering why it was named "execute-phase", which sounded rather strange to me ;-)

13 | Posted by Dan Allen on May 06, 2009 at 08:34 AM EST

cli:execute and cli:execute-phase are not the same thing. Please see the user guide, which explains this: http://wiki.github.com/mrdon/maven-cli-plugin. In short, execute-phase runs the lifecycle, whereas execute just invokes a goal directly on a plugin.

You mean to tell me if you create a brand new project, with a fresh repository, you still get an error with execute-phase? I find that hard to believe. If you are getting the error just when using your project, you have some complication in your project configuration that is causing it not to work.

14 | Posted by shaolang on May 06, 2009 at 11:29 AM EST

@Dan, yup, I've read the user guide and understand the difference between execute and execute-phase.

But the truth is, execute-phase does not work because it seems to somehow uses maven-cli-plugin's metadata (artifactId, groupId, etc) for the other plugins (surefire, compiler, etc). Well, Alex just confirmed and shared the same experience I had.

I am not familiar with Maven code; it will take me a while to find out where the bug is... of course, I hope you will find it before I do... :p

15 | Posted by Dan Allen on May 06, 2009 at 11:40 AM EST

Good, I'm glad we are on the same page. I just didn't want others to get confused.

If you could share with me a project that demonstrates this problem I can work to debug through it. The problem is, all my projects seem to be working fine for me, so there is nothing for me to debug yet ;)

16 | Posted by shaolang on May 06, 2009 at 10:06 PM EST

No luck.

I did the following: 1. archetype:generate a new project with an empty repository. 2. Edited pom.xml to include maven-cli-plugin 3. cli:execute 4. Ran test phase in cli prompt.

And cli bombed.

There is nothing else in my settings.xml except specifying where my localRepository is. Btw, I'm using Maven 2.1.0.

I will try to dig deeper to find out where the problem lies...

17 | Posted by Dan Allen on May 06, 2009 at 10:18 PM EST

I have not yet tested with Maven 2.1, though you just gave me the idea to give it a try. You should run cli:execute-phase if you want to run the test phase. Otherwise, you are running just an alias to the test plugin.

Without pasting the entire stacktrace, can you show the message of what bombs?

18 | Posted by Dan Allen on May 06, 2009 at 10:32 PM EST

Aha! I see the problem you all are having. I built the plugin using Maven 2.0.8. Apparently, Maven 2.1 cannot run a plugin that is created with Maven 2.0 (or at least this plugin). I rebuilt the plugin using Maven 2.1.0 and now I can run it with Maven 2.1 and 2.0!. I will do a new release soon.

I also added a feature where you can customize the prompt. That way, you can keep your consoles running in different windows straight!

19 | Posted by shaolang on May 06, 2009 at 10:38 PM EST

sorry, I meant cli:execute-phase.

and thanks for finding the problem so fast while I was still trying to wrap my head around Maven API... :-)

20 | Posted by Dan Allen on May 07, 2009 at 01:09 AM EST

Okay, I really got to the bottom of it this time. In short, the plugin is not compatible with Maven 2.1. And after a very thorough debugging session I can assure you that it is a bug in Maven 2.1. It is doing something wrong with the classloading and is getting too eager to throw and exception (really it should be a warning message).

Fortunately, the solution is very easy. Download the source of Maven 2.1.0. Comment out line 323 of maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginManager.java:

// throw new PluginManagerException( msg );

Then execute a build from the source folder, which will produce a new distribution. Then use that distribution. I really don't know why they are insisting on throwing an exception here. Everything works fine if it is ignored (of course they should fix whatever the problem is).

21 | Posted by Kelly Robinson on May 07, 2009 at 05:28 PM EST

Thanks for this great post Dan! I'm looking forward to seeing some of the Maven 2.1 issues resolved, as that's definitely a blocker in an organization where I can't easily compile/install my own modules. Other than that though, yet another EXTREMELY helpful way to get more mileage out of maven!

22 | Posted by Dan Allen on May 07, 2009 at 06:46 PM EST

@Kelly glad you enjoyed the post.

You should know that you don't have to recompile any plugins to patch Maven 2.1. What you end up recompiling would go in your Maven installation (i.e., M2_HOME). So it's really not that invasive at all (unless you really have no control over the Maven installation itself).

23 | Posted by sud on May 08, 2009 at 01:00 AM EST

Thanks for sharing. This is one of the very few useful posts on maven that goes beyond your standard intro to maven or maven vs ant bashing blog posts.

Lots of nice productivity boosts.

24 | Posted by sapporo on May 08, 2009 at 05:47 AM EST

Very nice indeed!

OTOH, I think you do get most if not all of this goodness by simply using Maven from your IDE (m2eclipse or similar).

25 | Posted by Olivier Billard on May 12, 2009 at 08:11 AM EST

This plugin seems great, except with this Maven 2.1.0 issue. Can we expect a compatible release soon ? Thanks !

26 | Posted by james on May 29, 2009 at 01:07 PM EST

any chance you could add remote port support for the execute-phase goal (only execute seems to support this currently)?

27 | Posted by Dan Allen on May 29, 2009 at 01:15 PM EST

The plan in the next version is to merge the two goals so that you get the best of both worlds. That should satisfy your request.

28 | Posted by james on May 29, 2009 at 03:15 PM EST

while i'm asking, it would be awesome to have a way to define aliases outside of the project pom file. would love to define them in my settings.xml.

29 | Posted by Dan Allen on May 29, 2009 at 03:27 PM EST

Please post feature requests here: http://github.com/mrdon/maven-cli-plugin/issues

30 | Posted by shaolang on June 21, 2009 at 07:29 AM EST

harlow, Dan. I've forked maven-cli-plugin to try refactoring the code and hopefully solve the "maven 2.1.0 incompatibility problem in execute-phase."

The good news is my fork has solved that problem. I'm still refactoring, though, so I do not know whether to release the code. Advice?

31 | Posted by Lucian on July 23, 2009 at 07:43 PM EST

@Alex and @Sebastien Try and upgrade to the version 0.6.6

32 | Posted by Arbi Sookazian on January 27, 2010 at 06:26 PM EST

I tried the maven-cli-plugin from jboss repo with Maven 2.0.8 and got an error related to "org.apache.maven.execution.MavenSession.setCurrentProject()". So I then used Maven 2.2.1 and am getting this:

[INFO] Internal error in the plugin manager getting plugin 'org.apache.maven.plu gins:maven-site-plugin': Plugin 'org.apache.maven.plugins:maven-site-plugin:2.0- beta-7' has an invalid descriptor: 1) Plugin's descriptor contains the wrong group ID: org.twdata.maven 2) Plugin's descriptor contains the wrong artifact ID: maven-cli-plugin 3) Plugin's descriptor contains the wrong version: 0.6.4 [INFO] ------------------------------------------------------------------------ [INFO] Trace org.apache.maven.lifecycle.LifecycleExecutionException: Internal error in the pl ugin manager getting plugin 'org.apache.maven.plugins:maven-site-plugin': Plugin 'org.apache.maven.plugins:maven-site-plugin:2.0-beta-7' has an invalid descript or: 1) Plugin's descriptor contains the wrong group ID: org.twdata.maven 2) Plugin's descriptor contains the wrong artifact ID: maven-cli-plugin 3) Plugin's descriptor contains the wrong version: 0.6.4

33 | Posted by Arbi Sookazian on January 27, 2010 at 07:11 PM EST

It worked when I used http://twdata-m2-repository.googlecode.com/svn/ instead of http://repository.jboss.org/maven2 for pluginRepository.

How does this plugin accelerate the builds? I did not see any explanation of this in this wiki article or on Mr. Don's github site.

34 | Posted by Arbi Sookazian on January 27, 2010 at 07:15 PM EST

So it seems that this does not work with mvn 2.0.8 and the twdata-m2-repo version. It does work with mvn 2.1.1.

What are the mvn requirements for this plugin? Did not see it in this article or in the github wiki.

35 | Posted by Arbi Sookazian on January 27, 2010 at 07:18 PM EST

Sorry I meant it does work with mvn 2.2.1 and twdata-m2-repo version. With 2.1.1, nothing execs when I type 'clean' or 'package' at the cli prompt.

36 | Posted by gorgorotom on August 09, 2011 at 10:43 AM EST

Totally sux plugin like Maven in fact why make other fucking command on fucking command ? You dont like simply things, that's all You can do complicate thing with simple thing, do you know it ? maven = mind masturbation

37 | Posted by Javin @ OutOfMemoryError in Java on September 25, 2011 at 12:17 AM EST

There has lot been changed since the writeup of this post, now days Eclipse has wonderful integration with maven which makes life easy for developer who are managing dependency using Maven. thanks

38 | Posted by The cook on March 11, 2012 at 01:56 AM EST

Maven2/Eclipse combination is working well but I atill prefer command line option. BTW, any good Maven plugin for OSGI application packaging? OSGI looks promising if classpath issues are resolved.

Regards, Alex