diff --git a/build.gradle b/build.gradle
index 1b210f3bd..b92cfbd87 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ buildscript {
classpath 'com.palantir.jakartapackagealignment:jakarta-package-alignment:0.6.0'
classpath 'com.palantir.gradle.jdks:gradle-jdks:0.69.0'
classpath 'com.palantir.gradle.jdkslatest:gradle-jdks-latest:0.23.0'
- classpath 'com.palantir.gradle.plugintesting:gradle-plugin-testing:0.19.0'
+ classpath 'com.palantir.gradle.plugintesting:gradle-plugin-testing:0.23.0'
classpath 'com.palantir.gradle.externalpublish:gradle-external-publish-plugin:1.26.0'
classpath 'com.palantir.gradle.failure-reports:gradle-failure-reports:1.17.0'
classpath 'com.palantir.javaformat:gradle-palantir-java-format:2.81.0'
@@ -74,6 +74,8 @@ dependencies {
annotationProcessor "org.immutables:value"
compileOnly "org.immutables:value::annotations"
+ testImplementation 'com.palantir.gradle.plugintesting:gradle-plugin-testing-junit'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.withType(Test) {
diff --git a/src/test/groovy/com/palantir/gradle/versions/CheckOverbroadConstraintsIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/CheckOverbroadConstraintsIntegrationSpec.groovy
deleted file mode 100644
index 6f8ab4d09..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/CheckOverbroadConstraintsIntegrationSpec.groovy
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import org.gradle.testkit.runner.BuildResult
-import org.gradle.testkit.runner.TaskOutcome
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-
-class CheckOverbroadConstraintsIntegrationSpec extends IntegrationSpec {
-
- def setup() {
- buildFile << """
- plugins {
- id 'com.palantir.versions-lock'
- id 'com.palantir.versions-props'
- }
- """.stripIndent(true)
- file('versions.props')
- file('versions.lock')
- }
-
- def buildSucceed() {
- BuildResult result = runTasks('checkOverbroadConstraints')
- result.task(':checkOverbroadConstraints').outcome == TaskOutcome.SUCCESS
- result
- }
-
- void buildAndFailWith(String error) {
- BuildResult result = runTasksAndFail('checkOverbroadConstraints')
- assert result.output.contains(error)
- }
-
- def buildWithFixWorks() {
- def currentVersionsProps = file('versions.props').readLines()
- // Check that running with --fix modifies the file
- runTasks('checkOverbroadConstraints', '--fix')
- assert file('versions.props').readLines() != currentVersionsProps
-
- // Check that the task now succeeds
- runTasks('checkOverbroadConstraints')
- }
-
- def '#gradleVersionNumber: Task should run as part of :check'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- expect:
- def result = runTasks('check', '-m')
- result.output.contains(':checkOverbroadConstraints')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: All versions are pinned'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = """
- com.fasterxml.jackson.*:* = 2.9.3
- com.fasterxml.jackson.core:jackson-annotations = 2.9.5
- """.stripIndent(true)
- file('versions.lock').text = """
- com.fasterxml.jackson.core:jackson-annotations:2.9.5 (2 constraints: abcdef0)
- com.fasterxml.jackson.core:jackson-core:2.9.3 (2 constraints: abcdef1)
- """.stripIndent(true).trim()
-
- then:
- buildSucceed()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Not all versions are pinned throws error and fix works and no new line is added'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = """
- com.fasterxml.jackson.*:* = 2.9.3
- """.stripIndent(true)
- file('versions.lock').text = """
- com.fasterxml.jackson.core:jackson-annotations:2.9.5 (2 constraints: abcdef0)
- com.fasterxml.jackson.core:jackson-core:2.9.3 (2 constraints: abcdef1)
- """.stripIndent(true).trim()
-
- then:
- buildAndFailWith(String.join(
- "\n",
- "Over-broad version constraints found in versions.props.",
- "Over-broad constraints often arise due to wildcards in versions.props",
- "which apply to more dependencies than they should, this can lead to slow builds.",
- "The following additional pins are recommended:",
- "com.fasterxml.jackson.core:jackson-annotations = 2.9.5",
- "com.fasterxml.jackson.core:jackson-core = 2.9.3",
- "",
- "Run ./gradlew checkOverbroadConstraints --fix to add them.",
- "See https://github.com/palantir/gradle-consistent-versions?tab=readme-ov-file#gradlew-checkoverbroadconstraints for details"))
- buildWithFixWorks()
- file('versions.props').text == """
- com.fasterxml.jackson.core:jackson-annotations = 2.9.5
- com.fasterxml.jackson.core:jackson-core = 2.9.3
- """.stripIndent(true)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Fixes are inserted in the correct locations new lines are maintained'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = """
-
- com.random:* = 1.0.0
- com.fasterxml.jackson.*:* = 2.9.3
-
- # A random comment
- org.different:artifact = 2.0.0
-
- """.stripIndent(true)
- file('versions.lock').text = """
- com.random:random:1.0.0 (2 constraints: abcdef0)
- org.different:artifact:2.0.0 (2 constraints: abcdef0)
- com.fasterxml.jackson.core:jackson-annotations:2.9.5 (2 constraints: abcdef0)
- com.fasterxml.jackson.core:jackson-core:2.9.3 (2 constraints: abcdef1)
- """.stripIndent(true).trim()
-
- then:
- buildWithFixWorks()
- file('versions.props').text == """
-
- com.random:* = 1.0.0
- com.fasterxml.jackson.core:jackson-annotations = 2.9.5
- com.fasterxml.jackson.core:jackson-core = 2.9.3
-
- # A random comment
- org.different:artifact = 2.0.0
-
- """.stripIndent(true)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/CheckUnusedConstraintIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/CheckUnusedConstraintIntegrationSpec.groovy
deleted file mode 100644
index ecbea00f3..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/CheckUnusedConstraintIntegrationSpec.groovy
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import org.gradle.testkit.runner.BuildResult
-import org.gradle.testkit.runner.TaskOutcome
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-
-class CheckUnusedConstraintIntegrationSpec extends IntegrationSpec {
-
- def setup() {
- buildFile << """
- plugins {
- id 'java'
- id 'com.palantir.versions-lock'
- id 'com.palantir.versions-props'
- }
-
- repositories {
- mavenCentral()
- maven { url "${projectDir.toURI()}/maven" }
- }
-
- // Get rid of deprecation warnings for Gradle 7+
- versionRecommendations {
- excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime'
- }
- """.stripIndent(true)
- file('gradle.properties').text = """
- ignoreLockFile=true
- """.stripIndent(true)
- createFile('versions.props')
- }
-
- def buildSucceed() {
- BuildResult result = runTasks('checkUnusedConstraints')
- result.task(':checkUnusedConstraints').outcome == TaskOutcome.SUCCESS
- result
- }
-
- void buildAndFailWith(String error) {
- BuildResult result = runTasksAndFail('checkUnusedConstraints')
- assert result.output.contains(error)
- }
-
- def buildWithFixWorks() {
- def currentVersionsProps = file('versions.props').readLines()
- // Check that running with --fix modifies the file
- runTasks('checkUnusedConstraints', '--fix')
- assert file('versions.props').readLines() != currentVersionsProps
-
- // Check that the task now succeeds
- runTasks('checkUnusedConstraints')
- }
-
- def '#gradleVersionNumber: checkVersionsProps does not resolve artifacts'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- dependencies {
- implementation 'com.palantir.product:foo:1.0.0'
- }
- """.stripIndent(true)
- file("versions.props").text = ""
-
- // We're not producing a jar for this dependency, so artifact resolution would fail
- file('maven/com/palantir/product/foo/1.0.0/foo-1.0.0.pom') <<
- pomWithJarPackaging("com.palantir.product", "foo", "1.0.0")
-
- expect:
- buildSucceed()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Task should run as part of :check'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- expect:
- def result = runTasks('check', '-m')
- result.output.contains(':checkUnusedConstraints')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Version props conflict should succeed'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = """
- com.fasterxml.jackson.*:* = 2.9.3
- com.fasterxml.jackson.core:jackson-annotations = 2.9.5
- """.stripIndent(true)
- buildFile << """
- dependencies {
- implementation 'com.fasterxml.jackson.core:jackson-databind'
- }""".stripIndent(true)
-
- then:
- buildSucceed()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Most specific matching version should win'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = """
- org.slf4j:slf4j-api = 1.7.25
- org.slf4j:* = 1.7.20
- """.stripIndent(true)
-
- buildFile << """
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- }""".stripIndent(true)
-
- then:
- buildAndFailWith('There are unused pins in your versions.props: \n[org.slf4j:*]')
- buildWithFixWorks()
- file('versions.props').text.trim() == "org.slf4j:slf4j-api = 1.7.25"
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Most specific glob should win'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = """
- org.slf4j:slf4j-* = 1.7.25
- org.slf4j:* = 1.7.20
- """.stripIndent(true)
-
- buildFile << """
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- implementation 'org.slf4j:slf4j-jdk14'
- }""".stripIndent(true)
-
- then:
- buildAndFailWith('There are unused pins in your versions.props: \n[org.slf4j:*]')
- buildWithFixWorks()
- file('versions.props').text.trim() == "org.slf4j:slf4j-* = 1.7.25"
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Unused version should fail'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = "notused:atall = 42.42"
-
- then:
- buildAndFailWith("There are unused pins in your versions.props")
- buildWithFixWorks()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: Unused check should use exact matching'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- file('versions.props').text = """
- com.google.guava:guava-testlib = 23.0
- com.google.guava:guava = 22.0
- """.stripIndent(true)
- buildFile << """
- dependencies {
- implementation 'com.google.guava:guava'
- implementation 'com.google.guava:guava-testlib'
- }""".stripIndent(true)
-
- then:
- buildSucceed()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- static String pomWithJarPackaging(String group, String artifact, String version) {
- return """
-
-
- 4.0.0
- $group
- $artifact
- jar
- $version
-
-
-
- """.stripIndent(true)
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy
deleted file mode 100644
index 9a048860b..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import org.gradle.testkit.runner.BuildResult
-import org.gradle.testkit.runner.TaskOutcome
-import spock.lang.Unroll
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-import static com.palantir.gradle.versions.PomUtils.makePlatformPom
-
-/**
- * This tests the interaction of this plugin with Gradle's configuration-on-demand feature:
- * https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand
- */
-@Unroll
-class ConfigurationOnDemandSpec extends IntegrationSpec {
-
- static def PLUGIN_NAME = "com.palantir.consistent-versions"
- private File mavenRepo
-
- /*
- * Project structure (arrows indicate dependencies):
- *
- * upstream unrelated
- * ^ ^
- * | |
- * downstream1 downstream2
- */
- void setup() {
- mavenRepo = generateMavenRepo(
- 'com.example:dependency-of-upstream:1.2.3',
- 'com.example:dependency-of-upstream:100.1.1',
- 'com.example:dependency-of-downstream1:1.2.3',
- 'com.example:dependency-of-downstream2:1.2.3',
- 'com.example:dependency-of-unrelated:1.2.3',
- 'com.example:dep-with-version-bumped-by-unrelated:1.0.0',
- 'com.example:dep-with-version-bumped-by-unrelated:1.1.0',
- 'com.example:transitive-test-dep:1.0.0',
- 'com.example:transitive-test-dep:1.1.0',
- 'com.example:transitive-test-dep:1.2.0',
- )
-
- makePlatformPom(mavenRepo, "org", "platform", "1.0")
-
- buildFile.text = """
- plugins {
- id '${PLUGIN_NAME}'
- }
- allprojects {
- repositories {
- maven { url "file:///${mavenRepo.getAbsolutePath()}" }
- }
- }
- subprojects {
- tasks.register('writeClasspath') {
- doLast {
- println(configurations.runtimeClasspath.getFiles())
- }
- }
- }
-
- // Get rid of deprecation warnings for Gradle 7+
- versionRecommendations {
- excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime'
- }
- """.stripIndent(true)
-
- file('versions.props').text = """
- com.example:dependency-of-upstream = 1.2.3
- com.example:dependency-of-downstream1 = 1.2.3
- com.example:dependency-of-downstream2 = 1.2.3
- com.example:dependency-of-unrelated = 1.2.3
- # 1.0.0 is a minimum, we expect this to be locked to 1.1.0
- com.example:dep-with-version-bumped-by-unrelated = 1.0.0
- """.stripIndent(true)
-
- addSubproject("upstream", """
- plugins {
- id 'java'
- }
- println 'configuring upstream'
- dependencies {
- implementation 'com.example:dependency-of-upstream'
- }
- """.stripIndent(true))
-
- addSubproject("downstream1", """
- plugins {
- id 'java'
- }
- println 'configuring downstream1'
- dependencies {
- implementation project(':upstream')
- implementation 'com.example:dependency-of-downstream1'
- }
- """.stripIndent(true))
-
- addSubproject("downstream2", """
- plugins {
- id 'java'
- }
- println 'configuring downstream2'
- dependencies {
- implementation project(':upstream')
- implementation 'com.example:dependency-of-downstream2'
- implementation 'com.example:dep-with-version-bumped-by-unrelated'
- }
- """.stripIndent(true))
-
- addSubproject("unrelated", """
- plugins {
- id 'java'
- }
- println 'configuring unrelated'
- dependencies {
- implementation 'com.example:dependency-of-unrelated'
- implementation 'com.example:dep-with-version-bumped-by-unrelated:1.1.0'
- }
- """.stripIndent(true))
-
- file("gradle.properties").text = """
- org.gradle.configureondemand=true
- """.stripIndent(true)
- }
-
- def '#gradleVersionNumber: can write locks'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- BuildResult result = runTasks('--write-locks')
-
- then:
- result.output.contains('configuring upstream')
- result.output.contains('configuring downstream1')
- result.output.contains('configuring downstream2')
- result.output.contains('configuring unrelated')
-
- new File(projectDir, "versions.lock").exists()
- file("versions.lock").text.contains("com.example:dependency-of-upstream:1.2.3")
- file("versions.lock").text.contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: can write locks when a task in one project is specified'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks(':downstream1:build', '--write-locks')
-
- then:
- new File(projectDir, "versions.lock").exists()
- file("versions.lock").text.contains("com.example:dependency-of-unrelated:1.2.3")
- file("versions.lock").text.contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: applying the plugin does not force all projects to be configured'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- // Both absolute and relative formats work, as long as Gradle is run from the root project directory
- BuildResult result1 = runTasks(':downstream1:build')
- BuildResult result2 = runTasks('downstream1:build')
-
- then:
- result1.output.contains('configuring upstream')
- result1.output.contains('configuring downstream1')
- !result1.output.contains('configuring downstream2')
- !result1.output.contains('configuring unrelated')
-
- result2.output.contains('configuring upstream')
- result2.output.contains('configuring downstream1')
- !result2.output.contains('configuring downstream2')
- !result2.output.contains('configuring unrelated')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: resolving a classpath does not force all projects to be configured'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasks(':downstream1:writeClasspath')
-
- then:
- result.output.contains('configuring upstream')
- result.output.contains('configuring downstream1')
- !result.output.contains('configuring downstream2')
- !result.output.contains('configuring unrelated')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: after lockfile is written, versions constraints due to non-configured projects are still respected'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasks(':downstream2:writeClasspath')
-
- then:
- // Version used is 1.1.0 due to the unrelated project
- result.output.contains('dep-with-version-bumped-by-unrelated-1.1.0.jar')
- !result.output.contains('configured unrelated')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: transitive dependencies cause upstream projects to be configured sufficiently early'() {
- setup:
- gradleVersion = gradleVersionNumber
- addSubproject("a", """
- plugins { id 'java' }
- dependencies {
- implementation 'com.example:transitive-test-dep:1.0.0'
- }
- """.stripIndent(true))
- addSubproject("b", """
- plugins { id 'java' }
- dependencies {
- implementation project(':a')
- }
- """.stripIndent(true))
- addSubproject("c", """
- plugins { id 'java' }
- dependencies {
- implementation project(':b')
- }
- tasks.register('writeClasspathOfA') {
- doLast {
- println project(':a').configurations.runtimeClasspath.files
- }
- }
- """.stripIndent(true))
- addSubproject("u", """
- plugins { id 'java' }
- dependencies {
- implementation 'com.example:transitive-test-dep:1.1.0'
- }
- """.stripIndent(true))
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasks(':c:writeClasspathOfA')
-
- then:
- // Version used should be 1.1.0, indicating that the version.lock constraint was applied
- result.output.contains('transitive-test-dep-1.1.0.jar')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: task dependencies cause upstream projects to be configured sufficiently early'() {
- setup:
- gradleVersion = gradleVersionNumber
- addSubproject("a", """
- plugins { id 'java' }
- dependencies {
- implementation 'com.example:transitive-test-dep:1.0.0'
- }
- """.stripIndent(true))
- addSubproject("b", """
- tasks.register('foo') {
- dependsOn ':a:writeClasspath'
- }
- """.stripIndent(true))
- addSubproject("c", """
- tasks.register('bar') {
- dependsOn ':b:foo'
- }
- """.stripIndent(true))
- addSubproject("u", """
- plugins { id 'java' }
- dependencies {
- implementation 'com.example:transitive-test-dep:1.1.0'
- }
- """.stripIndent(true))
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasks(':c:bar')
-
- then:
- // Version used should be 1.1.0, indicating that the version.lock constraint was applied
- result.output.contains('transitive-test-dep-1.1.0.jar')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: verification tasks pass when all projects are configured'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- // Note: Not specifying the project causes all projects to be configured regardless of CoD
- BuildResult result = runTasks('checkUnusedConstraints', 'verifyLocks')
-
- then:
- result.output.contains('configuring upstream')
- result.task(':checkUnusedConstraints').outcome == TaskOutcome.SUCCESS
- result.task(':verifyLocks').outcome == TaskOutcome.SUCCESS
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: checkUnusedConstraints fails and warns when not all projects are configured'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasksAndFail(':checkUnusedConstraints')
-
- then:
- !result.output.contains('configuring upstream')
- result.task(':checkUnusedConstraints').outcome == TaskOutcome.FAILED
- result.output.contains("The gradle-consistent-versions checkUnusedConstraints task " +
- "must have all projects configured to work accurately, but due to Gradle " +
- "configuration-on-demand, not all projects were configured.")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: verifyLocks fails and warns when not all projects are configured'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasksAndFail(':verifyLocks')
-
- then:
- !result.output.contains('configuring upstream')
- result.task(':verifyLocks').outcome == TaskOutcome.FAILED
- result.output.contains("All projects must have been configured for this task to work correctly, but due to " +
- "Gradle configuration-on-demand, not all projects were configured.")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- // As failing tasks can't be considered UP-TO-DATE, we only need to check the case where the task passing
- // is followed by the task running with incomplete configuration.
- def '#gradleVersionNumber: verification tasks are not UP-TO-DATE when the set of configured projects differs'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- runTasks('build')
-
- then:
- runTasksAndFail(':checkUnusedConstraints')
- runTasksAndFail(':verifyLocks')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: the why task works when all projects are configured'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasks('why', '--hash=0805f935')
-
- then:
- result.task(':why').outcome == TaskOutcome.SUCCESS
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: the why task somehow forces all projects to be configured'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
- BuildResult result = runTasks(':why', '--hash=0805f935')
-
- then:
- result.output.contains('configuring upstream')
- result.output.contains('configuring downstream1')
- result.output.contains('configuring downstream2')
- result.output.contains('configuring unrelated')
- result.task(':why').outcome == TaskOutcome.SUCCESS
- result.output.contains('com.example:dependency-of-unrelated:1.2.3\n\tprojects -> 1.2.3')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/ConsistentVersionsPluginIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/ConsistentVersionsPluginIntegrationSpec.groovy
deleted file mode 100644
index 9d8353684..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/ConsistentVersionsPluginIntegrationSpec.groovy
+++ /dev/null
@@ -1,619 +0,0 @@
-/*
- * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.gradle.util.GradleVersion
-import spock.lang.Unroll
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-import static com.palantir.gradle.versions.PomUtils.makePlatformPom
-
-@Unroll
-class ConsistentVersionsPluginIntegrationSpec extends IntegrationSpec {
-
- static def PLUGIN_NAME = "com.palantir.consistent-versions"
- private File mavenRepo
-
- void setup() {
- mavenRepo = generateMavenRepo(
- "ch.qos.logback:logback-classic:1.1.11 -> org.slf4j:slf4j-api:1.7.22",
- "org.slf4j:slf4j-api:1.7.22",
- "org.slf4j:slf4j-api:1.7.25",
- "test-alignment:module-that-should-be-aligned-up:1.0",
- "test-alignment:module-that-should-be-aligned-up:1.1",
- "test-alignment:module-with-higher-version:1.1")
-
- makePlatformPom(mavenRepo, "org", "platform", "1.0")
-
- buildFile << """
- apply plugin: '${PLUGIN_NAME}'
- allprojects {
- tasks.register("resolveConfigurations", {
- project.configurations.all { configuration ->
- // Resolving these throws deprecation warnings for Gradle 8
- def deprecatedResolve = configuration.name == "default" || configuration.name == "archives"
- if (deprecatedResolve ||
- (configuration.metaClass.respondsTo(configuration, "isCanBeResolved") &&
- !configuration.isCanBeResolved())) {
- return
- }
- configuration.resolve()
- }
- })
-
- repositories {
- maven { url "file:///${mavenRepo.getAbsolutePath()}" }
- }
- }
-
- subprojects {
- // Parallel 'resolveConfigurations' sometimes breaks unless we force the root one to run first.
- tasks.named("resolveConfigurations", { it.mustRunAfter ":resolveConfigurations" })
- }
- """.stripIndent(true)
-
- keepFiles = true
- definePluginOutsideOfPluginBlock = true
- }
-
- def '#gradleVersionNumber: can write locks using --write-locks'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasks('--write-locks')
-
- then:
- new File(projectDir, "versions.lock").exists()
- runTasks('resolveConfigurations')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: can write locks using writeVersionsLocks'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasksWithConfigurationCache('writeVersionsLocks')
-
- then:
- new File(projectDir, "versions.lock").exists()
- runTasks('resolveConfigurations')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: can write locks using abbreviated writeVersionsLocks'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- when:
- runTasksWithConfigurationCache('wVL')
-
- then:
- new File(projectDir, "versions.lock").exists()
- runTasks('resolveConfigurations')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: locks are consistent whether or not we do --write-locks for glob-forced direct dependency"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- runtimeOnly 'ch.qos.logback:logback-classic:1.1.11' // brings in slf4j-api 1.7.22
- }
-
- task resolve { doLast { configurations.runtimeClasspath.resolve() } }
- '''.stripIndent(true)
-
- file('versions.props') << 'org.slf4j:* = 1.7.25'
-
- expect:
- runTasks('resolve', '--write-locks')
- runTasks('resolve')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: getVersion function works"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- }
-
- task demo {
- doLast { println "demo=" + getVersion('org.slf4j:slf4j-api', configurations.compileClasspath) }
- }
- '''.stripIndent(true)
-
- // Pretend we have a lock file
- file('versions.lock') << ''
-
- file('versions.props') << 'org.slf4j:* = 1.7.25'
-
- expect:
- runTasks('demo').output.contains("demo=1.7.25")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: getVersion function works even when writing locks"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- }
-
- task demo {
- doLast { println "demo=" + getVersion('org.slf4j:slf4j-api') }
- }
- '''.stripIndent(true)
-
- file('versions.props') << 'org.slf4j:* = 1.7.25'
-
- expect:
- runTasks('demo', '--write-locks').output.contains("demo=1.7.25")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: virtual platform is respected across projects"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- addSubproject('foo', """
- apply plugin: 'java'
- dependencies {
- implementation 'test-alignment:module-that-should-be-aligned-up:1.0'
- }
- """.stripIndent(true))
-
- addSubproject('bar', """
- apply plugin: 'java'
- dependencies {
- implementation 'test-alignment:module-with-higher-version:1.1'
- }
- """.stripIndent(true))
-
- file('versions.props') << """
- # Just to create a platform around test-alignment:*
- test-alignment:* = 1.0
- """.stripIndent(true)
-
- when:
- runTasks('--write-locks')
-
- then:
- def expectedLock = """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- test-alignment:module-that-should-be-aligned-up:1.1 (1 constraints: a5041a2c)
-
- test-alignment:module-with-higher-version:1.1 (1 constraints: a6041b2c)
- """.stripIndent(true)
- file('versions.lock').text == expectedLock
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: star dependencies in the absence of dependency versions"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- addSubproject('foo', """
- apply plugin: 'java'
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- }
- """.stripIndent(true))
-
- file('versions.props') << """
- org.slf4j:* = 1.7.25
- """.stripIndent(true)
-
- when:
- runTasks('--write-locks')
-
- then:
- def expectedLock = """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- org.slf4j:slf4j-api:1.7.25 (1 constraints: 4105483b)
- """.stripIndent(true)
- file('versions.lock').text == expectedLock
-
- // Ensure that this is a required constraint
- runTasks('why', '--hash', '4105483b').output.contains "projects -> 1.7.25"
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: writeLocks and verifyLocks work in the presence of versions props constraints"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- makePlatformPom(mavenRepo, "org1", "platform", "1.0")
- makePlatformPom(mavenRepo, "org2", "platform", "1.0")
-
- addSubproject('foo', """
- apply plugin: 'java'
-
- configurations {
- other
- }
-
- dependencies {
- implementation 'org.slf4j:slf4j-api'
-
- rootConfiguration platform('org1:platform')
- rootConfiguration platform('org2:platform')
- }
-
- task resolveLockedConfigurations {
- doLast {
- configurations.compileClasspath.resolve()
- configurations.runtimeClasspath.resolve()
- }
- }
-
- // This is to ensure that the platform deps are successfully resolvable on a non-locked configuration
- task resolveNonLockedConfiguration {
- doLast {
- configurations.other.resolve()
- }
- }
- """.stripIndent(true))
-
- file('versions.props') << """
- org1:platform = 1.0
- org2:* = 1.0
- org.slf4j:slf4j-api = 1.7.25
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
-
- file('versions.lock').text == """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- org.slf4j:slf4j-api:1.7.25 (1 constraints: 4105483b)
-
- org1:platform:1.0 (1 constraints: a5041a2c)
-
- org2:platform:1.0 (1 constraints: a5041a2c)
- """.stripIndent(true)
-
- and: 'Ensure you can verify locks and resolve the actual locked configurations'
- runTasks('verifyLocks', 'resolveLockedConfigurations', 'resolveNonLockedConfiguration')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: versions props contents do not get published as constraints"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- allprojects {
- apply plugin: 'java'
- }
- """.stripIndent(true)
-
- String publish = """
- apply plugin: 'maven-publish'
- publishing.publications {
- maven(MavenPublication) {
- from components.java
- }
- }
- """.stripIndent(true)
-
- addSubproject('foo', """
- apply plugin: 'java'
- $publish
- dependencies {
- implementation 'ch.qos.logback:logback-classic'
- }
- """.stripIndent(true))
-
- file('versions.props') << """
- org.slf4j:* = 1.7.25
- ch.qos.logback:* = 1.1.11
- should:not-publish = 1.0
- """.stripIndent(true)
-
- if (GradleVersion.version(gradleVersionNumber) < GradleVersion.version("6.0")) {
- settingsFile << """
- enableFeaturePreview('GRADLE_METADATA')
- """.stripIndent(true)
- }
-
- when:
- runTasks('--write-locks', 'generateMetadataFileForMavenPublication')
-
- def logbackDep = new MetadataFile.Dependency(
- group: 'ch.qos.logback',
- module: 'logback-classic',
- version: [requires: '1.1.11'])
- def slf4jDep = new MetadataFile.Dependency(
- group: 'org.slf4j',
- module: 'slf4j-api',
- version: [requires: '1.7.25'])
-
- then: "foo's metadata file has the right dependency constraints"
- def fooMetadataFilename = new File(projectDir, "foo/build/publications/maven/module.json")
- def fooMetadata = new ObjectMapper().readValue(fooMetadataFilename, MetadataFile)
-
- fooMetadata.variants == [
- new MetadataFile.Variant(
- name: 'apiElements',
- dependencies: null,
- dependencyConstraints: [logbackDep, slf4jDep]),
- new MetadataFile.Variant(
- name: 'runtimeElements',
- dependencies: [logbackDep],
- dependencyConstraints: [logbackDep, slf4jDep]),
- ] as Set
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: intransitive dependency on published configuration should not break realizing it later"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- addSubproject('source', """
- configurations {
- transitive
- intransitive
- }
- dependencies {
- // This wrecks us
- intransitive project(':target'), { transitive = false }
-
- transitive project(':target')
- }
-
- task resolveIntransitively {
- doLast {
- configurations.intransitive.resolvedConfiguration
- }
- }
- task resolveTransitively {
- mustRunAfter resolveIntransitively
- doLast {
- configurations.transitive.resolvedConfiguration
- }
- }
- """.stripIndent(true))
-
- addSubproject('target', """
- apply plugin: 'java'
-
- dependencies {
- // Test the lazy action on published configurations like apiElements, runtimeElements
- // that copies over platform dependencies from rootConfiguration.
- rootConfiguration platform("org:platform")
- }
- """.stripIndent(true))
-
- file('versions.props') << """
- org:platform = 1.0
- """.stripIndent(true)
-
- // This is just for debugging
- buildFile << """
- allprojects {
- configurations.all {
- incoming.beforeResolve {
- println "Resolving: \$it"
- }
- }
- }
- """.stripIndent(true)
-
- runTasks('--write-locks')
-
- expect:
- runTasks('resolveIntransitively', 'resolveTransitively')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
-
- def "#gradleVersionNumber: works with included builds"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- // add included build
- def includedBuild = directory("included-build")
- settingsFile << """
- includeBuild '${includedBuild.name}'
- """.stripIndent(true)
-
- // configure the included build
- file("settings.gradle", includedBuild) << """
- rootProject.name = 'included-build'
-
- include 'innerA'
- include 'innerB'
- """.stripIndent(true)
-
- file ("build.gradle", includedBuild) << """
- buildscript {
- repositories {
- mavenCentral()
- }
- }
- plugins {
- id '${PLUGIN_NAME}'
- }
- allprojects {
- group 'com.palantir.included-build'
- version '1.0.0'
-
- repositories {
- maven { url "file:///${mavenRepo.getAbsolutePath()}" }
- }
- }
-
- subprojects {
- apply plugin: 'java'
- apply plugin: 'maven-publish'
- publishing {
- repositories {
- maven {
- url = "${mavenRepo.absolutePath}"
- }
- }
-
- publications {
- maven(MavenPublication) {
- from components.java
- }
- }
- }
- }
-
- """.stripIndent(true)
- def innerA = directory("innerA", includedBuild)
- file("build.gradle", innerA) << """
- apply plugin: 'java'
-
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- runtimeOnly 'ch.qos.logback:logback-classic:1.1.11' // brings in slf4j-api 1.7.22
- }
- """.stripIndent(true)
-
- def innerB = directory("innerB", includedBuild)
- file("build.gradle", innerB) << """
- apply plugin: 'java'
-
- dependencies {
- implementation 'test-alignment:module-with-higher-version'
- }
- """.stripIndent(true)
-
- file('versions.props', includedBuild) << """
- org.slf4j:slf4j-api = 1.7.25
- test-alignment:* = 1.1
- """.stripIndent(true)
-
- // configure main build
- buildFile << """
- apply plugin: 'java'
-
- group 'com.palantir.main-build'
- version '1.2.3'
-
- apply plugin: 'maven-publish'
- publishing {
- repositories {
- maven {
- url = "${mavenRepo.absolutePath}"
- }
- }
-
- publications {
- maven(MavenPublication) {
- from components.java
- }
- }
- }
-
- dependencies {
- implementation 'test-alignment:module-that-should-be-aligned-up'
- }
- """.stripIndent(true)
-
- file('versions.props') << """
- test-alignment:* = 1.0
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
-
- and: 'inner versions lock is expected'
- file("versions.lock", includedBuild).text == """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- ch.qos.logback:logback-classic:1.1.11 (1 constraints: 36052a3b)
-
- org.slf4j:slf4j-api:1.7.25 (2 constraints: 7d12a137)
-
- test-alignment:module-with-higher-version:1.1 (1 constraints: a6041b2c)
- """.stripIndent(true)
-
- and: 'root build: versions lock is expected'
- file("versions.lock").text == """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- test-alignment:module-that-should-be-aligned-up:1.0 (1 constraints: a5041a2c)
- """.stripIndent(true)
-
- when: 'we add a dependencies on the inner build'
- buildFile << """
- dependencies {
- implementation 'com.palantir.included-build:innerA'
- implementation 'com.palantir.included-build:innerB'
- }
- """.stripIndent(true)
-
- then: 'build succeeds'
- runTasks('--write-locks')
-
- println runTasks(
- ':publishMavenPublicationToMavenRepository',
- ':included-build:innerA:publishMavenPublicationToMavenRepository',
- ':included-build:innerB:publishMavenPublicationToMavenRepository').output
-
- and: 'root build: dependency is bumped - there is a difference in resolution between Gradle versions hence why we do not compare contents directly'
- String rootVersionsLock = file("versions.lock").text
- rootVersionsLock.contains "ch.qos.logback:logback-classic:1.1.11 (1 constraints: 36052a3b)"
- rootVersionsLock.contains "test-alignment:module-that-should-be-aligned-up:1.1 (1 constraints: a5041a2c)"
- rootVersionsLock.contains "test-alignment:module-with-higher-version:1.1 (1 constraints: a6041b2c)"
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/IntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/IntegrationSpec.groovy
deleted file mode 100644
index 29815fd33..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/IntegrationSpec.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import groovy.transform.CompileStatic
-import nebula.test.IntegrationTestKitSpec
-import nebula.test.dependencies.DependencyGraph
-import nebula.test.dependencies.GradleDependencyGenerator
-
-class IntegrationSpec extends IntegrationTestKitSpec {
- void setup() {
- keepFiles = true
- debug = true
- settingsFile.createNewFile()
- }
-
- @CompileStatic
- protected File generateMavenRepo(String... graph) {
- DependencyGraph dependencyGraph = new DependencyGraph(graph)
- GradleDependencyGenerator generator = new GradleDependencyGenerator(
- dependencyGraph, new File(projectDir, "build/testrepogen").toString())
- return generator.generateTestMavenRepo()
- }
-
-
- /**
- * Runs the specified tasks twice with configuration cache and verifies cache behavior.
- * Returns true if the configuration cache was properly used on the second run.
- */
- boolean runTasksWithConfigurationCache(String... tasks) {
- def firstRun = createRunner(tasks + ['--configuration-cache'] as String[]).build()
- assert firstRun.output.contains('Configuration cache entry stored.'),
- "Expected first run to store configuration cache, but output was: ${firstRun.output}"
-
- def secondRun = createRunner(tasks + ['--configuration-cache'] as String[]).build()
- assert secondRun.output.contains('Configuration cache entry reused.'),
- "Expected second run to reuse configuration cache, but output was: ${secondRun.output}"
-
- File configCacheDir = new File(projectDir, ".gradle/configuration-cache")
- if (configCacheDir.exists()) {
- configCacheDir.deleteDir()
- }
- assert !configCacheDir.exists(), "Configuration cache directory was not deleted"
-
- return true
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/SlsPackagingCompatibilityIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/SlsPackagingCompatibilityIntegrationSpec.groovy
deleted file mode 100644
index b8d92efa4..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/SlsPackagingCompatibilityIntegrationSpec.groovy
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import org.gradle.testkit.runner.TaskOutcome
-import spock.lang.IgnoreIf
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-
-/**
- * https://github.com/palantir/sls-packaging does some funky stuff when resolving inter-project dependencies for the
- * purposes of detecting published recommended product dependencies, so we want to make double sure that GCV doesn't
- * accidentally break it.
- */
-class SlsPackagingCompatibilityIntegrationSpec extends IntegrationSpec {
-
- static def PLUGIN_NAME = "com.palantir.consistent-versions"
-
- void setup() {
- File mavenRepo = generateMavenRepo(
- "org.slf4j:slf4j-api:1.7.24",
- )
- buildFile << """
- buildscript {
- repositories {
- mavenCentral()
- }
- }
- plugins {
- id '${PLUGIN_NAME}'
- id 'com.palantir.sls-java-service-distribution' version '7.31.0' apply false
- }
- allprojects {
- repositories {
- maven { url "file:///${mavenRepo.getAbsolutePath()}" }
- }
- }
- """.stripIndent(true)
- }
-
- @IgnoreIf(
- reason = """
- sls-packaging is creating a configuration as part of a task input, which is happening far too late. \
- Once gradle has done a resolution, it will not look at any new Configurations that have popped up \
- since then. See https://github.com/palantir/gradle-consistent-versions/pull/1443 for more details.""",
- value = { data.gradleVersionNumber.startsWith("9") })
- def '#gradleVersionNumber can consume recommended product dependencies project'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- file("versions.props") << """
- org.slf4j:* = 1.7.24
- """.stripIndent(true)
-
- buildFile << """
- allprojects {
- version = '1.0.0'
- }
- """.stripIndent(true)
-
- addSubproject('api', """
- apply plugin: 'java'
- apply plugin: 'com.palantir.sls-recommended-dependencies'
-
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- }
-
- recommendedProductDependencies {
- productDependency {
- productGroup = 'org'
- productName = 'product'
- minimumVersion = '1.1.0'
- maximumVersion = '1.x.x'
- }
- }
- """.stripIndent(true))
-
- addSubproject('service', """
- apply plugin: 'java'
- apply plugin: 'com.palantir.sls-java-service-distribution'
-
- dependencies {
- // Gets picked up by the productDependenciesConfig which is runtimeClasspath
- implementation project(':api')
- }
- """.stripIndent(true))
-
- expect:
- def wroteLocks = runTasks('--write-locks')
- // Maybe this is a bit too much but for a fixed version of sls-packaging, we expect this to not change
- wroteLocks.tasks(TaskOutcome.SUCCESS).collect { it.path } as Set == [
- ':api:compileRecommendedProductDependencies',
- ':api:processResources',
- ':service:mergeDiagnosticsJson',
- ':service:resolveProductDependencies',
- ':service:createManifest',
- ':api:classes',
- ':api:configureProductDependencies',
- ':api:jar',
- ':service:jar'
- ] as Set
-
- runTasks('createManifest', 'verifyLocks')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
-
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.groovy
deleted file mode 100644
index 15eabba38..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.groovy
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import groovy.xml.XmlNodePrinter
-import nebula.test.IntegrationSpec
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-
-class VersionPropsIdeaPluginIntegrationSpec extends IntegrationSpec {
-
- def setup() {
- //language=gradle
- buildFile << """
- repositories {
- maven {
- url 'https://test'
- }
- maven {
- url 'https://demo/'
- }
- mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } }
- }
-
- apply plugin: 'com.palantir.version-props-idea'
- apply plugin: 'idea'
- """.stripIndent(true)
-
- def ideaDir = new File(projectDir, '.idea')
- ideaDir.mkdirs()
- }
-
- def "#gradleVersionNumber: plugin creates gcv-maven-repositories.xml file in .idea folder"() {
- setup:
- gradleVersion = gradleVersionNumber
- when: 'we run the first time'
- runTasksSuccessfully('-Didea.active=true')
-
- then: 'we generate the correct config'
- def repoFile = new File(projectDir, '.idea/gcv-maven-repositories.xml')
- repoFile.exists()
-
- // language=xml
- def expectedXml = '''
-
-
-
-
-
- '''.stripIndent(true).trim()
-
- def projectNode = new XmlParser().parse(repoFile)
- nodeToXmlString(projectNode) == expectedXml
-
- when: 'we run the second time'
- def secondRun = runTasksSuccessfully('-Didea.active=true')
-
- then: "if nothing has changed, the task is then up-to-date"
- secondRun.wasUpToDate(":writeMavenRepositories")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- private static String nodeToXmlString(debugRunConf) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream()
- new XmlNodePrinter(new PrintWriter(baos)).print(debugRunConf)
- return baos.toString().trim()
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/VersionsLockPluginIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/VersionsLockPluginIntegrationSpec.groovy
deleted file mode 100644
index 8744d10c4..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/VersionsLockPluginIntegrationSpec.groovy
+++ /dev/null
@@ -1,1128 +0,0 @@
-/*
- * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import nebula.test.dependencies.DependencyGraph
-import nebula.test.dependencies.GradleDependencyGenerator
-import one.util.streamex.StreamEx
-import org.gradle.testkit.runner.BuildResult
-import org.gradle.testkit.runner.TaskOutcome
-import org.gradle.util.GradleVersion
-import spock.lang.Unroll
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-import static com.palantir.gradle.versions.PomUtils.makePlatformPom
-
-@Unroll
-class VersionsLockPluginIntegrationSpec extends IntegrationSpec {
-
- static def PLUGIN_NAME = "com.palantir.versions-lock"
-
- void setup() {
- File mavenRepo = generateMavenRepo(
- "ch.qos.logback:logback-classic:1.2.3 -> org.slf4j:slf4j-api:1.7.25",
- "org.slf4j:slf4j-api:1.7.11",
- "org.slf4j:slf4j-api:1.7.20",
- "org.slf4j:slf4j-api:1.7.24",
- "org.slf4j:slf4j-api:1.7.25",
- "junit:junit:4.10",
- "org:test-dep-that-logs:1.0 -> org.slf4j:slf4j-api:1.7.11",
- "org:another-transitive-dependency:3.2.1",
- "org:another-direct-dependency:1.2.3 -> org:another-transitive-dependency:3.2.1",
- )
- makePlatformPom(mavenRepo, "org", "platform", "1.0")
-
- buildFile << """
- buildscript {
- repositories {
- mavenCentral()
- }
- }
- plugins {
- id '${PLUGIN_NAME}'
- }
- allprojects {
- repositories {
- maven { url "file:///${mavenRepo.getAbsolutePath()}" }
- }
-
- task resolveConfigurations {
- doLast {
- if (pluginManager.hasPlugin('java')) {
- configurations.compileClasspath.resolve()
- configurations.runtimeClasspath.resolve()
- }
- }
- }
- }
- """
- }
-
- def '#gradleVersionNumber: can write locks'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- expect:
- runTasks('--write-locks')
- new File(projectDir, "versions.lock").exists()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- private def standardSetup() {
- addSubproject('foo', '''
- apply plugin: 'java'
- dependencies {
- implementation 'org.slf4j:slf4j-api:1.7.24'
- }
- '''.stripIndent(true))
-
- addSubproject('bar', '''
- apply plugin: 'java'
- dependencies {
- implementation "org.slf4j:slf4j-api:${project.bar_version}"
- }
- '''.stripIndent(true))
- file("gradle.properties") << "bar_version=1.7.11"
-
- addSubproject('forced', '''
- apply plugin: 'java'
- dependencies {
- implementation "org.slf4j:slf4j-api"
- }
- configurations.all {
- resolutionStrategy {
- force "org.slf4j:slf4j-api:1.7.20"
- }
- }
- '''.stripIndent(true))
- }
-
- def '#gradleVersionNumber: cannot resolve without a root lock file'() {
- setup:
- gradleVersion = gradleVersionNumber
- standardSetup()
-
- expect:
- def result = runTasksAndFail('resolveConfigurations')
- result.output.readLines().any {
- it.matches ".*Root lock file '([^']+)' doesn't exist, please run.*"
- }
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: can resolve without a root lock file if lock file is ignored'() {
- setup:
- gradleVersion = gradleVersionNumber
- standardSetup()
-
- expect:
- runTasks('resolveConfigurations', '-PignoreLockFile')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: consolidates subproject dependencies'() {
- def expectedError = "Locked by versions.lock"
- setup:
- gradleVersion = gradleVersionNumber
- standardSetup()
- buildFile << '''
- subprojects {
- configurations.matching { it.name == 'runtimeClasspath' }.all {
- resolutionStrategy.activateDependencyLocking()
- }
- }
- '''.stripIndent(true)
-
- when: "I write locks"
- runTasks('resolveConfigurations', '--write-locks')
-
- then: "Lock files are consistent with version resolved at root"
- file("versions.lock").text.readLines().any { it.startsWith('org.slf4j:slf4j-api:1.7.24') }
-
- ["foo", "bar"].each { verifyLockfile(file(it), "org.slf4j:slf4j-api:1.7.24") }
-
- then: "Manually forced version overrides unified dependency"
- verifyLockfile(file("forced"), "org.slf4j:slf4j-api:1.7.20")
-
- then: "I can resolve configurations"
- runTasks('resolveConfigurations')
-
- when: "I make bar's version constraint incompatible with the force"
- BuildResult incompatible = runTasksAndFail("-Pbar_version=1.7.25", 'resolveConfigurations')
-
- then: "Resolution fails"
- incompatible.output.contains(expectedError)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: works on just root project'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3' // brings in slf4j-api 1.7.25
- }
- '''.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
- def lines = file('versions.lock').readLines()
- lines.contains('ch.qos.logback:logback-classic:1.2.3 (1 constraints: 0805f935)')
- lines.contains('org.slf4j:slf4j-api:1.7.25 (1 constraints: 400d4d2a)')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: get a conflict even if no lock files applied'() {
- def expectedError = "Locked by versions.lock"
- setup:
- gradleVersion = gradleVersionNumber
- standardSetup()
-
- when: "I write locks"
- runTasks('--write-locks')
-
- then: "Root lock file has expected resolution result"
- file("versions.lock").text.readLines().any { it.contains('org.slf4j:slf4j-api:1.7.24') }
-
- then: "I can resolve configurations"
- runTasks('resolveConfigurations')
-
- when: "I make bar's version constraint incompatible with the force"
- def incompatible = runTasksAndFail("-Pbar_version=1.7.25", 'resolveConfigurations')
-
- then: "Resolution fails"
- incompatible.output.contains(expectedError)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: fails fast when subproject that is depended on has same name as root project'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- def expectedError = "This plugin doesn't work if the root project shares both group and name with a subproject"
- buildFile << """
- allprojects {
- group 'same'
- }
- """.stripIndent(true)
- settingsFile << "rootProject.name = 'foobar'\n"
- addSubproject("foobar", '''
- apply plugin: 'java-library'
- '''.stripIndent(true))
- addSubproject("other", '''
- apply plugin: 'java-library'
- dependencies {
- implementation project(':foobar')
- }
- '''.stripIndent(true))
- // Otherwise the lack of a lock file will throw first
- file('versions.lock') << ""
-
- expect:
- def error = runTasksAndFail()
- error.output.contains(expectedError)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: fails fast when multiple subprojects share the same coordinate'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- def expectedError = "All subprojects must have unique \$group:\$name"
- buildFile << """
- allprojects {
- group 'same'
- }
- """.stripIndent(true)
- // both projects will have name = 'a'
- addSubproject("foo:a")
- addSubproject("bar:a")
- // Otherwise the lack of a lock file will throw first
- file('versions.lock') << ""
-
- expect:
- def error = runTasksAndFail()
- error.output.contains(expectedError)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- @Override
- File addSubproject(String name) {
- File subprojectDir = new File(projectDir, name.replace(":", "/"))
- subprojectDir.mkdirs()
- StreamEx.split(name, ":")
- .scanLeft { left, right -> "$left:$right" }
- .each { subproject -> settingsFile << "include \"${subproject}\"${LINE_END}" }
-
- return subprojectDir
- }
-
- def "#gradleVersionNumber: detects failOnVersionConflict on locked configuration"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- configurations.compileClasspath.resolutionStrategy.failOnVersionConflict()
- """.stripIndent(true)
- file('versions.lock').text = ''
-
- expect:
- def failure = runTasksAndFail()
- failure.output.contains('Must not use failOnVersionConflict')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: ignores failOnVersionConflict on non-locked configuration"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- configurations {
- foo {
- resolutionStrategy.failOnVersionConflict()
- }
- }
- """.stripIndent(true)
- file('versions.lock').text = ''
-
- expect:
- runTasks()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: fails if new dependency added that was not in the lock file'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- def expectedError = "Found dependencies that were not in the lock state"
- DependencyGraph dependencyGraph = new DependencyGraph("org:a:1.0", "org:b:1.0")
- GradleDependencyGenerator generator = new GradleDependencyGenerator(dependencyGraph)
- def mavenRepo = generator.generateTestMavenRepo()
-
- buildFile << """
- repositories {
- maven { url "file:///${mavenRepo.absolutePath}" }
- }
-
- subprojects {
- apply plugin: 'java'
- }
- """.stripIndent(true)
-
- addSubproject('foo', '''
- dependencies {
- implementation 'org:a:1.0'
- }
- '''.stripIndent(true))
-
- runTasks('--write-locks')
-
- when:
- file('foo/build.gradle') << """
- dependencies {
- implementation 'org:b:1.0'
- }
- """.stripIndent(true)
-
- then: 'Check fails because locks are not up to date'
- def failure = runTasksAndFail(':check')
- failure.task(':verifyLocks').outcome == TaskOutcome.FAILED
- failure.output.contains(expectedError)
-
- and: 'Can finally write locks once again'
- runTasks('--write-locks')
- runTasks('verifyLocks')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: does not fail if unifiedClasspath is unresolvable'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- file('versions.lock') << """\
- org.slf4j:slf4j-api:1.7.11 (0 constraints: 0000000)
- """.stripIndent(true)
-
- addSubproject('foo', '''
- apply plugin: 'java'
- dependencies {
- implementation 'org.slf4j:slf4j-api:1.7.20'
- }
- '''.stripIndent(true))
-
- expect:
- runTasks('dependencies', '--configuration', 'unifiedClasspath')
- runTasks()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: fails if dependency was removed but still in the lock file'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- def expectedError = "Locked dependencies missing from the resolution result"
- DependencyGraph dependencyGraph = new DependencyGraph("org:a:1.0", "org:b:1.0")
- GradleDependencyGenerator generator = new GradleDependencyGenerator(dependencyGraph)
- def mavenRepo = generator.generateTestMavenRepo()
-
- buildFile << """
- repositories {
- maven { url "file:///${mavenRepo.absolutePath}" }
- }
-
- subprojects {
- apply plugin: 'java'
- }
- """.stripIndent(true)
-
- addSubproject('foo', '''
- dependencies {
- implementation 'org:a:1.0'
- implementation 'org:b:1.0'
- }
- '''.stripIndent(true))
-
- runTasks('--write-locks')
-
- when:
- file('foo/build.gradle').text = """
- dependencies {
- implementation 'org:a:1.0'
- }
- """.stripIndent(true)
-
- then: 'Check fails because locks are not up to date'
- def failure = runTasksAndFail(':check')
- failure.task(':verifyLocks').outcome == TaskOutcome.FAILED
- failure.output.contains(expectedError)
-
- and: 'Can finally write locks once again'
- runTasks('--write-locks')
- runTasks('verifyLocks')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: why works"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3' // brings in slf4j-api 1.7.25
- }
- '''.stripIndent(true)
-
- when:
- runTasks('--write-locks')
-
- then:
- def result = runTasks('why', '--dependency', 'slf4j-api')
- result.output.contains('org.slf4j:slf4j-api:1.7.25')
- result.output.contains('ch.qos.logback:logback-classic -> 1.7.25')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: why with hash works"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3' // brings in slf4j-api 1.7.25
- }
- '''.stripIndent(true)
-
- when:
- runTasks('--write-locks')
-
- then:
- def result = runTasks('why', '--hash', '400d4d2a') // slf4j-api
- result.output.contains('org.slf4j:slf4j-api:1.7.25')
- result.output.contains('ch.qos.logback:logback-classic -> 1.7.25')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: why with comma-delimited multiple hashes works"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3' // brings in slf4j-api 1.7.25
- implementation 'org:another-direct-dependency:1.2.3' // brings in org:another-transitive-dependency:3.2.1
- }
- '''.stripIndent(true)
-
- when:
- runTasks('--write-locks')
-
- then:
- def result = runTasks('why', '--hash', '400d4d2a,050d6518') // both transitive dependencies
- result.output.contains('org.slf4j:slf4j-api:1.7.25')
- result.output.contains('ch.qos.logback:logback-classic -> 1.7.25')
- result.output.contains('org:another-transitive-dependency:3.2.1')
- result.output.contains('org:another-direct-dependency -> 3.2.1')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: does not fail if subproject evaluated later applies base plugin in own build file'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- addSubproject('foo', """
- apply plugin: 'java-library'
- dependencies {
- implementation project(':foo:bar')
- }
- """.stripIndent(true))
-
- // Need to make sure bar is evaluated after foo, so we're nesting it!
- def subdir = new File(projectDir, 'foo/bar')
- subdir.mkdirs()
- settingsFile << "include ':foo:bar'"
- new File(subdir, 'build.gradle') << """
- apply plugin: 'java-library'
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: locks platform"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation platform('org:platform:1.0')
- }
- """.stripIndent(true)
-
- when:
- runTasks('--write-locks')
-
- then:
- file('versions.lock').readLines() == [
- '# Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.',
- '',
- 'org:platform:1.0 (1 constraints: a5041a2c)',
- ]
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: verifyLocks is cacheable"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation "org.slf4j:slf4j-api:\$depVersion"
- }
- """
-
- file('gradle.properties') << 'depVersion = 1.7.20'
-
- when:
- runTasks('--write-locks')
-
- then: 'verifyLocks is up to date the second time'
- runTasks('verifyLocks').task(':verifyLocks').outcome == TaskOutcome.SUCCESS
- runTasks('verifyLocks').task(':verifyLocks').outcome == TaskOutcome.UP_TO_DATE
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
-
- def "#gradleVersionNumber: verifyLocks current lock state does not get poisoned by existing lock file"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation "org.slf4j:slf4j-api:\$depVersion"
- }
- """
-
- file('gradle.properties') << 'depVersion = 1.7.20'
-
- when:
- runTasks('--write-locks')
-
- then: 'verifyLocks fails if we lower the dep version'
- def fail = runTasksAndFail('verifyLocks', '-PdepVersion=1.7.11')
-
- and: 'it expects the correct version to be 1.7.11'
- fail.output.contains """\
- > Found dependencies whose dependents changed:
- -org.slf4j:slf4j-api:1.7.20 (1 constraints: 3c05433b)
- +org.slf4j:slf4j-api:1.7.11 (1 constraints: 3c05423b)
- """.stripIndent(true)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: excludes from compileOnly do not obscure real dependency"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3'
- }
- configurations.compileOnly {
- // convoluted, but the idea is to exclude a transitive
- exclude group: 'org.slf4j', module: 'slf4j-api'
- }
- """.stripIndent(true)
-
- when:
- runTasks('--write-locks')
-
- then: 'slf4j-api still appears in the lock file'
- file('versions.lock').readLines() == [
- '# Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.',
- '',
- 'ch.qos.logback:logback-classic:1.2.3 (1 constraints: 0805f935)',
- '',
- 'org.slf4j:slf4j-api:1.7.25 (1 constraints: 400d4d2a)',
- ]
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: can resolve configuration dependency"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- addSubproject("foo", """
- apply plugin: 'java'
- dependencies {
- implementation project(path: ":bar", configuration: "fun")
- }
- """.stripIndent(true))
-
- addSubproject("bar", """
- configurations {
- fun
- }
-
- dependencies {
- fun 'ch.qos.logback:logback-classic:1.2.3'
- }
- """.stripIndent(true))
-
- // Make sure that we can still add dependencies to the original 'fun' configuration after resolving lock state.
- //
- // Adding a constraint to 'fun' calls Configuration.preventIllegalMutation() which fails if observedState is
- // GRAPH_RESOLVED or ARTIFACTS_RESOLVED. That would happen if a configuration that extends from it has been
- // resolved.
- buildFile << """
- configurations.unifiedClasspath.incoming.afterResolve {
- project(':bar').dependencies.constraints {
- fun 'some:other-dep'
- }
- }
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks', 'classes')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: inter-project normal dependency works"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- addSubproject("foo", """
- apply plugin: 'java'
- dependencies {
- implementation project(":bar")
- }
- """.stripIndent(true))
-
- addSubproject("bar", """
- apply plugin: 'java'
- """.stripIndent(true))
-
- expect:
- runTasks('--write-locks', 'classes')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: test dependencies appear in a separate block"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3'
- testImplementation 'org:test-dep-that-logs:1.0'
- }
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
- def expected = """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- ch.qos.logback:logback-classic:1.2.3 (1 constraints: 0805f935)
-
- org.slf4j:slf4j-api:1.7.25 (2 constraints: 7917e690)
-
-
-
- [Test dependencies]
-
- org:test-dep-that-logs:1.0 (1 constraints: a5041a2c)
- """.stripIndent(true)
- file('versions.lock').text == expected
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: locks dependencies from extra source sets that end in test"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- sourceSets {
- eteTest
- }
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3'
- testImplementation 'junit:junit:4.10'
- eteTestImplementation 'org:test-dep-that-logs:1.0'
- }
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
- def expected = """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- ch.qos.logback:logback-classic:1.2.3 (1 constraints: 0805f935)
-
- org.slf4j:slf4j-api:1.7.25 (2 constraints: 7917e690)
-
-
-
- [Test dependencies]
-
- junit:junit:4.10 (1 constraints: d904fd30)
-
- org:test-dep-that-logs:1.0 (1 constraints: a5041a2c)
- """.stripIndent(true)
- file('versions.lock').text == expected
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: versionsLock.testProject() works"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation 'junit:junit:4.10'
- }
-
- versionsLock.testProject()
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
- def expected = """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
-
-
- [Test dependencies]
-
- junit:junit:4.10 (1 constraints: d904fd30)
- """.stripIndent(true)
- file('versions.lock').text == expected
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: constraints on production do not affect scope of test only dependencies"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- constraints {
- implementation 'ch.qos.logback:logback-classic:1.2.3'
- }
- dependencies {
- testImplementation 'ch.qos.logback:logback-classic'
- }
- }
- """.stripIndent(true)
-
- expect:
- runTasks('--write-locks')
- def expected = """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
-
-
- [Test dependencies]
-
- ch.qos.logback:logback-classic:1.2.3 (1 constraints: 0805f935)
-
- org.slf4j:slf4j-api:1.7.25 (1 constraints: 400d4d2a)
- """.stripIndent(true)
- file('versions.lock').text == expected
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: published constraints are derived from lock file (with local constraints)"() {
- setup:
- // Test with local constraints enabled
- file('gradle.properties') << 'com.palantir.gradle.versions.publishLocalConstraints = true'
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- allprojects {
- apply plugin: 'java'
- }
- """.stripIndent(true)
-
- String publish = """
- apply plugin: 'maven-publish'
- group = 'com.palantir.published-constraints'
- version = '1.2.3'
- publishing.publications {
- maven(MavenPublication) {
- from components.java
- }
- }
- """.stripIndent(true)
-
- addSubproject('foo', """
- $publish
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3'
- }
- """.stripIndent(true))
-
- addSubproject('bar', """
- $publish
- dependencies {
- implementation 'junit:junit:4.10'
- }
- """.stripIndent(true))
-
- if (GradleVersion.version(gradleVersionNumber) < GradleVersion.version("6.0")) {
- settingsFile << """
- enableFeaturePreview('GRADLE_METADATA')
- """.stripIndent(true)
- }
-
- runTasks('--write-locks')
-
- when:
- runTasks('generatePomFileForMavenPublication', 'generateMetadataFileForMavenPublication')
-
- def junitDep = new MetadataFile.Dependency(
- group: 'junit',
- module: 'junit',
- version: [requires: '4.10'])
- def logbackDep = new MetadataFile.Dependency(
- group: 'ch.qos.logback',
- module: 'logback-classic',
- version: [requires: '1.2.3'])
- def slf4jDep = new MetadataFile.Dependency(
- group: 'org.slf4j',
- module: 'slf4j-api',
- version: [requires: '1.7.25'])
- def fooDep = new MetadataFile.Dependency(
- group: 'com.palantir.published-constraints',
- module: 'foo',
- version: [requires: '1.2.3'])
- def barDep = new MetadataFile.Dependency(
- group: 'com.palantir.published-constraints',
- module: 'bar',
- version: [requires: '1.2.3'])
-
- then: "foo's metadata file has the right dependency constraints"
- def fooMetadataFilename = new File(projectDir, "foo/build/publications/maven/module.json")
- def fooMetadata = new ObjectMapper().readValue(fooMetadataFilename, MetadataFile)
-
- fooMetadata.variants == [
- new MetadataFile.Variant(
- name: 'runtimeElements',
- dependencies: [logbackDep],
- dependencyConstraints: [barDep, junitDep, logbackDep, slf4jDep]),
- new MetadataFile.Variant(
- name: 'apiElements',
- dependencies: null,
- dependencyConstraints: [barDep, junitDep, logbackDep, slf4jDep])
- ] as Set
-
- and: "bar's metadata file has the right dependency constraints"
- def barMetadataFilename = new File(projectDir, "bar/build/publications/maven/module.json")
- def barMetadata = new ObjectMapper().readValue(barMetadataFilename, MetadataFile)
-
- barMetadata.variants == [
- new MetadataFile.Variant(
- name: 'runtimeElements',
- dependencies: [junitDep],
- dependencyConstraints: [fooDep, junitDep, logbackDep, slf4jDep]),
- new MetadataFile.Variant(
- name: 'apiElements',
- dependencies: null,
- dependencyConstraints: [fooDep, junitDep, logbackDep, slf4jDep]),
- ] as Set
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: published constraints are derived from lock file (without local constraints)"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- allprojects {
- apply plugin: 'java'
- apply plugin: 'maven-publish'
- publishing.publications {
- maven(MavenPublication) {
- from components.java
- }
- }
- }
- """.stripIndent(true)
-
- addSubproject('foo', """
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3'
- }
- """.stripIndent(true))
-
- addSubproject('bar', """
- dependencies {
- implementation 'junit:junit:4.10'
- }
- """.stripIndent(true))
-
- if (GradleVersion.version(gradleVersionNumber) < GradleVersion.version("6.0")) {
- settingsFile << """
- enableFeaturePreview('GRADLE_METADATA')
- """.stripIndent(true)
- }
-
- runTasks('--write-locks')
-
- when:
- runTasks('generatePomFileForMavenPublication', 'generateMetadataFileForMavenPublication')
-
- def junitDep = new MetadataFile.Dependency(
- group: 'junit',
- module: 'junit',
- version: [requires: '4.10'])
- def logbackDep = new MetadataFile.Dependency(
- group: 'ch.qos.logback',
- module: 'logback-classic',
- version: [requires: '1.2.3'])
- def slf4jDep = new MetadataFile.Dependency(
- group: 'org.slf4j',
- module: 'slf4j-api',
- version: [requires: '1.7.25'])
-
- then: "foo's metadata file has the right dependency constraints"
- def fooMetadataFilename = new File(projectDir, "foo/build/publications/maven/module.json")
- def fooMetadata = new ObjectMapper().readValue(fooMetadataFilename, MetadataFile)
-
- fooMetadata.variants == [
- new MetadataFile.Variant(
- name: 'apiElements',
- dependencies: null,
- dependencyConstraints: [junitDep, logbackDep, slf4jDep]),
- new MetadataFile.Variant(
- name: 'runtimeElements',
- dependencies: [logbackDep],
- dependencyConstraints: [junitDep, logbackDep, slf4jDep]),
- ] as Set
-
- and: "bar's metadata file has the right dependency constraints"
- def barMetadataFilename = new File(projectDir, "bar/build/publications/maven/module.json")
- def barMetadata = new ObjectMapper().readValue(barMetadataFilename, MetadataFile)
-
- barMetadata.variants == [
- new MetadataFile.Variant(
- name: 'apiElements',
- dependencies: null,
- dependencyConstraints: [junitDep, logbackDep, slf4jDep]),
- new MetadataFile.Variant(
- name: 'runtimeElements',
- dependencies: [junitDep],
- dependencyConstraints: [junitDep, logbackDep, slf4jDep]),
- ] as Set
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: can depend on artifact"() {
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation "junit:junit:4.10@zip"
- }
- """.stripIndent(true)
-
- expect:
- runTasks("--write-locks")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: direct test dependency that is also a production transitive ends up in production"() {
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic:1.2.3'
- testImplementation 'org.slf4j:slf4j-api:1.7.25'
- }
- """.stripIndent(true)
-
- expect:
- runTasks("--write-locks")
- file('versions.lock').text == """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
-
- ch.qos.logback:logback-classic:1.2.3 (1 constraints: 0805f935)
-
- org.slf4j:slf4j-api:1.7.25 (2 constraints: 8012a437)
- """.stripIndent(true)
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: does not write lock file when property 'gcvSkipWriteLocks' is set"() {
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- apply plugin: 'java'
- dependencies {
- testImplementation 'org.slf4j:slf4j-api:1.7.25'
- }
- """.stripIndent(true)
-
- def lockFileContent = """\
- # Run ./gradlew writeVersionsLocks to regenerate this file. Blank lines are to minimize merge conflicts.
- """.stripIndent(true)
-
- file('versions.lock').text = lockFileContent
-
- expect:
- def result = runTasks("--write-locks", "-PgcvSkipWriteLocks")
-
- file('versions.lock').text == lockFileContent
- assert result.getOutput().contains("Skipped writing lock state")
- assert !result.getOutput().contains("Finished writing lock state")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- boolean verifyLockfile(File projectDir, String... lines) {
- // Gradle 7+ only uses a single lockfile per project:
- // https://docs.gradle.org/current/userguide/upgrading_version_6.html#locking_single
- if (GradleVersion.version(gradleVersion) >= GradleVersion.version("7.0.0")) {
- def lockfile = file("gradle.lockfile", projectDir).text
- lines.each{ assert lockfile.contains(it + "=runtimeClasspath") }
- } else {
- def lockfile = file("gradle/dependency-locks/runtimeClasspath.lockfile", projectDir).text
- lines.each{ assert lockfile.contains(it) }
- }
- }
-}
diff --git a/src/test/groovy/com/palantir/gradle/versions/VersionsPropsPluginIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/VersionsPropsPluginIntegrationSpec.groovy
deleted file mode 100644
index a0caafa1b..000000000
--- a/src/test/groovy/com/palantir/gradle/versions/VersionsPropsPluginIntegrationSpec.groovy
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.palantir.gradle.versions
-
-import groovy.util.slurpersupport.GPathResult
-import groovy.util.slurpersupport.NodeChildren
-import org.gradle.util.GradleVersion
-import spock.lang.Unroll
-
-import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS
-import static com.palantir.gradle.versions.PomUtils.makePlatformPom
-
-@Unroll
-class VersionsPropsPluginIntegrationSpec extends IntegrationSpec {
- static def PLUGIN_NAME = "com.palantir.versions-props"
-
- void setup() {
- File mavenRepo = generateMavenRepo(
- "ch.qos.logback:logback-classic:1.2.3 -> org.slf4j:slf4j-api:1.7.25",
- "ch.qos.logback:logback-classic:1.1.11 -> org.slf4j:slf4j-api:1.7.22",
- "org.slf4j:slf4j-api:1.7.21",
- "org.slf4j:slf4j-api:1.7.22",
- "org.slf4j:slf4j-api:1.7.24",
- "org.slf4j:slf4j-api:1.7.25",
- "com.fasterxml.jackson.core:jackson-databind:2.9.0 -> com.fasterxml.jackson.core:jackson-annotations:2.9.0",
- "com.fasterxml.jackson.core:jackson-annotations:2.9.0",
- "com.fasterxml.jackson.core:jackson-annotations:2.9.7",
- "com.fasterxml.jackson.core:jackson-databind:2.9.7"
- )
- makePlatformPom(mavenRepo, "org", "platform", "1.0")
-
- buildFile << """
- buildscript {
- repositories {
- mavenCentral()
- }
- }
- plugins {
- id '${PLUGIN_NAME}'
- }
- allprojects {
- repositories {
- maven { url "file:///${mavenRepo.getAbsolutePath()}" }
- }
- }
-
- // Make it easy to verify what versions of dependencies you got.
- allprojects {
- configurations.matching { it.name == 'runtimeClasspath' }.all {
- resolutionStrategy.activateDependencyLocking()
- }
- task resolveConfigurations {
- doLast {
- if (pluginManager.hasPlugin('java')) {
- configurations.compileClasspath.resolve()
- configurations.runtimeClasspath.resolve()
- }
- }
- }
- }
- """.stripIndent(true)
- }
-
- def '#gradleVersionNumber: star dependency constraint is injected for direct dependency'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- file("versions.props") << """
- org.slf4j:* = 1.7.24
- """.stripIndent(true)
-
- def fooProject = addSubproject('foo', """
- apply plugin: 'java'
- dependencies {
- implementation 'org.slf4j:slf4j-api'
- }
- """.stripIndent(true))
-
- expect:
- runTasks('resolveConfigurations', '--write-locks')
-
- verifyLockfile(fooProject, "org.slf4j:slf4j-api:1.7.24")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: star dependency constraint is not forcefully downgraded for transitive dependency'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- file("versions.props") << """
- org.slf4j:* = 1.7.21
- ch.qos.logback:logback-classic = 1.1.11 # brings in slf4j-api 1.7.22
- """.stripIndent(true)
-
- def fooProject = addSubproject('foo', """
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic'
- }
- """.stripIndent(true))
-
- expect:
- runTasks('resolveConfigurations', '--write-locks')
-
- verifyLockfile(fooProject, "org.slf4j:slf4j-api:1.7.22")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: star dependency constraint upgrades transitive dependency'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- file("versions.props") << """
- org.slf4j:* = 1.7.25
- ch.qos.logback:logback-classic = 1.1.11 # brings in slf4j-api 1.7.22
- """.stripIndent(true)
-
- def fooProject = addSubproject('foo', """
- apply plugin: 'java'
- dependencies {
- implementation 'ch.qos.logback:logback-classic'
- }
- """.stripIndent(true))
-
- expect:
- runTasks('resolveConfigurations', '--write-locks')
-
- verifyLockfile(fooProject, "org.slf4j:slf4j-api:1.7.25")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: imported platform generated correctly in pom'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- file("versions.props") << """
- org:platform = 1.0
- # This shouldn't end up in the POM
- other:constraint = 1.0.0
- """.stripIndent(true)
-
- addSubproject('foo', """
- apply plugin: 'java-library'
- apply plugin: 'maven-publish'
- dependencies {
- rootConfiguration platform('org:platform')
- }
- publishing {
- publications {
- main(MavenPublication) {
- from components.java
- }
- }
- }
- """.stripIndent(true))
-
- expect:
- runTasks('foo:generatePomFile')
-
- file('foo/build/publications/main/pom-default.xml').exists()
-
- def slurper = new XmlSlurper()
- def pom = slurper.parse(file('foo/build/publications/main/pom-default.xml'))
- NodeChildren dependencies = pom.dependencyManagement.dependencies.dependency
- dependencies.collect { convertToMap(it) } as Set == [
- [
- groupId: 'org',
- artifactId: 'platform',
- version: '1.0',
- scope: 'import',
- type: 'pom'
- ],
- ] as Set
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: non-glob module forces do not get added to a matching platform too'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- apply plugin: 'java'
- dependencies {
- implementation 'com.fasterxml.jackson.core:jackson-databind'
- }
- '''.stripIndent(true)
- file('versions.props') << '''
- com.fasterxml.jackson.core:jackson-databind = 2.9.0
- com.fasterxml.jackson.*:* = 2.9.7
- '''.stripIndent(true)
-
- when:
- runTasks('resolveConfigurations', '--write-locks')
-
- then:
- verifyLockfile(projectDir, "com.fasterxml.jackson.core:jackson-databind:2.9.0", "com.fasterxml.jackson.core:jackson-annotations:2.9.7")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: throws if resolving configuration in afterEvaluate"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- configurations { foo }
-
- afterEvaluate {
- configurations.foo.resolve()
- }
- '''
- file('versions.props') << ''
-
- expect:
- def e = runTasksAndFail()
- e.output.contains("Not allowed to resolve")
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: does not throw if excluded configuration is resolved early"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << '''
- configurations { foo }
-
- versionRecommendations {
- excludeConfigurations 'foo'
- }
-
- afterEvaluate {
- configurations.foo.resolve()
- }
- '''
- file('versions.props') << ''
-
- expect:
- runTasks()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def "#gradleVersionNumber: creates rootConfiguration even if versions props file missing"() {
- setup:
- gradleVersion = gradleVersionNumber
-
- buildFile << """
- dependencies {
- constraints {
- rootConfiguration 'org.slf4j:slf4j-api:1.7.25'
- }
- }
- """.stripIndent(true)
- file('versions.props').delete()
-
- expect:
- runTasks()
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- def '#gradleVersionNumber: build succeeds without versions.props or versions.lock'() {
- setup:
- gradleVersion = gradleVersionNumber
-
- addSubproject('foo', """
- apply plugin: 'java'
- """.stripIndent(true))
-
- expect:
- runTasks('build')
-
- where:
- gradleVersionNumber << GRADLE_VERSIONS
- }
-
- boolean verifyLockfile(File projectDir, String... lines) {
- // Gradle 7+ only uses a single lockfile per project:
- // https://docs.gradle.org/current/userguide/upgrading_version_6.html#locking_single
- if (GradleVersion.version(gradleVersion) >= GradleVersion.version("7.0.0")) {
- def lockfile = file("gradle.lockfile", projectDir).text
- lines.each{ assert lockfile.contains(it + "=runtimeClasspath") }
- } else {
- def lockfile = file("gradle/dependency-locks/runtimeClasspath.lockfile", projectDir).text
- lines.each{ assert lockfile.contains(it) }
- }
- }
-
- /**
- * Recursively converts a node's children to a map of (tag name): (value inside tag).
- *
- * See: https://stackoverflow.com/a/26889997/274699
- */
- def convertToMap(GPathResult node) {
- node.children().collectEntries {
- [ it.name(), it.childNodes() ? convertToMap(it) : it.text() ]
- }
- }
-}
diff --git a/src/test/java/com/palantir/gradle/versions/CheckOverbroadConstraintsIntegrationSpec.java b/src/test/java/com/palantir/gradle/versions/CheckOverbroadConstraintsIntegrationSpec.java
new file mode 100644
index 000000000..f0c6de603
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/CheckOverbroadConstraintsIntegrationSpec.java
@@ -0,0 +1,139 @@
+/*
+ * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.palantir.gradle.testing.execution.GradleInvoker;
+import com.palantir.gradle.testing.execution.InvocationResult;
+import com.palantir.gradle.testing.execution.TaskOutcome;
+import com.palantir.gradle.testing.junit.GradlePluginTests;
+import com.palantir.gradle.testing.project.RootProject;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@GradlePluginTests
+class CheckOverbroadConstraintsIntegrationSpec {
+
+ @BeforeEach
+ void setup(RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ plugins {
+ id 'com.palantir.versions-lock'
+ id 'com.palantir.versions-props'
+ }
+ """);
+ rootProject.file("versions.props").createEmpty();
+ rootProject.file("versions.lock").createEmpty();
+ }
+
+ @Test
+ void task_should_run_as_part_of_check(GradleInvoker gradle) {
+ InvocationResult result = gradle.withArgs("check", "-m").buildsSuccessfully();
+ assertThat(result.output()).contains(":checkOverbroadConstraints");
+ }
+
+ @Test
+ void all_versions_are_pinned(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("""
+ com.fasterxml.jackson.*:* = 2.9.3
+ com.fasterxml.jackson.core:jackson-annotations = 2.9.5
+ """);
+ rootProject.file("versions.lock").overwrite("""
+ com.fasterxml.jackson.core:jackson-annotations:2.9.5 (2 constraints: abcdef0)
+ com.fasterxml.jackson.core:jackson-core:2.9.3 (2 constraints: abcdef1)
+ """);
+
+ InvocationResult result = gradle.withArgs("checkOverbroadConstraints").buildsSuccessfully();
+ assertThat(result.task(":checkOverbroadConstraints"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ }
+
+ @Test
+ void not_all_versions_are_pinned_throws_error_and_fix_works_and_no_new_line_is_added(
+ GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("""
+ com.fasterxml.jackson.*:* = 2.9.3
+ """);
+ rootProject.file("versions.lock").overwrite("""
+ com.fasterxml.jackson.core:jackson-annotations:2.9.5 (2 constraints: abcdef0)
+ com.fasterxml.jackson.core:jackson-core:2.9.3 (2 constraints: abcdef1)
+ """);
+
+ InvocationResult failure = gradle.withArgs("checkOverbroadConstraints").buildsWithFailure();
+ assertThat(failure.output()).contains("""
+ Over-broad version constraints found in versions.props.
+ Over-broad constraints often arise due to wildcards in versions.props
+ which apply to more dependencies than they should, this can lead to slow builds.
+ The following additional pins are recommended:
+ com.fasterxml.jackson.core:jackson-annotations = 2.9.5
+ com.fasterxml.jackson.core:jackson-core = 2.9.3
+
+ Run ./gradlew checkOverbroadConstraints --fix to add them.
+ See https://github.com/palantir/gradle-consistent-versions?tab=readme-ov-file#gradlew-checkoverbroadconstraints for details
+ """);
+
+ List currentVersionsProps =
+ rootProject.file("versions.props").text().lines().toList();
+ gradle.withArgs("checkOverbroadConstraints", "--fix").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text().lines().toList()).isNotEqualTo(currentVersionsProps);
+
+ gradle.withArgs("checkOverbroadConstraints").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text()).isEqualTo("""
+ com.fasterxml.jackson.core:jackson-annotations = 2.9.5
+ com.fasterxml.jackson.core:jackson-core = 2.9.3
+ """);
+ }
+
+ @Test
+ void fixes_are_inserted_in_the_correct_locations_new_lines_are_maintained(
+ GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("""
+
+ com.random:* = 1.0.0
+ com.fasterxml.jackson.*:* = 2.9.3
+ \s\s\s\s
+ # A random comment
+ org.different:artifact = 2.0.0
+ \s\s\s\s
+ """);
+ rootProject.file("versions.lock").overwrite("""
+ com.random:random:1.0.0 (2 constraints: abcdef0)
+ org.different:artifact:2.0.0 (2 constraints: abcdef0)
+ com.fasterxml.jackson.core:jackson-annotations:2.9.5 (2 constraints: abcdef0)
+ com.fasterxml.jackson.core:jackson-core:2.9.3 (2 constraints: abcdef1)
+ """);
+
+ List currentVersionsProps =
+ rootProject.file("versions.props").text().lines().toList();
+ gradle.withArgs("checkOverbroadConstraints", "--fix").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text().lines().toList()).isNotEqualTo(currentVersionsProps);
+
+ gradle.withArgs("checkOverbroadConstraints").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text()).isEqualTo("""
+
+ com.random:* = 1.0.0
+ com.fasterxml.jackson.core:jackson-annotations = 2.9.5
+ com.fasterxml.jackson.core:jackson-core = 2.9.3
+ \s\s\s\s
+ # A random comment
+ org.different:artifact = 2.0.0
+ \s\s\s\s
+ """);
+ }
+}
diff --git a/src/test/java/com/palantir/gradle/versions/CheckUnusedConstraintIntegrationSpec.java b/src/test/java/com/palantir/gradle/versions/CheckUnusedConstraintIntegrationSpec.java
new file mode 100644
index 000000000..4203fa2ff
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/CheckUnusedConstraintIntegrationSpec.java
@@ -0,0 +1,192 @@
+/*
+ * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.palantir.gradle.testing.execution.GradleInvoker;
+import com.palantir.gradle.testing.execution.InvocationResult;
+import com.palantir.gradle.testing.execution.TaskOutcome;
+import com.palantir.gradle.testing.junit.GradlePluginTests;
+import com.palantir.gradle.testing.project.RootProject;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@GradlePluginTests
+class CheckUnusedConstraintIntegrationSpec {
+
+ @BeforeEach
+ void setup(RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ plugins {
+ id 'java'
+ id 'com.palantir.versions-lock'
+ id 'com.palantir.versions-props'
+ }
+
+ repositories {
+ mavenCentral()
+ maven { url "%s/maven" }
+ }
+ \s\s\s\s
+ // Get rid of deprecation warnings for Gradle 7+
+ versionRecommendations {
+ excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime'
+ }
+ """, rootProject.path().toUri());
+ rootProject.gradlePropertiesFile().append("""
+ ignoreLockFile=true
+ """);
+ rootProject.file("versions.props").createEmpty();
+ }
+
+ @Test
+ void check_versions_props_does_not_resolve_artifacts(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ dependencies {
+ implementation 'com.palantir.product:foo:1.0.0'
+ }
+ """);
+ rootProject.file("versions.props").overwrite("");
+
+ // We're not producing a jar for this dependency, so artifact resolution would fail
+ rootProject.file("maven/com/palantir/product/foo/1.0.0/foo-1.0.0.pom").overwrite("""
+
+
+ 4.0.0
+ com.palantir.product
+ foo
+ jar
+ 1.0.0
+
+
+
+ """);
+
+ InvocationResult result = gradle.withArgs("checkUnusedConstraints").buildsSuccessfully();
+ assertThat(result.task(":checkUnusedConstraints"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ }
+
+ @Test
+ void task_should_run_as_part_of_check(GradleInvoker gradle) {
+ InvocationResult result = gradle.withArgs("check", "-m").buildsSuccessfully();
+ assertThat(result.output()).contains(":checkUnusedConstraints");
+ }
+
+ @Test
+ void version_props_conflict_should_succeed(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("""
+ com.fasterxml.jackson.*:* = 2.9.3
+ com.fasterxml.jackson.core:jackson-annotations = 2.9.5
+ """);
+ rootProject.buildGradle().append("""
+ dependencies {
+ implementation 'com.fasterxml.jackson.core:jackson-databind'
+ }\
+ """);
+
+ InvocationResult result = gradle.withArgs("checkUnusedConstraints").buildsSuccessfully();
+ assertThat(result.task(":checkUnusedConstraints"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ }
+
+ @Test
+ void most_specific_matching_version_should_win(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("""
+ org.slf4j:slf4j-api = 1.7.25
+ org.slf4j:* = 1.7.20
+ """);
+
+ rootProject.buildGradle().append("""
+ dependencies {
+ implementation 'org.slf4j:slf4j-api'
+ }\
+ """);
+
+ InvocationResult failure = gradle.withArgs("checkUnusedConstraints").buildsWithFailure();
+ assertThat(failure.output()).contains("There are unused pins in your versions.props: \n[org.slf4j:*]");
+
+ List currentVersionsProps =
+ rootProject.file("versions.props").text().lines().toList();
+ gradle.withArgs("checkUnusedConstraints", "--fix").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text().lines().toList()).isNotEqualTo(currentVersionsProps);
+
+ gradle.withArgs("checkUnusedConstraints").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text().trim()).isEqualTo("org.slf4j:slf4j-api = 1.7.25");
+ }
+
+ @Test
+ void most_specific_glob_should_win(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("""
+ org.slf4j:slf4j-* = 1.7.25
+ org.slf4j:* = 1.7.20
+ """);
+
+ rootProject.buildGradle().append("""
+ dependencies {
+ implementation 'org.slf4j:slf4j-api'
+ implementation 'org.slf4j:slf4j-jdk14'
+ }\
+ """);
+
+ InvocationResult failure = gradle.withArgs("checkUnusedConstraints").buildsWithFailure();
+ assertThat(failure.output()).contains("There are unused pins in your versions.props: \n[org.slf4j:*]");
+
+ List currentVersionsProps =
+ rootProject.file("versions.props").text().lines().toList();
+ gradle.withArgs("checkUnusedConstraints", "--fix").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text().lines().toList()).isNotEqualTo(currentVersionsProps);
+
+ gradle.withArgs("checkUnusedConstraints").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text().trim()).isEqualTo("org.slf4j:slf4j-* = 1.7.25");
+ }
+
+ @Test
+ void unused_version_should_fail(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("notused:atall = 42.42");
+
+ InvocationResult failure = gradle.withArgs("checkUnusedConstraints").buildsWithFailure();
+ assertThat(failure.output()).contains("There are unused pins in your versions.props");
+
+ List currentVersionsProps =
+ rootProject.file("versions.props").text().lines().toList();
+ gradle.withArgs("checkUnusedConstraints", "--fix").buildsSuccessfully();
+ assertThat(rootProject.file("versions.props").text().lines().toList()).isNotEqualTo(currentVersionsProps);
+
+ gradle.withArgs("checkUnusedConstraints").buildsSuccessfully();
+ }
+
+ @Test
+ void unused_check_should_use_exact_matching(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.file("versions.props").overwrite("""
+ com.google.guava:guava-testlib = 23.0
+ com.google.guava:guava = 22.0
+ """);
+ rootProject.buildGradle().append("""
+ dependencies {
+ implementation 'com.google.guava:guava'
+ implementation 'com.google.guava:guava-testlib'
+ }\
+ """);
+
+ InvocationResult result = gradle.withArgs("checkUnusedConstraints").buildsSuccessfully();
+ assertThat(result.task(":checkUnusedConstraints"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ }
+}
diff --git a/src/test/java/com/palantir/gradle/versions/ConfigurationOnDemandSpec.java b/src/test/java/com/palantir/gradle/versions/ConfigurationOnDemandSpec.java
new file mode 100644
index 000000000..9c5eee062
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/ConfigurationOnDemandSpec.java
@@ -0,0 +1,401 @@
+/*
+ * (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.palantir.gradle.testing.execution.GradleInvoker;
+import com.palantir.gradle.testing.execution.InvocationResult;
+import com.palantir.gradle.testing.execution.TaskOutcome;
+import com.palantir.gradle.testing.junit.GradlePluginTests;
+import com.palantir.gradle.testing.project.RootProject;
+import com.palantir.gradle.testing.project.SubProject;
+import java.io.File;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * This tests the interaction of this plugin with Gradle's configuration-on-demand feature:
+ * https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand
+ */
+@GradlePluginTests
+class ConfigurationOnDemandSpec {
+
+ private static final String PLUGIN_NAME = "com.palantir.consistent-versions";
+ private File mavenRepo;
+
+ /*
+ * Project structure (arrows indicate dependencies):
+ *
+ * upstream unrelated
+ * ^ ^
+ * | |
+ * downstream1 downstream2
+ */
+ @BeforeEach
+ void setup(
+ RootProject rootProject,
+ SubProject upstreamProject,
+ SubProject downstream1Project,
+ SubProject downstream2Project,
+ SubProject unrelatedProject) {
+ mavenRepo = MavenRepoUtils.generateMavenRepo(
+ rootProject.path().resolve("build"),
+ "com.example:dependency-of-upstream:1.2.3",
+ "com.example:dependency-of-upstream:100.1.1",
+ "com.example:dependency-of-downstream1:1.2.3",
+ "com.example:dependency-of-downstream2:1.2.3",
+ "com.example:dependency-of-unrelated:1.2.3",
+ "com.example:dep-with-version-bumped-by-unrelated:1.0.0",
+ "com.example:dep-with-version-bumped-by-unrelated:1.1.0",
+ "com.example:transitive-test-dep:1.0.0",
+ "com.example:transitive-test-dep:1.1.0",
+ "com.example:transitive-test-dep:1.2.0");
+
+ PomUtils.makePlatformPom(mavenRepo, "org", "platform", "1.0");
+
+ rootProject.buildGradle().overwrite("""
+ plugins {
+ id '%s'
+ }
+ allprojects {
+ repositories {
+ maven { url "file:///%s" }
+ }
+ }
+ subprojects {
+ tasks.register('writeClasspath') {
+ doLast {
+ println(configurations.runtimeClasspath.getFiles())
+ }
+ }
+ }
+ \s\s\s\s
+ // Get rid of deprecation warnings for Gradle 7+
+ versionRecommendations {
+ excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime'
+ }
+ """, PLUGIN_NAME, mavenRepo.getAbsolutePath());
+
+ rootProject.file("versions.props").overwrite("""
+ com.example:dependency-of-upstream = 1.2.3
+ com.example:dependency-of-downstream1 = 1.2.3
+ com.example:dependency-of-downstream2 = 1.2.3
+ com.example:dependency-of-unrelated = 1.2.3
+ # 1.0.0 is a minimum, we expect this to be locked to 1.1.0
+ com.example:dep-with-version-bumped-by-unrelated = 1.0.0
+ """);
+
+ rootProject.settingsGradle().rootProjectName("test");
+ rootProject.settingsGradle().include("upstream");
+ rootProject.settingsGradle().include("downstream1");
+ rootProject.settingsGradle().include("downstream2");
+ rootProject.settingsGradle().include("unrelated");
+
+ upstreamProject.buildGradle().append("""
+ plugins {
+ id 'java'
+ }
+ println 'configuring upstream'
+ dependencies {
+ implementation 'com.example:dependency-of-upstream'
+ }
+ """);
+
+ downstream1Project.buildGradle().append("""
+ plugins {
+ id 'java'
+ }
+ println 'configuring downstream1'
+ dependencies {
+ implementation project(':upstream')
+ implementation 'com.example:dependency-of-downstream1'
+ }
+ """);
+
+ downstream2Project.buildGradle().append("""
+ plugins {
+ id 'java'
+ }
+ println 'configuring downstream2'
+ dependencies {
+ implementation project(':upstream')
+ implementation 'com.example:dependency-of-downstream2'
+ implementation 'com.example:dep-with-version-bumped-by-unrelated'
+ }
+ """);
+
+ unrelatedProject.buildGradle().append("""
+ plugins {
+ id 'java'
+ }
+ println 'configuring unrelated'
+ dependencies {
+ implementation 'com.example:dependency-of-unrelated'
+ implementation 'com.example:dep-with-version-bumped-by-unrelated:1.1.0'
+ }
+ """);
+
+ rootProject.gradlePropertiesFile().append("""
+ org.gradle.configureondemand=true
+ """);
+ }
+
+ @Test
+ void can_write_locks(GradleInvoker gradle, RootProject rootProject) {
+ InvocationResult result = gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ assertThat(result.output()).contains("configuring upstream");
+ assertThat(result.output()).contains("configuring downstream1");
+ assertThat(result.output()).contains("configuring downstream2");
+ assertThat(result.output()).contains("configuring unrelated");
+
+ assertThat(rootProject.path().resolve("versions.lock")).exists();
+ assertThat(rootProject.file("versions.lock").text()).contains("com.example:dependency-of-upstream:1.2.3");
+ assertThat(rootProject.file("versions.lock").text())
+ .contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0");
+ }
+
+ @Test
+ void can_write_locks_when_a_task_in_one_project_is_specified(GradleInvoker gradle, RootProject rootProject) {
+ gradle.withArgs(":downstream1:build", "--write-locks").buildsSuccessfully();
+
+ assertThat(rootProject.path().resolve("versions.lock")).exists();
+ assertThat(rootProject.file("versions.lock").text()).contains("com.example:dependency-of-unrelated:1.2.3");
+ assertThat(rootProject.file("versions.lock").text())
+ .contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0");
+ }
+
+ @Test
+ void applying_the_plugin_does_not_force_all_projects_to_be_configured(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ // Both absolute and relative formats work, as long as Gradle is run from the root project directory
+ InvocationResult result1 = gradle.withArgs(":downstream1:build").buildsSuccessfully();
+ InvocationResult result2 = gradle.withArgs("downstream1:build").buildsSuccessfully();
+
+ assertThat(result1.output()).contains("configuring upstream");
+ assertThat(result1.output()).contains("configuring downstream1");
+ assertThat(result1.output()).doesNotContain("configuring downstream2");
+ assertThat(result1.output()).doesNotContain("configuring unrelated");
+
+ assertThat(result2.output()).contains("configuring upstream");
+ assertThat(result2.output()).contains("configuring downstream1");
+ assertThat(result2.output()).doesNotContain("configuring downstream2");
+ assertThat(result2.output()).doesNotContain("configuring unrelated");
+ }
+
+ @Test
+ void resolving_a_classpath_does_not_force_all_projects_to_be_configured(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ InvocationResult result = gradle.withArgs(":downstream1:writeClasspath").buildsSuccessfully();
+
+ assertThat(result.output()).contains("configuring upstream");
+ assertThat(result.output()).contains("configuring downstream1");
+ assertThat(result.output()).doesNotContain("configuring downstream2");
+ assertThat(result.output()).doesNotContain("configuring unrelated");
+ }
+
+ @Test
+ void after_lockfile_is_written_versions_constraints_due_to_non_configured_projects_are_still_respected(
+ GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ InvocationResult result = gradle.withArgs(":downstream2:writeClasspath").buildsSuccessfully();
+
+ // Version used is 1.1.0 due to the unrelated project
+ assertThat(result.output()).contains("dep-with-version-bumped-by-unrelated-1.1.0.jar");
+ assertThat(result.output()).doesNotContain("configured unrelated");
+ }
+
+ @Test
+ void transitive_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early(
+ GradleInvoker gradle,
+ RootProject rootProject,
+ SubProject aProject,
+ SubProject bProject,
+ SubProject cProject,
+ SubProject uProject) {
+ rootProject.settingsGradle().include("a");
+ rootProject.settingsGradle().include("b");
+ rootProject.settingsGradle().include("c");
+ rootProject.settingsGradle().include("u");
+
+ aProject.buildGradle().append("""
+ plugins { id 'java' }
+ dependencies {
+ implementation 'com.example:transitive-test-dep:1.0.0'
+ }
+ """);
+
+ bProject.buildGradle().append("""
+ plugins { id 'java' }
+ dependencies {
+ implementation project(':a')
+ }
+ """);
+
+ cProject.buildGradle().append("""
+ plugins { id 'java' }
+ dependencies {
+ implementation project(':b')
+ }
+ tasks.register('writeClasspathOfA') {
+ doLast {
+ println project(':a').configurations.runtimeClasspath.files
+ }
+ }
+ """);
+
+ uProject.buildGradle().append("""
+ plugins { id 'java' }
+ dependencies {
+ implementation 'com.example:transitive-test-dep:1.1.0'
+ }
+ """);
+
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+ InvocationResult result = gradle.withArgs(":c:writeClasspathOfA").buildsSuccessfully();
+
+ // Version used should be 1.1.0, indicating that the version.lock constraint was applied
+ assertThat(result.output()).contains("transitive-test-dep-1.1.0.jar");
+ }
+
+ @Test
+ void task_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early(
+ GradleInvoker gradle,
+ RootProject rootProject,
+ SubProject aProject,
+ SubProject bProject,
+ SubProject cProject,
+ SubProject uProject) {
+ rootProject.settingsGradle().include("a");
+ rootProject.settingsGradle().include("b");
+ rootProject.settingsGradle().include("c");
+ rootProject.settingsGradle().include("u");
+
+ aProject.buildGradle().append("""
+ plugins { id 'java' }
+ dependencies {
+ implementation 'com.example:transitive-test-dep:1.0.0'
+ }
+ """);
+
+ bProject.buildGradle().append("""
+ tasks.register('foo') {
+ dependsOn ':a:writeClasspath'
+ }
+ """);
+
+ cProject.buildGradle().append("""
+ tasks.register('bar') {
+ dependsOn ':b:foo'
+ }
+ """);
+
+ uProject.buildGradle().append("""
+ plugins { id 'java' }
+ dependencies {
+ implementation 'com.example:transitive-test-dep:1.1.0'
+ }
+ """);
+
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+ InvocationResult result = gradle.withArgs(":c:bar").buildsSuccessfully();
+
+ // Version used should be 1.1.0, indicating that the version.lock constraint was applied
+ assertThat(result.output()).contains("transitive-test-dep-1.1.0.jar");
+ }
+
+ @Test
+ void verification_tasks_pass_when_all_projects_are_configured(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ // Note: Not specifying the project causes all projects to be configured regardless of CoD
+ InvocationResult result =
+ gradle.withArgs("checkUnusedConstraints", "verifyLocks").buildsSuccessfully();
+
+ assertThat(result.output()).contains("configuring upstream");
+ assertThat(result.task(":checkUnusedConstraints"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ assertThat(result.task(":verifyLocks"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ }
+
+ @Test
+ void check_unused_constraints_fails_and_warns_when_not_all_projects_are_configured(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ InvocationResult result = gradle.withArgs(":checkUnusedConstraints").buildsWithFailure();
+
+ assertThat(result.output()).doesNotContain("configuring upstream");
+ assertThat(result.task(":checkUnusedConstraints"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.FAILED));
+ assertThat(result.output())
+ .contains("The gradle-consistent-versions checkUnusedConstraints task "
+ + "must have all projects configured to work accurately, but due to Gradle "
+ + "configuration-on-demand, not all projects were configured.");
+ }
+
+ @Test
+ void verify_locks_fails_and_warns_when_not_all_projects_are_configured(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ InvocationResult result = gradle.withArgs(":verifyLocks").buildsWithFailure();
+
+ assertThat(result.output()).doesNotContain("configuring upstream");
+ assertThat(result.task(":verifyLocks"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.FAILED));
+ assertThat(result.output())
+ .contains("All projects must have been configured for this task to work correctly, but due to "
+ + "Gradle configuration-on-demand, not all projects were configured.");
+ }
+
+ @Test
+ void verification_tasks_are_not_up_to_date_when_the_set_of_configured_projects_differs(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+ gradle.withArgs("build").buildsSuccessfully();
+
+ gradle.withArgs(":checkUnusedConstraints").buildsWithFailure();
+ gradle.withArgs(":verifyLocks").buildsWithFailure();
+ }
+
+ @Test
+ void the_why_task_works_when_all_projects_are_configured(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ InvocationResult result = gradle.withArgs("why", "--hash=0805f935").buildsSuccessfully();
+ assertThat(result.task(":why"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ }
+
+ @Test
+ void the_why_task_somehow_forces_all_projects_to_be_configured(GradleInvoker gradle) {
+ gradle.withArgs("--write-locks").buildsSuccessfully();
+
+ InvocationResult result = gradle.withArgs(":why", "--hash=0805f935").buildsSuccessfully();
+
+ assertThat(result.output()).contains("configuring upstream");
+ assertThat(result.output()).contains("configuring downstream1");
+ assertThat(result.output()).contains("configuring downstream2");
+ assertThat(result.output()).contains("configuring unrelated");
+ assertThat(result.task(":why"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.SUCCESS));
+ assertThat(result.output()).contains("com.example:dependency-of-unrelated:1.2.3\n\tprojects -> 1.2.3");
+ }
+}
diff --git a/src/test/java/com/palantir/gradle/versions/MavenRepoUtils.java b/src/test/java/com/palantir/gradle/versions/MavenRepoUtils.java
new file mode 100644
index 000000000..2ea1a40ef
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/MavenRepoUtils.java
@@ -0,0 +1,40 @@
+/*
+ * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import nebula.test.dependencies.DependencyGraph;
+import nebula.test.dependencies.GradleDependencyGenerator;
+
+public final class MavenRepoUtils {
+ private MavenRepoUtils() {}
+
+ public static File generateMavenRepo(Path tempDir, String... graph) {
+ DependencyGraph dependencyGraph = new DependencyGraph(graph);
+ try {
+ Path repoPath = Files.createDirectories(tempDir.resolve("testrepogen"));
+ GradleDependencyGenerator generator = new GradleDependencyGenerator(dependencyGraph, repoPath.toString());
+ return generator.generateTestMavenRepo();
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to create Maven repo", e);
+ }
+ }
+}
diff --git a/src/test/java/com/palantir/gradle/versions/PomUtils.java b/src/test/java/com/palantir/gradle/versions/PomUtils.java
new file mode 100644
index 000000000..9a50eb9d8
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/PomUtils.java
@@ -0,0 +1,52 @@
+/*
+ * (c) Copyright 2020 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public final class PomUtils {
+ private PomUtils() {}
+
+ public static void makePlatformPom(File repo, String group, String name, String version) {
+ Path dir = repo.toPath().resolve(group).resolve(name).resolve(version);
+ try {
+ Files.createDirectories(dir);
+ String pomContent = """
+
+
+ 4.0.0
+ pom
+ %s
+ %s
+ %s
+
+
+
+
+
+ """.formatted(group, name, version);
+ Files.writeString(dir.resolve("platform-1.0.pom"), pomContent);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to create platform POM", e);
+ }
+ }
+}
diff --git a/src/test/java/com/palantir/gradle/versions/SlsPackagingCompatibilityIntegrationSpec.java b/src/test/java/com/palantir/gradle/versions/SlsPackagingCompatibilityIntegrationSpec.java
new file mode 100644
index 000000000..9e8e89497
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/SlsPackagingCompatibilityIntegrationSpec.java
@@ -0,0 +1,138 @@
+/*
+ * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.palantir.gradle.testing.execution.GradleInvoker;
+import com.palantir.gradle.testing.execution.InvocationResult;
+import com.palantir.gradle.testing.junit.GradlePluginTests;
+import com.palantir.gradle.testing.project.RootProject;
+import com.palantir.gradle.testing.project.SubProject;
+import java.io.File;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIf;
+
+/**
+ * https://github.com/palantir/sls-packaging does some funky stuff when resolving inter-project dependencies for the
+ * purposes of detecting published recommended product dependencies, so we want to make double sure that GCV doesn't
+ * accidentally break it.
+ */
+@GradlePluginTests
+class SlsPackagingCompatibilityIntegrationSpec {
+
+ private static final String PLUGIN_NAME = "com.palantir.consistent-versions";
+ private File mavenRepo;
+
+ @BeforeEach
+ void setup(RootProject rootProject) {
+ mavenRepo = MavenRepoUtils.generateMavenRepo(rootProject.path().resolve("build"), "org.slf4j:slf4j-api:1.7.24");
+ rootProject.buildGradle().append("""
+ buildscript {
+ repositories {
+ mavenCentral()
+ }
+ }
+ plugins {
+ id '%s'
+ id 'com.palantir.sls-java-service-distribution' version '7.31.0' apply false
+ }
+ allprojects {
+ repositories {
+ maven { url "file:///%s" }
+ }
+ }
+ """, PLUGIN_NAME, mavenRepo.getAbsolutePath());
+ }
+
+ @DisabledIf("isGradle9OrLater")
+ @Test
+ void can_consume_recommended_product_dependencies_project(
+ GradleInvoker gradle, RootProject rootProject, SubProject apiProject, SubProject serviceProject) {
+ rootProject.file("versions.props").overwrite("""
+ org.slf4j:* = 1.7.24
+ """);
+
+ rootProject.buildGradle().append("""
+ allprojects {
+ version = '1.0.0'
+ }
+ """);
+
+ rootProject.settingsGradle().include("api");
+ rootProject.settingsGradle().include("service");
+ apiProject.buildGradle().append("""
+ apply plugin: 'java'
+ apply plugin: 'com.palantir.sls-recommended-dependencies'
+ \s\s\s\s
+ dependencies {
+ implementation 'org.slf4j:slf4j-api'
+ }
+ \s\s\s\s
+ recommendedProductDependencies {
+ productDependency {
+ productGroup = 'org'
+ productName = 'product'
+ minimumVersion = '1.1.0'
+ maximumVersion = '1.x.x'
+ }
+ }
+ """);
+
+ serviceProject.buildGradle().append("""
+ apply plugin: 'java'
+ apply plugin: 'com.palantir.sls-java-service-distribution'
+ \s\s\s\s
+ dependencies {
+ // Gets picked up by the productDependenciesConfig which is runtimeClasspath
+ implementation project(':api')
+ }
+ """);
+
+ InvocationResult wroteLocks = gradle.withArgs("--write-locks").buildsSuccessfully();
+ // Maybe this is a bit too much but for a fixed version of sls-packaging, we expect this to not change
+ Set successfulTasks = wroteLocks
+ .output()
+ .lines()
+ .filter(line -> line.contains("> Task :"))
+ .map(line -> line.substring(line.indexOf(":"), line.length()).trim())
+ .collect(Collectors.toSet());
+
+ assertThat(successfulTasks)
+ .containsExactlyInAnyOrder(
+ ":api:compileRecommendedProductDependencies",
+ ":api:processResources",
+ ":service:mergeDiagnosticsJson",
+ ":service:resolveProductDependencies",
+ ":service:createManifest",
+ ":api:classes",
+ ":api:configureProductDependencies",
+ ":api:jar",
+ ":service:jar");
+
+ gradle.withArgs("createManifest", "verifyLocks").buildsSuccessfully();
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean isGradle9OrLater() {
+ String gradleVersion = org.gradle.util.GradleVersion.current().getVersion();
+ return gradleVersion.startsWith("9");
+ }
+}
diff --git a/src/test/java/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.java b/src/test/java/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.java
new file mode 100644
index 000000000..4077ce7b5
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/VersionPropsIdeaPluginIntegrationSpec.java
@@ -0,0 +1,73 @@
+/*
+ * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.palantir.gradle.testing.execution.GradleInvoker;
+import com.palantir.gradle.testing.execution.InvocationResult;
+import com.palantir.gradle.testing.execution.TaskOutcome;
+import com.palantir.gradle.testing.junit.GradlePluginTests;
+import com.palantir.gradle.testing.project.RootProject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@GradlePluginTests
+class VersionPropsIdeaPluginIntegrationSpec {
+
+ @BeforeEach
+ void setup(RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ repositories {
+ maven {
+ url 'https://test'
+ }
+ maven {
+ url 'https://demo/'
+ }
+ mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } }
+ }
+
+ apply plugin: 'com.palantir.version-props-idea'
+ apply plugin: 'idea'
+ """);
+
+ Path ideaDir = rootProject.path().resolve(".idea");
+ ideaDir.toFile().mkdirs();
+ }
+
+ @Test
+ void plugin_creates_gcv_maven_repositories_xml_file_in_idea_folder(GradleInvoker gradle, RootProject rootProject)
+ throws IOException {
+ gradle.withArgs("-Didea.active=true").buildsSuccessfully();
+
+ Path repoFile = rootProject.path().resolve(".idea/gcv-maven-repositories.xml");
+ assertThat(repoFile).exists();
+
+ String content = Files.readString(repoFile);
+ assertThat(content).contains("");
+ assertThat(content).contains("");
+ assertThat(content).contains("");
+
+ InvocationResult secondRun = gradle.withArgs("-Didea.active=true").buildsSuccessfully();
+ assertThat(secondRun.task(":writeMavenRepositories"))
+ .hasValueSatisfying(task -> assertThat(task.outcome()).isEqualTo(TaskOutcome.UP_TO_DATE));
+ }
+}
diff --git a/src/test/java/com/palantir/gradle/versions/VersionsPropsPluginIntegrationSpec.java b/src/test/java/com/palantir/gradle/versions/VersionsPropsPluginIntegrationSpec.java
new file mode 100644
index 000000000..ae79fbe6a
--- /dev/null
+++ b/src/test/java/com/palantir/gradle/versions/VersionsPropsPluginIntegrationSpec.java
@@ -0,0 +1,321 @@
+/*
+ * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.palantir.gradle.versions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.palantir.gradle.testing.execution.GradleInvoker;
+import com.palantir.gradle.testing.execution.InvocationResult;
+import com.palantir.gradle.testing.junit.GradlePluginTests;
+import com.palantir.gradle.testing.project.RootProject;
+import com.palantir.gradle.testing.project.SubProject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+@GradlePluginTests
+class VersionsPropsPluginIntegrationSpec {
+ private static final String PLUGIN_NAME = "com.palantir.versions-props";
+ private File mavenRepo;
+
+ @BeforeEach
+ void setup(RootProject rootProject) {
+ mavenRepo = MavenRepoUtils.generateMavenRepo(
+ rootProject.path().resolve("build"),
+ "ch.qos.logback:logback-classic:1.2.3 -> org.slf4j:slf4j-api:1.7.25",
+ "ch.qos.logback:logback-classic:1.1.11 -> org.slf4j:slf4j-api:1.7.22",
+ "org.slf4j:slf4j-api:1.7.21",
+ "org.slf4j:slf4j-api:1.7.22",
+ "org.slf4j:slf4j-api:1.7.24",
+ "org.slf4j:slf4j-api:1.7.25",
+ "com.fasterxml.jackson.core:jackson-databind:2.9.0 ->"
+ + " com.fasterxml.jackson.core:jackson-annotations:2.9.0",
+ "com.fasterxml.jackson.core:jackson-annotations:2.9.0",
+ "com.fasterxml.jackson.core:jackson-annotations:2.9.7",
+ "com.fasterxml.jackson.core:jackson-databind:2.9.7");
+ PomUtils.makePlatformPom(mavenRepo, "org", "platform", "1.0");
+
+ rootProject.buildGradle().append("""
+ buildscript {
+ repositories {
+ mavenCentral()
+ }
+ }
+ plugins {
+ id '%s'
+ }
+ allprojects {
+ repositories {
+ maven { url "file:///%s" }
+ }
+ }
+
+ // Make it easy to verify what versions of dependencies you got.
+ allprojects {
+ configurations.matching { it.name == 'runtimeClasspath' }.all {
+ resolutionStrategy.activateDependencyLocking()
+ }
+ task resolveConfigurations {
+ doLast {
+ if (pluginManager.hasPlugin('java')) {
+ configurations.compileClasspath.resolve()
+ configurations.runtimeClasspath.resolve()
+ }
+ }
+ }
+ }
+ """, PLUGIN_NAME, mavenRepo.getAbsolutePath());
+ }
+
+ @Test
+ void star_dependency_constraint_is_injected_for_direct_dependency(
+ GradleInvoker gradle, RootProject rootProject, SubProject fooProject) {
+ rootProject.file("versions.props").overwrite("""
+ org.slf4j:* = 1.7.24
+ """);
+
+ rootProject.settingsGradle().include("foo");
+ fooProject.buildGradle().append("""
+ apply plugin: 'java'
+ dependencies {
+ implementation 'org.slf4j:slf4j-api'
+ }
+ """);
+
+ gradle.withArgs("resolveConfigurations", "--write-locks").buildsSuccessfully();
+
+ verifyLockfile(fooProject.path(), "org.slf4j:slf4j-api:1.7.24");
+ }
+
+ @Test
+ void star_dependency_constraint_is_not_forcefully_downgraded_for_transitive_dependency(
+ GradleInvoker gradle, RootProject rootProject, SubProject fooProject) {
+ rootProject.file("versions.props").overwrite("""
+ org.slf4j:* = 1.7.21
+ ch.qos.logback:logback-classic = 1.1.11 # brings in slf4j-api 1.7.22
+ """);
+
+ rootProject.settingsGradle().include("foo");
+ fooProject.buildGradle().append("""
+ apply plugin: 'java'
+ dependencies {
+ implementation 'ch.qos.logback:logback-classic'
+ }
+ """);
+
+ gradle.withArgs("resolveConfigurations", "--write-locks").buildsSuccessfully();
+
+ verifyLockfile(fooProject.path(), "org.slf4j:slf4j-api:1.7.22");
+ }
+
+ @Test
+ void star_dependency_constraint_upgrades_transitive_dependency(
+ GradleInvoker gradle, RootProject rootProject, SubProject fooProject) {
+ rootProject.file("versions.props").overwrite("""
+ org.slf4j:* = 1.7.25
+ ch.qos.logback:logback-classic = 1.1.11 # brings in slf4j-api 1.7.22
+ """);
+
+ rootProject.settingsGradle().include("foo");
+ fooProject.buildGradle().append("""
+ apply plugin: 'java'
+ dependencies {
+ implementation 'ch.qos.logback:logback-classic'
+ }
+ """);
+
+ gradle.withArgs("resolveConfigurations", "--write-locks").buildsSuccessfully();
+
+ verifyLockfile(fooProject.path(), "org.slf4j:slf4j-api:1.7.25");
+ }
+
+ @Test
+ void imported_platform_generated_correctly_in_pom(
+ GradleInvoker gradle, RootProject rootProject, SubProject fooProject) throws Exception {
+ rootProject.file("versions.props").overwrite("""
+ org:platform = 1.0
+ # This shouldn't end up in the POM
+ other:constraint = 1.0.0
+ """);
+
+ rootProject.settingsGradle().include("foo");
+ fooProject.buildGradle().append("""
+ apply plugin: 'java-library'
+ apply plugin: 'maven-publish'
+ dependencies {
+ rootConfiguration platform('org:platform')
+ }
+ publishing {
+ publications {
+ main(MavenPublication) {
+ from components.java
+ }
+ }
+ }
+ """);
+
+ gradle.withArgs("foo:generatePomFile").buildsSuccessfully();
+
+ Path pomFile = fooProject.path().resolve("build/publications/main/pom-default.xml");
+ assertThat(pomFile).exists();
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document pom = builder.parse(pomFile.toFile());
+
+ NodeList dependencies =
+ pom.getElementsByTagName("dependencyManagement").item(0).getChildNodes();
+ Element dependenciesElement = null;
+ for (int i = 0; i < dependencies.getLength(); i++) {
+ if (dependencies.item(i).getNodeName().equals("dependencies")) {
+ dependenciesElement = (Element) dependencies.item(i);
+ break;
+ }
+ }
+
+ assertThat(dependenciesElement).isNotNull();
+ NodeList dependencyList = dependenciesElement.getElementsByTagName("dependency");
+ assertThat(dependencyList.getLength()).isEqualTo(1);
+
+ Element dependency = (Element) dependencyList.item(0);
+ Map depMap = new HashMap<>();
+ depMap.put("groupId", dependency.getElementsByTagName("groupId").item(0).getTextContent());
+ depMap.put(
+ "artifactId",
+ dependency.getElementsByTagName("artifactId").item(0).getTextContent());
+ depMap.put("version", dependency.getElementsByTagName("version").item(0).getTextContent());
+ depMap.put("scope", dependency.getElementsByTagName("scope").item(0).getTextContent());
+ depMap.put("type", dependency.getElementsByTagName("type").item(0).getTextContent());
+
+ assertThat(depMap).containsEntry("groupId", "org");
+ assertThat(depMap).containsEntry("artifactId", "platform");
+ assertThat(depMap).containsEntry("version", "1.0");
+ assertThat(depMap).containsEntry("scope", "import");
+ assertThat(depMap).containsEntry("type", "pom");
+ }
+
+ @Test
+ void non_glob_module_forces_do_not_get_added_to_a_matching_platform_too(
+ GradleInvoker gradle, RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ apply plugin: 'java'
+ dependencies {
+ implementation 'com.fasterxml.jackson.core:jackson-databind'
+ }
+ """);
+ rootProject.file("versions.props").overwrite("""
+ com.fasterxml.jackson.core:jackson-databind = 2.9.0
+ com.fasterxml.jackson.*:* = 2.9.7
+ """);
+
+ gradle.withArgs("resolveConfigurations", "--write-locks").buildsSuccessfully();
+
+ verifyLockfile(
+ rootProject.path(),
+ "com.fasterxml.jackson.core:jackson-databind:2.9.0",
+ "com.fasterxml.jackson.core:jackson-annotations:2.9.7");
+ }
+
+ @Test
+ void throws_if_resolving_configuration_in_after_evaluate(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ configurations { foo }
+ \s\s\s\s
+ afterEvaluate {
+ configurations.foo.resolve()
+ }
+ """);
+ rootProject.file("versions.props").overwrite("");
+
+ InvocationResult result = gradle.withArgs().buildsWithFailure();
+ assertThat(result.output()).contains("Not allowed to resolve");
+ }
+
+ @Test
+ void does_not_throw_if_excluded_configuration_is_resolved_early(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ configurations { foo }
+ \s\s\s\s
+ versionRecommendations {
+ excludeConfigurations 'foo'
+ }
+ \s\s\s\s
+ afterEvaluate {
+ configurations.foo.resolve()
+ }
+ """);
+ rootProject.file("versions.props").overwrite("");
+
+ gradle.withArgs().buildsSuccessfully();
+ }
+
+ @Test
+ void creates_root_configuration_even_if_versions_props_file_missing(GradleInvoker gradle, RootProject rootProject) {
+ rootProject.buildGradle().append("""
+ dependencies {
+ constraints {
+ rootConfiguration 'org.slf4j:slf4j-api:1.7.25'
+ }
+ }
+ """);
+ rootProject.path().resolve("versions.props").toFile().delete();
+
+ gradle.withArgs().buildsSuccessfully();
+ }
+
+ @Test
+ void build_succeeds_without_versions_props_or_versions_lock(
+ GradleInvoker gradle, RootProject rootProject, SubProject fooProject) {
+ rootProject.settingsGradle().include("foo");
+ fooProject.buildGradle().append("""
+ apply plugin: 'java'
+ """);
+
+ gradle.withArgs("build").buildsSuccessfully();
+ }
+
+ private void verifyLockfile(Path projectDir, String... lines) {
+ Path lockfile = projectDir.resolve("gradle.lockfile");
+ if (!lockfile.toFile().exists()) {
+ lockfile = projectDir.resolve("gradle/dependency-locks/runtimeClasspath.lockfile");
+ }
+ assertThat(lockfile).exists();
+
+ try {
+ String lockfileContent = Files.readString(lockfile);
+ for (String line : lines) {
+ if (lockfile.getFileName().toString().equals("gradle.lockfile")) {
+ assertThat(lockfileContent).contains(line + "=runtimeClasspath");
+ } else {
+ assertThat(lockfileContent).contains(line);
+ }
+ }
+ } catch (IOException e) {
+ throw new java.io.UncheckedIOException(e);
+ }
+ }
+}
diff --git a/versions.lock b/versions.lock
index 219bed39c..cd3211b43 100644
--- a/versions.lock
+++ b/versions.lock
@@ -44,11 +44,11 @@ cglib:cglib-nodep:3.2.2 (1 constraints: 490ded24)
com.netflix.nebula:nebula-test:10.6.2 (2 constraints: d61de72d)
-com.palantir.gradle.plugintesting:configuration-cache-spec:0.19.0 (1 constraints: 3c053e3b)
+com.palantir.gradle.plugintesting:configuration-cache-spec:0.23.0 (1 constraints: 3705303b)
-com.palantir.gradle.plugintesting:gradle-plugin-testing-junit:0.19.0 (1 constraints: 3c053e3b)
+com.palantir.gradle.plugintesting:gradle-plugin-testing-junit:0.23.0 (1 constraints: 3705303b)
-com.palantir.gradle.plugintesting:plugin-testing-core:0.19.0 (1 constraints: 3c053e3b)
+com.palantir.gradle.plugintesting:plugin-testing-core:0.23.0 (1 constraints: 3705303b)
commons-io:commons-io:2.19.0 (1 constraints: db190cdf)
diff --git a/versions.props b/versions.props
index df95f0b5e..fb93acd1a 100644
--- a/versions.props
+++ b/versions.props
@@ -1,6 +1,7 @@
com.fasterxml.jackson.*:* = 2.20.0
com.fasterxml.jackson.core:jackson-annotations = 2.20
com.google.guava:guava = 33.5.0-jre
+com.palantir.gradle.plugintesting:gradle-plugin-testing-junit = 0.23.0
org.assertj:* = 3.27.6
org.immutables:value = 2.11.6
org.junit.jupiter:* = 6.0.1