com.hubspot.maven.plugins.dependency.scope.DependencyScopeMojo Maven / Gradle / Ivy
package com.hubspot.maven.plugins.dependency.scope;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@Mojo(name = "check", defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyCollection = ResolutionScope.TEST, threadSafe = true)
public class DependencyScopeMojo extends AbstractMojo {
private static final Set RUNTIME_SCOPES = new HashSet<>(Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME));
@Parameter( defaultValue = "${session}", required = true, readonly = true )
protected MavenSession session;
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
@Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
private ArtifactRepository localRepository;
@Parameter(property = "useParallelDependencyResolution", defaultValue = "true")
private boolean useParallelDependencyResolution;
@Parameter(defaultValue = "false")
private boolean fail;
@Parameter(defaultValue = "false")
private boolean skip;
@Component
private DependencyGraphBuilder dependencyGraphBuilder;
@Component
private MavenProjectBuilder projectBuilder;
private ListeningExecutorService executorService;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (skip) {
getLog().info("Skipping plugin execution");
return;
}
executorService = newExecutorService();
DependencyNode node = buildDependencyNode();
TraversalContext context = TraversalContext.newContextFor(node);
List>> futures = new ArrayList<>();
for (DependencyNode dependency : node.getChildren()) {
if (!Artifact.SCOPE_TEST.equals(dependency.getArtifact().getScope())) {
TraversalContext subcontext = context.stepInto(project, dependency);
futures.add(findViolations(dependency, subcontext));
}
}
Set violations = resolve(Futures.allAsList(futures));
if (!violations.isEmpty()) {
printViolations(violations);
if (fail) {
throw new MojoFailureException("Test dependency scope issues found");
}
} else {
getLog().info("No test dependency scope issues found");
}
}
private ListenableFuture> findViolations(final DependencyNode node,
final TraversalContext context) {
final SettableFuture> future = SettableFuture.create();
Futures.addCallback(buildDependencyProject(node), new FutureCallback() {
@Override
public void onSuccess(MavenProject dependencyProject) {
try {
final Set violations = Sets.newConcurrentHashSet();
for (Dependency dependency : dependencyProject.getDependencies()) {
if (RUNTIME_SCOPES.contains(dependency.getScope()) && context.isOverriddenToTestScope(dependency)) {
violations.add(new DependencyViolation(context, dependency));
}
}
if (node.getChildren().isEmpty()) {
future.set(violations);
return;
}
final CountDownLatch latch = new CountDownLatch(node.getChildren().size());
for (DependencyNode child : node.getChildren()) {
TraversalContext subcontext = context.stepInto(dependencyProject, child);
Futures.addCallback(findViolations(child, subcontext), new FutureCallback>() {
@Override
public void onSuccess(Set result) {
try {
violations.addAll(result);
} finally {
latch.countDown();
if (latch.getCount() == 0) {
future.set(violations);
}
}
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
});
}
} catch (Throwable t) {
future.setException(t);
}
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
});
return future;
}
private DependencyNode buildDependencyNode() throws MojoExecutionException {
try {
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
buildingRequest.setProject(project);
return dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);
} catch (DependencyGraphBuilderException e) {
throw new MojoExecutionException("Error building dependency graph", e);
}
}
private ListenableFuture buildDependencyProject(final DependencyNode node) {
return executorService.submit(new Callable() {
@Override
public MavenProject call() throws Exception {
try {
return projectBuilder.buildFromRepository(
node.getArtifact(),
project.getRemoteArtifactRepositories(),
localRepository
);
} catch (ProjectBuildingException e) {
throw new MojoExecutionException("Error building dependency project", e);
}
}
});
}
private void printViolations(Set violations) {
Map> violationsByDependency = new HashMap<>();
for (DependencyViolation violation : violations) {
String key = readableGATC(violation.getDependency());
if (!violationsByDependency.containsKey(key)) {
violationsByDependency.put(key, new TreeSet<>(artifactNameComparator()));
}
violationsByDependency.get(key).add(violation);
}
for (Entry> dependencyViolation : violationsByDependency.entrySet()) {
StringBuilder message = new StringBuilder();
message.append("Found a problem with test-scoped dependency ").append(dependencyViolation.getKey());
for (DependencyViolation violation : dependencyViolation.getValue()) {
message.append("\n")
.append(" ")
.append("Scope ")
.append(violation.getDependency().getScope())
.append(" was expected by artifact: ")
.append(readableGATCV(violation.getSource().currentArtifact()));
}
if (fail) {
getLog().error(message);
} else {
getLog().warn(message);
}
}
}
private ListeningExecutorService newExecutorService() {
if (useParallelDependencyResolution) {
getLog().debug("Using parallel dependency resolution");
return MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(
Math.min(Runtime.getRuntime().availableProcessors() * 5, 20),
new ThreadFactoryBuilder().setNameFormat("dependency-project-builder-%s")
.setDaemon(true)
.build()
));
} else {
getLog().debug("Using single-threaded dependency resolution");
return MoreExecutors.newDirectExecutorService();
}
}
private static Set resolve(ListenableFuture>> future)
throws MojoExecutionException {
try {
return Sets.newHashSet(Iterables.concat(future.get()));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MojoExecutionException("Interrupted while checking dependency scopes", e);
} catch (ExecutionException e) {
Throwables.propagateIfInstanceOf(e.getCause(), MojoExecutionException.class);
throw new MojoExecutionException("Error while checking dependency scopes", e.getCause());
}
}
private static Comparator artifactNameComparator() {
return new Comparator() {
@Override
public int compare(DependencyViolation a, DependencyViolation b) {
return readableGATCV(a.getSource().currentArtifact()).compareTo(readableGATCV(b.getSource().currentArtifact()));
}
};
}
private static String readableGATC(Dependency dependency) {
String name = dependency.getGroupId() + ":" + dependency.getArtifactId();
if (dependency.getType() != null && !"jar".equals(dependency.getType())) {
name += ":" + dependency.getType();
}
if (dependency.getClassifier() != null) {
name += ":" + dependency.getClassifier();
}
return name;
}
private static String readableGATCV(Artifact artifact) {
String name = artifact.getGroupId() + ":" + artifact.getArtifactId();
if (artifact.getType() != null && !"jar".equals(artifact.getType())) {
name += ":" + artifact.getType();
}
if (artifact.getClassifier() != null) {
name += ":" + artifact.getClassifier();
}
// this modifies the artifact internal state :/
artifact.isSnapshot();
name += ":" + artifact.getBaseVersion();
return name;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy