kafsemo.org

Upgrading to Maven 3

2012-07-26

Jason van Zyl asks

Are there major issues anyone is having with upgrading to Maven 3.x from Maven 2.x? If there are still any blockers I'd like to fix them.

As someone who’s put some effort into upgrading to Maven 3 at work, I thought I’d mention one blocker.

Maven 1 to Maven 2 was a complete rewrite for builds — your project.xml became a pom.xml, for a clear separation. Maven 3 is largely an extension of the 2.x series and intended to be compatible. As a user, the compatibility is good, but the internals break a few things for plugin writers. And, like any users of Maven at scale, we have custom plugins.

mojo-executor was the problem. It’s a library to invoke Maven goals from within a mojo, so your build plugin can call out to other plugins for its implementation. Under Maven 2 the code to invoke Maven recursively used a PluginManager. However in Maven 3, although you can get access to an instance of PluginManager, it’s not going to help you:

    public void executeMojo( MavenProject project, MojoExecution execution, MavenSession session )
        throws MojoExecutionException, ArtifactResolutionException, MojoFailureException, ArtifactNotFoundException,
        InvalidDependencyVersionException, PluginManagerException, PluginConfigurationException
    {
        throw new UnsupportedOperationException();
    }

Under Maven 3 we need to get a BuildPluginManager instead. So, we need two paths in the code: one that tries to get a BuildPluginManager and a fallback that uses the PluginManager if that’s all we can get. So what happens when we load a mojo that uses the new class BuildPluginManager into Maven 2? It doesn’t know about it: we get a NoClassDefFoundError. Anyone who’s written Java code to deal with potentially missing APIs knows the next step: avoid static use of that class. Rather than defining a field and asking Maven to inject an instance, try to get hold of an instance at runtime with a lookup:

    /**
     * The Maven PluginManager component.
     *
     * @component
     * @required
     */
    private PluginManager pluginManager;

    public void execute() throws MojoExecutionException {
        ExecutionEnvironment env;

        try {
            Object o = mavenSession.lookup("org.apache.maven.plugin.BuildPluginManager");
            
            env = executionEnvironment(mavenProject, mavenSession, (BuildPluginManager) o);
        } catch (ComponentLookupException e) {
            env = executionEnvironment(mavenProject, mavenSession, pluginManager);
        }
...

So, if we can look up the Maven 3 version, use it. Otherwise, fall back on the Maven 2 version that was injected. This gives us a plugin that works with old and new Mavens, which is essential during a gradual migration.

That, along with any number of other tweaks, has worked well. My day-to-day Maven is mvn3 for most projects and I’m enjoying parallel builds, much better pom validation and a few years of bug fixes. It’s hugely frustrating to work around bugs that are already fixed by sticking with obsolete software. I don’t recommend it.

(Music: The Fatima Mansions, “Popemobile to Paraguay”)
(More from this year, or the front page? [K])