Finding dependencies artifacts in your Maven plug-in

This article explains how to retrieve the location of dependency artifacts in a Maven plug-in. This solution only applies to Maven 3 (and more exactly, for versions >= 3.1). Indeed, from version 3.1, Maven’s repository management library, Aether, is hosted by Eclipse and not by Sonatype anymore.

What this article explains is not new.
I just gathered various pieces from here and there. However, they were not that easy to find and this is why I wrote this post.

Here is the beginning of the story.
I have a Maven plug-in with its own project type. The packaging is a ZIP file. And dependencies to the same kind of project can be specified in the POM. So, basically, my plug-in needed to be able to locate a dependency’s built package, be it on a remote Maven repository, in the local repository, or even in the reactor. Looking into the local repository was not hard.

Injecting the local repository component in my mojo was working fine.

@Parameter( defaultValue = "${localRepository}", readonly = true, required = true )
private ArtifactRepository local;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {

	for( Artifact unresolvedArtifact : this.project.getDependencyArtifacts()) {

		// Find the artifact in the local repository.
		Artifact art = this.local.find( unresolvedArtifact );

		File file = art.getFile();
		// ...
}

What was more complicated was dealing with reactor packaging.
What does it mean? If you type in mvn clean install, Maven will package your projects and copy the artifacts in the local repository. But, if you type in mvn clean package, the built artifacts will not be copied to the local repository. They will only be available in the target directory.

Consequently, if you have a multi-module project and dependencies across these modules (one of them depends on another one), and that you type mvn clean package, the build will fail because one of the dependencies was not resolved. In fact, Maven, or at least, Maven 3, is totally able of resolving such an artifact. The artifact location will not be in the local repository but under the module’s target directory. You only need to know which code use.

The solution for this problem lies into a single thing. We address remote, local and reactor dependencies resolution in the same way.

package whatever;

import java.io.File;
import java.util.List;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;

@Mojo( name="mojoName" )
public class ReactorDependenciesResolverMojo extends AbstractMojo {

	@Parameter( defaultValue = "${project}", readonly = true )
	private MavenProject project;

	@Component
	private RepositorySystem repoSystem;

	@Parameter( defaultValue = "${repositorySystemSession}", readonly = true, required = true )
	private RepositorySystemSession repoSession;

	@Parameter( defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true )
	private List<RemoteRepository> repositories;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {

		for( Artifact unresolvedArtifact : this.project.getDependencyArtifacts()) {

			// Here, it becomes messy. We ask Maven to resolve the artifact's location.
			// It may imply downloading it from a remote repository,
			// searching the local repository or looking into the reactor's cache.

			// To achieve this, we must use Aether
			// (the dependency mechanism behind Maven).
			String artifactId = unresolvedArtifact.getArtifactId();
			org.eclipse.aether.artifact.Artifact aetherArtifact = new DefaultArtifact(
					unresolvedArtifact.getGroupId(),
					unresolvedArtifact.getArtifactId(),
					unresolvedArtifact.getClassifier(),
					unresolvedArtifact.getType(),
					unresolvedArtifact.getVersion());

			ArtifactRequest req = new ArtifactRequest().setRepositories( this.repositories ).setArtifact( aetherArtifact );
			ArtifactResult resolutionResult;
			try {
				resolutionResult = this.repoSystem.resolveArtifact( this.repoSession, req );

			} catch( ArtifactResolutionException e ) {
				throw new MojoExecutionException( &quot;Artifact &quot; + artifactId + &quot; could not be resolved.&quot;, e );
			}

			// The file should exists, but we never know.
			File file = resolutionResult.getArtifact().getFile();
			if( file == null || ! file.exists()) {
				getLog().warn( "Artifact " + artifactId + " has no attached file. Its content will not be copied in the target model directory." );
				continue;
			}

			// Do whatever you want with the file...
		}
	}
}

I also created a Gist about it.
If you want an example of a unit test that configures such a mojo, please refer to the Gist. And if you want a real condition tests, consider writing integration tests with the Maven invoker plugin, rather than (only) relying on unit tests.


About this entry