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.plugin.internal;
20  
21  import java.util.Collection;
22  import java.util.LinkedHashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Objects;
26  
27  import org.apache.maven.RepositoryUtils;
28  import org.apache.maven.model.Dependency;
29  import org.apache.maven.model.Plugin;
30  import org.apache.maven.plugin.PluginResolutionException;
31  import org.apache.maven.plugin.PluginValidationManager;
32  import org.codehaus.plexus.component.annotations.Component;
33  import org.codehaus.plexus.component.annotations.Requirement;
34  import org.codehaus.plexus.logging.Logger;
35  import org.codehaus.plexus.util.StringUtils;
36  import org.eclipse.aether.DefaultRepositorySystemSession;
37  import org.eclipse.aether.RepositorySystem;
38  import org.eclipse.aether.RepositorySystemSession;
39  import org.eclipse.aether.RequestTrace;
40  import org.eclipse.aether.artifact.Artifact;
41  import org.eclipse.aether.artifact.DefaultArtifact;
42  import org.eclipse.aether.collection.CollectRequest;
43  import org.eclipse.aether.collection.DependencyCollectionException;
44  import org.eclipse.aether.collection.DependencySelector;
45  import org.eclipse.aether.graph.DependencyFilter;
46  import org.eclipse.aether.graph.DependencyNode;
47  import org.eclipse.aether.graph.DependencyVisitor;
48  import org.eclipse.aether.repository.RemoteRepository;
49  import org.eclipse.aether.resolution.ArtifactDescriptorException;
50  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
51  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
52  import org.eclipse.aether.resolution.ArtifactRequest;
53  import org.eclipse.aether.resolution.ArtifactResolutionException;
54  import org.eclipse.aether.resolution.DependencyRequest;
55  import org.eclipse.aether.resolution.DependencyResolutionException;
56  import org.eclipse.aether.util.artifact.JavaScopes;
57  import org.eclipse.aether.util.filter.AndDependencyFilter;
58  import org.eclipse.aether.util.filter.ScopeDependencyFilter;
59  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
60  import org.eclipse.aether.util.graph.selector.AndDependencySelector;
61  import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
62  
63  /**
64   * Assists in resolving the dependencies of a plugin. <strong>Warning:</strong> This is an internal utility class that
65   * is only public for technical reasons, it is not part of the public API. In particular, this class can be changed or
66   * deleted without prior notice.
67   *
68   * @since 3.0
69   * @author Benjamin Bentmann
70   */
71  @Component(role = PluginDependenciesResolver.class)
72  public class DefaultPluginDependenciesResolver implements PluginDependenciesResolver {
73  
74      private static final String REPOSITORY_CONTEXT = "plugin";
75  
76      @Requirement
77      private Logger logger;
78  
79      @Requirement
80      private RepositorySystem repoSystem;
81  
82      @Requirement
83      private PluginValidationManager pluginValidationManager;
84  
85      private Artifact toArtifact(Plugin plugin, RepositorySystemSession session) {
86          return new DefaultArtifact(
87                  plugin.getGroupId(),
88                  plugin.getArtifactId(),
89                  null,
90                  "jar",
91                  plugin.getVersion(),
92                  session.getArtifactTypeRegistry().get("maven-plugin"));
93      }
94  
95      public Artifact resolve(Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
96              throws PluginResolutionException {
97          RequestTrace trace = RequestTrace.newChild(null, plugin);
98  
99          Artifact pluginArtifact = toArtifact(plugin, session);
100 
101         try {
102             DefaultRepositorySystemSession pluginSession = new DefaultRepositorySystemSession(session);
103             pluginSession.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(true, false));
104 
105             ArtifactDescriptorRequest request =
106                     new ArtifactDescriptorRequest(pluginArtifact, repositories, REPOSITORY_CONTEXT);
107             request.setTrace(trace);
108             ArtifactDescriptorResult result = repoSystem.readArtifactDescriptor(pluginSession, request);
109 
110             if (result.getDependencies() != null) {
111                 for (org.eclipse.aether.graph.Dependency dependency : result.getDependencies()) {
112                     if ("org.apache.maven".equals(dependency.getArtifact().getGroupId())
113                             && "maven-compat".equals(dependency.getArtifact().getArtifactId())
114                             && !JavaScopes.TEST.equals(dependency.getScope())) {
115                         pluginValidationManager.reportPluginValidationIssue(
116                                 session,
117                                 pluginArtifact,
118                                 "Plugin depends on the deprecated Maven 2.x compatibility layer, which may not be supported in Maven 4.x");
119                     }
120                 }
121             }
122 
123             pluginArtifact = result.getArtifact();
124 
125             if (logger.isWarnEnabled()) {
126                 if (!result.getRelocations().isEmpty()) {
127                     String message = pluginArtifact instanceof org.apache.maven.repository.internal.RelocatedArtifact
128                             ? ((org.apache.maven.repository.internal.RelocatedArtifact) pluginArtifact).getMessage()
129                             : null;
130                     logger.warn("The artifact " + result.getRelocations().get(0) + " has been relocated to "
131                             + pluginArtifact + (message != null ? ": " + message : ""));
132                 }
133             }
134 
135             String requiredMavenVersion = (String) result.getProperties().get("prerequisites.maven");
136             if (requiredMavenVersion != null) {
137                 Map<String, String> props = new LinkedHashMap<>(pluginArtifact.getProperties());
138                 props.put("requiredMavenVersion", requiredMavenVersion);
139                 pluginArtifact = pluginArtifact.setProperties(props);
140             }
141         } catch (ArtifactDescriptorException e) {
142             throw new PluginResolutionException(plugin, e);
143         }
144 
145         try {
146             ArtifactRequest request = new ArtifactRequest(pluginArtifact, repositories, REPOSITORY_CONTEXT);
147             request.setTrace(trace);
148             pluginArtifact = repoSystem.resolveArtifact(session, request).getArtifact();
149         } catch (ArtifactResolutionException e) {
150             throw new PluginResolutionException(plugin, e);
151         }
152 
153         return pluginArtifact;
154     }
155 
156     /**
157      * @since 3.3.0
158      */
159     public DependencyNode resolveCoreExtension(
160             Plugin plugin,
161             DependencyFilter dependencyFilter,
162             List<RemoteRepository> repositories,
163             RepositorySystemSession session)
164             throws PluginResolutionException {
165         return resolveInternal(plugin, null /* pluginArtifact */, dependencyFilter, repositories, session);
166     }
167 
168     public DependencyNode resolve(
169             Plugin plugin,
170             Artifact pluginArtifact,
171             DependencyFilter dependencyFilter,
172             List<RemoteRepository> repositories,
173             RepositorySystemSession session)
174             throws PluginResolutionException {
175         return resolveInternal(plugin, pluginArtifact, dependencyFilter, repositories, session);
176     }
177 
178     private DependencyNode resolveInternal(
179             Plugin plugin,
180             Artifact pluginArtifact,
181             DependencyFilter dependencyFilter,
182             List<RemoteRepository> repositories,
183             RepositorySystemSession session)
184             throws PluginResolutionException {
185         RequestTrace trace = RequestTrace.newChild(null, plugin);
186 
187         if (pluginArtifact == null) {
188             pluginArtifact = toArtifact(plugin, session);
189         }
190 
191         DependencyFilter collectionFilter = new ScopeDependencyFilter("provided", "test");
192         DependencyFilter resolutionFilter = AndDependencyFilter.newInstance(collectionFilter, dependencyFilter);
193 
194         DependencyNode node;
195 
196         try {
197             DependencySelector selector =
198                     AndDependencySelector.newInstance(session.getDependencySelector(), new WagonExcluder());
199 
200             DefaultRepositorySystemSession pluginSession = new DefaultRepositorySystemSession(session);
201             pluginSession.setDependencySelector(selector);
202             pluginSession.setDependencyGraphTransformer(session.getDependencyGraphTransformer());
203 
204             CollectRequest request = new CollectRequest();
205             request.setRequestContext(REPOSITORY_CONTEXT);
206             request.setRepositories(repositories);
207             request.setRoot(new org.eclipse.aether.graph.Dependency(pluginArtifact, null));
208             for (Dependency dependency : plugin.getDependencies()) {
209                 org.eclipse.aether.graph.Dependency pluginDep =
210                         RepositoryUtils.toDependency(dependency, session.getArtifactTypeRegistry());
211                 if (!JavaScopes.SYSTEM.equals(pluginDep.getScope())) {
212                     pluginDep = pluginDep.setScope(JavaScopes.RUNTIME);
213                 }
214                 request.addDependency(pluginDep);
215             }
216 
217             DependencyRequest depRequest = new DependencyRequest(request, resolutionFilter);
218             depRequest.setTrace(trace);
219 
220             request.setTrace(RequestTrace.newChild(trace, depRequest));
221 
222             node = repoSystem.collectDependencies(pluginSession, request).getRoot();
223 
224             if (logger.isDebugEnabled()) {
225                 node.accept(new GraphLogger());
226             }
227 
228             depRequest.setRoot(node);
229             repoSystem.resolveDependencies(session, depRequest);
230         } catch (DependencyCollectionException e) {
231             throw new PluginResolutionException(plugin, e);
232         } catch (DependencyResolutionException e) {
233             throw new PluginResolutionException(plugin, e.getCause());
234         }
235 
236         return node;
237     }
238 
239     // Keep this class in sync with org.apache.maven.project.DefaultProjectDependenciesResolver.GraphLogger
240     class GraphLogger implements DependencyVisitor {
241 
242         private String indent = "";
243 
244         public boolean visitEnter(DependencyNode node) {
245             StringBuilder buffer = new StringBuilder(128);
246             buffer.append(indent);
247             org.eclipse.aether.graph.Dependency dep = node.getDependency();
248             if (dep != null) {
249                 org.eclipse.aether.artifact.Artifact art = dep.getArtifact();
250 
251                 buffer.append(art);
252                 if (StringUtils.isNotEmpty(dep.getScope())) {
253                     buffer.append(':').append(dep.getScope());
254                 }
255 
256                 if (dep.isOptional()) {
257                     buffer.append(" (optional)");
258                 }
259 
260                 // TODO We currently cannot tell which <dependencyManagement> section contained the management
261                 //      information. When the resolver provides this information, these log messages should be updated
262                 //      to contain it.
263                 if ((node.getManagedBits() & DependencyNode.MANAGED_SCOPE) == DependencyNode.MANAGED_SCOPE) {
264                     final String premanagedScope = DependencyManagerUtils.getPremanagedScope(node);
265                     buffer.append(" (scope managed from ");
266                     buffer.append(Objects.toString(premanagedScope, "default"));
267                     buffer.append(')');
268                 }
269 
270                 if ((node.getManagedBits() & DependencyNode.MANAGED_VERSION) == DependencyNode.MANAGED_VERSION) {
271                     final String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(node);
272                     buffer.append(" (version managed from ");
273                     buffer.append(Objects.toString(premanagedVersion, "default"));
274                     buffer.append(')');
275                 }
276 
277                 if ((node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == DependencyNode.MANAGED_OPTIONAL) {
278                     final Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(node);
279                     buffer.append(" (optionality managed from ");
280                     buffer.append(Objects.toString(premanagedOptional, "default"));
281                     buffer.append(')');
282                 }
283 
284                 if ((node.getManagedBits() & DependencyNode.MANAGED_EXCLUSIONS) == DependencyNode.MANAGED_EXCLUSIONS) {
285                     final Collection<org.eclipse.aether.graph.Exclusion> premanagedExclusions =
286                             DependencyManagerUtils.getPremanagedExclusions(node);
287 
288                     buffer.append(" (exclusions managed from ");
289                     buffer.append(Objects.toString(premanagedExclusions, "default"));
290                     buffer.append(')');
291                 }
292 
293                 if ((node.getManagedBits() & DependencyNode.MANAGED_PROPERTIES) == DependencyNode.MANAGED_PROPERTIES) {
294                     final Map<String, String> premanagedProperties =
295                             DependencyManagerUtils.getPremanagedProperties(node);
296 
297                     buffer.append(" (properties managed from ");
298                     buffer.append(Objects.toString(premanagedProperties, "default"));
299                     buffer.append(')');
300                 }
301             }
302 
303             logger.debug(buffer.toString());
304             indent += "   ";
305             return true;
306         }
307 
308         public boolean visitLeave(DependencyNode node) {
309             indent = indent.substring(0, indent.length() - 3);
310             return true;
311         }
312     }
313 }