View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package org.basepom.mojo.dvc;
16  
17  import static com.google.common.base.Preconditions.checkNotNull;
18  import static com.google.common.base.Preconditions.checkState;
19  
20  import org.basepom.mojo.dvc.dependency.DependencyMap;
21  import org.basepom.mojo.dvc.dependency.DependencyMapBuilder;
22  import org.basepom.mojo.dvc.dependency.DependencyTreeResolver;
23  import org.basepom.mojo.dvc.model.ResolverDefinition;
24  import org.basepom.mojo.dvc.model.VersionCheckExcludes;
25  import org.basepom.mojo.dvc.strategy.StrategyProvider;
26  import org.basepom.mojo.dvc.version.VersionResolutionCollection;
27  
28  import java.util.Arrays;
29  import java.util.List;
30  
31  import com.google.common.base.Strings;
32  import com.google.common.collect.ImmutableList;
33  import com.google.common.collect.ImmutableSet;
34  import com.google.common.collect.ImmutableSetMultimap;
35  import org.apache.maven.execution.MavenSession;
36  import org.apache.maven.plugin.AbstractMojo;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.plugin.MojoFailureException;
39  import org.apache.maven.plugins.annotations.Component;
40  import org.apache.maven.plugins.annotations.Parameter;
41  import org.apache.maven.project.DefaultProjectBuildingRequest;
42  import org.apache.maven.project.MavenProject;
43  import org.apache.maven.project.ProjectBuilder;
44  import org.apache.maven.project.ProjectBuildingRequest;
45  import org.apache.maven.project.ProjectDependenciesResolver;
46  import org.eclipse.aether.DefaultRepositorySystemSession;
47  import org.eclipse.aether.RepositorySystem;
48  import org.eclipse.aether.RepositorySystemSession;
49  import org.eclipse.aether.artifact.Artifact;
50  import org.eclipse.aether.resolution.VersionRangeRequest;
51  import org.eclipse.aether.util.artifact.JavaScopes;
52  import org.eclipse.aether.util.graph.version.SnapshotVersionFilter;
53  
54  /**
55   * Base code for all the mojos. Contains the dependency resolvers and the common options.
56   */
57  public abstract class AbstractDependencyVersionsMojo
58          extends AbstractMojo
59          implements Context {
60  
61      private static final ImmutableSet<String> VALID_SCOPES = ImmutableSet.of(
62              ScopeLimitingFilter.COMPILE_PLUS_RUNTIME,
63              JavaScopes.COMPILE,
64              JavaScopes.RUNTIME,
65              JavaScopes.TEST);
66  
67      protected final PluginLog log = new PluginLog(this.getClass());
68  
69      @Parameter(defaultValue = "${project}", readonly = true)
70      public MavenProject project;
71  
72      @Parameter(defaultValue = "${session}", readonly = true)
73      public MavenSession mavenSession;
74  
75      @Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
76      public List<MavenProject> reactorProjects;
77  
78      @Component
79      public ProjectBuilder mavenProjectBuilder;
80  
81      @Component
82      public ProjectDependenciesResolver projectDependenciesResolver;
83  
84      @Component
85      public RepositorySystem repositorySystem;
86  
87      /**
88       * The strategy provider. This can be requested by other pieces to add additional strategies.
89       */
90      @Component
91      public StrategyProvider strategyProvider;
92  
93      /**
94       * List of version checks that will be removed from the version check. This allows potential conflicts to be excluded.
95       * <br>
96       * <pre>
97       * &lt;exclusions&gt;
98       *   &lt;exclusion&gt;
99       *     &lt;dependency&gt;...&lt;/dependency&gt;
100      *     &lt;expected&gt;...&lt;/expected&gt;
101      *     &lt;resolved&gt;...&lt;/resolved&gt;
102      *   &lt;/exclusion&gt;
103      * &lt;/exclusions&gt;
104      * </pre>
105      * <p>
106      * Each element consists of a dependency pattern <code>[groupId]:[artifactId]</code> that supports wildcards and an expected version (which is the version
107      * is expected by the artifact) and a resolved version (the version that the dependency resolution has chosen).
108      * </p>
109      */
110     @Parameter(alias = "exceptions")
111     public VersionCheckExcludes[] exclusions = new VersionCheckExcludes[0];
112 
113     /**
114      * Skip the plugin execution.
115      */
116     @Parameter(defaultValue = "false", property = "dvc.skip")
117     public boolean skip = false;
118 
119     /**
120      * Include POM projects when running on a multi-module project. Dependency resolution on a pom project almost never makes sense as it does not actually
121      * build any artifacts.
122      *
123      * @since 3.0.0
124      */
125     @Parameter(defaultValue = "false", property = "dvc.include-pom-projects")
126     public boolean includePomProjects = false;
127 
128     /**
129      * Silence all non-output and non-error messages.
130      *
131      * @since 3.0.0
132      */
133     @Parameter(defaultValue = "false", property = "dvc.quiet")
134     public boolean quiet = false;
135 
136     /**
137      * Dependency resolution scope. Defaults to <code>test</code>. Valid choices are <code>compile+runtime</code>, <code>compile</code>,
138      * <code>test</code> and <code>runtime</code>.
139      *
140      * @since 3.0.0
141      */
142     @Parameter(defaultValue = "test", property = "scope")
143     public String scope = JavaScopes.TEST;
144 
145     /**
146      * Use deep scan or regular scan. Deep scan looks at all dependencies in the dependency tree, while regular scan only looks one level deep into the direct
147      * dependencies.
148      *
149      * @since 3.0.0
150      */
151     @Parameter(defaultValue = "false", property = "dvc.deep-scan")
152     public boolean deepScan = false;
153 
154     /**
155      * List only direct dependencies or all dependencies.
156      *
157      * @since 3.0.0
158      */
159     @Parameter(defaultValue = "false", property = "dvc.direct-only")
160     public boolean directOnly = false;
161 
162     /**
163      * List only managed dependencies or all dependencies.
164      *
165      * @since 3.0.0
166      */
167     @Parameter(defaultValue = "false", property = "dvc.managed-only")
168     public boolean managedOnly = false;
169 
170     /**
171      * Run dependency resolution in parallel with multiple threads. Should only ever set to <code>false</code> if the plugin shows stability problems when
172      * resolving dependencies. Please <a href="issue-management.html">file a bug</a> in that case, too.
173      *
174      * @since 3.0.0
175      */
176     @Parameter(defaultValue = "true", property = "dvc.fast-resolution")
177     public boolean fastResolution = true;
178 
179     /**
180      * Fail the build if an artifact in <code>system</code> scope can not be resolved. Those are notoriously dependent on the local build environment and some
181      * outright fail (e.g. referencing the <code>tools.jar</code>, which no longer exists in a JDK8+ environment).
182      * <br>
183      * Setting this flag to <code>true</code> will fail the build if any <code>system</code> scoped artifact can not be resolved. This is almost never desired,
184      * except when building a project with a direct <code>system</code> scoped dependency.
185      *
186      * @since 3.0.0
187      */
188     @Parameter(defaultValue = "false", property = "dvc.unresolved-system-artifacts-fail-build")
189     protected boolean unresolvedSystemArtifactsFailBuild = false;
190 
191     /**
192      * Require all optional dependencies to exist and fail the build if any optional dependency can not resolved. This is almost never needed and actually
193      * causes problems for some projects that use large public dependencies from central that in turn pull in non-public dependencies as optional.
194      *
195      * @since 3.2.0
196      */
197     @Parameter(defaultValue = "false", property = "dvc.optional-dependencies-must-exist")
198     protected boolean optionalDependenciesMustExist = false;
199 
200 
201     /**
202      * List of resolvers to apply specific strategies to dependencies.
203      *
204      * <pre>
205      * &lt;resolvers&gt;
206      *   &lt;resolver&gt;
207      *     &lt;strategy&gt;...&lt;strategy&gt;
208      *     &lt;includes&gt;
209      *       &lt;include&gt;...&lt;include&gt;
210      *     &lt;includes&gt;
211      *   &lt;resolver&gt;
212      * &lt;resolvers&gt;
213      * </pre>
214      * <p>
215      * A resolver maps a specific strategy to a list of includes. The include syntax is <code>[group-id]:[artifact-id]</code> where each pattern segment
216      * supports full and partial wildcards (<code>*</code>).
217      * <br>
218      * The plugin includes some default strategies: <code>apr</code>, <code>default</code>, <code>single-digit</code> and
219      * <code>two-digits-backward-compatible</code>. Additional strategies can be defined and added to the plugin classpath.
220      */
221     @Parameter
222     public ResolverDefinition[] resolvers = new ResolverDefinition[0];
223 
224     /**
225      * Sets the default strategy to use to evaluate whether two dependency versions are compatible or not.
226      * <p>
227      * The <code>default</code> resolution strategy matches the Maven dependency resolution itself; any two dependencies that maven considers compatible will be
228      * accepted.
229      *
230      * @since 3.0.0
231      */
232     @Parameter(defaultValue = "default", property = "dvc.default-strategy")
233     public String defaultStrategy = "default";
234 
235     protected StrategyCache strategyCache;
236     protected RepositorySystemSession snapshotFilteredSession;
237 
238     @Override
239     @SuppressWarnings("PMD.AvoidRethrowingException")
240     public void execute()
241             throws MojoExecutionException, MojoFailureException {
242         try {
243             for (VersionCheckExcludes exclusion : exclusions) {
244                 checkState(exclusion.isValid(), "Invalid exclusion specification: '%s'", exclusion);
245             }
246 
247             checkState(!Strings.nullToEmpty(scope).trim().isEmpty() && VALID_SCOPES.contains(scope), "Scope '%s' is invalid", scope);
248 
249             if (skip) {
250                 log.report(quiet, "Skipping plugin execution");
251                 return;
252             }
253 
254             if (!includePomProjects && "pom".equals(project.getPackaging())) {
255                 log.report(quiet, "Ignoring POM project");
256                 return;
257             }
258 
259             log.debug("Starting %s mojo run!", this.getClass().getSimpleName());
260 
261             this.strategyCache = new StrategyCache(strategyProvider, resolvers, defaultStrategy);
262             this.snapshotFilteredSession = new DefaultRepositorySystemSession(mavenSession.getRepositorySession())
263                     .setVersionFilter(new SnapshotVersionFilter());
264 
265             final ScopeLimitingFilter scopeFilter = createScopeFilter();
266             final DependencyMap rootDependencyMap = new DependencyMapBuilder(this).mapProject(project, scopeFilter);
267 
268             try (DependencyTreeResolver dependencyTreeResolver = new DependencyTreeResolver(this, rootDependencyMap)) {
269                 final ImmutableSetMultimap<QualifiedName, VersionResolutionCollection> resolutionMap = dependencyTreeResolver.computeResolutionMap(project,
270                         scopeFilter);
271                 doExecute(resolutionMap, rootDependencyMap);
272             }
273         } catch (MojoExecutionException | MojoFailureException e) {
274             throw e;
275         } catch (Exception e) {
276             throw new MojoExecutionException("While running mojo: ", e);
277         } finally {
278             log.debug("Ended %s mojo run!", this.getClass().getSimpleName());
279         }
280     }
281 
282     /**
283      * Subclasses need to implement this method.
284      *
285      * @param resolutionMap     The prebuilt resolution map from qualified names to version resolution collections.
286      * @param rootDependencyMap The prebuilt dependency map for all the root dependencies.
287      * @throws Exception When an execution error occurs.
288      */
289     protected abstract void doExecute(ImmutableSetMultimap<QualifiedName, VersionResolutionCollection> resolutionMap, DependencyMap rootDependencyMap)
290             throws Exception;
291 
292     /**
293      * Defines the scope used to resolve the project dependencies. The project dependencies will be limited to the dependencies that match this filter. The list
294      * mojo overrides this to limit the scope in which dependencies are listed. By default, include everything.
295      *
296      * @return The {@link ScopeLimitingFilter} instance for the project dependencies.
297      */
298     protected ScopeLimitingFilter createScopeFilter() {
299         return ScopeLimitingFilter.computeDependencyScope(scope);
300     }
301 
302     @Override
303     public boolean useFastResolution() {
304         return fastResolution;
305     }
306 
307     @Override
308     public boolean useDeepScan() {
309         return deepScan;
310     }
311 
312     @Override
313     public boolean isOptionalDependenciesMustExist() {
314         return optionalDependenciesMustExist;
315     }
316 
317     @Override
318     public StrategyCache getStrategyCache() {
319         return strategyCache;
320     }
321 
322     @Override
323     public ProjectBuilder getProjectBuilder() {
324         return mavenProjectBuilder;
325     }
326 
327     @Override
328     public ProjectDependenciesResolver getProjectDependenciesResolver() {
329         return projectDependenciesResolver;
330     }
331 
332     @Override
333     public MavenProject getRootProject() {
334         return project;
335     }
336 
337     @Override
338     public List<MavenProject> getReactorProjects() {
339         return ImmutableList.copyOf(reactorProjects);
340     }
341 
342     @Override
343     public RepositorySystemSession getRepositorySystemSession() {
344         return snapshotFilteredSession;
345     }
346 
347     @Override
348     public RepositorySystem getRepositorySystem() {
349         return repositorySystem;
350     }
351 
352     @Override
353     public ProjectBuildingRequest createProjectBuildingRequest() {
354         DefaultProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(mavenSession.getProjectBuildingRequest());
355         buildingRequest.setRemoteRepositories(project.getRemoteArtifactRepositories());
356         return buildingRequest;
357     }
358 
359     @Override
360     public VersionRangeRequest createVersionRangeRequest(Artifact artifact) {
361         checkNotNull(artifact, "artifact is null");
362         return new VersionRangeRequest(artifact, project.getRemoteProjectRepositories(), "");
363     }
364 
365     @Override
366     public List<VersionCheckExcludes> getExclusions() {
367         return Arrays.asList(exclusions);
368     }
369 
370     @Override
371     public boolean isUnresolvedSystemArtifactsFailBuild() {
372         return unresolvedSystemArtifactsFailBuild;
373     }
374 }