001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package org.basepom.mojo.dvc.dependency; 016 017import static com.google.common.base.Preconditions.checkNotNull; 018import static com.google.common.collect.ImmutableSet.toImmutableSet; 019 020import org.basepom.mojo.dvc.Context; 021import org.basepom.mojo.dvc.PluginLog; 022import org.basepom.mojo.dvc.QualifiedName; 023 024import java.util.Objects; 025 026import com.google.common.base.Throwables; 027import com.google.common.collect.ImmutableList; 028import com.google.common.collect.ImmutableMap; 029import com.google.common.collect.ImmutableSet; 030import org.apache.maven.RepositoryUtils; 031import org.apache.maven.project.DefaultDependencyResolutionRequest; 032import org.apache.maven.project.DependencyResolutionException; 033import org.apache.maven.project.DependencyResolutionRequest; 034import org.apache.maven.project.DependencyResolutionResult; 035import org.apache.maven.project.MavenProject; 036import org.apache.maven.project.ProjectBuildingException; 037import org.apache.maven.project.ProjectBuildingResult; 038import org.eclipse.aether.artifact.Artifact; 039import org.eclipse.aether.artifact.DefaultArtifact; 040import org.eclipse.aether.graph.Dependency; 041import org.eclipse.aether.graph.DependencyFilter; 042import org.eclipse.aether.graph.DependencyNode; 043import org.eclipse.aether.repository.RemoteRepository; 044import org.eclipse.aether.transfer.ArtifactTransferException; 045import org.eclipse.aether.transfer.NoRepositoryLayoutException; 046import org.eclipse.aether.util.artifact.JavaScopes; 047 048/** 049 * Builds a map of dependencies required by a specific project or another dependency. 050 */ 051public final class DependencyMapBuilder { 052 053 private static final PluginLog LOG = new PluginLog(DependencyMapBuilder.class); 054 055 private final Context context; 056 057 public DependencyMapBuilder(final Context context) { 058 this.context = checkNotNull(context, "context is null"); 059 } 060 061 /** 062 * Create a map of dependencies for a given dependency node (representing an element on the dependency tree). 063 * 064 * @param dependencyNode The dependency node to use. 065 * @param projectScopeFilter A scope limiting filter to mask out dependencies out of scope. 066 * @return A map of dependencies for this given dependency node. 067 * @throws DependencyResolutionException Dependency resolution failed. 068 * @throws ProjectBuildingException Maven project could not be built. 069 */ 070 public DependencyMap mapDependency(final DependencyNode dependencyNode, 071 final DependencyFilter projectScopeFilter) 072 throws DependencyResolutionException, ProjectBuildingException { 073 checkNotNull(dependencyNode, "dependencyNode is null"); 074 checkNotNull(projectScopeFilter, "projectScopeFilter is null"); 075 076 // build the project 077 final ProjectBuildingResult result = context.getProjectBuilder() 078 .build(convertFromAetherDependency(dependencyNode), false, context.createProjectBuildingRequest()); 079 080 // now resolve the project representing the dependency. 081 final MavenProject project = result.getProject(); 082 return mapProject(project, projectScopeFilter); 083 } 084 085 /** 086 * Create a map of names to dependencies for a given project. 087 * 088 * @param project The current maven project. 089 * @param scopeFilter A scope limiting filter to mask out dependencies out of scope. 090 * @return A map of dependencies for this given dependency node. 091 * @throws DependencyResolutionException Dependency resolution failed. 092 */ 093 public DependencyMap mapProject(final MavenProject project, 094 final DependencyFilter scopeFilter) 095 throws DependencyResolutionException { 096 checkNotNull(project, "project is null"); 097 checkNotNull(scopeFilter, "scopeFilter is null"); 098 099 final DependencyResolutionRequest request = new DefaultDependencyResolutionRequest(); 100 request.setRepositorySession(context.getRepositorySystemSession()); 101 request.setMavenProject(project); 102 request.setResolutionFilter(scopeFilter); 103 104 DependencyResolutionResult result; 105 106 try { 107 result = context.getProjectDependenciesResolver().resolve(request); 108 } catch (DependencyResolutionException e) { 109 result = e.getResult(); 110 // try to resolve using the reactor projects 111 final ImmutableSet<ProjectKey> reactorProjects = context.getReactorProjects().stream() 112 .map(ProjectKey::fromProject).collect(toImmutableSet()); 113 114 // resolve all dependencies that are matched by the reactor. 115 final ImmutableSet<Dependency> reactorDependencies = result.getUnresolvedDependencies().stream() 116 .filter(d -> reactorProjects.contains(ProjectKey.fromDependency(d))) 117 .collect(toImmutableSet()); 118 119 result.getUnresolvedDependencies().removeAll(reactorDependencies); 120 result.getResolvedDependencies().addAll(reactorDependencies); 121 122 if (!context.isUnresolvedSystemArtifactsFailBuild()) { 123 final ImmutableSet<Dependency> systemDependencies = result.getUnresolvedDependencies().stream() 124 .filter(d -> JavaScopes.SYSTEM.equals(d.getScope())) 125 .collect(toImmutableSet()); 126 127 result.getUnresolvedDependencies().removeAll(systemDependencies); 128 result.getResolvedDependencies().addAll(systemDependencies); 129 } 130 131 if (!context.isOptionalDependenciesMustExist()) { 132 final ImmutableSet<Dependency> optionalDependencies = result.getUnresolvedDependencies().stream() 133 .filter(Dependency::isOptional) 134 .collect(toImmutableSet()); 135 136 result.getUnresolvedDependencies().removeAll(optionalDependencies); 137 result.getResolvedDependencies().addAll(optionalDependencies); 138 } 139 140 if (!result.getUnresolvedDependencies().isEmpty()) { 141 final Throwable t = Throwables.getRootCause(e); 142 RemoteRepository repository = null; 143 144 if (t instanceof NoRepositoryLayoutException) { 145 repository = ((NoRepositoryLayoutException) t).getRepository(); 146 } else if (t instanceof ArtifactTransferException) { 147 repository = ((ArtifactTransferException) t).getRepository(); 148 } 149 150 if (repository != null && "legacy".equals(repository.getContentType())) { 151 LOG.warn("Could not access a legacy repository for artifacts: %s; Reason: %s", result.getUnresolvedDependencies(), t.getMessage()); 152 } else { 153 throw e; 154 } 155 } 156 } 157 158 final DependencyNode graph = result.getDependencyGraph(); 159 final ImmutableMap.Builder<QualifiedName, DependencyNode> dependencyCollector = ImmutableMap.builder(); 160 final ImmutableMap<QualifiedName, DependencyNode> directDependencies = loadDependencyTree(graph, scopeFilter, dependencyCollector); 161 dependencyCollector.putAll(directDependencies); 162 return new DependencyMap(dependencyCollector.build(), directDependencies); 163 } 164 165 private ImmutableMap<QualifiedName, DependencyNode> loadDependencyTree(final DependencyNode node, 166 final DependencyFilter filter, 167 final ImmutableMap.Builder<QualifiedName, DependencyNode> allDependencyCollector) { 168 final ImmutableMap.Builder<QualifiedName, DependencyNode> builder = ImmutableMap.builder(); 169 for (final DependencyNode dependencyNode : node.getChildren()) { 170 if (dependencyNode.getManagedBits() != 0) { 171 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_VERSION) != 0) { 172 LOG.debug("%s -> Managed Version!", dependencyNode.getArtifact()); 173 } 174 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_SCOPE) != 0) { 175 LOG.debug("%s -> Managed Scope!", dependencyNode.getArtifact()); 176 } 177 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) != 0) { 178 LOG.debug("%s -> Managed Optional!", dependencyNode.getArtifact()); 179 } 180 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_PROPERTIES) != 0) { 181 LOG.debug("%s -> Managed Properties!", dependencyNode.getArtifact()); 182 } 183 if ((dependencyNode.getManagedBits() & DependencyNode.MANAGED_EXCLUSIONS) != 0) { 184 LOG.debug("%s -> Managed Exclusions!", dependencyNode.getArtifact()); 185 } 186 } 187 188 if (filter.accept(dependencyNode, ImmutableList.of(node))) { 189 final QualifiedName name = QualifiedName.fromDependencyNode(dependencyNode); 190 builder.put(name, dependencyNode); 191 192 allDependencyCollector.putAll(loadDependencyTree(dependencyNode, filter, allDependencyCollector)); 193 } 194 } 195 return builder.build(); 196 } 197 198 static org.apache.maven.artifact.Artifact convertFromAetherDependency(final DependencyNode dependencyNode) { 199 Artifact aetherArtifact = convertToPomArtifact(dependencyNode.getArtifact()); 200 201 final org.apache.maven.artifact.Artifact mavenArtifact = RepositoryUtils.toArtifact(aetherArtifact); 202 mavenArtifact.setScope(dependencyNode.getDependency().getScope()); 203 mavenArtifact.setOptional(dependencyNode.getDependency().isOptional()); 204 205 return mavenArtifact; 206 } 207 208 static Artifact convertToPomArtifact(final Artifact artifact) { 209 // pom artifact has no classifier. If this is already a pom artifact, don't touch it. 210 if (artifact.getClassifier().isEmpty() && "pom".equals(artifact.getExtension())) { 211 return artifact; 212 } 213 214 // create a POM artifact. 215 return new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), "pom", artifact.getVersion()); 216 } 217 218 private static final class ProjectKey { 219 220 private final String groupId; 221 private final String artifactId; 222 private final String version; 223 224 public static ProjectKey fromProject(final MavenProject project) { 225 checkNotNull(project, "project; is null"); 226 return new ProjectKey(project.getGroupId(), project.getArtifactId(), project.getVersion()); 227 } 228 229 public static ProjectKey fromDependency(final Dependency dependency) { 230 checkNotNull(dependency, "artifact; is null"); 231 return new ProjectKey(dependency.getArtifact().getGroupId(), 232 dependency.getArtifact().getArtifactId(), 233 dependency.getArtifact().getVersion()); 234 } 235 236 private ProjectKey(final String groupId, final String artifactId, final String version) { 237 this.groupId = checkNotNull(groupId, "groupId is null"); 238 this.artifactId = checkNotNull(artifactId, "artifactId is null"); 239 this.version = checkNotNull(version, "version is null"); 240 } 241 242 @Override 243 public boolean equals(Object o) { 244 if (this == o) { 245 return true; 246 } 247 if (o == null || getClass() != o.getClass()) { 248 return false; 249 } 250 ProjectKey that = (ProjectKey) o; 251 return groupId.equals(that.groupId) 252 && artifactId.equals(that.artifactId) 253 && version.equals(that.version); 254 } 255 256 @Override 257 public int hashCode() { 258 return Objects.hash(groupId, artifactId, version); 259 } 260 } 261}