org.gradle.configurationcache.ConfigurationCacheBuildOptionsIntegrationTest.groovy Maven / Gradle / Ivy
/*
* Copyright 2019 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.configurationcache
import org.gradle.internal.reflect.problems.ValidationProblemId
import org.gradle.internal.reflect.validation.ValidationMessageChecker
import org.gradle.internal.reflect.validation.ValidationTestFor
import spock.lang.Issue
import static org.junit.Assume.assumeFalse
class ConfigurationCacheBuildOptionsIntegrationTest extends AbstractConfigurationCacheIntegrationTest implements ValidationMessageChecker {
def setup() {
expectReindentedValidationMessage()
}
@Issue("https://github.com/gradle/gradle/issues/13333")
def "absent #operator orElse #orElseKind used as task input"() {
assumeFalse(
'task dependency inference for orElse(taskOutput) not implemented yet!',
orElseKind == 'task output'
)
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
abstract class PrintString : DefaultTask() {
@get:Input
abstract val string: Property
@TaskAction
fun printString() {
println("The string is " + string.get())
}
}
abstract class ProduceString : DefaultTask() {
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun printString() {
outputFile.get().asFile.writeText("absent")
}
}
val producer = tasks.register("produceString") {
outputFile.set(layout.buildDirectory.file("output.txt"))
}
val stringProvider = providers
.$operator("string")
.orElse($orElseArgument)
tasks.register("printString") {
string.set(stringProvider)
}
"""
def printString = { string ->
switch (operator) {
case 'systemProperty':
configurationCacheRun "printString", "-Dstring=$string"
break
case 'environmentVariable':
withEnvironmentVars(string: string)
configurationCacheRun "printString"
break
}
}
when:
configurationCacheRun "printString"
then:
output.count("The string is absent") == 1
configurationCache.assertStateStored()
problems.assertResultHasProblems(result) {
withNoInputs()
}
when:
printString "alice"
then:
output.count("The string is alice") == 1
configurationCache.assertStateLoaded()
when:
printString "bob"
then:
output.count("The string is bob") == 1
configurationCache.assertStateLoaded()
where:
[operator, orElseKind] << [
['systemProperty', 'environmentVariable'],
['primitive', 'provider', 'task output']
].combinations()
orElseArgument = orElseKind == 'primitive'
? '"absent"'
: orElseKind == 'provider'
? 'providers.provider { "absent" }'
: 'producer.flatMap { it.outputFile }.map { it.asFile.readText() }'
}
@ValidationTestFor(
ValidationProblemId.VALUE_NOT_SET
)
@Issue("https://github.com/gradle/gradle/issues/13334")
def "task input property with convention set to absent #operator is reported correctly"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
val stringProvider = providers
.$operator("string")
abstract class PrintString @Inject constructor(objects: ObjectFactory) : DefaultTask() {
@get:Input
val string: Property = objects.property().convention("absent")
@TaskAction
fun printString() {
println("The string is " + string.orNull)
}
}
tasks.register("printString") {
string.set(stringProvider)
}
"""
when:
configurationCacheFails "printString"
then:
failureDescriptionContains missingValueMessage { type('Build_gradle.PrintString').property('string') }
configurationCache.assertStateStored()
when:
configurationCacheFails "printString"
then:
failureDescriptionContains missingValueMessage { type('Build_gradle.PrintString').property('string') }
configurationCache.assertStateLoaded()
where:
operator << ['systemProperty', 'environmentVariable']
}
@Issue("https://github.com/gradle/gradle/issues/13334")
def "absent #operator used as optional task input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
val stringProvider = providers
.$operator("string")
abstract class PrintString : DefaultTask() {
@get:Input
@get:Optional
abstract val string: Property
@TaskAction
fun printString() {
println("The string is " + (string.orNull ?: "absent"))
}
}
tasks.register("printString") {
string.set(stringProvider)
}
"""
when:
configurationCacheRun "printString"
then:
output.count("The string is absent") == 1
configurationCache.assertStateStored()
when:
configurationCacheRun "printString"
then:
output.count("The string is absent") == 1
configurationCache.assertStateLoaded()
where:
operator << ['systemProperty', 'environmentVariable']
}
def "system property from #systemPropertySource used as task and build logic input"() {
given:
def configurationCache = newConfigurationCacheFixture()
createDir('root') {
file('build.gradle.kts') << """
$greetTask
val greetingProp = providers.systemProperty("greeting")
if (greetingProp.get() == "hello") {
tasks.register("greet") {
greeting.set("hello, hello")
}
} else {
tasks.register("greet") {
greeting.set(greetingProp)
}
}
"""
}
def runGreetWith = { String greeting ->
inDirectory('root')
switch (systemPropertySource) {
case SystemPropertySource.COMMAND_LINE:
return configurationCacheRun('greet', "-Dgreeting=$greeting")
case SystemPropertySource.GRADLE_PROPERTIES:
file('root/gradle.properties').text = "systemProp.greeting=$greeting"
return configurationCacheRun('greet')
}
throw new IllegalArgumentException('source')
}
when:
runGreetWith 'hi'
then:
output.count("Hi!") == 1
configurationCache.assertStateStored()
and: "the input is reported"
problems.assertResultHasProblems(result) {
withInput("Build file 'build.gradle.kts': system property 'greeting'")
}
when:
runGreetWith 'hi'
then:
output.count("Hi!") == 1
configurationCache.assertStateLoaded()
when:
runGreetWith 'hello'
then:
output.count("Hello, hello!") == 1
configurationCache.assertStateStored()
where:
systemPropertySource << SystemPropertySource.values()
}
enum SystemPropertySource {
COMMAND_LINE,
GRADLE_PROPERTIES
@Override
String toString() {
name().toLowerCase().replace('_', ' ')
}
}
def "#kind property used as task and build logic input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
$greetTask
val greetingProp = providers.${kind}Property("greeting")
if (greetingProp.get() == "hello") {
tasks.register("greet") {
greeting.set("hello, hello")
}
} else {
tasks.register("greet") {
greeting.set(greetingProp)
}
}
"""
when:
configurationCacheRun("greet", "-${option}greeting=hi")
then:
output.count("Hi!") == 1
configurationCache.assertStateStored()
problems.assertResultHasProblems(result) {
withInput("Build file 'build.gradle.kts': $reportedInput")
}
when:
configurationCacheRun("greet", "-${option}greeting=hi")
then:
output.count("Hi!") == 1
configurationCache.assertStateLoaded()
when:
configurationCacheRun("greet", "-${option}greeting=hello")
then:
output.count("Hello, hello!") == 1
configurationCache.assertStateStored()
outputContains "$description property 'greeting' has changed"
where:
kind | option | description | reportedInput
'system' | 'D' | 'system' | "system property 'greeting'"
// 'gradle' | 'P' | 'Gradle' | "Gradle property 'greeting'"
}
def "mapped system property used as task input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile("""
val sysPropProvider = providers
.systemProperty("thread.pool.size")
.map(Integer::valueOf)
.orElse(1)
abstract class TaskA : DefaultTask() {
@get:Input
abstract val threadPoolSize: Property
@TaskAction
fun act() {
println("ThreadPoolSize = " + threadPoolSize.get())
}
}
tasks.register("a") {
threadPoolSize.set(sysPropProvider)
}
""")
when:
configurationCacheRun("a")
then:
output.count("ThreadPoolSize = 1") == 1
configurationCache.assertStateStored()
when:
configurationCacheRun("a", "-Dthread.pool.size=4")
then:
output.count("ThreadPoolSize = 4") == 1
configurationCache.assertStateLoaded()
when:
configurationCacheRun("a", "-Dthread.pool.size=3")
then:
output.count("ThreadPoolSize = 3") == 1
configurationCache.assertStateLoaded()
}
@Issue("https://github.com/gradle/gradle/issues/19658")
def "map orElse chain used as task input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildFile '''
abstract class PrintValueTask extends DefaultTask {
@Input
abstract Property getValue();
@TaskAction
void printValue() {
println("*" + value.get() + "*")
}
}
def chain = providers
.systemProperty("foo")
.orElse(providers.systemProperty("bar"))
.map { "foo | bar = $it" }
.orElse(providers.systemProperty("baz"))
.map { "($it)" }
tasks.register("ok", PrintValueTask.class) { task ->
task.value = chain
}
'''
when:
configurationCacheRun 'ok', '-Dfoo=foo'
then:
outputContains "*(foo | bar = foo)*"
configurationCache.assertStateStored()
when:
configurationCacheRun 'ok', '-Dbar=bar'
then:
outputContains "*(foo | bar = bar)*"
configurationCache.assertStateLoaded()
when:
configurationCacheRun 'ok', '-Dbaz=baz'
then:
outputContains "*(baz)*"
configurationCache.assertStateLoaded()
}
@Issue("https://github.com/gradle/gradle/issues/19649")
def "zip orElse chain used as task input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildFile '''
def userProvider = providers.gradleProperty("ci").map { "" }.orElse(providers.systemProperty("user"))
def versionMajorProvider = providers.gradleProperty("versionMajor").orElse("1")
def versionMinorProvider = providers.gradleProperty("versionMinor").orElse("2")
def versionNameProvider = versionMajorProvider
.zip(versionMinorProvider) { major, minor ->
"$major.$minor"
}
.zip(userProvider) { prev, user ->
"$prev-$user"
}
abstract class PrintVersionName extends DefaultTask {
@Input
abstract Property getVersionName()
@TaskAction
def printVersionName() {
println('*' + versionName.get() + '*')
}
}
tasks.register("ok", PrintVersionName.class) {
versionName = versionNameProvider
}
'''
when:
configurationCacheRun 'ok', '-Duser=alice'
then:
outputContains '*1.2-alice*'
configurationCache.assertStateStored()
when:
configurationCacheRun 'ok', '-Duser=bob'
then:
outputContains '*1.2-bob*'
configurationCache.assertStateLoaded()
}
def "zipped properties used as task input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
val prefix = providers.systemProperty("messagePrefix")
val suffix = providers.systemProperty("messageSuffix")
val zipped = prefix.zip(suffix) { p, s -> p + " " + s + "!" }
abstract class PrintLn : DefaultTask() {
@get:Input
abstract val message: Property
@TaskAction
fun act() { println(message.get()) }
}
tasks.register("ok") {
message.set(zipped)
}
"""
when:
configurationCacheRun("ok", "-DmessagePrefix=fizz", "-DmessageSuffix=buzz")
then:
output.count("fizz buzz!") == 1
configurationCache.assertStateStored()
when:
configurationCacheRun("ok", "-DmessagePrefix=foo", "-DmessageSuffix=bar")
then:
output.count("foo bar!") == 1
configurationCache.assertStateLoaded()
}
def "system property #usage used as build logic input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
val isCi = providers.systemProperty("ci")
if ($expression) {
tasks.register("run") {
doLast { println("ON CI") }
}
} else {
tasks.register("run") {
doLast { println("NOT CI") }
}
}
"""
when:
configurationCacheRun "run"
then:
output.count("NOT CI") == 1
configurationCache.assertStateStored()
when:
configurationCacheRun "run"
then:
output.count("NOT CI") == 1
configurationCache.assertStateLoaded()
when:
configurationCacheRun "run", "-Dci=true"
then:
output.count("ON CI") == 1
configurationCache.assertStateStored()
where:
expression | usage
"isCi.map(String::toBoolean).getOrElse(false)" | "value"
"isCi.isPresent" | "presence"
}
def "environment variable used as task and build logic input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
$greetTask
val greetingVar = providers.environmentVariable("GREETING")
if (greetingVar.get().startsWith("hello")) {
tasks.register("greet") {
greeting.set("hello, hello")
}
} else {
tasks.register("greet") {
greeting.set(greetingVar)
}
}
"""
when:
withEnvironmentVars(GREETING: "hi")
configurationCacheRun("greet")
then:
output.count("Hi!") == 1
configurationCache.assertStateStored()
when:
withEnvironmentVars(GREETING: "hi")
configurationCacheRun("greet")
then:
output.count("Hi!") == 1
configurationCache.assertStateLoaded()
when:
withEnvironmentVars(GREETING: "hello")
configurationCacheRun("greet")
then:
output.count("Hello, hello!") == 1
outputContains "environment variable 'GREETING' has changed"
configurationCache.assertStateStored()
}
def "file contents #usage used as build logic input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
val ciFile = layout.projectDirectory.file("ci")
val isCi = providers.fileContents(ciFile)
if (isCi.$expression) {
tasks.register("run") {
doLast { println("ON CI") }
}
} else {
tasks.register("run") {
doLast { println("NOT CI") }
}
}
"""
when:
configurationCacheRun "run"
then:
output.count("NOT CI") == 1
configurationCache.assertStateStored()
when:
configurationCacheRun "run"
then:
output.count("NOT CI") == 1
configurationCache.assertStateLoaded()
when:
file("ci").text = "true"
configurationCacheRun "run"
then:
output.count("ON CI") == 1
configurationCache.assertStateStored()
when: "file is touched but unchanged"
file("ci").text = "true"
configurationCacheRun "run"
then: "cache is still valid"
output.count("ON CI") == 1
configurationCache.assertStateLoaded()
when: "file is changed"
file("ci").text = "false"
configurationCacheRun "run"
then: "cache is NO longer valid"
output.count(usage.endsWith("presence") ? "ON CI" : "NOT CI") == 1
outputContains "file 'ci' has changed"
configurationCache.assertStateStored()
where:
expression | usage
"asText.map(String::toBoolean).getOrElse(false)" | "text"
"asText.isPresent" | "text presence"
"asBytes.map { String(it).toBoolean() }.getOrElse(false)" | "bytes"
"asBytes.isPresent" | "bytes presence"
}
def "mapped file contents used as task input"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
val threadPoolSizeProvider = providers
.fileContents(layout.projectDirectory.file("thread.pool.size"))
.asText
.map(Integer::valueOf)
abstract class TaskA : DefaultTask() {
@get:Input
abstract val threadPoolSize: Property
@TaskAction
fun act() {
println("ThreadPoolSize = " + threadPoolSize.get())
}
}
tasks.register("a") {
threadPoolSize.set(threadPoolSizeProvider)
}
"""
when:
file("thread.pool.size").text = "4"
configurationCacheRun("a")
then:
output.count("ThreadPoolSize = 4") == 1
configurationCache.assertStateStored()
when: "the file is changed"
file("thread.pool.size").text = "3"
configurationCacheRun("a")
then: "the configuration cache is NOT invalidated"
output.count("ThreadPoolSize = 3") == 1
configurationCache.assertStateLoaded()
}
def "file contents provider used as #usage has no value when underlying file provider has no value"() {
given:
def configurationCache = newConfigurationCacheFixture()
buildKotlinFile """
$greetTask
val emptyFileProperty = objects.fileProperty()
val fileContents = providers.fileContents(emptyFileProperty).asText
val greetingFromFile: $operatorType = fileContents.$operator("hello")
tasks.register("greet") {
greeting.set(greetingFromFile)
}
"""
when:
configurationCacheRun("greet")
then:
output.count("Hello!") == 1
configurationCache.assertStateStored()
when:
configurationCacheRun("greet")
then:
output.count("Hello!") == 1
configurationCache.assertStateLoaded()
where:
operator | operatorType | usage
"getOrElse" | "String" | "build logic input"
"orElse" | "Provider" | "task input"
}
def "mapped systemProperty in producer task"() {
given:
buildFile '''
import java.nio.file.Files
abstract class MyTask extends DefaultTask {
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@Input
abstract Property getInputCount()
@TaskAction
void doTask() {
File outputFile = getOutputDir().get().asFile
outputFile.deleteDir()
outputFile.mkdirs()
for (int i = 0; i < getInputCount().get(); i++) {
new File(outputFile, i.toString()).mkdirs()
}
}
}
abstract class ConsumerTask extends DefaultTask {
@InputFiles
abstract ConfigurableFileCollection getMyInputs()
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
void doTask() {
File outputFile = getOutputFile().get().asFile
outputFile.delete()
outputFile.parentFile.mkdirs()
String outputContent = ""
for(File f: getMyInputs().files) {
outputContent += f.canonicalPath + "\\n"
}
Files.write(outputFile.toPath(), outputContent.getBytes())
}
}
tasks.register("myTask", MyTask.class) {
it.getInputCount().set(project.providers.systemProperty("generateInputs").map { Integer.parseInt(it) })
it.getOutputDir().set(new File(project.buildDir, "mytask"))
}
tasks.register("consumer", ConsumerTask.class) {
it.getOutputFile().set(new File(project.buildDir, "consumer/out.txt"))
MyTask myTask = (MyTask) tasks.getByName("myTask")
Provider> inputs = myTask.outputDir.map {
File[] files = it.asFile.listFiles()
Set result = new HashSet()
for (File f : files) {
if (f.name.toInteger() % 2 == 0) {
result.add(f)
}
}
System.err.println("Computing task inputs for consumer")
return result
}
it.getMyInputs().from(inputs)
}
'''
def configurationCache = newConfigurationCacheFixture()
def consumedFileNames = {
file('build/consumer/out.txt').readLines().collect {
new File(it).name
}.toSet()
}
when:
configurationCacheRun('consumer', '-DgenerateInputs=4')
then:
consumedFileNames() == ['0', '2'] as Set
configurationCache.assertStateStored()
when:
configurationCacheRun('consumer', '-DgenerateInputs=6')
then:
consumedFileNames() == ['0', '2', '4'] as Set
configurationCache.assertStateLoaded()
}
def "system property used at configuration time can be captured by task"() {
given:
buildFile """
def sysProp = providers.systemProperty("some.prop")
println('sys prop value at configuration time = ' + sysProp.orNull)
task ok {
doLast {
println('sys prop value at execution time = ' + sysProp.orNull)
}
}
"""
def configurationCache = newConfigurationCacheFixture()
when:
configurationCacheRun 'ok', '-Dsome.prop=42'
then:
outputContains 'sys prop value at configuration time = 42'
outputContains 'sys prop value at execution time = 42'
configurationCache.assertStateStored()
when:
configurationCacheRun 'ok', '-Dsome.prop=42'
then:
outputDoesNotContain 'sys prop value at configuration time = 42'
outputContains 'sys prop value at execution time = 42'
configurationCache.assertStateLoaded()
when:
configurationCacheRun 'ok', '-Dsome.prop=37'
then:
outputContains 'sys prop value at configuration time = 37'
outputContains 'sys prop value at execution time = 37'
configurationCache.assertStateStored()
}
@Issue("gradle/gradle#14465")
def "configuration is cacheable when providers are used in settings"() {
given:
def configurationCache = newConfigurationCacheFixture()
settingsFile << """
providers.systemProperty("org.gradle.booleanProperty").orElse(false).get()
"""
when:
configurationCacheRun "help", "-Dorg.gradle.booleanProperty=true"
then:
configurationCache.assertStateStored()
when:
configurationCacheRun "help", "-Dorg.gradle.booleanProperty=true"
then:
configurationCache.assertStateLoaded()
}
@Issue("gradle/gradle#14465")
def "configuration cache is invalidated after property change when providers are used in settings"() {
given:
def configurationCache = newConfigurationCacheFixture()
settingsFile << """
providers.systemProperty("org.gradle.booleanProperty").orElse(false).get()
"""
configurationCacheRun "help", "-Dorg.gradle.booleanProperty=true"
when:
configurationCacheRun "help", "-Dorg.gradle.booleanProperty=false"
then:
configurationCache.assertStateStored()
output.contains("because system property 'org.gradle.booleanProperty' has changed.")
}
private static String getGreetTask() {
"""
abstract class Greet : DefaultTask() {
@get:Input
abstract val greeting: Property
@TaskAction
fun greet() {
println(greeting.get().capitalize() + "!")
}
}
""".stripIndent()
}
private void withEnvironmentVars(Map environment) {
executer.withEnvironmentVars(environment)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy