Understanding Properties and Providers
Gradle provides properties that are important for lazy configuration. When implementing a custom task or plugin, it’s imperative that you use these lazy properties.
Gradle represents lazy properties with two interfaces:
Properties and providers manage values and configurations in a build script.
In this example, configuration
is a Property<String>
that is set to the configurationProvider
Provider<String>
.
The configurationProvider
lazily provides the value "Hello, Gradle!"
:
abstract class MyIntroTask : DefaultTask() {
@get:Input
abstract val configuration: Property<String>
@TaskAction
fun printConfiguration() {
println("Configuration value: ${configuration.get()}")
}
}
val configurationProvider: Provider<String> = project.provider { "Hello, Gradle!" }
tasks.register("myIntroTask", MyIntroTask::class) {
configuration.set(configurationProvider)
}
abstract class MyIntroTask extends DefaultTask {
@Input
abstract Property<String> getConfiguration()
@TaskAction
void printConfiguration() {
println "Configuration value: ${configuration.get()}"
}
}
Provider<String> configurationProvider = project.provider { "Hello, Gradle!" }
tasks.register("myIntroTask", MyIntroTask) {
it.setConfiguration(configurationProvider)
}
Understanding Properties
Properties in Gradle are variables that hold values. They can be defined and accessed within the build script to store information like file paths, version numbers, or custom values.
Properties can be set and retrieved using the project
object:
// Setting a property
val simpleMessageProperty: Property<String> = project.objects.property(String::class)
simpleMessageProperty.set("Hello, World from a Property!")
// Accessing a property
println(simpleMessageProperty.get())
// Setting a property
def simpleMessageProperty = project.objects.property(String)
simpleMessageProperty.set("Hello, World from a Property!")
// Accessing a property
println(simpleMessageProperty.get())
Properties:
-
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).
Understanding Providers
Providers are objects that represent a value that may not be immediately available. Providers are useful for lazy evaluation and can be used to model values that may change over time or depend on other tasks or inputs:
// Setting a provider
val simpleMessageProvider: Provider<String> = project.providers.provider { "Hello, World from a Provider!" }
// Accessing a provider
println(simpleMessageProvider.get())
// Setting a provider
def simpleMessageProvider = project.providers.provider { "Hello, World from a Provider!" }
// Accessing a provider
println(simpleMessageProvider.get())
Providers:
-
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 wherever aProvider
is required.
Using Gradle Managed Properties
Gradle’s managed properties allow you to declare properties as abstract getters (Java, Groovy) or abstract properties (Kotlin).
Gradle then automatically provides the implementation for these properties, managing their state.
A property may be mutable, meaning that it has both a get()
method and set()
method:
abstract class MyPropertyTask : DefaultTask() {
@get:Input
abstract val messageProperty: Property<String> // message property
@TaskAction
fun printMessage() {
println(messageProperty.get())
}
}
tasks.register<MyPropertyTask>("myPropertyTask") {
messageProperty.set("Hello, Gradle!")
}
abstract class MyPropertyTask extends DefaultTask {
@Input
abstract Property<String> messageProperty = project.objects.property(String)
@TaskAction
void printMessage() {
println(messageProperty.get())
}
}
tasks.register('myPropertyTask', MyPropertyTask) {
messageProperty.set("Hello, Gradle!")
}
Or read-only, meaning that it has only a get()
method.
The read-only properties are providers:
abstract class MyProviderTask : DefaultTask() {
final val messageProvider: Provider<String> = project.providers.provider { "Hello, Gradle!" } // message provider
@TaskAction
fun printMessage() {
println(messageProvider.get())
}
}
tasks.register<MyProviderTask>("MyProviderTask") {
}
abstract class MyProviderTask extends DefaultTask {
final Provider<String> messageProvider = project.providers.provider { "Hello, Gradle!" }
@TaskAction
void printMessage() {
println(messageProvider.get())
}
}
tasks.register('MyProviderTask', MyProviderTask)
Mutable Managed Properties
A mutable managed property is declared using an abstract getter method of type Property<T>
, where T
can be any serializable type or a fully managed Gradle type.
The property must not have any setter methods.
Here is an example of a task type with an uri
property of type URI
:
public abstract class Download extends DefaultTask {
@Input
public abstract Property<URI> getUri(); // abstract getter of type Property<T>
@TaskAction
void run() {
System.out.println("Downloading " + getUri().get()); // Use the `uri` property
}
}
Note that for a property to be considered a mutable managed property, the property’s getter methods must be abstract
and have public
or protected
visibility.
The property type must be one of the following:
Property Type | Note |
---|---|
|
Where |
|
Configurable regular file location, whose value is mutable |
|
Configurable directory location, whose value is mutable |
|
List of elements of type |
|
Set of elements of type |
|
Map of |
|
A mutable |
|
A mutable |
Read-only Managed Properties (Providers)
You can declare a read-only managed property, also known as a provider, using a getter method of type Provider<T>
.
The method implementation needs to derive the value.
It can, for example, derive the value from other properties.
Here is an example of a task type with a uri
provider that is derived from a location
property:
public abstract class Download extends DefaultTask {
@Input
public abstract Property<String> getLocation();
@Internal
public Provider<URI> getUri() {
return getLocation().map(l -> URI.create("https://" + l));
}
@TaskAction
void run() {
System.out.println("Downloading " + getUri().get()); // Use the `uri` provider (read-only property)
}
}
Read-only Managed Nested Properties (Nested Providers)
You can declare a read-only managed nested property by adding an abstract getter method for the property to a type annotated with @Nested
.
The property should not have any setter methods.
Gradle provides the implementation for the getter method and creates a value for the property.
This pattern is useful when a custom type has a nested complex type which has the same lifecycle.
If the lifecycle is different, consider using Property<NestedType>
instead.
Here is an example of a task type with a resource
property.
The Resource
type is also a custom Gradle type and defines some managed properties:
public abstract class Download extends DefaultTask {
@Nested
public abstract Resource getResource(); // Use an abstract getter method annotated with @Nested
@TaskAction
void run() {
// Use the `resource` property
System.out.println("Downloading https://" + getResource().getHostName().get() + "/" + getResource().getPath().get());
}
}
public interface Resource {
@Input
Property<String> getHostName();
@Input
Property<String> getPath();
}
Read-only Managed "name" Property (Provider)
If the type contains an abstract property called "name" of type String
, Gradle provides an implementation for the getter
method, and extends each constructor with a "name" parameter, which comes before all other constructor parameters.
If the type is an interface, Gradle will provide a constructor with a single "name" parameter and @Inject
semantics.
You can have your type implement or extend the Named interface, which defines such a read-only "name" property:
import org.gradle.api.Named
interface MyType : Named {
// Other properties and methods...
}
class MyTypeImpl(override val name: String) : MyType {
// Implement other properties and methods...
}
// Usage
val instance = MyTypeImpl("myName")
println(instance.name) // Prints: myName
Using Gradle Managed Types
A managed type as an abstract class or interface with no fields and whose properties are all managed. These types have their state entirely managed by Gradle.
For example, this managed type is defined as an interface:
public interface Resource {
@Input
Property<String> getHostName();
@Input
Property<String> getPath();
}
A named managed type is a managed type that additionally has an abstract property "name" of type String
.
Named managed types are especially useful as the element type of NamedDomainObjectContainer:
interface MyNamedType {
val name: String
}
class MyNamedTypeImpl(override val name: String) : MyNamedType
class MyPluginExtension(project: Project) {
val myNamedContainer: NamedDomainObjectContainer<MyNamedType> =
project.container(MyNamedType::class.java) { name ->
project.objects.newInstance(MyNamedTypeImpl::class.java, name)
}
}
interface MyNamedType {
String getName()
}
class MyNamedTypeImpl implements MyNamedType {
String name
MyNamedTypeImpl(String name) {
this.name = name
}
}
class MyPluginExtension {
NamedDomainObjectContainer<MyNamedType> myNamedContainer
MyPluginExtension(Project project) {
myNamedContainer = project.container(MyNamedType) { name ->
new MyNamedTypeImpl(name)
}
}
}
Using Java Bean Properties
Sometimes you may see properties implemented in the Java bean property style.
That is, they do not use a Property<T>
or Provider<T>
types but are instead implemented with concrete setter and getter methods (or corresponding conveniences in Groovy or Kotlin).
This style of property definition is legacy in Gradle and is discouraged:
public class MyTask extends DefaultTask {
private String someProperty;
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(String someProperty) {
this.someProperty = someProperty;
}
@TaskAction
public void myAction() {
System.out.println("SomeProperty: " + someProperty);
}
}