diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineExactDependencies.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineExactDependencies.java index 301c6281b..3d69f7577 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineExactDependencies.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineExactDependencies.java @@ -39,6 +39,8 @@ import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedDependency; +import org.gradle.api.artifacts.component.ComponentIdentifier; +import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; @@ -99,11 +101,45 @@ public static Stream referencedClasses(File classFile) { } public static String asString(ResolvedArtifact artifact) { - return asString(artifact.getModuleVersion().getId()); + ModuleVersionIdentifier moduleVersionId = artifact.getModuleVersion().getId(); + StringBuilder builder = new StringBuilder() + .append(moduleVersionId.getGroup()) + .append(":") + .append(moduleVersionId.getName()); + if (artifact.getClassifier() != null) { + builder + .append("::") + .append(artifact.getClassifier()); + } + return builder.toString(); + } + + public static String asDependencyStringWithName(ResolvedArtifact artifact) { + return asDependencyString(artifact, true); } - public static String asString(ModuleVersionIdentifier id) { - return id.getGroup() + ":" + id.getName(); + public static String asDependencyStringWithoutName(ResolvedArtifact artifact) { + return asDependencyString(artifact, false); + } + + private static String asDependencyString(ResolvedArtifact artifact, boolean withName) { + ComponentIdentifier componentId = artifact.getId().getComponentIdentifier(); + if (componentId instanceof ProjectComponentIdentifier) { + ProjectComponentIdentifier projectComponentId = (ProjectComponentIdentifier) componentId; + StringBuilder builder = new StringBuilder() + .append("project('") + .append(projectComponentId.getProjectPath()) + .append("')"); + if (withName) { + builder + .append(" (") + .append(artifact.getId().getDisplayName()) + .append(")"); + } + return builder.toString(); + } + + return asString(artifact); } @ThreadSafe diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckUnusedDependenciesTask.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckUnusedDependenciesTask.java index 006d3d8b7..0616839f0 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckUnusedDependenciesTask.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/tasks/CheckUnusedDependenciesTask.java @@ -30,7 +30,6 @@ import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.file.FileCollection; @@ -95,11 +94,15 @@ public final void checkUnusedDependencies() { .collect(Collectors.toList()); if (!declaredButUnused.isEmpty()) { // TODO(dfox): don't print warnings for jars that define service loaded classes (e.g. meta-inf) - StringBuilder sb = new StringBuilder(); - sb.append(String.format("Found %s dependencies unused during compilation, please delete them from '%s' or " - + "choose one of the suggested fixes:\n", declaredButUnused.size(), buildFile())); + StringBuilder builder = new StringBuilder(); + builder.append(String.format( + "Found %s dependencies unused during compilation, please delete them from '%s' or choose one of " + + "the suggested fixes:\n", declaredButUnused.size(), buildFile())); for (ResolvedArtifact resolvedArtifact : declaredButUnused) { - sb.append('\t').append(BaselineExactDependencies.asString(resolvedArtifact)).append('\n'); + builder + .append('\t') + .append(BaselineExactDependencies.asDependencyStringWithName(resolvedArtifact)) + .append('\n'); // Suggest fixes by looking at all transitive classes, filtering the ones we have declarations on, // and mapping the remaining ones back to the jars they came from. @@ -117,16 +120,17 @@ public final void checkUnusedDependencies() { .collect(Collectors.toSet()); if (!didYouMean.isEmpty()) { - sb.append("\t\tDid you mean:\n"); + builder.append("\t\tDid you mean:\n"); didYouMean.stream() - .map(BaselineExactDependencies::asString) + .map(BaselineExactDependencies::asDependencyStringWithoutName) .sorted() - .forEach(transitive -> sb.append("\t\t\timplementation '") - .append(transitive) - .append("\' \n")); + .forEach(dependencyString -> builder + .append("\t\t\timplementation ") + .append(dependencyString) + .append("\n")); } } - throw new GradleException(sb.toString()); + throw new GradleException(builder.toString()); } } @@ -138,16 +142,14 @@ public final void checkUnusedDependencies() { */ private void excludeSourceOnlyDependencies() { sourceOnlyConfigurations.get().forEach(config -> - config.getResolvedConfiguration().getFirstLevelModuleDependencies().forEach(dependency -> { - ignoreDependency(config, dependency.getModule().getId()); - dependency.getModuleArtifacts().forEach(artifact -> - ignoreDependency(config, artifact.getModuleVersion().getId())); - })); + config.getResolvedConfiguration().getFirstLevelModuleDependencies().stream() + .flatMap(dependency -> dependency.getModuleArtifacts().stream()) + .forEach(artifact -> ignoreDependency(config, artifact))); } - private void ignoreDependency(Configuration config, ModuleVersionIdentifier id) { - String dependencyId = BaselineExactDependencies.asString(id); - getLogger().info("Ignoring {} dependency: '{}'", config.getName(), dependencyId); + private void ignoreDependency(Configuration config, ResolvedArtifact artifact) { + String dependencyId = BaselineExactDependencies.asString(artifact); + getLogger().info("Ignoring {} dependency: {}", config.getName(), dependencyId); ignore.add(dependencyId); } diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineExactDependenciesTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineExactDependenciesTest.groovy index ab45cebf7..c71277916 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineExactDependenciesTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineExactDependenciesTest.groovy @@ -153,6 +153,16 @@ class BaselineExactDependenciesTest extends AbstractPluginTest { result.task(':sub-project-no-deps:checkImplicitDependencies').getOutcome() == TaskOutcome.SUCCESS } + def 'checkUnusedDependencies fails when a redundant project dep is present'() { + when: + setupMultiProject() + + then: + BuildResult result = with(':checkUnusedDependencies', '--stacktrace').withDebug(true).buildAndFail() + result.output.contains "project(':sub-project-with-deps') (sub-project-with-deps.jar (project :sub-project-with-deps))" + result.output.contains "implementation project(':sub-project-no-deps')" + } + /** * Sets up a multi-module project with 2 sub projects. The root project has a transitive dependency on sub-project-no-deps * and so checkImplicitDependencies should fail on it.