001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 * http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014
015package org.basepom.mojo.dvc;
016
017import static com.google.common.base.Preconditions.checkNotNull;
018import static com.google.common.base.Preconditions.checkState;
019
020import org.basepom.mojo.dvc.dependency.DependencyMap;
021import org.basepom.mojo.dvc.dependency.DependencyMapBuilder;
022import org.basepom.mojo.dvc.dependency.DependencyTreeResolver;
023import org.basepom.mojo.dvc.model.ResolverDefinition;
024import org.basepom.mojo.dvc.model.VersionCheckExcludes;
025import org.basepom.mojo.dvc.strategy.StrategyProvider;
026import org.basepom.mojo.dvc.version.VersionResolutionCollection;
027
028import java.util.Arrays;
029import java.util.List;
030
031import com.google.common.base.Strings;
032import com.google.common.collect.ImmutableList;
033import com.google.common.collect.ImmutableSet;
034import com.google.common.collect.ImmutableSetMultimap;
035import org.apache.maven.execution.MavenSession;
036import org.apache.maven.plugin.AbstractMojo;
037import org.apache.maven.plugin.MojoExecutionException;
038import org.apache.maven.plugin.MojoFailureException;
039import org.apache.maven.plugins.annotations.Component;
040import org.apache.maven.plugins.annotations.Parameter;
041import org.apache.maven.project.DefaultProjectBuildingRequest;
042import org.apache.maven.project.MavenProject;
043import org.apache.maven.project.ProjectBuilder;
044import org.apache.maven.project.ProjectBuildingRequest;
045import org.apache.maven.project.ProjectDependenciesResolver;
046import org.eclipse.aether.DefaultRepositorySystemSession;
047import org.eclipse.aether.RepositorySystem;
048import org.eclipse.aether.RepositorySystemSession;
049import org.eclipse.aether.artifact.Artifact;
050import org.eclipse.aether.resolution.VersionRangeRequest;
051import org.eclipse.aether.util.artifact.JavaScopes;
052import org.eclipse.aether.util.graph.version.SnapshotVersionFilter;
053
054/**
055 * Base code for all the mojos. Contains the dependency resolvers and the common options.
056 */
057public abstract class AbstractDependencyVersionsMojo
058        extends AbstractMojo
059        implements Context {
060
061    private static final ImmutableSet<String> VALID_SCOPES = ImmutableSet.of(
062            ScopeLimitingFilter.COMPILE_PLUS_RUNTIME,
063            JavaScopes.COMPILE,
064            JavaScopes.RUNTIME,
065            JavaScopes.TEST);
066
067    protected final PluginLog log = new PluginLog(this.getClass());
068
069    @Parameter(defaultValue = "${project}", readonly = true)
070    public MavenProject project;
071
072    @Parameter(defaultValue = "${session}", readonly = true)
073    public MavenSession mavenSession;
074
075    @Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
076    public List<MavenProject> reactorProjects;
077
078    @Component
079    public ProjectBuilder mavenProjectBuilder;
080
081    @Component
082    public ProjectDependenciesResolver projectDependenciesResolver;
083
084    @Component
085    public RepositorySystem repositorySystem;
086
087    /**
088     * The strategy provider. This can be requested by other pieces to add additional strategies.
089     */
090    @Component
091    public StrategyProvider strategyProvider;
092
093    /**
094     * List of version checks that will be removed from the version check. This allows potential conflicts to be excluded.
095     * <br>
096     * <pre>
097     * &lt;exclusions&gt;
098     *   &lt;exclusion&gt;
099     *     &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}