View Javadoc
1   package org.apache.maven.plugin.surefire;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.LinkedHashMap;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.repository.ArtifactRepository;
32  import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
33  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
34  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
35  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
36  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
37  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
38  import org.apache.maven.artifact.versioning.VersionRange;
39  import org.apache.maven.model.Dependency;
40  import org.apache.maven.model.Plugin;
41  import org.apache.maven.plugin.MojoExecutionException;
42  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
43  import org.apache.maven.project.ProjectBuildingRequest;
44  import org.apache.maven.repository.RepositorySystem;
45  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
46  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
47  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
48  
49  import javax.annotation.Nonnull;
50  import javax.annotation.Nullable;
51  
52  import static java.util.Arrays.asList;
53  import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE;
54  import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE_PLUS_RUNTIME;
55  import static org.apache.maven.artifact.Artifact.SCOPE_RUNTIME;
56  import static org.apache.maven.artifact.ArtifactUtils.artifactMapByVersionlessId;
57  import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
58  import static org.apache.maven.shared.artifact.filter.resolve.ScopeFilter.including;
59  
60  /**
61   * Does dependency resolution and artifact handling for the surefire plugin.
62   *
63   * @author Stephen Connolly
64   * @author Kristian Rosenvold
65   */
66  final class SurefireDependencyResolver
67  {
68      static final String PROVIDER_GROUP_ID = "org.apache.maven.surefire";
69  
70      private static final String[] PROVIDER_CLASSPATH_ORDER = {
71              "surefire-junit3",
72              "surefire-junit4",
73              "surefire-junit47",
74              "surefire-testng",
75              "surefire-junit-platform",
76              "surefire-api",
77              "surefire-logger-api",
78              "surefire-shared-utils",
79              "common-java5",
80              "common-junit3",
81              "common-junit4",
82              "common-junit48",
83              "common-testng-utils"
84      };
85  
86      private final RepositorySystem repositorySystem;
87  
88      private final ConsoleLogger log;
89  
90      private final ArtifactRepository localRepository;
91  
92      private final List<ArtifactRepository> pluginRemoteRepositories;
93  
94      private final List<ArtifactRepository> projectRemoteRepositories;
95  
96      private final String pluginName;
97  
98      private final DependencyResolver dependencyResolver;
99  
100     private final boolean offline;
101 
102     SurefireDependencyResolver( RepositorySystem repositorySystem, ConsoleLogger log,
103                                 ArtifactRepository localRepository,
104                                 List<ArtifactRepository> pluginRemoteRepositories,
105                                 List<ArtifactRepository> projectRemoteRepositories, String pluginName,
106                                 DependencyResolver dependencyResolver, boolean offline )
107     {
108         this.repositorySystem = repositorySystem;
109         this.log = log;
110         this.localRepository = localRepository;
111         this.pluginRemoteRepositories = pluginRemoteRepositories;
112         this.projectRemoteRepositories = projectRemoteRepositories;
113         this.pluginName = pluginName;
114         this.dependencyResolver = dependencyResolver;
115         this.offline = offline;
116     }
117 
118     static boolean isWithinVersionSpec( @Nullable Artifact artifact, @Nonnull String versionSpec )
119     {
120         if ( artifact == null )
121         {
122             return false;
123         }
124         try
125         {
126             VersionRange range = createFromVersionSpec( versionSpec );
127             try
128             {
129                 return range.containsVersion( artifact.getSelectedVersion() );
130             }
131             catch ( NullPointerException e )
132             {
133                 return range.containsVersion( new DefaultArtifactVersion( artifact.getBaseVersion() ) );
134             }
135         }
136         catch ( InvalidVersionSpecificationException | OverConstrainedVersionException e )
137         {
138             throw new RuntimeException( "Bug in plugin. Please report with stacktrace" );
139         }
140     }
141 
142     Map<String, Artifact> resolvePluginDependencies( ProjectBuildingRequest request,
143                                                      Plugin plugin, Map<String, Artifact> pluginResolvedDependencies )
144             throws MojoExecutionException
145     {
146         Collection<Dependency> pluginDependencies = plugin.getDependencies();
147         try
148         {
149             Iterable<ArtifactResult> resolvedArtifacts = dependencyResolver.resolveDependencies( request,
150                 pluginDependencies, null, including( RuntimeArtifactFilter.SCOPES ) );
151 
152             Map<String, Artifact> resolved = new LinkedHashMap<>();
153             for ( ArtifactResult resolvedArtifact : resolvedArtifacts )
154             {
155                 Artifact artifact = resolvedArtifact.getArtifact();
156                 String key = artifact.getGroupId() + ":" + artifact.getArtifactId();
157                 Artifact resolvedPluginDependency = pluginResolvedDependencies.get( key );
158                 if ( resolvedPluginDependency != null )
159                 {
160                     resolved.put( key, artifact );
161                 }
162             }
163             return resolved;
164         }
165         catch ( DependencyResolverException e )
166         {
167             throw new MojoExecutionException( e.getLocalizedMessage(), e );
168         }
169     }
170 
171     ArtifactResolutionResult resolvePluginArtifact( Artifact artifact )
172     {
173         return resolvePluginArtifact( artifact, new RuntimeArtifactFilter() );
174     }
175 
176     ArtifactResolutionResult resolveProjectArtifact( Artifact artifact )
177     {
178         return resolveProjectArtifact( artifact, new RuntimeArtifactFilter() );
179     }
180 
181     private ArtifactResolutionResult resolvePluginArtifact( Artifact artifact, ArtifactFilter filter )
182     {
183         return resolveArtifact( artifact, pluginRemoteRepositories, filter );
184     }
185 
186     private ArtifactResolutionResult resolveProjectArtifact( Artifact artifact, ArtifactFilter filter )
187     {
188         return resolveArtifact( artifact, projectRemoteRepositories, filter );
189     }
190 
191     private ArtifactResolutionResult resolveArtifact( Artifact artifact, List<ArtifactRepository> repositories,
192                                                       ArtifactFilter filter )
193     {
194         ArtifactResolutionRequest request = new ArtifactResolutionRequest()
195                 .setOffline( offline )
196                 .setArtifact( artifact )
197                 .setLocalRepository( localRepository )
198                 .setResolveTransitively( true )
199                 .setCollectionFilter( filter )
200                 .setRemoteRepositories( repositories );
201 
202         return repositorySystem.resolve( request );
203     }
204 
205     @Nonnull
206     Set<Artifact> getProviderClasspath( String providerArtifactId, String providerVersion )
207     {
208         Dependency provider = toProviderDependency( providerArtifactId, providerVersion );
209 
210         Artifact providerArtifact = repositorySystem.createDependencyArtifact( provider );
211 
212         ArtifactResolutionResult result = resolvePluginArtifact( providerArtifact );
213 
214         if ( log.isDebugEnabled() )
215         {
216             for ( Artifact artifact : result.getArtifacts() )
217             {
218                 String artifactPath = artifact.getFile().getAbsolutePath();
219                 String scope = artifact.getScope();
220                 log.debug( "Adding to " + pluginName + " test classpath: " + artifactPath + " Scope: " + scope );
221             }
222         }
223 
224         return orderProviderArtifacts( result.getArtifacts() );
225     }
226 
227     @Nonnull
228     Map<String, Artifact> getProviderClasspathAsMap( String providerArtifactId, String providerVersion )
229     {
230         return artifactMapByVersionlessId( getProviderClasspath( providerArtifactId, providerVersion ) );
231     }
232 
233     private static Set<Artifact> orderProviderArtifacts( Set<Artifact> providerArtifacts )
234     {
235         Set<Artifact> orderedProviderArtifacts = new LinkedHashSet<>();
236         for ( String order : PROVIDER_CLASSPATH_ORDER )
237         {
238             Iterator<Artifact> providerArtifactsIt = providerArtifacts.iterator();
239             while ( providerArtifactsIt.hasNext() )
240             {
241                 Artifact providerArtifact = providerArtifactsIt.next();
242                 if ( providerArtifact.getArtifactId().equals( order ) )
243                 {
244                     orderedProviderArtifacts.add( providerArtifact );
245                     providerArtifactsIt.remove();
246                 }
247             }
248         }
249         orderedProviderArtifacts.addAll( providerArtifacts );
250         return orderedProviderArtifacts;
251     }
252 
253     private static Dependency toProviderDependency( String providerArtifactId, String providerVersion )
254     {
255         Dependency dependency = new Dependency();
256         dependency.setGroupId( PROVIDER_GROUP_ID );
257         dependency.setArtifactId( providerArtifactId );
258         dependency.setVersion( providerVersion );
259         dependency.setType( "jar" );
260         return dependency;
261     }
262 
263     static class RuntimeArtifactFilter implements ArtifactFilter
264     {
265         private static final Collection<String> SCOPES =
266                 asList( SCOPE_COMPILE, SCOPE_COMPILE_PLUS_RUNTIME, SCOPE_RUNTIME );
267 
268         private final Artifact filter;
269 
270         RuntimeArtifactFilter()
271         {
272             this( null );
273         }
274 
275         RuntimeArtifactFilter( Artifact filter )
276         {
277             this.filter = filter;
278         }
279 
280         @Override
281         public boolean include( Artifact artifact )
282         {
283             String scope = artifact.getScope();
284             return ( filter == null || artifact.equals( filter ) )
285                 && !artifact.isOptional() && ( scope == null || SCOPES.contains( scope ) );
286         }
287     }
288 }