How do I obtain a fully resolved Model of a pom file?
This is basically a rephrasing of How can i programmaticaly build the effective model of a pom file?
I\'m b
I did it :-)
help:effective-pom and dependency:tree really did not help at all.
I had to look at how maven builds the Model for the MavenProject that gets injected in the mojo. help:effective-pom already receives the resolved Model, and dependency:tree only builds a DependencyGraph, but it doesn't load the whole model for a pom into memory.
By using the code below I was able to get a Model object with all the information from the parent, with resolved ${property} expressions, and expanded transitive dependencies.
Here's how:
1) Get a ModelResolver
You will need an instance of interface org.apache.maven.model.resolution.ModelResolver. Unfortunately, maven doesn't provide one easily via dependency injection (at least I couldn't find one), so we'll have to build one. To make things even better, the only two implementations of that interface are package protected, so you need to use some reflection magic to instantiate it. The concrete classes that implement it are DefaultModelResolver and ProjectModelResolver. I was able to build a DefaultModelResolver like this
/**
* The Maven Project Object
*
* @parameter expression="${project}"
* @required2.0
* @readonly
*/
protected MavenProject project;
/**
* @component
*/
protected ArtifactResolver artifactResolver;
/**
* @component
*/
protected RemoteRepositoryManager remoteRepositoryManager;
private Object invoke( Object object, String method )
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return object.getClass().getMethod( method ).invoke( object );
}
private org.apache.maven.model.resolution.ModelResolver makeModelResolver() throws MojoExecutionException {
try {
ProjectBuildingRequest projectBuildingRequest =
(ProjectBuildingRequest) invoke( project, "getProjectBuildingRequest" );
Class c = Class.forName("org.apache.maven.repository.internal.DefaultModelResolver");
Constructor ct = c.getConstructor(new Class[]{RepositorySystemSession.class,
RequestTrace.class, String.class,
ArtifactResolver.class, RemoteRepositoryManager.class,
List.class});
ct.setAccessible(true);
return (org.apache.maven.model.resolution.ModelResolver) ct.newInstance(new Object[]{
projectBuildingRequest.getRepositorySession(),
null, null, artifactResolver, remoteRepositoryManager,
project.getRemoteProjectRepositories()});
} catch (Exception e) {
throw new MojoExecutionException("Error instantiating DefaultModelResolver", e);
}
}
2) Build the Model
When you have a modelResolver, you can build the Model from a pom file like this:
public Model resolveEffectiveModel(File pomfile) {
try {
return modelBuilder.build(makeModelBuildRequest(pomfile)).getEffectiveModel();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ModelBuildingRequest makeModelBuildRequest(File artifactFile) {
DefaultModelBuildingRequest mbr = new DefaultModelBuildingRequest();
mbr.setPomFile(artifactFile);
mbr.setModelResolver(modelResolver); // <-- the hard-to-get modelResolver
return mbr;
}
Doesn't look pretty, but it worked for me.. :P
Just in case anyone want it, here is an example running in Groovy. It uses the Grape to dynamically load the depdencies needed to consume the pom.xml. It loads both the runtime classpath and the test classpath.
@Grapes([
@Grab(group='org.apache.maven', module='maven-core', version='3.0.5'),
@Grab(group='org.apache.maven', module='maven-compat', version='3.0.5'),
@Grab(group='com.jcabi', module='jcabi-aether', version='0.10.1')
])
// http://www.programcreek.com/java-api-examples/index.php?api=org.apache.maven.project.MavenProjectBuilder See # 20
import org.codehaus.plexus.DefaultPlexusContainer
import org.apache.maven.project.MavenProjectBuilder
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.project.DefaultProjectBuilderConfiguration
import org.apache.maven.artifact.repository.DefaultArtifactRepository
import com.jcabi.aether.Aether
import org.sonatype.aether.repository.RemoteRepository;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import org.sonatype.aether.artifact.Artifact;
container=new DefaultPlexusContainer();
projectBuilder=(MavenProjectBuilder)container.lookup(MavenProjectBuilder.class.getName());
layout=(ArtifactRepositoryLayout)container.lookup(ArtifactRepositoryLayout.class.getName(),"default");
def projectInfo(localRepoUrl, pom){
File pomFile = new File(pom);
String localRepoUrl2 = "file://" + localRepoUrl;
File local = new File(localRepoUrl);
ArtifactRepository localRepo=new DefaultArtifactRepository("local",localRepoUrl2,layout);
pbConfig=new DefaultProjectBuilderConfiguration().setLocalRepository(localRepo);
project = projectBuilder.build( pomFile, pbConfig );
aether = new Aether(project, local);
[ runtime: resolveDependencies(aether, project, "runtime"),
test : resolveDependencies(aether, project, "test") ];
}
def resolveDependencies (aether, project, scope) {
depLists = project.getDependencies().collect {
art = new DefaultArtifact(it.getGroupId(), it.getArtifactId(), it.getClassifier(), it.getType(),
it.getVersion());
Collection<Artifact> deps = aether.resolve( art, scope );
deps.collect{ it.getFile().getAbsolutePath() }
}
[ dependencies : depLists.collect {it.first()}, classpath : depLists.flatten() ]
}
println projectInfo("c:/Users/lpmsmith/.m2/repository", "pom.xml");
the maven help plugin does something similar when "mvn help:effective-pom" is executed.
see http://svn.apache.org/viewvc/maven/plugins/tags/maven-help-plugin-2.1.1/src/main/java/org/apache/maven/plugins/help/EffectivePomMojo.java?view=markup for the sources.
I think this will not show the transitive depedencies.
The mvn dependency:tree goal does that: http://svn.apache.org/viewvc/maven/plugins/tags/maven-dependency-plugin-2.4/src/main/java/org/apache/maven/plugin/dependency/TreeMojo.java?view=markup
maybe you can create a mixture of both?
Romain provided the good answer above, but it was using a deprecated class that was removed from maven 3.x The updated version is this :
@Parameter( defaultValue = "${project}", readonly = true )
private MavenProject project;
@Component
private RepositorySystem repositorySystem;
@Component
private ProjectBuilder mavenProjectBuilder;
@Parameter( defaultValue = "${session}", readonly = true )
private MavenSession session;
private MavenProject getMavenProject(String groupId, String artifactId, String version) throws ProjectBuildingException {
Artifact pomArtifact = repositorySystem.createProjectArtifact(groupId, artifactId, version);
ProjectBuildingResult build = mavenProjectBuilder.build(pomArtifact, session.getProjectBuildingRequest());
return build.getProject();
}
A working example is in the hierarchy-maven-plugin
The source code you seek is in help:effective-pom
, somewhere.
--- Edit update ---
After a quick glance, it would seem that you would need to build a Maven Project
from the read pom. This likely will involve a number of steps that include resolution of the parent project of the POM, downloading and parsing other Maven plugin artifacts and wiring all of the references together.
Reading the child-level pom alone won't do it.
Maybe is too late for you but if it can help others in the future. So I did it like that:
@Component
private RepositorySystem repositorySystem;
@Component
private MavenProjectBuilder mavenProjectBuilder;
@Parameter(property = "project.remoteArtifactRepositories")
protected List<ArtifactRepository> remoteRepositories;
@Parameter(property = "localRepository")
protected ArtifactRepository localRepository;
...
Artifact pomArtifact = repositorySystem.createProjectArtifact(groupId, artifactId,version);
MavenProject project = mavenProjectBuilder.buildFromRepository(pomArtifact
, remoteRepositories, localRepository);
And that's it. It should work. If you have some special packaging (e.g. bundle...) In the target pom project make sure the plugins associated to those packaging are installed in your current project.