org.gradle.integtests.resolve.api.ResolutionResultApiIntegrationTest.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2018 the original author or authors.
*
* 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 org.gradle.integtests.resolve.api
import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
import org.gradle.integtests.fixtures.FluidDependenciesResolveRunner
import org.gradle.integtests.fixtures.resolve.ResolveTestFixture
import org.junit.runner.RunWith
import spock.lang.Issue
import spock.lang.Unroll
@RunWith(FluidDependenciesResolveRunner)
class ResolutionResultApiIntegrationTest extends AbstractDependencyResolutionTest {
ResolveTestFixture resolve = new ResolveTestFixture(buildFile, 'conf')
/*
The ResolutionResult API is also covered by the dependency report integration tests.
*/
def "selection reasons are described"() {
given:
mavenRepo.module("org", "leaf", "1.0").publish()
mavenRepo.module("org", "leaf", "2.0").publish()
mavenRepo.module("org", "foo", "0.5").publish()
mavenRepo.module("org", "foo", "1.0").dependsOn('org', 'leaf', '1.0').publish()
mavenRepo.module("org", "bar", "1.0").dependsOn('org', 'leaf', '2.0').publish()
mavenRepo.module("org", "baz", "1.0").dependsOn('org', 'foo', '1.0').publish()
file("settings.gradle") << "rootProject.name = 'cool-project'"
file("build.gradle") << """
version = '5.0'
repositories {
maven { url "${mavenRepo.uri}" }
}
configurations {
conf
}
configurations.conf.resolutionStrategy.force 'org:leaf:2.0'
dependencies {
conf 'org:foo:0.5', 'org:bar:1.0', 'org:baz:1.0'
}
task resolutionResult {
doLast {
def result = configurations.conf.incoming.resolutionResult
result.allComponents {
if(it.id instanceof ModuleComponentIdentifier) {
println it.id.module + ":" + it.id.version + " " + it.selectionReason.description
}
else if(it.id instanceof ProjectComponentIdentifier) {
println it.moduleVersion.name + ":" + it.moduleVersion.version + " " + it.selectionReason.description
}
}
}
}
"""
when:
run "resolutionResult"
then:
output.contains """
cool-project:5.0 root
foo:1.0 between versions 1.0 and 0.5
leaf:2.0 forced
bar:1.0 requested
baz:1.0 requested
"""
}
def "resolution result API gives access to dependency reasons in case of conflict"() {
given:
mavenRepo.with {
def leaf1 = module('org.test', 'leaf', '1.0').publish()
def leaf2 = module('org.test', 'leaf', '1.1').publish()
module('org.test', 'a', '1.0')
.dependsOn(leaf1, reason: 'first reason')
.withModuleMetadata()
.publish()
module('org.test', 'b', '1.0')
.dependsOn(leaf2, reason: 'second reason')
.withModuleMetadata()
.publish()
}
when:
file("build.gradle") << """
configurations {
conf
}
repositories {
maven { url "${mavenRepo.uri}" }
}
dependencies {
conf 'org.test:a:1.0'
conf 'org.test:b:1.0'
}
task checkDeps {
doLast {
def result = configurations.conf.incoming.resolutionResult
result.allComponents {
if (it.id instanceof ModuleComponentIdentifier && it.id.module == 'leaf') {
def selectionReason = it.selectionReason
assert selectionReason.conflictResolution
def descriptions = selectionReason.descriptions.reverse()
assert descriptions.size() > 1
descriptions.each {
println "\$it.cause : \$it.description"
}
def descriptors = descriptions.findAll { it.cause == ComponentSelectionCause.REQUESTED }
assert descriptors.description == ['first reason', 'second reason']
}
}
}
}
"""
then:
run "checkDeps"
}
def "resolution result API gives access to dependency reasons in case of conflict and selection by rule"() {
given:
mavenRepo.with {
def leaf1 = module('org.test', 'leaf', '1.0').publish()
def leaf2 = module('org.test', 'leaf', '1.1').publish()
module('org.test', 'a', '1.0')
.dependsOn('org.test', 'leaf', '0.9')
.withModuleMetadata()
.publish()
module('org.test', 'b', '1.0')
.dependsOn(leaf2, reason: 'second reason')
.withModuleMetadata()
.publish()
}
settingsFile << """rootProject.name='test'"""
file("build.gradle") << """
configurations {
conf {
resolutionStrategy {
dependencySubstitution {
all {
if (it.requested instanceof ModuleComponentSelector) {
if (it.requested.module == 'leaf' && it.requested.version == '0.9') {
it.useTarget("substitute 0.9 with 1.0", group: 'org.test', name: it.requested.module, version: '1.0')
}
}
}
}
}
}
}
repositories {
maven { url "${mavenRepo.uri}" }
}
dependencies {
conf 'org.test:a:1.0'
conf 'org.test:b:1.0'
}
"""
resolve.prepare()
buildFile << """
checkDeps {
doLast {
def result = configurations.conf.incoming.resolutionResult
result.allComponents {
if (it.id instanceof ModuleComponentIdentifier && it.id.module == 'leaf') {
def selectionReason = it.selectionReason
assert selectionReason.conflictResolution
def descriptions = selectionReason.descriptions.reverse()
assert descriptions.size() > 1
descriptions.each {
println "\$it.cause : \$it.description"
}
def descriptors = descriptions.findAll { it.cause == ComponentSelectionCause.REQUESTED }
assert descriptors.description == ['requested', 'second reason']
}
}
}
}
"""
when:
run "checkDeps"
then:
resolve.expectGraph {
root(":", ":test:") {
module('org.test:a:1.0:runtime') {
edge('org.test:leaf:0.9', 'org.test:leaf:1.1')
.byConflictResolution("between versions 1.1 and 1.0") // conflict with the version requested by 'b'
.byReason('second reason') // this comes from 'b'
.selectedByRule("substitute 0.9 with 1.0")
}
module('org.test:b:1.0:runtime') {
module('org.test:leaf:1.1')
.selectedByRule("substitute 0.9 with 1.0")
.byConflictResolution("between versions 1.1 and 1.0")
.byReason('second reason')
}
}
}
}
@Unroll
def "constraint are not mis-showing up as a separate REQUESTED and do not overwrite selection by rule"() {
given:
mavenRepo.module("org", "foo", "1.0").publish()
mavenRepo.module("org", "bar", "1.0").publish()
buildFile << """
repositories {
maven { url "${mavenRepo.uri}" }
}
configurations {
conf
}
dependencies {
conf "org:foo:1.0"
constraints {
conf("org:foo:1.0") {
version {
rejectAll()
}
if ($useReason) { because("This reason comes from a constraint") }
}
}
}
configurations.all {
resolutionStrategy.eachDependency {
if (requested.name == 'foo') {
because("fix comes from component selection rule").useTarget("org:bar:1.0")
}
}
}
task checkWithApi {
doLast {
def result = configurations.conf.incoming.resolutionResult
result.allComponents {
if (it.id instanceof ModuleComponentIdentifier) {
println "Module \$it.id"
it.selectionReason.descriptions.each {
println " \$it.cause : \$it.description"
}
}
}
}
}
"""
when:
run 'checkWithApi'
then:
outputContains("""Module org:bar:1.0
REQUESTED : requested
SELECTED_BY_RULE : fix comes from component selection rule
CONSTRAINT : ${useReason?'This reason comes from a constraint':'constraint'}
""")
where:
useReason << [true, false]
}
@Unroll
def "direct dependency reasons are not mis-showing up as a separate REQUESTED and do not overwrite selection by rule"() {
given:
mavenRepo.module("org", "foo", "1.0").publish()
mavenRepo.module("org", "bar", "1.0").publish()
buildFile << """
repositories {
maven { url "${mavenRepo.uri}" }
}
configurations {
conf
}
dependencies {
conf("org:foo:1.0") {
if ($useReason) { because("This is a direct dependency reason") }
}
}
configurations.all {
resolutionStrategy.eachDependency {
if (requested.name == 'foo') {
because("fix comes from component selection rule").useTarget("org:bar:1.0")
}
}
}
task checkWithApi {
doLast {
def result = configurations.conf.incoming.resolutionResult
result.allComponents {
if (it.id instanceof ModuleComponentIdentifier) {
println "Module \$it.id"
it.selectionReason.descriptions.each {
println " \$it.cause : \$it.description"
}
}
}
}
}
"""
when:
run 'checkWithApi'
then:
outputContains("""Module org:bar:1.0
REQUESTED : ${useReason?'This is a direct dependency reason':'requested'}
SELECTED_BY_RULE : fix comes from component selection rule
""")
where:
useReason << [true, false]
}
void "expired cache entry doesn't break reading from cache"() {
given:
mavenRepo.module("org", "foo", "1.0").publish()
mavenRepo.module("org", "bar", "1.0").publish()
buildFile << """
repositories {
maven { url "${mavenRepo.uri}" }
}
configurations {
conf
}
def attr = Attribute.of("myAttribute", String)
dependencies {
conf("org:foo:1.0") {
because 'first reason' // must have custom reasons to show the problem
}
conf("org:bar") {
because 'second reason'
attributes {
attribute(attr, 'val') // make sure attributes are properly serialized and read back
}
}
constraints {
conf("org:bar") {
version {
require "[0.1, 2.0["
prefer "1.0"
}
}
}
}
task resolveTwice {
doLast {
def result = configurations.conf.incoming.resolutionResult
result.allComponents {
it.selectionReason.descriptions.each {
println "\${it.cause} : \${it.description}"
}
}
println 'Waiting for the cache to expire'
// see org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.CachedStoreFactory
Thread.sleep(800) // must be > cache expiry
println 'Read result again'
result.allComponents {
it.selectionReason.descriptions.each {
println "\${it.cause} : \${it.description}"
}
}
}
}
"""
executer.withArgument('-Dorg.gradle.api.internal.artifacts.ivyservice.resolveengine.store.cacheExpiryMs=500')
when:
run 'resolveTwice'
then:
noExceptionThrown()
}
def "each dependency is associated to its resolved variant"() {
mavenRepo.module("org", "dep", "1.0").publish()
mavenRepo.module("com", "foo", "1.0").publish()
mavenRepo.module("com", "bar", "1.0").publish()
mavenRepo.module("com", "baz", "1.0").publish()
settingsFile << """
include 'lib', 'tool'
"""
buildFile << """
allprojects {
repositories {
maven { url "${mavenRepo.uri}" }
}
apply plugin: 'java-library'
}
project(":lib") {
dependencies {
api "org:dep:1.0"
}
}
project(":tool") {
apply plugin: 'java-test-fixtures'
dependencies {
api "com:baz:1.0"
testFixturesApi "com:foo:1.0"
testFixturesImplementation "com:bar:1.0"
}
}
dependencies {
implementation(project(":lib"))
testImplementation(testFixtures(project(":tool")))
testImplementation(testFixtures(project(":tool"))) // intentional duplication
}
"""
withResolutionResultDumper("testCompileClasspath", "testRuntimeClasspath")
when:
succeeds 'resolve'
then:
outputContains """
testCompileClasspath
project :lib (apiElements)
org:dep:1.0 (compile)
project :tool (testFixturesApiElements)
project :tool (apiElements)
com:baz:1.0 (compile)
com:foo:1.0 (compile)
"""
and:
outputContains """testRuntimeClasspath
project :lib (runtimeElements)
org:dep:1.0 (runtime)
project :tool (testFixturesRuntimeElements)
project :tool (runtimeElements)
com:baz:1.0 (runtime)
com:foo:1.0 (runtime)
com:bar:1.0 (runtime)
"""
}
def "requested dependency attributes are reported on dependency result as desugared attributes"() {
settingsFile << "include 'platform'"
buildFile << """
project(":platform") {
apply plugin: 'java-platform'
}
apply plugin: 'java-library'
dependencies {
implementation(platform(project(":platform")))
}
task checkDependencyAttributes {
doLast {
configurations.compileClasspath.incoming.resolutionResult.root.dependencies.each {
def desugaredCategory = Attribute.of("org.gradle.category", String)
assert it.requested.attributes.getAttribute(desugaredCategory) == 'platform'
}
}
}
"""
expect:
succeeds 'checkDependencyAttributes'
}
def "reports duplicated dependencies in all variants"() {
mavenRepo.module('org', 'foo', '1.0').publish()
mavenRepo.module('org', 'bar', '1.0').publish()
mavenRepo.module('org', 'baz', '1.0').publish()
mavenRepo.module('org', 'gaz', '1.0').publish()
file("producer/build.gradle") << """
plugins {
id 'java-library'
id 'java-test-fixtures'
}
dependencies {
testFixturesApi('org:foo:1.0')
testFixturesImplementation('org:bar:1.0')
testFixturesImplementation('org:baz:1.0')
api('org:baz:1.0')
implementation('org:gaz:1.0')
}
"""
buildFile << """
plugins {
id 'java-library'
}
allprojects {
repositories {
maven { url "${mavenRepo.uri}" }
}
}
dependencies {
implementation(project(':producer'))
testImplementation(testFixtures(project(':producer')))
}
"""
settingsFile << """
include 'producer'
"""
withResolutionResultDumper("testCompileClasspath", "testRuntimeClasspath")
when: "baz should appear in both apiElements and testFixturesRuntimeElements"
succeeds 'resolve'
then:
outputContains("""
testCompileClasspath
project :producer (apiElements)
org:baz:1.0 (compile)
project :producer (testFixturesApiElements)
project :producer (apiElements)
org:foo:1.0 (compile)
""")
and:
outputContains("""
testRuntimeClasspath
project :producer (runtimeElements)
org:baz:1.0 (runtime)
org:gaz:1.0 (runtime)
project :producer (testFixturesRuntimeElements)
project :producer (runtimeElements)
org:foo:1.0 (runtime)
org:bar:1.0 (runtime)
org:baz:1.0 (runtime)
""")
}
def "reports if we try to get dependencies from a different variant"() {
mavenRepo.module('org', 'foo', '1.0').publish()
file("producer/build.gradle") << """
plugins {
id 'java-library'
id 'java-test-fixtures'
}
dependencies {
testFixturesApi('org:foo:1.0')
}
"""
buildFile << """
plugins {
id 'java-library'
}
allprojects {
repositories {
maven { url "${mavenRepo.uri}" }
}
}
dependencies {
implementation(project(':producer'))
testImplementation(testFixtures(project(':producer')))
}
task resolve {
doLast {
def result = configurations.testCompileClasspath.incoming.resolutionResult
def rootComponent = result.root
def childComponent = result.allComponents.find { it.toString() == 'project :producer' }
def childVariant = childComponent.variants[0]
// try to get dependencies for child variant on the wrong component
println(rootComponent.getDependenciesForVariant(childVariant))
}
}
"""
settingsFile << """
include 'producer'
"""
when:
fails 'resolve'
then:
failure.assertHasCause("Variant 'apiElements' doesn't belong to resolved component 'project :'. There's no resolved variant with the same name. Most likely you are using a variant from another component to get the dependencies of this component.")
}
@Issue("https://github.com/gradle/gradle/issues/12643")
def "resolved variant of a selected node shouldn't be null"() {
buildFile << """
apply plugin: 'java-library'
${mavenCentralRepository()}
configurations.all {
resolutionStrategy.capabilitiesResolution.withCapability('com.google.collections:google-collections') {
selectHighestVersion()
}
}
dependencies {
implementation 'com.google.guava:guava:28.1-jre'
implementation 'com.google.collections:google-collections:1.0'
components {
withModule('com.google.guava:guava') {
allVariants {
withCapabilities {
addCapability('com.google.collections', 'google-collections', id.version)
}
}
}
}
}
task resolve {
doLast {
def result = configurations.compileClasspath.incoming.resolutionResult
result.allDependencies {
assert it instanceof ResolvedDependencyResult
assert it.resolvedVariant != null
}
}
}
"""
expect:
succeeds 'resolve'
}
private void withResolutionResultDumper(String... configurations) {
def confList = configurations.collect { configuration ->
"""
// dump variant dependencies
def result_$configuration = configurations.${configuration}.incoming.resolutionResult
dump("$configuration", result_${configuration}.root, null, 0)
// check that configuration attributes are visible and desugared
def consumerAttributes_$configuration = configurations.${configuration}.attributes
assert result_${configuration}.requestedAttributes.keySet().size() == consumerAttributes_${configuration}.keySet().size()
consumerAttributes_${configuration}.keySet().each {
println "Checking \$it of type \$it.type"
def desugared = Attribute.of(it.name, String)
assert result_${configuration}.requestedAttributes.getAttribute(desugared) == consumerAttributes_${configuration}.getAttribute(it).toString()
}
"""
}
buildFile << """
task resolve {
doLast {
{ -> ${confList.join('\n')} }()
println()
println 'Waiting for the cache to expire'
// see org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.CachedStoreFactory
Thread.sleep(800) // must be > cache expiry
println 'Read result again to make sure serialization state is ok'
println();
{ -> ${confList.join('\n')} }()
}
}
void dump(String root, ResolvedComponentResult result, ResolvedVariantResult variant, int depth, Set visited = []) {
if (visited.add([result, variant])) {
if (variant == null) {
println(root)
}
def dependencies = variant == null ? result.dependencies : result.getDependenciesForVariant(variant)
depth++
dependencies.each {
if (it instanceof ResolvedDependencyResult) {
def resolvedVariant = it.resolvedVariant
def selected = it.selected
println(" " * depth + "\$selected (\$resolvedVariant)")
dump(root, selected, resolvedVariant, depth, visited)
} else {
println(" " * depth + "\$it (unresolved)")
}
}
}
}
"""
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy