What's in Maven 3.2.1?

Apache Maven 3.2.1 was released four weeks ago without much fanfare, and while there’s no grand list of new features there are some infrastructure changes worth mentioning because they fundamentally change how Maven can work. This week I’ll do a series of short entries on the new features and detail the infrastructure changes. There are official release notes for those interested but I’ll try to provide more colour and the high-level use cases driving the changes in Maven 3.2.1. Let’s start with a couple of the new features.

Transitive dependency excludes (MNG-2315)

It is sometimes useful to clip a dependency’s transitive dependencies. A dependency may have incorrectly specified scopes, or dependencies that conflict with other dependencies in your project. Using wildcard excludes makes it easy to exclude all a dependency’s transitive dependencies. In the case below you may be working with the maven-embedder and you want to manage the dependencies you use yourself, so you clip all the transitive dependencies:

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/maven-4.0.0.xsd">
  ...
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-embedder</artifactId>
      <version>3.1.0</version>
      <exclusions>
        <exclusion>
          <groupId>*</groupId>
          <artifactId>*</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    ...
  </dependencies>
  ...
</project>

Sometimes projects just make crappy POMs and you need to remake the dependency tree. This is one technique that can help. This feature has actually been present in Maven for quite a while but no one documented it. Everyone knows the Maven project creates awesome documentation, but we’re equally skilled at not writing it as well. Whoops.

Provide a way to customize lifecycle mapping logic (MNG-5581)

Implementations of the new LifecycleMappingDelegate interface can create custom lifecycles. The delegate has access to the existing lifecycles so it can create projections of existing lifecycles or create completely new ones. What does that mean? A simple example might be to create a lifecycle called post which might extract only Mojos which execute in the process-classes phase.

package io.takari.maven.plugins.post;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.inject.Named;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.Lifecycle;
import org.apache.maven.lifecycle.LifecycleMappingDelegate;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoNotFoundException;
import org.apache.maven.plugin.PluginDescriptorParsingException;
import org.apache.maven.plugin.PluginNotFoundException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.project.MavenProject;

@Named("post")
public class PostLifecycleMappingDelegate implements LifecycleMappingDelegate {

  private static final String POST_PHASE = "process-classes";

  public Map<String, List<MojoExecution>> calculateLifecycleMappings(
    MavenSession session, 
    MavenProject project, 
    Lifecycle lifecycle, 
    String lifecyclePhase) 
    
    throws PluginNotFoundException,
            PluginResolutionException, 
            PluginDescriptorParsingException, 
            MojoNotFoundException, 
            InvalidPluginDescriptorException {

    List<MojoExecution> mojoExecutions = new ArrayList<MojoExecution>();

    for (Plugin plugin : project.getBuild().getPlugins()) {
      for (PluginExecution execution : plugin.getExecutions()) {
        if (POST_PHASE.equals(execution.getPhase())) {
          for (String goal : execution.getGoals()) {
            MojoExecution mojoExecution = 
              new MojoExecution(plugin, goal, execution.getId());
            mojoExecution.setLifecyclePhase(execution.getPhase());
            mojoExecutions.add(mojoExecution);
          }
        }
      }
    }

    return Collections.singletonMap("post", mojoExecutions);
  }
}

In this particular case we are converting a large Ant build to Maven, and developers are used to an Ant target called post which does post processing on the generated class files. So a developer can run:

mvn post

From the command line and the above code will execute which sifts through the default JAR lifecycle and pulls out only the Mojos which run in the process-classes phase.

While everyone agrees that we want something more canonically Maven, but when you’re trying to convert a build that’s tens of millions of lines and have Ant and Maven fully co-exist happily together in the same build you need to make some compromises on the path to Mavenization. This particular technique has worked well for us in this migration and very well might have other interesting uses.

Next up: How Maven builds the way it does, and why it needs to change

 

Comments

Maven Training

To use Maven correctly you'll need to understand the fundamentals. This class is designed to deliver just that.

Introduction to Maven
 

Stay Connected

 

Newsletter

Subscribe to our newsletter and stay up to date with the latest news and events!