Lazy Configuration
- Lazy properties
- Creating a Property or Provider instance
- Connecting properties together
- Working with files
- Working with task inputs and outputs
- Working with collections
- Working with maps
- Applying a convention to a property
- Making a property unmodifiable
- Guidelines
- Future development
- Provider Files API Reference
- Property Files API Reference
- Lazy Collections API Reference
- Lazy Objects API Reference
As a build grows in complexity, knowing when and where a particular value is configured can become difficult to reason about. Gradle provides several ways to manage this complexity using lazy configuration.
Lazy properties
Gradle provides lazy properties, which delay the calculation of a property’s value until it’s actually required. These provide three main benefits to build script and plugin authors:
-
Build authors can wire together Gradle models without worrying when a particular property’s value will be known. For example, you may want to set the input source files of a task based on the source directories property of an extension but the extension property value isn’t known until the build script or some other plugin configures them.
-
Build authors can wire an output property of a task into an input property of some other task and Gradle automatically determines the task dependencies based on this connection. Property instances carry information about which task, if any, produces their value. Build authors do not need to worry about keeping task dependencies in sync with configuration changes.
-
Build authors can avoid resource intensive work during the configuration phase, which can have a large impact on build performance. For example, when a configuration value comes from parsing a file but is only used when functional tests are run, using a property instance to capture this means that the file is parsed only when the functional tests are run, but not when, for example,
clean
is run.
Gradle represents lazy properties with two interfaces:
-
Provider represents a value that can only be queried and cannot be changed.
-
Properties with these types are read-only.
-
The method Provider.get() returns the current value of the property.
-
A
Provider
can be created from anotherProvider
using Provider.map(Transformer). -
Many other types extend
Provider
and can be used where-ever aProvider
is required.
-
-
Property represents a value that can be queried and also changed.
-
Properties with these types are configurable.
-
Property
extends theProvider
interface. -
The method Property.set(T) specifies a value for the property, overwriting whatever value may have been present.
-
The method Property.set(Provider) specifies a
Provider
for the value for the property, overwriting whatever value may have been present. This allows you to wire togetherProvider
andProperty
instances before the values are configured. -
A
Property
can be created by the factory method ObjectFactory.property(Class).
-
Lazy properties are intended to be passed around and only queried when required. Usually, this will happen during the execution phase. For more information about the Gradle build phases, please see Build Lifecycle.
The following demonstrates a task with a configurable greeting
property and a read-only message
property that is derived from this:
abstract class Greeting : DefaultTask() { (1)
@get:Input
abstract val greeting: Property<String> (2)
@Internal
val message: Provider<String> = greeting.map { it + " from Gradle" } (3)
@TaskAction
fun printMessage() {
logger.quiet(message.get())
}
}
tasks.register<Greeting>("greeting") {
greeting.set("Hi") (4)
greeting = "Hi" (5)
}
abstract class Greeting extends DefaultTask { (1)
@Input
abstract Property<String> getGreeting() (2)
@Internal
final Provider<String> message = greeting.map { it + ' from Gradle' } (3)
@TaskAction
void printMessage() {
logger.quiet(message.get())
}
}
tasks.register("greeting", Greeting) {
greeting.set('Hi') (4)
greeting = 'Hi' (5)
}
1 | A task that displays a greeting |
2 | A configurable greeting |
3 | Read-only property calculated from the greeting |
4 | Configure the greeting |
5 | Alternative notation to calling Property.set() (experimental opt-in for Kotlin, see Kotlin DSL Primer) |
gradle greeting
$ gradle greeting > Task :greeting Hi from Gradle BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
The Greeting
task has a property of type Property<String>
to represent the configurable greeting and a property of type Provider<String>
to represent the calculated, read-only, message. The message Provider
is created from the greeting Property
using the map()
method, and so its value is kept up-to-date as the value of the greeting property changes.
Note that Gradle Groovy DSL generates setter methods for each In Kotlin DSL, the |
Creating a Property or Provider instance
Neither Provider
nor its subtypes such as Property
are intended to be implemented by a build script or plugin author. Gradle provides factory methods to create instances of these types instead. See the Quick Reference for all of the types and factories available. In the previous example, we have seen 2 factory methods:
-
ObjectFactory.property(Class) create a new
Property
instance. An instance of the ObjectFactory can be referenced from Project.getObjects() or by injectingObjectFactory
through a constructor or method. -
Provider.map(Transformer) creates a new
Provider
from an existingProvider
orProperty
instance.
A Provider
can also be created by the factory method ProviderFactory.provider(Callable). You should prefer using map()
instead, as this has some useful benefits, which we will see later.
There are no specific methods create a provider using a Similarly, when writing a plugin or build script with Kotlin, the Kotlin compiler will take care of converting a Kotlin function into a |
Connecting properties together
An important feature of lazy properties is that they can be connected together so that changes to one property are automatically reflected in other properties. Here’s an example where the property of a task is connected to a property of a project extension:
// A project extension
interface MessageExtension {
// A configurable greeting
abstract val greeting: Property<String>
}
// A task that displays a greeting
abstract class Greeting : DefaultTask() {
// Configurable by the user
@get:Input
abstract val greeting: Property<String>
// Read-only property calculated from the greeting
@Internal
val message: Provider<String> = greeting.map { it + " from Gradle" }
@TaskAction
fun printMessage() {
logger.quiet(message.get())
}
}
// Create the project extension
val messages = project.extensions.create<MessageExtension>("messages")
// Create the greeting task
tasks.register<Greeting>("greeting") {
// Attach the greeting from the project extension
// Note that the values of the project extension have not been configured yet
greeting.set(messages.greeting)
}
messages.apply {
// Configure the greeting on the extension
// Note that there is no need to reconfigure the task's `greeting` property. This is automatically updated as the extension property changes
greeting.set("Hi")
}
// A project extension
interface MessageExtension {
// A configurable greeting
Property<String> getGreeting()
}
// A task that displays a greeting
abstract class Greeting extends DefaultTask {
// Configurable by the user
@Input
abstract Property<String> getGreeting()
// Read-only property calculated from the greeting
@Internal
final Provider<String> message = greeting.map { it + ' from Gradle' }
@TaskAction
void printMessage() {
logger.quiet(message.get())
}
}
// Create the project extension
project.extensions.create('messages', MessageExtension)
// Create the greeting task
tasks.register("greeting", Greeting) {
// Attach the greeting from the project extension
// Note that the values of the project extension have not been configured yet
greeting = messages.greeting
}
messages {
// Configure the greeting on the extension
// Note that there is no need to reconfigure the task's `greeting` property. This is automatically updated as the extension property changes
greeting = 'Hi'
}
gradle greeting
$ gradle greeting > Task :greeting Hi from Gradle BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
This example calls the Property.set(Provider) method to attach a Provider
to a Property
to supply the value of the property. In this case, the Provider
happens to be a Property
as well, but you can connect any Provider
implementation, for example one created using Provider.map()
Working with files
In Working with Files, we introduced four collection types for File
-like objects:
Read-only Type | Configurable Type |
---|---|
All of these types are also considered lazy types.
In this section, we are going to introduce more strongly typed models types to represent elements of the file system: Directory and RegularFile. These types shouldn’t be confused with the standard Java File type as they are used to tell Gradle, and other people, that you expect more specific values such as a directory or a non-directory, regular file.
Gradle provides two specialized Property
subtypes for dealing with values of these types: RegularFileProperty and DirectoryProperty. ObjectFactory has methods to create these: ObjectFactory.fileProperty() and ObjectFactory.directoryProperty().
A DirectoryProperty
can also be used to create a lazily evaluated Provider
for a Directory
and RegularFile
via DirectoryProperty.dir(String) and DirectoryProperty.file(String) respectively. These methods create providers whose values are calculated relative to the location for the DirectoryProperty
they were created from. The values returned from these providers will reflect changes to the DirectoryProperty
.
// A task that generates a source file and writes the result to an output directory
abstract class GenerateSource : DefaultTask() {
// The configuration file to use to generate the source file
@get:InputFile
abstract val configFile: RegularFileProperty
// The directory to write source files to
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@TaskAction
fun compile() {
val inFile = configFile.get().asFile
logger.quiet("configuration file = $inFile")
val dir = outputDir.get().asFile
logger.quiet("output dir = $dir")
val className = inFile.readText().trim()
val srcFile = File(dir, "${className}.java")
srcFile.writeText("public class ${className} { }")
}
}
// Create the source generation task
tasks.register<GenerateSource>("generate") {
// Configure the locations, relative to the project and build directories
configFile.set(layout.projectDirectory.file("src/config.txt"))
outputDir.set(layout.buildDirectory.dir("generated-source"))
}
// Change the build directory
// Don't need to reconfigure the task properties. These are automatically updated as the build directory changes
layout.buildDirectory.set(layout.projectDirectory.dir("output"))
// A task that generates a source file and writes the result to an output directory
abstract class GenerateSource extends DefaultTask {
// The configuration file to use to generate the source file
@InputFile
abstract RegularFileProperty getConfigFile()
// The directory to write source files to
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@TaskAction
def compile() {
def inFile = configFile.get().asFile
logger.quiet("configuration file = $inFile")
def dir = outputDir.get().asFile
logger.quiet("output dir = $dir")
def className = inFile.text.trim()
def srcFile = new File(dir, "${className}.java")
srcFile.text = "public class ${className} { ... }"
}
}
// Create the source generation task
tasks.register('generate', GenerateSource) {
// Configure the locations, relative to the project and build directories
configFile = layout.projectDirectory.file('src/config.txt')
outputDir = layout.buildDirectory.dir('generated-source')
}
// Change the build directory
// Don't need to reconfigure the task properties. These are automatically updated as the build directory changes
layout.buildDirectory = layout.projectDirectory.dir('output')
gradle generate
$ gradle generate > Task :generate configuration file = /home/user/gradle/samples/src/config.txt output dir = /home/user/gradle/samples/output/generated-source BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
gradle generate
$ gradle generate > Task :generate configuration file = /home/user/gradle/samples/kotlin/src/config.txt output dir = /home/user/gradle/samples/kotlin/output/generated-source BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
This example creates providers that represent locations in the project and build directories through Project.getLayout() with ProjectLayout.getBuildDirectory() and ProjectLayout.getProjectDirectory().
To close the loop, note that a DirectoryProperty
, or a simple Directory
, can be turned into a FileTree
that allows the files and directories contained in the directory to be queried with DirectoryProperty.getAsFileTree() or Directory.getAsFileTree(). Moreover, from a DirectoryProperty
, or a Directory
, you can also create FileCollection
instances containing a set of the files contained in the directory with DirectoryProperty.files(Object...) or Directory.files(Object...).
Working with task inputs and outputs
Many builds have several tasks connected together, where one task consumes the outputs of another task as an input. To make this work, we would need to configure each task to know where to look for its inputs and place its outputs, make sure that the producing and consuming tasks are configured with the same location, and attach task dependencies between the tasks. This can be cumbersome and brittle if any of these values are configurable by a user or configured by multiple plugins, as task properties need to be configured in the correct order and locations and task dependencies kept in sync as values change.
The Property
API makes this easier by keeping track of not just the value for a property, which we have seen already, but also the task that produces the value, so that you don’t have to specify it as well. As an example consider the following plugin with a producer and consumer task which are wired together:
abstract class Producer : DefaultTask() {
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun produce() {
val message = "Hello, World!"
val output = outputFile.get().asFile
output.writeText( message)
logger.quiet("Wrote '${message}' to ${output}")
}
}
abstract class Consumer : DefaultTask() {
@get:InputFile
abstract val inputFile: RegularFileProperty
@TaskAction
fun consume() {
val input = inputFile.get().asFile
val message = input.readText()
logger.quiet("Read '${message}' from ${input}")
}
}
val producer = tasks.register<Producer>("producer")
val consumer = tasks.register<Consumer>("consumer")
consumer {
// Connect the producer task output to the consumer task input
// Don't need to add a task dependency to the consumer task. This is automatically added
inputFile.set(producer.flatMap { it.outputFile })
}
producer {
// Set values for the producer lazily
// Don't need to update the consumer.inputFile property. This is automatically updated as producer.outputFile changes
outputFile.set(layout.buildDirectory.file("file.txt"))
}
// Change the build directory.
// Don't need to update producer.outputFile and consumer.inputFile. These are automatically updated as the build directory changes
layout.buildDirectory.set(layout.projectDirectory.dir("output"))
abstract class Producer extends DefaultTask {
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
void produce() {
String message = 'Hello, World!'
def output = outputFile.get().asFile
output.text = message
logger.quiet("Wrote '${message}' to ${output}")
}
}
abstract class Consumer extends DefaultTask {
@InputFile
abstract RegularFileProperty getInputFile()
@TaskAction
void consume() {
def input = inputFile.get().asFile
def message = input.text
logger.quiet("Read '${message}' from ${input}")
}
}
def producer = tasks.register("producer", Producer)
def consumer = tasks.register("consumer", Consumer)
consumer.configure {
// Connect the producer task output to the consumer task input
// Don't need to add a task dependency to the consumer task. This is automatically added
inputFile = producer.flatMap { it.outputFile }
}
producer.configure {
// Set values for the producer lazily
// Don't need to update the consumer.inputFile property. This is automatically updated as producer.outputFile changes
outputFile = layout.buildDirectory.file('file.txt')
}
// Change the build directory.
// Don't need to update producer.outputFile and consumer.inputFile. These are automatically updated as the build directory changes
layout.buildDirectory = layout.projectDirectory.dir('output')
gradle consumer
$ gradle consumer > Task :producer Wrote 'Hello, World!' to /home/user/gradle/samples/output/file.txt > Task :consumer Read 'Hello, World!' from /home/user/gradle/samples/output/file.txt BUILD SUCCESSFUL in 0s 2 actionable tasks: 2 executed
gradle consumer
$ gradle consumer > Task :producer Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/output/file.txt > Task :consumer Read 'Hello, World!' from /home/user/gradle/samples/kotlin/output/file.txt BUILD SUCCESSFUL in 0s 2 actionable tasks: 2 executed
In the example above, the task outputs and inputs are connected before any location is defined. The setters can be called at any time before the task is executed and the change will automatically affect all related input and output properties.
Another important thing to note in this example is the absence of any explicit task dependency. Task outputs represented using Providers
keep track of which task produces their value, and using them as task inputs will implicitly add the correct task dependencies.
Implicit task dependencies also works for input properties that are not files.
abstract class Producer : DefaultTask() {
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun produce() {
val message = "Hello, World!"
val output = outputFile.get().asFile
output.writeText( message)
logger.quiet("Wrote '${message}' to ${output}")
}
}
abstract class Consumer : DefaultTask() {
@get:Input
abstract val message: Property<String>
@TaskAction
fun consume() {
logger.quiet(message.get())
}
}
val producer = tasks.register<Producer>("producer") {
// Set values for the producer lazily
// Don't need to update the consumer.inputFile property. This is automatically updated as producer.outputFile changes
outputFile.set(layout.buildDirectory.file("file.txt"))
}
tasks.register<Consumer>("consumer") {
// Connect the producer task output to the consumer task input
// Don't need to add a task dependency to the consumer task. This is automatically added
message.set(producer.flatMap { it.outputFile }.map { it.asFile.readText() })
}
abstract class Producer extends DefaultTask {
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
void produce() {
String message = 'Hello, World!'
def output = outputFile.get().asFile
output.text = message
logger.quiet("Wrote '${message}' to ${output}")
}
}
abstract class Consumer extends DefaultTask {
@Input
abstract Property<String> getMessage()
@TaskAction
void consume() {
logger.quiet(message.get())
}
}
def producer = tasks.register('producer', Producer) {
// Set values for the producer lazily
// Don't need to update the consumer.inputFile property. This is automatically updated as producer.outputFile changes
outputFile = layout.buildDirectory.file('file.txt')
}
tasks.register('consumer', Consumer) {
// Connect the producer task output to the consumer task input
// Don't need to add a task dependency to the consumer task. This is automatically added
message = producer.flatMap { it.outputFile }.map { it.asFile.text }
}
gradle consumer
$ gradle consumer > Task :producer Wrote 'Hello, World!' to /home/user/gradle/samples/build/file.txt > Task :consumer Hello, World! BUILD SUCCESSFUL in 0s 2 actionable tasks: 2 executed
gradle consumer
$ gradle consumer > Task :producer Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/build/file.txt > Task :consumer Hello, World! BUILD SUCCESSFUL in 0s 2 actionable tasks: 2 executed
Working with collections
Gradle provides two lazy property types to help configure Collection
properties. These work exactly like any other Provider
and, just like file providers, they have additional modeling around them:
-
For
List
values the interface is called ListProperty. You can create a newListProperty
using ObjectFactory.listProperty(Class) and specifying the element type. -
For
Set
values the interface is called SetProperty. You can create a newSetProperty
using ObjectFactory.setProperty(Class) and specifying the element type.
This type of property allows you to overwrite the entire collection value with HasMultipleValues.set(Iterable) and HasMultipleValues.set(Provider) or add new elements through the various add
methods:
-
HasMultipleValues.add(T): Add a single element to the collection
-
HasMultipleValues.add(Provider): Add a lazily calculated element to the collection
-
HasMultipleValues.addAll(Provider): Add a lazily calculated collection of elements to the list
Just like every Provider
, the collection is calculated when Provider.get() is called. The following example shows the ListProperty in action:
abstract class Producer : DefaultTask() {
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun produce() {
val message = "Hello, World!"
val output = outputFile.get().asFile
output.writeText( message)
logger.quiet("Wrote '${message}' to ${output}")
}
}
abstract class Consumer : DefaultTask() {
@get:InputFiles
abstract val inputFiles: ListProperty<RegularFile>
@TaskAction
fun consume() {
inputFiles.get().forEach { inputFile ->
val input = inputFile.asFile
val message = input.readText()
logger.quiet("Read '${message}' from ${input}")
}
}
}
val producerOne = tasks.register<Producer>("producerOne")
val producerTwo = tasks.register<Producer>("producerTwo")
tasks.register<Consumer>("consumer") {
// Connect the producer task outputs to the consumer task input
// Don't need to add task dependencies to the consumer task. These are automatically added
inputFiles.add(producerOne.get().outputFile)
inputFiles.add(producerTwo.get().outputFile)
}
// Set values for the producer tasks lazily
// Don't need to update the consumer.inputFiles property. This is automatically updated as producer.outputFile changes
producerOne { outputFile.set(layout.buildDirectory.file("one.txt")) }
producerTwo { outputFile.set(layout.buildDirectory.file("two.txt")) }
// Change the build directory.
// Don't need to update the task properties. These are automatically updated as the build directory changes
layout.buildDirectory.set(layout.projectDirectory.dir("output"))
abstract class Producer extends DefaultTask {
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
void produce() {
String message = 'Hello, World!'
def output = outputFile.get().asFile
output.text = message
logger.quiet("Wrote '${message}' to ${output}")
}
}
abstract class Consumer extends DefaultTask {
@InputFiles
abstract ListProperty<RegularFile> getInputFiles()
@TaskAction
void consume() {
inputFiles.get().each { inputFile ->
def input = inputFile.asFile
def message = input.text
logger.quiet("Read '${message}' from ${input}")
}
}
}
def producerOne = tasks.register('producerOne', Producer)
def producerTwo = tasks.register('producerTwo', Producer)
tasks.register('consumer', Consumer) {
// Connect the producer task outputs to the consumer task input
// Don't need to add task dependencies to the consumer task. These are automatically added
inputFiles.add(producerOne.get().outputFile)
inputFiles.add(producerTwo.get().outputFile)
}
// Set values for the producer tasks lazily
// Don't need to update the consumer.inputFiles property. This is automatically updated as producer.outputFile changes
producerOne.configure { outputFile = layout.buildDirectory.file('one.txt') }
producerTwo.configure { outputFile = layout.buildDirectory.file('two.txt') }
// Change the build directory.
// Don't need to update the task properties. These are automatically updated as the build directory changes
layout.buildDirectory = layout.projectDirectory.dir('output')
gradle consumer
$ gradle consumer > Task :producerOne Wrote 'Hello, World!' to /home/user/gradle/samples/output/one.txt > Task :producerTwo Wrote 'Hello, World!' to /home/user/gradle/samples/output/two.txt > Task :consumer Read 'Hello, World!' from /home/user/gradle/samples/output/one.txt Read 'Hello, World!' from /home/user/gradle/samples/output/two.txt BUILD SUCCESSFUL in 0s 3 actionable tasks: 3 executed
gradle consumer
$ gradle consumer > Task :producerOne Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/output/one.txt > Task :producerTwo Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/output/two.txt > Task :consumer Read 'Hello, World!' from /home/user/gradle/samples/kotlin/output/one.txt Read 'Hello, World!' from /home/user/gradle/samples/kotlin/output/two.txt BUILD SUCCESSFUL in 0s 3 actionable tasks: 3 executed
Working with maps
Gradle provides a lazy MapProperty type to allow Map
values to be configured.
You can create a MapProperty
instance using ObjectFactory.mapProperty(Class, Class).
Similar to other property types, a MapProperty
has a set() method that you can use to specify the value for the property. There are some additional methods to allow entries with lazy values to be added to the map.
abstract class Generator: DefaultTask() {
@get:Input
abstract val properties: MapProperty<String, Int>
@TaskAction
fun generate() {
properties.get().forEach { entry ->
logger.quiet("${entry.key} = ${entry.value}")
}
}
}
// Some values to be configured later
var b = 0
var c = 0
tasks.register<Generator>("generate") {
properties.put("a", 1)
// Values have not been configured yet
properties.put("b", providers.provider { b })
properties.putAll(providers.provider { mapOf("c" to c, "d" to c + 1) })
}
// Configure the values. There is no need to reconfigure the task
b = 2
c = 3
abstract class Generator extends DefaultTask {
@Input
abstract MapProperty<String, Integer> getProperties()
@TaskAction
void generate() {
properties.get().each { key, value ->
logger.quiet("${key} = ${value}")
}
}
}
// Some values to be configured later
def b = 0
def c = 0
tasks.register('generate', Generator) {
properties.put("a", 1)
// Values have not been configured yet
properties.put("b", providers.provider { b })
properties.putAll(providers.provider { [c: c, d: c + 1] })
}
// Configure the values. There is no need to reconfigure the task
b = 2
c = 3
gradle consumer
$ gradle generate > Task :generate a = 1 b = 2 c = 3 d = 4 BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
Applying a convention to a property
Often you want to apply some convention, or default value, to a property to be used if no value has been configured for the property. You can use the convention()
method for this. This method accepts either a value or a Provider
and this will be used as the value until some other value is configured.
tasks.register("show") {
val property = objects.property(String::class)
// Set a convention
property.convention("convention 1")
doLast {
println("value = " + property.get())
// Can replace the convention
property.convention("convention 2")
println("value = " + property.get())
property.set("value")
// Once a value is set, the convention is ignored
property.convention("ignored convention")
println("value = " + property.get())
}
}
tasks.register('show') {
def property = objects.property(String)
// Set a convention
property.convention("convention 1")
doLast {
println("value = " + property.get())
// Can replace the convention
property.convention("convention 2")
println("value = " + property.get())
property.set("value")
// Once a value is set, the convention is ignored
property.convention("ignored convention")
println("value = " + property.get())
}
}
gradle show
$ gradle show > Task :show value = convention 1 value = convention 2 value = value BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
Making a property unmodifiable
Most properties of a task or project are intended to be configured by plugins or build scripts and then the resulting value used to do something useful. For example, a property that specifies the output directory for a compilation task may start off with a value specified by a plugin, then a build script might change the value to some custom location, then this value is used by the task when it runs. However, once the task starts to run, we want to prevent any further change to the property. This way we avoid errors that result from different consumers, such as the task action or Gradle’s up-to-date checks or build caching or other tasks, using different values for the property.
Lazy properties provide several methods that you can use to disallow changes to their value once the value has been configured.
The finalizeValue() method calculates the final value for the property and prevents further changes to the property.
When the value of the property comes from a Provider
, the provider is queried for its current value and the result becomes the final value for the property.
This final value replaces the provider and the property no longer tracks the value of the provider.
Calling this method also makes a property instance unmodifiable and any further attempts to change the value of the property will fail.
Gradle automatically makes the properties of a task final when the task starts execution.
The finalizeValueOnRead() method is similar, except that the property’s final value is not calculated until the value
of the property is queried. In other words, this method calculates the final value lazily as required, whereas finalizeValue()
calculates the final value eagerly.
This method can be used when the value may be expensive to calculate or may not have been configured yet, but you also want to ensure that all consumers of the property see the same value when they query the value.
Guidelines
This section will introduce guidelines to be successful with the Provider API. To see those guidelines in action, have a look at gradle-site-plugin, a Gradle plugin demonstrating established techniques and practices for plugin development.
-
The Property and Provider types have all of the overloads you need to query or configure a value. For this reason, you should follow the following guidelines:
-
Avoid simplifying calls like
obj.getProperty().get()
andobj.getProperty().set(T)
in your code by introducing additional getters and setters. -
When migrating your plugin to use providers, follow these guidelines:
-
If it’s a new property, expose it as a Property or Provider using a single getter.
-
If it’s incubating, change it to use a Property or Provider using a single getter.
-
If it’s a stable property, add a new Property or Provider and deprecate the old one. You should wire the old getter/setters into the new property as appropriate.
-
Future development
Going forward, new properties will use the Provider API. The Groovy Gradle DSL adds convenience methods to make the use of Providers mostly transparent in build scripts. Existing tasks will have their existing "raw" properties replaced by Providers as needed and in a backwards compatible way. New tasks will be designed with the Provider API.
Provider Files API Reference
Use these types for read-only values:
- Provider<RegularFile>
-
File on disk
- Provider<Directory>
-
Directory on disk
- FileCollection
-
Unstructured collection of files
- FileTree
-
Hierarchy of files
- Factories
-
-
Project.fileTree(Object) will produce a ConfigurableFileTree, or you can use Project.zipTree(Object) and Project.tarTree(Object)
-
Property Files API Reference
Use these types for mutable values:
- RegularFileProperty
-
File on disk
- Factories
- DirectoryProperty
-
Directory on disk
- Factories
- ConfigurableFileCollection
-
Unstructured collection of files
- Factories
- ConfigurableFileTree
-
Hierarchy of files
- Factories
- SourceDirectorySet
-
Hierarchy of source directories
Lazy Collections API Reference
Use these types for mutable values:
- ListProperty<T>
-
a property whose value is
List<T>
- Factories
- SetProperty<T>
-
a property whose value is
Set<T>
- Factories
Lazy Objects API Reference
Use these types for read only values:
- Provider<T>
-
a property whose value is an instance of
T
- Factories
-
-
ProviderFactory.provider(Callable). Always prefer one of the other factory methods over this method.
Use these types for mutable values:
- Property<T>
-
a property whose value is an instance of
T
- Factories