When we upgraded to the latest version of Maven (3.0.4), some of our in-house plugins had issues with dependency resolution.
We already has similar problems when migrating from Maven 2 to Maven 3, due to the new library used by Maven to access the repository system: Aether. These problems only came when using plugins that had to resolve dependencies by themselves.
Running these plugins with Maven 2 require the use of the org.apache.maven.artifact.resolver package (ArtifactResolver and ArtifactCollector classes, a. o.). Although still valid when running the plugin with Maven 3, this can lead to some side effects we experienced, such as:
- Dependencies explicitly marked as excluded still being resolved
- Dependencies taken from a Maven 1 repository being unresolvable because coordinates stated as null:null:jar (seems specific to Maven 3.0.4+)
- Inconsistencies with the dependency mechanism used internally by Maven (see here)
So we had to update our plugins to use the new Aether library to perform dependency resolution, and found a good starting point on the Sonatype blog.
Let’s take one sample method that would need to be updated. For instance, the following method is responsible of resolving all dependencies of a project:
public static Set<Artifact> getDependencyArtifacts(MavenProject project, ArtifactResolver resolver, ArtifactRepository localRepository, List<?> remoteRepositories, ArtifactMetadataSource artifactMetadataSource) throws MojoExecutionException { ArtifactResolutionResult resolutionResult; try { resolutionResult = resolver.resolveTransitively(project.getArtifacts(), project.getArtifact(), remoteRepositories, localRepository, artifactMetadataSource); } catch (ArtifactResolutionException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (ArtifactNotFoundException e) { throw new MojoExecutionException(e.getMessage(), e); } return resolutionResult.getArtifacts(); }
This is quite a simple process, as all needed components (project, resolver, localRepository, remoteRepositories and artifactMetadataSource) can be injected by Plexus, either by an expression or a component “annotation”:
/** * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project; /** * Local repository to be used by the plugin to resolve dependencies. * @parameter expression="${localRepository}" */ protected ArtifactRepository localRepository; /** * List of remote repositories to be used by the plugin to resolve dependencies. * @parameter expression="${project.remoteArtifactRepositories}" */ protected List remoteRepositories; /** * @component */ protected ArtifactResolver resolver; /** * @component */ protected ArtifactMetadataSource artifactMetadataSource;
As already said, this code can still be executed with Maven 3, thanks to the great job the Maven guys did in making it globally backward compatible. But this sometimes fails to do the job, and this is not the way we should write new plugins!
The Sonatype blog post is quite nice, but using it as is would mix a lot of the same concepts. Yes, part of the classes you manipulate to handle dependency resolution are both in Aether and Maven API, notably Maven Artifact and Aether Artifact. A plugin will usually work on the Maven Artifact side, so we rather want to use the Maven packages as much as possible, with ideally no explicit reference about the underlying library that Maven uses. That would also minimize the modifications needed inside the plugin when upgrading to Maven 3.
Unfortunately, there’s no way to achieve dependency resolution without at least one import of an Aether class, but still this is valuable as it is limited to the dependency resolution process, not the dependencies or artifact structures, which is still nice to reduce the impact on the business part of the plugin work.
Here is the method modified to be compliant with Maven 3.0.4 and Aether 1.13.1
public static Set getDependencyArtifacts(MavenProject project, RepositorySystemSession repoSession, ProjectDependenciesResolver projectDependenciesResolver) throws MojoExecutionException { DefaultDependencyResolutionRequest dependencyResolutionRequest = new DefaultDependencyResolutionRequest(project, repoSession); DependencyResolutionResult dependencyResolutionResult; try { dependencyResolutionResult = projectDependenciesResolver.resolve(dependencyResolutionRequest); } catch (DependencyResolutionException ex) { throw new MojoExecutionException(ex.getMessage(), ex); } Set artifacts = new LinkedHashSet(); if (dependencyResolutionResult.getDependencyGraph() != null && !dependencyResolutionResult.getDependencyGraph().getChildren().isEmpty()) { RepositoryUtils.toArtifacts(artifacts, dependencyResolutionResult.getDependencyGraph().getChildren(), Collections.singletonList(project.getArtifact().getId()), null); } return artifacts; }
The only Aether class that we still have to use is RepositorySystemSession, all other are core Maven ones, especially the Artifact one. Also note that you do not have to depend on Aether explicitly, as it will be taken transitively from the org.apache.maven:maven-core:jar:3.0.4 dependency.
As for the previous implementation, the required parameters can be obtained with Plexus injections:
/** * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project; /** * The current repository/network configuration of Maven. * * @parameter default-value="{repositorySystemSession}" * @readonly */ protected RepositorySystemSession repoSession; /** * @component */ protected ProjectDependenciesResolver projectDependenciesResolver;
With an isolation of dependency resolution inside specific methods that will not have their return type changed, a plugin will easily be updated to support Maven 3 dependency resolution mechanism.
One drawback is the use of RepositoryUtils.toArtifacts, which is marked by Maven as intended to be used internally.
Similarly, resolving one artifact can be done like this:
public static void resolveArtifact(Artifact artifact, RepositorySystem repositorySystem, List remoteRepositories, ArtifactRepository localRepository) throws MojoExecutionException { ArtifactResolutionRequest request = new ArtifactResolutionRequest() .setArtifact(artifact) .setRemoteRepositories(remoteRepositories) .setLocalRepository(localRepository); ArtifactResolutionResult resolutionResult = repositorySystem.resolve(request); if(resolutionResult.hasExceptions()){ throw new MojoExecutionException("Could not resolve artifact: " + artifact, resolutionResult.getExceptions().get(0)); } }
This code does not depend directly on any Aether class.
Using the above samples, plugins can easily be migrated to fully comply with the new Maven 3 dependency resolution library.