org.gradle.integtests.resolve.DependencyManagementResultsAsInputsIntegrationTest.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 2021 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
import groovy.test.NotYetImplemented
import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ComponentSelectionReasons
import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.DefaultComponentSelectionDescriptor
import org.gradle.api.internal.artifacts.result.DefaultResolvedVariantResult
import org.gradle.api.internal.attributes.ImmutableAttributesFactory
import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest
import org.gradle.internal.Describables
import org.gradle.internal.component.external.model.DefaultModuleComponentArtifactIdentifier
import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
import org.gradle.internal.component.external.model.ImmutableCapability
import org.gradle.internal.component.local.model.DefaultLibraryComponentSelector
import org.gradle.test.fixtures.file.TestFile
import org.gradle.util.GradleVersion
import spock.lang.Issue
class DependencyManagementResultsAsInputsIntegrationTest extends AbstractHttpDependencyResolutionTest {
def setup() {
server.start()
settingsFile << """
includeBuild 'composite-lib'
rootProject.name = 'root'
include 'project-lib'
"""
def variantDeclaration = { sysPropName ->
"""
def myAttribute = Attribute.of("my.attribute.name", String)
dependencies.attributesSchema { attribute(myAttribute) }
configurations {
runtimeElements {
attributes { attribute(myAttribute, System.getProperty('$sysPropName', 'default-value')) }
}
}
"""
}
def unselectedVariantDeclaration = { sysPropName ->
"""
def myUnselectedAttribute = Attribute.of("my.unselected.attribute.name", String)
dependencies.attributesSchema { attribute(myUnselectedAttribute) }
configurations {
mainSourceElements {
attributes { attribute(myUnselectedAttribute, System.getProperty('$sysPropName', 'default-value')) }
}
}
"""
}
file('composite-lib/settings.gradle') << ""
file('composite-lib/build.gradle') << """
plugins { id 'java-library' }
group = 'composite-lib'
${variantDeclaration('compositeLibAttrValue')}
"""
def util = mavenHttpRepo.module("org.external", "external-util").publish().allowAll()
mavenHttpRepo.module("org.external", "external-lib")
.dependsOn(util)
.publish()
.allowAll()
mavenHttpRepo.module("org.external", "external-lib2")
.dependsOn(util)
.withModuleMetadata()
.publish()
.allowAll()
mavenHttpRepo.module("org.external", "external-tool").publish().allowAll()
file('lib/file-lib.jar') << 'content'
buildFile << """
project(':project-lib') {
apply plugin: 'java-library'
${variantDeclaration('projectLibAttrValue')}
${unselectedVariantDeclaration('projectUnselectedLibAttrValue')}
}
apply plugin: 'java-library'
repositories { maven { url "${mavenHttpRepo.uri}" } }
dependencies {
implementation('org.external:external-lib:1.0') {
because(System.getProperty('selectionReason', 'original'))
}
implementation 'org.external:external-lib2:1.0'
implementation project('project-lib')
implementation files('lib/file-lib.jar')
implementation 'composite-lib:composite-lib'
}
@CacheableRule
abstract class ChangingAttributeRule implements ComponentMetadataRule {
final String attrValue
@Inject ChangingAttributeRule(String attrValue) { this.attrValue = attrValue }
void execute(ComponentMetadataContext context) {
context.details.allVariants {
attributes.attribute(Attribute.of("my.attribute.name", String), attrValue)
}
}
}
dependencies {
components {
withModule('org.external:external-lib', ChangingAttributeRule) {
params(System.getProperty('externalLibAttrValue', 'default-value'))
}
}
}
"""
withOriginalSourceIn("project-lib")
withOriginalSourceIn("composite-lib")
}
def "can not use ResolvedArtifactResult as task input annotated with #annotation"() {
executer.beforeExecute {
executer.noDeprecationChecks() // Cannot convert the provided notation to a File or URI
executer.withArgument("-Dorg.gradle.internal.max.validation.errors=20")
}
given:
buildFile << """
interface NestedBean {
$annotation
Property getNested()
}
abstract class TaskWithInput extends DefaultTask {
private final NestedBean nested = project.objects.newInstance(NestedBean.class)
$annotation
ResolvedArtifactResult getDirect() { null }
$annotation
Provider getProviderInput() { propertyInput }
$annotation
abstract Property getPropertyInput();
$annotation
abstract SetProperty getSetPropertyInput();
$annotation
abstract ListProperty getListPropertyInput();
$annotation
abstract MapProperty getMapPropertyInput();
@Nested
abstract NestedBean getNestedInput();
}
tasks.register('verify', TaskWithInput) {
def artifacts = configurations.runtimeClasspath.incoming.artifacts
propertyInput.set(artifacts.resolvedArtifacts.map { it[0] })
setPropertyInput.set(artifacts.resolvedArtifacts)
listPropertyInput.set(artifacts.resolvedArtifacts)
mapPropertyInput.put("some", artifacts.resolvedArtifacts.map { it[0] })
nestedInput.nested.set(artifacts.resolvedArtifacts.map { it[0] })
doLast {
println(setPropertyInput.get())
}
}
"""
when:
fails "verify"
then:
failureDescriptionStartsWith("Some problems were found with the configuration of task ':verify' (type 'TaskWithInput').")
failureDescriptionContains("Type 'TaskWithInput' property 'direct' has $annotation annotation used on property of type 'ResolvedArtifactResult'.")
failureDescriptionContains("Type 'TaskWithInput' property 'providerInput' has $annotation annotation used on property of type 'Provider'.")
failureDescriptionContains("Type 'TaskWithInput' property 'propertyInput' has $annotation annotation used on property of type 'Property'.")
failureDescriptionContains("Type 'TaskWithInput' property 'setPropertyInput' has $annotation annotation used on property of type 'SetProperty'.")
failureDescriptionContains("Type 'TaskWithInput' property 'listPropertyInput' has $annotation annotation used on property of type 'ListProperty'.")
failureDescriptionContains("Type 'TaskWithInput' property 'mapPropertyInput' has $annotation annotation used on property of type 'MapProperty'.")
failureDescriptionContains("Type 'TaskWithInput' property 'nestedInput.nested' has $annotation annotation used on property of type 'Property'.")
// Because
failureDescriptionContains("ResolvedArtifactResult is not supported on task properties annotated with $annotation.")
// Possible solutions
failureDescriptionContains("1. Extract artifact metadata and annotate with @Input.")
failureDescriptionContains("2. Extract artifact files and annotate with @InputFiles.")
// Documentation
failureDescriptionContains("Please refer to https://docs.gradle.org/${GradleVersion.current().version}/userguide/validation_problems.html#unsupported_value_type for more details about this problem.")
where:
annotation | _
"@Input" | _
"@InputFile" | _
"@InputFiles" | _
}
def "can use #type as task input"() {
given:
buildFile << """
import ${DefaultModuleIdentifier.name}
import ${DefaultModuleVersionIdentifier.name}
import ${DefaultModuleComponentIdentifier.name}
import ${ImmutableCapability.name}
import ${DefaultModuleComponentArtifactIdentifier.name}
import ${ImmutableAttributesFactory.name}
import ${DefaultResolvedVariantResult.name}
import ${Describables.name}
import ${DefaultComponentSelectionDescriptor.name}
import ${ComponentSelectionReasons.name}
import ${DefaultLibraryComponentSelector.name}
abstract class TaskWithInput extends DefaultTask {
@Input
abstract Property<$type> getInput()
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
def action() {
println(input.get())
}
}
tasks.register("verify", TaskWithInput) {
outputFile.set(layout.buildDirectory.file('output.txt'))
input.set($factory)
}
"""
when:
succeeds("verify", "-Dn=foo")
then:
executedAndNotSkipped(":verify")
when:
succeeds("verify", "-Dn=foo")
then:
skipped(":verify")
when:
succeeds("verify", "-Dn=bar")
then:
executedAndNotSkipped(":verify")
where:
type | factory
// For ResolvedArtifactResult
"Attribute" | "Attribute.of(System.getProperty('n'), String)"
"AttributeContainer" | "services.get(ImmutableAttributesFactory).of(Attribute.of('some', String.class), System.getProperty('n'))"
"Capability" | "new ImmutableCapability('group', System.getProperty('n'), '1.0')"
"ModuleComponentIdentifier" | "new DefaultModuleComponentIdentifier(DefaultModuleIdentifier.newId('group', System.getProperty('n')),'1.0')"
"ComponentArtifactIdentifier" | "new DefaultModuleComponentArtifactIdentifier(new DefaultModuleComponentIdentifier(DefaultModuleIdentifier.newId('group', System.getProperty('n')),'1.0'), System.getProperty('n') + '-1.0.jar', 'jar', null)"
"ResolvedVariantResult" | "new DefaultResolvedVariantResult(new DefaultModuleComponentIdentifier(DefaultModuleIdentifier.newId('group', System.getProperty('n')), '1.0'), Describables.of('variantName'), services.get(ImmutableAttributesFactory).of(Attribute.of('some', String.class), System.getProperty('n')), [new ImmutableCapability('group', System.getProperty('n'), '1.0')], null)"
// For ResolvedComponentResult
"ModuleVersionIdentifier" | "DefaultModuleVersionIdentifier.newId('group', System.getProperty('n'), '1.0')"
// "ResolvedComponentResult" | "null"
// "DependencyResult" | "null"
"ComponentSelector" | "new DefaultLibraryComponentSelector(':sub', System.getProperty('n'))"
"ComponentSelectionReason" | "ComponentSelectionReasons.of(ComponentSelectionReasons.REQUESTED.withDescription(Describables.of('csd-' + System.getProperty('n'))))"
"ComponentSelectionDescriptor" | "new DefaultComponentSelectionDescriptor(ComponentSelectionCause.REQUESTED, Describables.of('csd-' + System.getProperty('n')))"
}
def "can map ResolvedArtifactResult file as task input"() {
given:
buildFile << """
abstract class TaskWithFilesInput extends DefaultTask {
@InputFiles
abstract ConfigurableFileCollection getInputFiles()
@OutputFile
abstract RegularFileProperty getOutputFile()
}
tasks.register('verify', TaskWithFilesInput) {
inputFiles.from(configurations.runtimeClasspath.incoming.artifacts.resolvedArtifacts.map { it.collect { it.file } })
outputFile.set(layout.buildDirectory.file('output.txt'))
doLast {
println(inputFiles.files)
}
}
"""
when:
succeeds "verify"
then:
executedAndNotSkipped ":project-lib:jar", ":verify"
when:
succeeds "verify"
then:
skipped ":project-lib:jar", ":verify"
when:
withChangedSourceIn("project-lib")
succeeds "verify"
then:
executedAndNotSkipped ":project-lib:jar", ":verify"
when:
succeeds "verify"
then:
skipped ":project-lib:jar", ":verify"
when:
withChangedSourceIn("composite-lib")
succeeds "verify"
then:
executedAndNotSkipped ":composite-lib:jar", ":verify"
when:
withNewExternalDependency()
succeeds ":verify"
then:
skipped ":project-lib:jar", ":composite-lib:jar"
executedAndNotSkipped ":verify"
}
def "can map ResolvedArtifactResult #inputProperty as task input"() {
given:
buildFile << """
abstract class TaskWithResultInput extends DefaultTask {
@Input
abstract ListProperty<${inputType}> getInput()
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction void action() {
println(input.get())
}
}
tasks.register("verify", TaskWithResultInput) {
def resolvedArtifacts = configurations.runtimeClasspath.incoming.artifacts.resolvedArtifacts
input.set(resolvedArtifacts.map { it.collect { it.${inputProperty} } })
outputFile.set(layout.buildDirectory.file('output.txt'))
}
"""
when:
succeeds "verify"
then:
executedAndNotSkipped ":project-lib:jar", ":verify"
when:
succeeds "verify", "-i"
then:
skipped ":project-lib:jar", ":verify"
when: "changing project library source code"
withChangedSourceIn("project-lib")
succeeds "verify"
then:
executedAndNotSkipped ":project-lib:jar"
skipped ":verify"
when:
succeeds "verify"
then:
skipped ":project-lib:jar", ":verify"
when: "changing composite library source code"
withChangedSourceIn("composite-lib")
succeeds "verify"
then:
executedAndNotSkipped ":composite-lib:jar"
skipped ":verify"
when: "adding a new external dependency"
withNewExternalDependency()
succeeds ":verify"
then:
skipped ":project-lib:jar", ":composite-lib:jar"
executedAndNotSkipped ":verify"
when: "changing project library variant metadata"
succeeds "verify", "-DprojectLibAttrValue=new-value"
then:
if (inputProperty == "variant") {
skipped ":project-lib:jar", ":composite-lib:jar"
executedAndNotSkipped ":verify"
} else {
skipped ":project-lib:jar", ":composite-lib:jar", ":verify"
}
when: "changing included library variant metadata"
succeeds "verify", "-DcompositeLibAttrValue=new-value"
then:
if (inputProperty == "variant") {
skipped ":project-lib:jar", ":composite-lib:jar"
executedAndNotSkipped ":verify"
} else {
skipped ":project-lib:jar", ":composite-lib:jar", ":verify"
}
when: "changing external library variant metadata"
succeeds "verify", "-DexternalLibAttrValue=new-value"
then:
if (inputProperty == "variant") {
skipped ":project-lib:jar", ":composite-lib:jar"
executedAndNotSkipped ":verify"
} else {
skipped ":project-lib:jar", ":composite-lib:jar", ":verify"
}
where:
inputProperty | inputType
"id" | "ComponentArtifactIdentifier"
"type" | "Class extends Artifact>"
"variant" | "ResolvedVariantResult"
}
@NotYetImplemented
@Issue("https://github.com/gradle/gradle/issues/13590")
def "can combine files and metadata from ResolvedArtifactResult as nested task inputs"() {
given:
buildFile << """
class ResolvedArtifactBean {
@InputFile
File file
@Input
ComponentArtifactIdentifier id
@Input
Class extends Artifact> type
@Input
ResolvedVariantResult variant
}
abstract class TaskWithFilesAndMetadataInput extends DefaultTask {
@Nested
abstract SetProperty> getResArtifacts()
@OutputFile
abstract RegularFileProperty getOutputFile()
}
tasks.register('verify', TaskWithFilesAndMetadataInput) {
def resolvedArtifacts = configurations.runtimeClasspath.incoming.artifacts.resolvedArtifacts
resArtifacts.set(
resolvedArtifacts.map { arts ->
arts.collect { art ->
objects.newInstance(ResolvedArtifactBean).tap { bean ->
bean.file = art.file
bean.id = art.id
bean.type = art.type
bean.variant = art.variant
}
}
}
)
outputFile.set(layout.buildDirectory.file('output.txt'))
doLast {
resArtifacts.get().each { art ->
println("\${art.file} - \${art.id} - \${art.type} - \${art.variant}")
}
}
}
"""
when:
succeeds "verify"
then:
executedAndNotSkipped ":project-lib:jar", ":verify"
when:
succeeds "verify"
then:
skipped ":project-lib:jar", ":verify"
when:
withChangedSourceIn("project-lib")
succeeds "verify"
then:
executedAndNotSkipped ":project-lib:jar", ":verify"
when:
succeeds "verify"
then:
skipped ":project-lib:jar", ":verify"
when:
withChangedSourceIn("composite-lib")
succeeds "verify"
then:
executedAndNotSkipped ":composite-lib:jar", ":verify"
when:
withNewExternalDependency()
succeeds "verify"
then:
skipped ":project-lib:jar", ":composite-lib:jar"
executedAndNotSkipped ":verify"
}
private def resolvedComponentResultSetup() {
buildFile << """
abstract class TaskWithGraphInput extends DefaultTask {
@Input
abstract Property getDepGraphRoot()
@OutputFile
abstract RegularFileProperty getOutputFile()
}
tasks.register('verify', TaskWithGraphInput) {
depGraphRoot.set(configurations.runtimeClasspath.incoming.resolutionResult.rootComponent)
outputFile.set(layout.buildDirectory.file('output.txt'))
doLast {
println(depGraphRoot.get())
}
}
"""
}
def "can use ResolvedComponentResult result as task input and changing source in '#changeLoc' doesn't invalidate the cache"() {
given:
resolvedComponentResultSetup()
when: "Task without changes is executed & not skipped"
succeeds "verify"
then:
executedAndNotSkipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Running it again with the same environment skips the task"
succeeds "verify"
then:
skipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "The change doesn't invalidate the cache"
withChangedSourceIn(changeLoc)
succeeds "verify"
then:
skipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
where:
changeLoc << ['project-lib', 'composite-lib']
}
def "can use ResolvedComponentResult result as task input and '#changeDesc' invalidates the cache"() {
given:
resolvedComponentResultSetup()
buildFile << """
if (Boolean.getBoolean("externalDependency")) {
dependencies { implementation 'org.external:external-tool:1.0' }
}
"""
when: "Task without changes is executed & not skipped"
succeeds "verify"
then:
executedAndNotSkipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Running it again with the same environment skips the task"
succeeds "verify"
then:
skipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Making a change invalidates the cache"
succeeds "verify", changeArg
then:
executedAndNotSkipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Keeping the change skips the task again"
succeeds "verify", changeArg
then:
skipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Losing the change invalidates the cache"
succeeds "verify"
then:
executedAndNotSkipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
where:
changeDesc | changeArg
"a new external dependency" | "-DexternalDependency=true"
"changing selection reasons" | "-DselectionReason=changed"
"changing project library variant metadata" | "-DprojectLibAttrValue=new-value"
"changing included library variant metadata" | "-DcompositeLibAttrValue=new-value"
"changing external library variant metadata" | "-DexternalLibAttrValue=new-value"
}
def "can use ResolvedComponentResult result as task input and '#changeDesc' invalidates the cache (returnAllVariants=true)"() {
given:
resolvedComponentResultSetup()
buildFile << """
if (Boolean.getBoolean("externalDependency")) {
dependencies { implementation 'org.external:external-tool:1.0' }
}
configurations.runtimeClasspath.returnAllVariants = true
"""
when: "Task without changes is executed & not skipped"
succeeds "verify"
then:
executedAndNotSkipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Running it again with the same environment skips the task"
succeeds "verify"
then:
skipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "The change invalidates the cache"
succeeds "verify", changeArg
then:
executedAndNotSkipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Keeping the change skips the task again"
succeeds "verify", changeArg
then:
skipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
when: "Losing the change invalidates the cache"
succeeds "verify"
then:
executedAndNotSkipped ":verify"
notExecuted ":project-lib:jar", ":composite-lib:jar"
where:
changeDesc | changeArg
"a new external dependency" | "-DexternalDependency=true"
"changing selection reasons" | "-DselectionReason=changed"
"changing project library variant metadata" | "-DprojectLibAttrValue=new-value"
"changing unselected project library variant metadata" | "-DprojectUnselectedLibAttrValue=new-value"
"changing included library variant metadata" | "-DcompositeLibAttrValue=new-value"
"changing external library variant metadata" | "-DexternalLibAttrValue=new-value"
}
private void withOriginalSourceIn(String basePath) {
sourceFileIn(basePath).tap {
text = """
class Main {}
""".stripIndent()
makeOlder()
}
}
private void withChangedSourceIn(String basePath) {
sourceFileIn(basePath).text = """
class Main {
public static void main(String[] args) {}
}
""".stripIndent()
}
private TestFile sourceFileIn(String basePath) {
return file("$basePath/src/main/java/Main.java")
}
private void withNewExternalDependency() {
buildFile << """
dependencies { implementation 'org.external:external-tool:1.0' }
"""
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy