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.surefire;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  import javax.inject.Inject;
24  import javax.inject.Named;
25  import javax.inject.Singleton;
26  
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Iterator;
30  import java.util.LinkedHashMap;
31  import java.util.LinkedHashSet;
32  import java.util.List;
33  import java.util.Map;
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.versioning.DefaultArtifactVersion;
40  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
41  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
42  import org.apache.maven.artifact.versioning.VersionRange;
43  import org.apache.maven.model.Dependency;
44  import org.apache.maven.model.Plugin;
45  import org.apache.maven.plugin.MojoExecutionException;
46  import org.eclipse.aether.RepositorySystem;
47  import org.eclipse.aether.RepositorySystemSession;
48  import org.eclipse.aether.collection.CollectRequest;
49  import org.eclipse.aether.graph.DependencyFilter;
50  import org.eclipse.aether.repository.RemoteRepository;
51  import org.eclipse.aether.resolution.ArtifactResult;
52  import org.eclipse.aether.resolution.DependencyRequest;
53  import org.eclipse.aether.resolution.DependencyResolutionException;
54  import org.eclipse.aether.resolution.DependencyResult;
55  import org.eclipse.aether.util.artifact.JavaScopes;
56  import org.eclipse.aether.util.filter.DependencyFilterUtils;
57  
58  import static org.apache.maven.artifact.ArtifactUtils.artifactMapByVersionlessId;
59  import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
60  
61  /**
62   * Does dependency resolution and artifact handling for the surefire plugin.
63   *
64   * @author Stephen Connolly
65   * @author Kristian Rosenvold
66   */
67  @Named
68  @Singleton
69  class SurefireDependencyResolver {
70  
71      static final String PROVIDER_GROUP_ID = "org.apache.maven.surefire";
72  
73      private static final String[] PROVIDER_CLASSPATH_ORDER = {
74          "surefire-junit3",
75          "surefire-junit4",
76          "surefire-junit47",
77          "surefire-testng",
78          "surefire-junit-platform",
79          "surefire-api",
80          "surefire-logger-api",
81          "surefire-shared-utils",
82          "common-java5",
83          "common-junit3",
84          "common-junit4",
85          "common-junit48",
86          "common-testng-utils"
87      };
88  
89      private final RepositorySystem repositorySystem;
90  
91      @Inject
92      SurefireDependencyResolver(RepositorySystem repositorySystem) {
93          this.repositorySystem = repositorySystem;
94      }
95  
96      static boolean isWithinVersionSpec(@Nullable Artifact artifact, @Nonnull String versionSpec) {
97          if (artifact == null) {
98              return false;
99          }
100         try {
101             VersionRange range = createFromVersionSpec(versionSpec);
102             try {
103                 return range.containsVersion(artifact.getSelectedVersion());
104             } catch (NullPointerException e) {
105                 return range.containsVersion(new DefaultArtifactVersion(artifact.getBaseVersion()));
106             }
107         } catch (InvalidVersionSpecificationException | OverConstrainedVersionException e) {
108             throw new RuntimeException("Bug in plugin. Please report with stacktrace");
109         }
110     }
111 
112     Map<String, Artifact> resolvePluginDependencies(
113             RepositorySystemSession session,
114             List<RemoteRepository> repositories,
115             Plugin plugin,
116             Map<String, Artifact> pluginResolvedDependencies)
117             throws MojoExecutionException {
118         Map<String, Artifact> resolved = new LinkedHashMap<>();
119         Collection<Dependency> pluginDependencies = plugin.getDependencies();
120 
121         for (Dependency dependency : pluginDependencies) {
122             Set<Artifact> artifacts = resolveDependencies(
123                     session, repositories, RepositoryUtils.toDependency(dependency, session.getArtifactTypeRegistry()));
124             for (Artifact artifact : artifacts) {
125                 String key = artifact.getGroupId() + ":" + artifact.getArtifactId();
126                 Artifact resolvedPluginDependency = pluginResolvedDependencies.get(key);
127                 if (resolvedPluginDependency != null) {
128                     resolved.put(key, artifact);
129                 }
130             }
131         }
132         return resolved;
133     }
134 
135     public Set<Artifact> resolveArtifacts(
136             RepositorySystemSession session, List<RemoteRepository> repositories, Artifact artifact)
137             throws MojoExecutionException {
138         return resolveDependencies(session, repositories, RepositoryUtils.toDependency(artifact, null));
139     }
140 
141     public Set<Artifact> resolveDependencies(
142             RepositorySystemSession session, List<RemoteRepository> repositories, Dependency dependency)
143             throws MojoExecutionException {
144         return resolveDependencies(
145                 session, repositories, RepositoryUtils.toDependency(dependency, session.getArtifactTypeRegistry()));
146     }
147 
148     private Set<Artifact> resolveDependencies(
149             RepositorySystemSession session,
150             List<RemoteRepository> repositories,
151             org.eclipse.aether.graph.Dependency dependency)
152             throws MojoExecutionException {
153 
154         try {
155             List<ArtifactResult> results = resolveDependencies(
156                     session, repositories, dependency, DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME));
157             return results.stream()
158                     .map(ArtifactResult::getArtifact)
159                     .map(RepositoryUtils::toArtifact)
160                     .collect(Collectors.toCollection(LinkedHashSet::new));
161 
162         } catch (DependencyResolutionException e) {
163             throw new MojoExecutionException(e.getMessage(), e);
164         }
165     }
166 
167     private List<ArtifactResult> resolveDependencies(
168             RepositorySystemSession session,
169             List<RemoteRepository> repositories,
170             org.eclipse.aether.graph.Dependency dependency,
171             DependencyFilter dependencyFilter)
172             throws DependencyResolutionException {
173 
174         // use a collect request without a root in order to not resolve optional dependencies
175         CollectRequest collectRequest = new CollectRequest(Collections.singletonList(dependency), null, repositories);
176 
177         DependencyRequest request = new DependencyRequest();
178         request.setCollectRequest(collectRequest);
179         request.setFilter(dependencyFilter);
180 
181         DependencyResult dependencyResult = repositorySystem.resolveDependencies(session, request);
182         return dependencyResult.getArtifactResults();
183     }
184 
185     @Nonnull
186     Set<Artifact> getProviderClasspath(
187             RepositorySystemSession session,
188             List<RemoteRepository> repositories,
189             String providerArtifactId,
190             String providerVersion)
191             throws MojoExecutionException {
192         Dependency provider = toProviderDependency(providerArtifactId, providerVersion);
193 
194         org.eclipse.aether.graph.Dependency dependency =
195                 RepositoryUtils.toDependency(provider, session.getArtifactTypeRegistry());
196 
197         Set<Artifact> result = resolveDependencies(session, repositories, dependency);
198 
199         return orderProviderArtifacts(result);
200     }
201 
202     @Nonnull
203     Map<String, Artifact> getProviderClasspathAsMap(
204             RepositorySystemSession session,
205             List<RemoteRepository> repositories,
206             String providerArtifactId,
207             String providerVersion)
208             throws MojoExecutionException {
209         return artifactMapByVersionlessId(
210                 getProviderClasspath(session, repositories, providerArtifactId, providerVersion));
211     }
212 
213     // FIXME
214     // method argument should be unchanged
215     // what if providerArtifacts will be unmodifiable
216     private static Set<Artifact> orderProviderArtifacts(Set<Artifact> providerArtifacts) {
217         Set<Artifact> orderedProviderArtifacts = new LinkedHashSet<>();
218         for (String order : PROVIDER_CLASSPATH_ORDER) {
219             Iterator<Artifact> providerArtifactsIt = providerArtifacts.iterator();
220             while (providerArtifactsIt.hasNext()) {
221                 Artifact providerArtifact = providerArtifactsIt.next();
222                 if (providerArtifact.getArtifactId().equals(order)) {
223                     orderedProviderArtifacts.add(providerArtifact);
224                     providerArtifactsIt.remove();
225                 }
226             }
227         }
228         orderedProviderArtifacts.addAll(providerArtifacts);
229         return orderedProviderArtifacts;
230     }
231 
232     private static Dependency toProviderDependency(String providerArtifactId, String providerVersion) {
233         Dependency dependency = new Dependency();
234         dependency.setGroupId(PROVIDER_GROUP_ID);
235         dependency.setArtifactId(providerArtifactId);
236         dependency.setVersion(providerVersion);
237         dependency.setType("jar");
238         return dependency;
239     }
240 }