From 7dba9c417a708f7609a3696685d49cf3c767f222 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Tue, 15 Oct 2019 14:31:36 +0900 Subject: [PATCH] Guard constraint resolution by a property since it isn't really possible to know if a constraint is added by a plugin and out of user control or not. --- README.md | 15 ++ .../versions/updates/DependencyUpdates.groovy | 3 +- .../updates/DependencyUpdatesTask.groovy | 6 +- .../gradle/versions/updates/Resolver.groovy | 20 ++- .../gradle/versions/ConstraintsSpec.groovy | 131 ++++++++++++++++++ .../gradle/versions/JavaLibrarySpec.groovy | 36 ----- 6 files changed, 167 insertions(+), 44 deletions(-) create mode 100644 src/test/groovy/com/github/benmanes/gradle/versions/ConstraintsSpec.groovy diff --git a/README.md b/README.md index 44bd70d9..9eedc51e 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,21 @@ tasks.withType { +#### Constraints + +If you use constraints, for example to define a BOM using the [`java-platform`](https://docs.gradle.org/current/userguide/java_platform_plugin.html) +plugin or to [manage](https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:dependency_constraints) +transitive dependency versions, you can enable checking of constraints by specifying the `checkConstraints` +attribute of the `dependencyUpdates` task. + +```groovy +tasks { + dependencyUpdates { + checkConstraints = true + } +} +``` + #### Kotlin DSL If using Gradle's [kotlin-dsl][kotlin_dsl], you could configure the `dependencyUpdates` like this: diff --git a/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdates.groovy b/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdates.groovy index a4d9a2d4..2d0b0756 100644 --- a/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdates.groovy +++ b/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdates.groovy @@ -45,6 +45,7 @@ class DependencyUpdates { String reportfileName boolean checkForGradleUpdate String gradleReleaseChannel + boolean checkConstraints /** Evaluates the dependencies and returns a reporter. */ DependencyUpdatesReporter run() { @@ -65,7 +66,7 @@ class DependencyUpdates { private Set resolveProjects(Map> projectConfigs) { projectConfigs.keySet().collect { proj -> Set configurations = projectConfigs.get(proj) - Resolver resolver = new Resolver(proj, resolutionStrategy) + Resolver resolver = new Resolver(proj, resolutionStrategy, checkConstraints) configurations.collect { Configuration config -> resolve(resolver, proj, config) }.flatten() as Set diff --git a/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdatesTask.groovy b/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdatesTask.groovy index fd33c9f6..440ccb32 100644 --- a/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdatesTask.groovy +++ b/src/main/groovy/com/github/benmanes/gradle/versions/updates/DependencyUpdatesTask.groovy @@ -57,6 +57,9 @@ class DependencyUpdatesTask extends DefaultTask { @Input boolean checkForGradleUpdate = true + @Input + boolean checkConstraints = false + Object outputFormatter = 'plain' Closure resolutionStrategy = null; @@ -80,7 +83,8 @@ class DependencyUpdatesTask extends DefaultTask { } def evaluator = new DependencyUpdates(project, resolutionStrategyAction, revisionLevel(), - outputFormatterProp(), outputDirectory(), getReportfileName(), checkForGradleUpdate, gradleReleaseChannelLevel()) + outputFormatterProp(), outputDirectory(), getReportfileName(), checkForGradleUpdate, gradleReleaseChannelLevel(), + checkConstraints) DependencyUpdatesReporter reporter = evaluator.run() reporter?.write() } diff --git a/src/main/groovy/com/github/benmanes/gradle/versions/updates/Resolver.groovy b/src/main/groovy/com/github/benmanes/gradle/versions/updates/Resolver.groovy index a6dbecc3..9cbefae2 100644 --- a/src/main/groovy/com/github/benmanes/gradle/versions/updates/Resolver.groovy +++ b/src/main/groovy/com/github/benmanes/gradle/versions/updates/Resolver.groovy @@ -57,12 +57,15 @@ class Resolver { final Action resolutionStrategy final boolean useSelectionRules final boolean collectProjectUrls + final boolean checkConstraints final ConcurrentMap projectUrls - Resolver(Project project, Action resolutionStrategy) { + Resolver(Project project, Action resolutionStrategy, + boolean checkConstraints) { this.projectUrls = new ConcurrentHashMap<>() this.resolutionStrategy = resolutionStrategy this.project = project + this.checkConstraints = checkConstraints; useSelectionRules = new VersionComparator(project) .compare(project.gradle.gradleVersion, '2.2') >= 0 @@ -118,7 +121,8 @@ class Resolver { createQueryDependency(dependency, revision) } - // Common use case for dependency constraints is a java-platform BOM project. + // Common use case for dependency constraints is a java-platform BOM project or to control + // version of transitive dependency. if (supportsConstraints(configuration)) { configuration.dependencyConstraints.each { dependency -> latest.add(createQueryDependency(dependency, revision)) @@ -240,7 +244,11 @@ class Resolver { if (supportsConstraints(copy)) { for (DependencyConstraint constraint : copy.dependencyConstraints) { Coordinate coordinate = Coordinate.from(constraint) - coordinates.put(coordinate.key, declared.get(coordinate.key)) + // Only add a constraint to the report if there is no dependency matching it, this means it + // is targeting a transitive dependency or is part of a platform. + if (!coordinates.containsKey(coordinate.key)) { + coordinates.put(coordinate.key, declared.get(coordinate.key)) + } } } @@ -360,11 +368,11 @@ class Resolver { return null } - private static boolean supportsConstraints(Configuration configuration) { - return configuration.metaClass.respondsTo(configuration, "getDependencyConstraints"); + private boolean supportsConstraints(Configuration configuration) { + return checkConstraints && configuration.metaClass.respondsTo(configuration, "getDependencyConstraints"); } - private static List getResolvableDependencies(Configuration configuration) { + private List getResolvableDependencies(Configuration configuration) { List coordinates = configuration.dependencies.findAll { dependency -> dependency instanceof ExternalDependency }.collect { dependency -> diff --git a/src/test/groovy/com/github/benmanes/gradle/versions/ConstraintsSpec.groovy b/src/test/groovy/com/github/benmanes/gradle/versions/ConstraintsSpec.groovy new file mode 100644 index 00000000..1e20d4db --- /dev/null +++ b/src/test/groovy/com/github/benmanes/gradle/versions/ConstraintsSpec.groovy @@ -0,0 +1,131 @@ +package com.github.benmanes.gradle.versions + +import org.gradle.testkit.runner.GradleRunner +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +final class ConstraintsSpec extends Specification { + + @Rule TemporaryFolder testProjectDir = new TemporaryFolder() + private File buildFile + + def "Show updates for an api dependency constraint"() { + given: + def mavenRepoUrl = getClass().getResource('/maven/').toURI() + buildFile = testProjectDir.newFile('build.gradle') + buildFile << + """ + plugins { + id 'java-library' + id 'com.github.ben-manes.versions' + } + + tasks.dependencyUpdates { + checkConstraints = true + } + + repositories { + maven { + url '${mavenRepoUrl}' + } + } + + dependencies { + constraints { + api 'com.google.inject:guice:2.0' + } + } + """.stripIndent() + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('dependencyUpdates') + .withPluginClasspath() + .build() + + then: + result.output.contains('com.google.inject:guice [2.0 -> 3.1]') + result.task(':dependencyUpdates').outcome == SUCCESS + } + + def "Does not override explicit dependency with constraint"() { + given: + def mavenRepoUrl = getClass().getResource('/maven/').toURI() + buildFile = testProjectDir.newFile('build.gradle') + buildFile << + """ + plugins { + id 'java-library' + id 'com.github.ben-manes.versions' + } + + tasks.dependencyUpdates { + checkConstraints = true + } + + repositories { + maven { + url '${mavenRepoUrl}' + } + } + + dependencies { + api 'com.google.inject:guice:3.0' + constraints { + api 'com.google.inject:guice:2.0' + } + } + """.stripIndent() + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('dependencyUpdates') + .withPluginClasspath() + .build() + + then: + result.output.contains('com.google.inject:guice [3.0 -> 3.1]') + result.task(':dependencyUpdates').outcome == SUCCESS + } + + def "Does not show updates for an api dependency constraint when disabled"() { + given: + def mavenRepoUrl = getClass().getResource('/maven/').toURI() + buildFile = testProjectDir.newFile('build.gradle') + buildFile << + """ + plugins { + id 'java-library' + id 'com.github.ben-manes.versions' + } + + repositories { + maven { + url '${mavenRepoUrl}' + } + } + + dependencies { + constraints { + api 'com.google.inject:guice:2.0' + } + } + """.stripIndent() + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('dependencyUpdates') + .withPluginClasspath() + .build() + + then: + result.output.contains('No dependencies found.') + result.task(':dependencyUpdates').outcome == SUCCESS + } +} diff --git a/src/test/groovy/com/github/benmanes/gradle/versions/JavaLibrarySpec.groovy b/src/test/groovy/com/github/benmanes/gradle/versions/JavaLibrarySpec.groovy index 37d5172c..fb199680 100644 --- a/src/test/groovy/com/github/benmanes/gradle/versions/JavaLibrarySpec.groovy +++ b/src/test/groovy/com/github/benmanes/gradle/versions/JavaLibrarySpec.groovy @@ -46,40 +46,4 @@ final class JavaLibrarySpec extends Specification { result.output.contains('com.google.inject:guice [2.0 -> 3.1]') result.task(':dependencyUpdates').outcome == SUCCESS } - - def "Show updates for an api dependency constraint in a java-library project"() { - given: - def mavenRepoUrl = getClass().getResource('/maven/').toURI() - buildFile = testProjectDir.newFile('build.gradle') - buildFile << - """ - plugins { - id 'java-library' - id 'com.github.ben-manes.versions' - } - - repositories { - maven { - url '${mavenRepoUrl}' - } - } - - dependencies { - constraints { - api 'com.google.inject:guice:2.0' - } - } - """.stripIndent() - - when: - def result = GradleRunner.create() - .withProjectDir(testProjectDir.root) - .withArguments('dependencyUpdates') - .withPluginClasspath() - .build() - - then: - result.output.contains('com.google.inject:guice [2.0 -> 3.1]') - result.task(':dependencyUpdates').outcome == SUCCESS - } }