From 992f0debbda978dc230103eacb41c609aac2dfaa Mon Sep 17 00:00:00 2001 From: "Vladislav.Soroka" Date: Thu, 25 Sep 2014 17:45:26 +0400 Subject: [PATCH] maven: use aether for maven3 dependencies resolving Related issues: IDEA-99301 Maven import does not honor order of dependencies on classpath IDEA-120647 a JUnit case succeeds in `mvn test` and eclipse but fails in intellij IDEA-89979 Module dependency ordering doesn't reflect maven pom dependency ordering IDEA-73255 IntelliJ test classpath doesn't match Maven's IDEA-110060 order of classpath entitis of project shall be the same as in maven pom IDEA-98262 Maven scope "provided" is overriden by "test" --- .../maven/server/AetherModelConverter.java | 88 +++++++++++++ .../server/Maven3ServerEmbedderImpl.java | 120 +++++++++++++++--- .../maven/server/MavenModelConverter.java | 8 +- .../server/embedder/MavenExecutionResult.java | 22 +++- 4 files changed, 216 insertions(+), 22 deletions(-) create mode 100644 plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/AetherModelConverter.java diff --git a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/AetherModelConverter.java b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/AetherModelConverter.java new file mode 100644 index 000000000000..3e56735b43bc --- /dev/null +++ b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/AetherModelConverter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.maven.server; + +import gnu.trove.THashMap; +import org.apache.maven.RepositoryUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; +import org.jetbrains.idea.maven.model.*; +import org.sonatype.aether.graph.DependencyNode; + +import java.io.File; +import java.rmi.RemoteException; +import java.util.*; + +/** + * {@link AetherModelConverter} provides adapted methods of {@link MavenModelConverter} for aether models conversion + * + * @author Vladislav.Soroka + * @since 9/24/2014 + */ +public class AetherModelConverter extends MavenModelConverter { + + public static MavenModel convertModelWithAetherDependencyTree(Model model, + List sources, + List testSources, + Collection dependencies, + Collection dependencyTree, + Collection extensions, + File localRepository) throws RemoteException { + MavenModel result = new MavenModel(); + result.setMavenId(new MavenId(model.getGroupId(), model.getArtifactId(), model.getVersion())); + + Parent parent = model.getParent(); + if (parent != null) { + result.setParent(new MavenParent(new MavenId(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()), + parent.getRelativePath())); + } + result.setPackaging(model.getPackaging()); + result.setName(model.getName()); + result.setProperties(model.getProperties() == null ? new Properties() : model.getProperties()); + result.setPlugins(convertPlugins(model)); + + Map convertedArtifacts = new THashMap(); + result.setExtensions(convertArtifacts(extensions, convertedArtifacts, localRepository)); + result.setDependencies(convertArtifacts(dependencies, convertedArtifacts, localRepository)); + result.setDependencyTree(convertAetherDependencyNodes(null, dependencyTree, convertedArtifacts, localRepository)); + + result.setRemoteRepositories(convertRepositories(model.getRepositories())); + result.setProfiles(convertProfiles(model.getProfiles())); + result.setModules(model.getModules()); + + convertBuild(result.getBuild(), model.getBuild(), sources, testSources); + return result; + } + + public static List convertAetherDependencyNodes(MavenArtifactNode parent, + Collection nodes, + Map nativeToConvertedMap, + File localRepository) { + List result = new ArrayList(nodes.size()); + for (DependencyNode each : nodes) { + Artifact a = RepositoryUtils.toArtifact(each.getDependency().getArtifact()); + MavenArtifact ma = convertArtifact(a, nativeToConvertedMap, localRepository); + + MavenArtifactNode newNode = new MavenArtifactNode( + parent, ma, MavenArtifactState.ADDED, null, each.getDependency().getScope(), + each.getPremanagedVersion(), each.getPremanagedScope()); + newNode.setDependencies(convertAetherDependencyNodes(newNode, each.getChildren(), nativeToConvertedMap, localRepository)); + result.add(newNode); + } + return result; + } +} diff --git a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java index 2a61f1982010..27f211762fec 100644 --- a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java +++ b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3ServerEmbedderImpl.java @@ -40,6 +40,7 @@ import org.apache.maven.plugin.LegacySupport; import org.apache.maven.plugin.internal.PluginDependenciesResolver; import org.apache.maven.profiles.activation.*; import org.apache.maven.project.*; +import org.apache.maven.project.ProjectDependenciesResolver; import org.apache.maven.project.inheritance.DefaultModelInheritanceAssembler; import org.apache.maven.project.interpolation.AbstractStringBasedModelInterpolator; import org.apache.maven.project.interpolation.ModelInterpolationException; @@ -69,6 +70,8 @@ import org.jetbrains.idea.maven.model.*; import org.jetbrains.idea.maven.server.embedder.*; import org.jetbrains.idea.maven.server.embedder.MavenExecutionResult; import org.sonatype.aether.RepositorySystemSession; +import org.sonatype.aether.graph.Dependency; +import org.sonatype.aether.repository.LocalRepositoryManager; import org.sonatype.aether.util.DefaultRepositorySystemSession; import org.sonatype.aether.util.graph.PreorderNodeListGenerator; @@ -97,6 +100,9 @@ import java.util.concurrent.atomic.AtomicReference; org.jetbrains.idea.maven.server.embedder.CustomMaven3ModelInterpolator2 <-> org.apache.maven.model.interpolation.StringSearchModelInterpolator */ public class Maven3ServerEmbedderImpl extends MavenRemoteObject implements MavenServerEmbedder { + + private final static boolean USE_MVN2_COMPATIBLE_DEPENDENCY_RESOLVING = System.getProperty("maven3.use.compat.resolver") != null; + @NotNull private final DefaultPlexusContainer myContainer; @NotNull private final Settings myMavenSettings; @@ -410,23 +416,41 @@ public class Maven3ServerEmbedderImpl extends MavenRemoteObject implements Maven project.setDependencyArtifacts(project.createArtifacts(getComponent(ArtifactFactory.class), null, null)); // - ArtifactResolutionRequest resolutionRequest = new ArtifactResolutionRequest(); - resolutionRequest.setArtifactDependencies(project.getDependencyArtifacts()); - resolutionRequest.setArtifact(project.getArtifact()); - resolutionRequest.setManagedVersionMap(project.getManagedVersionMap()); - resolutionRequest.setLocalRepository(myLocalRepository); - resolutionRequest.setRemoteRepositories(project.getRemoteArtifactRepositories()); - resolutionRequest.setListeners(listeners); + if (USE_MVN2_COMPATIBLE_DEPENDENCY_RESOLVING) { + ArtifactResolutionRequest resolutionRequest = new ArtifactResolutionRequest(); + resolutionRequest.setArtifactDependencies(project.getDependencyArtifacts()); + resolutionRequest.setArtifact(project.getArtifact()); + resolutionRequest.setManagedVersionMap(project.getManagedVersionMap()); + resolutionRequest.setLocalRepository(myLocalRepository); + resolutionRequest.setRemoteRepositories(project.getRemoteArtifactRepositories()); + resolutionRequest.setListeners(listeners); - resolutionRequest.setResolveRoot(false); - resolutionRequest.setResolveTransitively(true); + resolutionRequest.setResolveRoot(false); + resolutionRequest.setResolveTransitively(true); - ArtifactResolver resolver = getComponent(ArtifactResolver.class); - ArtifactResolutionResult result = resolver.resolve(resolutionRequest); + ArtifactResolver resolver = getComponent(ArtifactResolver.class); + ArtifactResolutionResult result = resolver.resolve(resolutionRequest); - project.setArtifacts(result.getArtifacts()); + project.setArtifacts(result.getArtifacts()); // end copied from DefaultMavenProjectBuilder.buildWithDependencies - ref.set(new MavenExecutionResult(project, exceptions)); + ref.set(new MavenExecutionResult(project, exceptions)); + } + else { + final DependencyResolutionResult dependencyResolutionResult = resolveDependencies(project, repositorySession); + final List dependencies = dependencyResolutionResult.getDependencies(); + + Set artifacts = new LinkedHashSet(dependencies.size()); + for (Dependency dependency : dependencies) { + final Artifact artifact = RepositoryUtils.toArtifact(dependency.getArtifact()); + artifact.setScope(dependency.getScope()); + artifact.setOptional(dependency.isOptional()); + artifacts.add(artifact); + resolveAsModule(artifact); + } + + project.setArtifacts(artifacts); + ref.set(new MavenExecutionResult(project, dependencyResolutionResult, exceptions)); + } } catch (Exception e) { ref.set(handleException(e)); @@ -437,6 +461,54 @@ public class Maven3ServerEmbedderImpl extends MavenRemoteObject implements Maven return ref.get(); } + private boolean resolveAsModule(Artifact a) { + MavenWorkspaceMap map = myWorkspaceMap; + if (map == null) return false; + + MavenWorkspaceMap.Data resolved = map.findFileAndOriginalId(MavenModelConverter.createMavenId(a)); + if (resolved == null) return false; + + a.setResolved(true); + a.setFile(resolved.getFile(a.getType())); + a.selectVersion(resolved.originalId.getVersion()); + return true; + } + + /** + * copied from {@link org.apache.maven.project.DefaultProjectBuilder#resolveDependencies(org.apache.maven.project.MavenProject, org.sonatype.aether.RepositorySystemSession)} + */ + private DependencyResolutionResult resolveDependencies(MavenProject project, RepositorySystemSession session) { + DependencyResolutionResult resolutionResult; + + try { + ProjectDependenciesResolver dependencyResolver = getComponent(ProjectDependenciesResolver.class); + DefaultDependencyResolutionRequest resolution = new DefaultDependencyResolutionRequest(project, session); + resolutionResult = dependencyResolver.resolve(resolution); + } + catch (DependencyResolutionException e) { + resolutionResult = e.getResult(); + } + + Set artifacts = new LinkedHashSet(); + if (resolutionResult.getDependencyGraph() != null) { + RepositoryUtils.toArtifacts(artifacts, resolutionResult.getDependencyGraph().getChildren(), + Collections.singletonList(project.getArtifact().getId()), null); + + // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not + LocalRepositoryManager lrm = session.getLocalRepositoryManager(); + for (Artifact artifact : artifacts) { + if (!artifact.isResolved()) { + String path = lrm.getPathForLocalArtifact(RepositoryUtils.toArtifact(artifact)); + artifact.setFile(new File(lrm.getRepository().getBasedir(), path)); + } + } + } + project.setResolvedArtifacts(artifacts); + project.setArtifacts(artifacts); + + return resolutionResult; + } + /** * adapted from {@link org.apache.maven.DefaultMaven#doExecute(org.apache.maven.execution.MavenExecutionRequest)} */ @@ -570,10 +642,24 @@ public class Maven3ServerEmbedderImpl extends MavenRemoteObject implements Maven MavenModel model = null; try { - model = MavenModelConverter - .convertModel(mavenProject.getModel(), mavenProject.getCompileSourceRoots(), mavenProject.getTestCompileSourceRoots(), - mavenProject.getArtifacts(), (rootNode == null ? Collections.emptyList() : rootNode.getChildren()), - mavenProject.getExtensionArtifacts(), getLocalRepositoryFile()); + if (USE_MVN2_COMPATIBLE_DEPENDENCY_RESOLVING) { + //noinspection unchecked + final List dependencyNodes = rootNode == null ? Collections.emptyList() : rootNode.getChildren(); + model = MavenModelConverter.convertModel( + mavenProject.getModel(), mavenProject.getCompileSourceRoots(), mavenProject.getTestCompileSourceRoots(), + mavenProject.getArtifacts(), dependencyNodes, mavenProject.getExtensionArtifacts(), getLocalRepositoryFile()); + } + else { + final DependencyResolutionResult dependencyResolutionResult = result.getDependencyResolutionResult(); + final org.sonatype.aether.graph.DependencyNode dependencyGraph = + dependencyResolutionResult != null ? dependencyResolutionResult.getDependencyGraph() : null; + + final List dependencyNodes = + dependencyGraph != null ? dependencyGraph.getChildren() : Collections.emptyList(); + model = AetherModelConverter.convertModelWithAetherDependencyTree( + mavenProject.getModel(), mavenProject.getCompileSourceRoots(), mavenProject.getTestCompileSourceRoots(), + mavenProject.getArtifacts(), dependencyNodes, mavenProject.getExtensionArtifacts(), getLocalRepositoryFile()); + } } catch (Exception e) { validate(mavenProject.getFile(), Collections.singleton(e), problems, null); diff --git a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/MavenModelConverter.java b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/MavenModelConverter.java index a374afd2fb4e..daecb94144ac 100644 --- a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/MavenModelConverter.java +++ b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/MavenModelConverter.java @@ -83,7 +83,7 @@ public class MavenModelConverter { return result; } - private static void convertBuild(MavenBuild result, Build build, List sources, List testSources) { + public static void convertBuild(MavenBuild result, Build build, List sources, List testSources) { convertBuildBase(result, build); result.setOutputDirectory(build.getOutputDirectory()); result.setTestOutputDirectory(build.getTestOutputDirectory()); @@ -122,7 +122,7 @@ public class MavenModelConverter { return patterns == null ? Collections.emptyList() : patterns; } - private static List convertRepositories(List repositories) { + public static List convertRepositories(List repositories) { if (repositories == null) return new ArrayList(); List result = new ArrayList(repositories.size()); @@ -191,7 +191,7 @@ public class MavenModelConverter { return result; } - private static MavenArtifact convertArtifact(Artifact artifact, Map nativeToConvertedMap, File localRepository) { + public static MavenArtifact convertArtifact(Artifact artifact, Map nativeToConvertedMap, File localRepository) { MavenArtifact result = nativeToConvertedMap.get(artifact); if (result == null) { result = convertArtifact(artifact, localRepository); @@ -228,7 +228,7 @@ public class MavenModelConverter { return result; } - private static List convertPlugins(Model mavenModel) throws RemoteException { + public static List convertPlugins(Model mavenModel) throws RemoteException { List result = new ArrayList(); Build build = mavenModel.getBuild(); diff --git a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/embedder/MavenExecutionResult.java b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/embedder/MavenExecutionResult.java index 4908debadea3..c7aba230b341 100644 --- a/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/embedder/MavenExecutionResult.java +++ b/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/embedder/MavenExecutionResult.java @@ -15,18 +15,32 @@ */ package org.jetbrains.idea.maven.server.embedder; +import org.apache.maven.project.DependencyResolutionResult; import org.apache.maven.project.MavenProject; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; public class MavenExecutionResult { private final MavenProject myMavenProject; private final List myExceptions; + private final DependencyResolutionResult myDependencyResolutionResult; public MavenExecutionResult(@Nullable MavenProject mavenProject, List exceptions) { + this(mavenProject, null, exceptions); + } + + public MavenExecutionResult(@Nullable MavenProject mavenProject, + @Nullable DependencyResolutionResult dependencyResolutionResult, + List exceptions) { myMavenProject = mavenProject; - myExceptions = exceptions; + myExceptions = exceptions == null ? new ArrayList() : exceptions; + myDependencyResolutionResult = dependencyResolutionResult; + if(myDependencyResolutionResult != null && myDependencyResolutionResult.getCollectionErrors() != null) { + myExceptions.addAll(myDependencyResolutionResult.getCollectionErrors()); + } } @Nullable @@ -34,6 +48,12 @@ public class MavenExecutionResult { return myMavenProject; } + @Nullable + public DependencyResolutionResult getDependencyResolutionResult() { + return myDependencyResolutionResult; + } + + @NotNull public List getExceptions() { return myExceptions; }