View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.assembly.artifact;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.ArrayDeque;
26  import java.util.ArrayList;
27  import java.util.Deque;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.LinkedHashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Optional;
34  import java.util.Set;
35  import java.util.stream.Collectors;
36  
37  import org.apache.maven.RepositoryUtils;
38  import org.apache.maven.artifact.Artifact;
39  import org.apache.maven.artifact.DefaultArtifact;
40  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
41  import org.apache.maven.artifact.versioning.VersionRange;
42  import org.apache.maven.model.DependencyManagement;
43  import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
44  import org.apache.maven.plugins.assembly.archive.ArchiveCreationException;
45  import org.apache.maven.plugins.assembly.archive.phase.ModuleSetAssemblyPhase;
46  import org.apache.maven.plugins.assembly.model.Assembly;
47  import org.apache.maven.plugins.assembly.model.DependencySet;
48  import org.apache.maven.plugins.assembly.model.ModuleBinaries;
49  import org.apache.maven.plugins.assembly.model.ModuleSet;
50  import org.apache.maven.project.MavenProject;
51  import org.codehaus.plexus.util.StringUtils;
52  import org.eclipse.aether.RepositorySystem;
53  import org.eclipse.aether.RepositorySystemSession;
54  import org.eclipse.aether.collection.CollectRequest;
55  import org.eclipse.aether.graph.DefaultDependencyNode;
56  import org.eclipse.aether.graph.Dependency;
57  import org.eclipse.aether.graph.DependencyFilter;
58  import org.eclipse.aether.graph.DependencyNode;
59  import org.eclipse.aether.graph.DependencyVisitor;
60  import org.eclipse.aether.resolution.DependencyRequest;
61  import org.eclipse.aether.resolution.DependencyResult;
62  import org.eclipse.aether.util.filter.DependencyFilterUtils;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  import static java.util.Objects.requireNonNull;
67  
68  /**
69   * @author jdcasey
70   */
71  @Singleton
72  @Named
73  public class DefaultDependencyResolver implements DependencyResolver {
74      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDependencyResolver.class);
75  
76      private final ArtifactHandlerManager artifactHandlerManager;
77  
78      private final RepositorySystem repositorySystem;
79  
80      @Inject
81      public DefaultDependencyResolver(ArtifactHandlerManager artifactHandlerManager, RepositorySystem repositorySystem) {
82          this.artifactHandlerManager = requireNonNull(artifactHandlerManager);
83          this.repositorySystem = requireNonNull(repositorySystem);
84      }
85  
86      @Override
87      public Map<DependencySet, Set<Artifact>> resolveDependencySets(
88              final Assembly assembly,
89              ModuleSet moduleSet,
90              final AssemblerConfigurationSource configSource,
91              List<DependencySet> dependencySets)
92              throws DependencyResolutionException {
93          Map<DependencySet, Set<Artifact>> result = new LinkedHashMap<>();
94  
95          for (DependencySet dependencySet : dependencySets) {
96  
97              final MavenProject currentProject = configSource.getProject();
98  
99              final ResolutionManagementInfo info = new ResolutionManagementInfo();
100             updateDependencySetResolutionRequirements(
101                     configSource.getMavenSession().getRepositorySession(), dependencySet, info, currentProject);
102             updateModuleSetResolutionRequirements(moduleSet, dependencySet, info, configSource);
103 
104             result.put(dependencySet, info.getArtifacts());
105         }
106         return result;
107     }
108 
109     @Override
110     public Map<DependencySet, Set<Artifact>> resolveDependencySets(
111             final Assembly assembly,
112             final AssemblerConfigurationSource configSource,
113             List<DependencySet> dependencySets)
114             throws DependencyResolutionException {
115         Map<DependencySet, Set<Artifact>> result = new LinkedHashMap<>();
116 
117         for (DependencySet dependencySet : dependencySets) {
118 
119             final MavenProject currentProject = configSource.getProject();
120 
121             final ResolutionManagementInfo info = new ResolutionManagementInfo();
122             updateDependencySetResolutionRequirements(
123                     configSource.getMavenSession().getRepositorySession(), dependencySet, info, currentProject);
124 
125             result.put(dependencySet, info.getArtifacts());
126         }
127         return result;
128     }
129 
130     void updateModuleSetResolutionRequirements(
131             ModuleSet set,
132             DependencySet dependencySet,
133             final ResolutionManagementInfo requirements,
134             final AssemblerConfigurationSource configSource)
135             throws DependencyResolutionException {
136         final ModuleBinaries binaries = set.getBinaries();
137         if (binaries != null) {
138             Set<MavenProject> projects;
139             try {
140                 projects = ModuleSetAssemblyPhase.getModuleProjects(set, configSource, LOGGER);
141             } catch (final ArchiveCreationException e) {
142                 throw new DependencyResolutionException(
143                         "Error determining project-set for moduleSet with binaries.", e);
144             }
145 
146             for (final MavenProject p : projects) {
147                 if (p.getArtifact() == null) {
148                     p.setArtifact(createArtifact(p.getGroupId(), p.getArtifactId(), p.getVersion(), p.getPackaging()));
149                 }
150             }
151 
152             if (binaries.isIncludeDependencies()) {
153                 updateDependencySetResolutionRequirements(
154                         configSource.getMavenSession().getRepositorySession(),
155                         dependencySet,
156                         requirements,
157                         projects.toArray(new MavenProject[0]));
158             }
159         }
160     }
161 
162     private Artifact createArtifact(String groupId, String artifactId, String version, String type) {
163         VersionRange versionRange = null;
164         if (version != null) {
165             versionRange = VersionRange.createFromVersion(version);
166         }
167         return new DefaultArtifact(
168                 groupId,
169                 artifactId,
170                 versionRange,
171                 null,
172                 type,
173                 null,
174                 artifactHandlerManager.getArtifactHandler(type),
175                 false);
176     }
177 
178     void updateDependencySetResolutionRequirements(
179             RepositorySystemSession systemSession,
180             final DependencySet set,
181             final ResolutionManagementInfo requirements,
182             final MavenProject... projects)
183             throws DependencyResolutionException {
184         for (final MavenProject project : projects) {
185             if (project == null) {
186                 continue;
187             }
188 
189             Set<Artifact> dependencyArtifacts = null;
190             if (set.isUseTransitiveDependencies()) {
191                 try {
192                     // we need resolve project again according to requested scope
193                     dependencyArtifacts = resolveTransitive(systemSession, set.getScope(), project);
194                 } catch (org.eclipse.aether.resolution.DependencyResolutionException e) {
195                     throw new DependencyResolutionException(e.getMessage(), e);
196                 }
197             } else {
198                 // FIXME remove using deprecated method
199                 dependencyArtifacts = project.getDependencyArtifacts();
200             }
201 
202             requirements.addArtifacts(dependencyArtifacts);
203             if (LOGGER.isDebugEnabled()) {
204                 LOGGER.debug(
205                         "Dependencies for project: {} are:\n{}",
206                         project.getId(),
207                         StringUtils.join(dependencyArtifacts.iterator(), "\n"));
208             }
209         }
210     }
211 
212     private Set<Artifact> resolveTransitive(
213             RepositorySystemSession repositorySession, String scope, MavenProject project)
214             throws org.eclipse.aether.resolution.DependencyResolutionException {
215 
216         // scope dependency filter
217         DependencyFilter scoopeDependencyFilter = DependencyFilterUtils.classpathFilter(scope);
218 
219         // get project dependencies filtered by requested scope
220         List<Dependency> dependencies = project.getDependencies().stream()
221                 .map(d -> RepositoryUtils.toDependency(d, repositorySession.getArtifactTypeRegistry()))
222                 .filter(d -> scoopeDependencyFilter.accept(new DefaultDependencyNode(d), null))
223                 .collect(Collectors.toList());
224 
225         List<Dependency> managedDependencies = Optional.ofNullable(project.getDependencyManagement())
226                 .map(DependencyManagement::getDependencies)
227                 .map(list -> list.stream()
228                         .map(d -> RepositoryUtils.toDependency(d, repositorySession.getArtifactTypeRegistry()))
229                         .collect(Collectors.toList()))
230                 .orElse(null);
231 
232         CollectRequest collectRequest = new CollectRequest();
233         collectRequest.setManagedDependencies(managedDependencies);
234         collectRequest.setRepositories(project.getRemoteProjectRepositories());
235         collectRequest.setDependencies(dependencies);
236         collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact()));
237 
238         DependencyRequest request = new DependencyRequest(collectRequest, scoopeDependencyFilter);
239 
240         DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySession, request);
241 
242         // cache for artifact mapping
243         Map<org.eclipse.aether.artifact.Artifact, Artifact> aetherToMavenArtifacts = new HashMap<>();
244         Deque<String> stack = new ArrayDeque<>();
245         stack.push(project.getArtifact().getId());
246 
247         Set<Artifact> artifacts = new HashSet<>();
248 
249         // we need rebuild artifact dependencyTrail - it is used by useTransitiveFiltering
250         dependencyResult.getRoot().accept(new DependencyVisitor() {
251             @Override
252             public boolean visitEnter(DependencyNode node) {
253                 if (node.getDependency() != null) {
254                     stack.push(aetherToMavenArtifacts
255                             .computeIfAbsent(node.getDependency().getArtifact(), RepositoryUtils::toArtifact)
256                             .getId());
257                 }
258                 return true;
259             }
260 
261             @Override
262             public boolean visitLeave(DependencyNode node) {
263                 Dependency dependency = node.getDependency();
264                 if (dependency != null) {
265                     Artifact artifact = aetherToMavenArtifacts.computeIfAbsent(
266                             dependency.getArtifact(), RepositoryUtils::toArtifact);
267                     if (artifact.isResolved() && artifact.getFile() != null) {
268                         List<String> depTrail = new ArrayList<>();
269                         stack.descendingIterator().forEachRemaining(depTrail::add);
270                         artifact.setDependencyTrail(depTrail);
271                         artifact.setOptional(dependency.isOptional());
272                         artifact.setScope(dependency.getScope());
273                         artifacts.add(artifact);
274                     }
275                     stack.pop();
276                 }
277                 return true;
278             }
279         });
280 
281         return artifacts;
282     }
283 }