惰性配置任务
随着构建变得越来越复杂,何时何地配置某个特定值变得难以追踪。Gradle 提供了几种使用 惰性配置 来管理这个问题的方法。

理解惰性属性
Gradle 提供了惰性属性,它会延迟计算属性的值,直到实际需要时才会进行。
惰性属性提供三个主要优点
-
延迟值解析:允许连接 Gradle 模型,而无需事先知道属性的值何时确定。例如,您可能想根据扩展的源目录属性来设置任务的输入源文件,但在构建脚本或某些其他插件配置这些属性之前,扩展属性的值是未知的。
-
自动任务依赖管理:将一个任务的输出连接到另一个任务的输入,自动确定任务依赖。属性实例会携带关于哪个任务(如果存在)产生其值的信息。构建作者无需担心任务依赖与配置更改保持同步。
-
改进构建性能:避免在配置阶段进行资源密集型工作,从而对构建性能产生积极影响。例如,当配置值来自解析文件,但仅在运行功能测试时才使用时,使用属性实例来捕获此值意味着只有在功能测试运行时(而不是在例如运行
clean
时)才会解析该文件。
Gradle 使用两个接口表示惰性属性
- Provider
-
表示一个只能查询但不能更改的值。
-
这些类型的属性是只读的。
-
方法 Provider.get() 返回属性的当前值。
-
可以使用 Provider.map(Transformer) 从另一个
Provider
创建新的Provider
。 -
许多其他类型扩展了
Provider
,可以在需要Provider
的地方使用。
-
- Property
-
表示一个可以查询和更改的值。
-
这些类型的属性是可配置的。
-
Property
扩展了Provider
接口。 -
方法 Property.set(T) 为属性指定一个值,覆盖可能已存在的任何值。
-
方法 Property.set(Provider) 为属性指定一个
Provider
作为值来源,覆盖可能已存在的任何值。这允许您在配置值之前将Provider
和Property
实例连接起来。 -
可以使用工厂方法 ObjectFactory.property(Class) 创建
Property
实例。
-
惰性属性旨在被传递,并且仅在需要时才查询其值。这通常发生在 执行阶段。
以下示例演示了一个任务,它包含一个可配置的 greeting
属性和一个只读的 message
属性
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 | 一个显示问候语的任务 |
2 | 一个可配置的问候语 |
3 | 从问候语计算出的只读属性 |
4 | 配置问候语 |
5 | 调用 Property.set() 的另一种表示法 |
$ gradle greeting > Task :greeting Hi from Gradle BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
Greeting
任务有一个类型为 Property<String>
的属性用于表示可配置的问候语,还有一个类型为 Provider<String>
的属性用于表示计算出的只读消息。通过使用 map()
方法,从问候语 Property
创建了消息 Provider
;其值随着问候语属性值的变化而保持最新。
创建 Property 或 Provider 实例
Provider
及其子类型(如 Property
)都不应由构建脚本或插件实现。相反,Gradle 提供了工厂方法来创建这些类型的实例。
在前面的示例中,展示了两个工厂方法
-
ObjectFactory.property(Class) 创建新的
Property
实例。可以从 Project.getObjects() 引用 ObjectFactory 的实例,也可以通过构造函数或方法注入ObjectFactory
。 -
Provider.map(Transformer) 从现有的
Provider
或Property
实例创建新的Provider
。
有关所有可用类型和工厂,请参阅快速参考。
还可以通过工厂方法 ProviderFactory.provider(Callable) 创建 Provider
。
没有使用 使用 Groovy 编写插件或构建脚本时,可以将 同样,使用 Kotlin 编写插件或构建脚本时,Kotlin 编译器会将 Kotlin 函数转换为 |
连接属性
惰性属性的一个重要特性是它们可以相互连接,以便一个属性的更改会自动反映在其他属性中。
这是一个任务属性连接到项目扩展属性的示例
// 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 = 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 = "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 > Task :greeting Hi from Gradle BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
此示例调用 Property.set(Provider) 方法,将一个 Provider
附加到 Property
,以提供属性的值。在这种情况下,Provider
恰好也是一个 Property
,但您可以连接任何 Provider
实现,例如使用 Provider.map()
创建的 Provider。
处理文件
在处理文件中,我们介绍了用于 File
-like 对象的四种集合类型
只读类型 | 可配置类型 |
---|---|
当顺序重要时,请避免使用 FileTree — 它没有保证的、稳定的文件顺序,可能导致不可预测的行为。 |
所有这些类型也被视为惰性类型。
有更强类型化的模型用于表示文件系统元素:Directory 和 RegularFile。这些类型不应与标准 Java File 类型混淆,因为它们用于告诉 Gradle 您期望更具体的值,例如目录或非目录的普通文件。
Gradle 提供了两种专门的 Property
子类型来处理这些类型的值:RegularFileProperty 和 DirectoryProperty。ObjectFactory 提供了创建这些类型的方法:ObjectFactory.fileProperty() 和 ObjectFactory.directoryProperty()。
DirectoryProperty
还可以通过 DirectoryProperty.dir(String) 和 DirectoryProperty.file(String) 分别为 Directory
和 RegularFile
创建惰性求值的 Provider
。这些方法创建的 Provider 的值是相对于它们所基于的 DirectoryProperty
的位置计算的。这些 Provider 返回的值将反映对 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 = 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")
// 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 > 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 > 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
此示例通过 Project.getLayout() 与 ProjectLayout.getBuildDirectory() 和 ProjectLayout.getProjectDirectory() 一起使用,创建表示项目和构建目录位置的 Provider。
最后,请注意 DirectoryProperty
或简单的 Directory
可以转换为 FileTree
,从而可以使用 DirectoryProperty.getAsFileTree() 或 Directory.getAsFileTree() 查询目录中包含的文件和目录。从 DirectoryProperty
或 Directory
中,您可以使用 DirectoryProperty.files(Object...) 或 Directory.files(Object...) 创建包含目录中一组文件的 FileCollection
实例。
处理任务输入和输出
许多构建都有多个相互连接的任务,其中一个任务将另一个任务的输出作为输入来消费。
为了实现这一点,我们需要配置每个任务以知道在哪里查找其输入以及在哪里放置其输出。确保生产任务和消费任务配置在同一位置,并在任务之间附加任务依赖。如果这些值中的任何一个可由用户配置或由多个插件配置,这可能会变得繁琐和脆弱,因为任务属性需要以正确的顺序和位置配置,并且随着值的变化,任务依赖需要保持同步。
Property
API 通过跟踪属性的值以及产生该值的任务,使这变得更容易。
例如,考虑以下包含生产者任务和消费者任务的插件,它们被连接在一起
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 = 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 = 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")
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 > 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 > 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
在上面的示例中,任务输出和输入在定义任何位置之前就被连接起来了。Setter 可以在任务执行之前的任何时候调用,并且更改将自动影响所有相关的输入和输出属性。
在此示例中需要注意的另一件重要事情是没有任何显式任务依赖。使用 Providers
表示的任务输出会跟踪哪个任务产生其值,并且将它们用作任务输入将隐式添加正确的任务依赖。
隐式任务依赖也适用于非文件的输入属性
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 = 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.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 > 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 > 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
处理集合
Gradle 提供了两种惰性属性类型来帮助配置 Collection
属性。
它们的作用与任何其他 Provider
完全相同,并且就像文件 Provider 一样,它们周围有额外的模型
-
对于
List
值,接口称为 ListProperty。
可以使用 ObjectFactory.listProperty(Class) 并指定元素类型来创建新的ListProperty
。 -
对于
Set
值,接口称为 SetProperty。
可以使用 ObjectFactory.setProperty(Class) 并指定元素类型来创建新的SetProperty
。
这种类型的属性允许您使用 HasMultipleValues.set(Iterable) 和 HasMultipleValues.set(Provider) 覆盖整个集合值,或者通过各种 add
方法添加新元素
-
HasMultipleValues.add(T): 向集合添加单个元素
-
HasMultipleValues.add(Provider): 向集合添加一个惰性计算的元素
-
HasMultipleValues.addAll(Provider): 向列表添加一个惰性计算的元素集合
就像每个 Provider
一样,集合在调用 Provider.get() 时进行计算。以下示例展示了 ListProperty 的实际应用
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 = layout.buildDirectory.file("one.txt") }
producerTwo { 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")
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 > 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 > 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
处理 Map
Gradle 提供了惰性 MapProperty 类型,以允许配置 Map
值。您可以使用 ObjectFactory.mapProperty(Class, Class) 创建 MapProperty
实例。
与其他属性类型类似,MapProperty
有一个 set() 方法,您可以使用它来指定属性的值。还有一些额外的方法允许向 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 generate > Task :generate a = 1 b = 2 c = 3 d = 4 BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
为属性应用约定值
通常,您希望为属性应用一些 约定 或默认值,以便在未配置任何值时使用。您可以使用 convention()
方法来实现这一点。此方法接受一个值或一个 Provider
,并且该值将一直被使用,直到配置了其他值。
tasks.register("show") {
val property = objects.property(String::class)
// Set a convention
property.convention("convention 1")
println("value = " + property.get())
// Can replace the convention
property.convention("convention 2")
println("value = " + property.get())
property.set("explicit value")
// Once a value is set, the convention is ignored
property.convention("ignored convention")
doLast {
println("value = " + property.get())
}
}
tasks.register("show") {
def property = objects.property(String)
// Set a convention
property.convention("convention 1")
println("value = " + property.get())
// Can replace the convention
property.convention("convention 2")
println("value = " + property.get())
property.set("explicit value")
// Once a value is set, the convention is ignored
property.convention("ignored convention")
doLast {
println("value = " + property.get())
}
}
$ gradle show value = convention 1 value = convention 2 > Task :show value = explicit value BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
从哪里应用约定值?
在配置阶段(即执行之前),有几个适合为属性设置约定值的位置。
// setting convention when registering a task from plugin
class GreetingPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.getTasks().register<GreetingTask>("hello") {
greeter.convention("Greeter")
}
}
}
apply<GreetingPlugin>()
tasks.withType<GreetingTask>().configureEach {
// setting convention from build script
guest.convention("Guest")
}
abstract class GreetingTask : DefaultTask() {
// setting convention from constructor
@get:Input
abstract val guest: Property<String>
init {
guest.convention("person2")
}
// setting convention from declaration
@Input
val greeter = project.objects.property<String>().convention("person1")
@TaskAction
fun greet() {
println("hello, ${guest.get()}, from ${greeter.get()}")
}
}
// setting convention when registering a task from plugin
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.getTasks().register("hello", GreetingTask) {
greeter.convention("Greeter")
}
}
}
apply plugin: GreetingPlugin
tasks.withType(GreetingTask).configureEach {
// setting convention from build script
guest.convention("Guest")
}
abstract class GreetingTask extends DefaultTask {
// setting convention from constructor
@Input
abstract Property<String> getGuest()
GreetingTask() {
guest.convention("person2")
}
// setting convention from declaration
@Input
final Property<String> greeter = project.objects.property(String).convention("person1")
@TaskAction
void greet() {
println("hello, ${guest.get()}, from ${greeter.get()}")
}
}
从插件的 apply()
方法
插件作者可以在插件的 apply()
方法中为惰性属性配置约定值,同时进行定义该属性的任务或扩展的初步配置。这对于常规插件(旨在分发和广泛使用)以及内部约定插件(通常以统一方式为整个构建配置第三方插件定义的属性)都非常有效。
// setting convention when registering a task from plugin
class GreetingPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.getTasks().register<GreetingTask>("hello") {
greeter.convention("Greeter")
}
}
}
// setting convention when registering a task from plugin
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.getTasks().register("hello", GreetingTask) {
greeter.convention("Greeter")
}
}
}
从构建脚本
构建工程师可以从共享构建逻辑中为惰性属性配置约定值,该逻辑以标准方式为整个构建配置任务(例如,来自第三方插件)。
apply<GreetingPlugin>()
tasks.withType<GreetingTask>().configureEach {
// setting convention from build script
guest.convention("Guest")
}
tasks.withType(GreetingTask).configureEach {
// setting convention from build script
guest.convention("Guest")
}
请注意,对于特定于项目的值,您应该首选设置显式值(例如,使用 Property.set(…)
或 ConfigurableFileCollection.setFrom(…)
),而不是使用约定值,因为约定值仅用于定义默认值。
从任务初始化
任务作者可以在任务构造函数或(如果是 Kotlin)初始化块中为惰性属性配置约定值。这种方法适用于具有简单默认值的属性,但如果需要额外的上下文(任务实现外部的)来设置合适的默认值,则不适合此方法。
// setting convention from constructor
@get:Input
abstract val guest: Property<String>
init {
guest.convention("person2")
}
// setting convention from constructor
@Input
abstract Property<String> getGuest()
GreetingTask() {
guest.convention("person2")
}
在属性声明旁边
您可以在声明属性的位置旁边为惰性属性配置约定值。请注意,此选项不适用于托管属性,并且与从任务构造函数配置约定值具有相同的注意事项。
// setting convention from declaration
@Input
val greeter = project.objects.property<String>().convention("person1")
// setting convention from declaration
@Input
final Property<String> greeter = project.objects.property(String).convention("person1")
使属性不可修改
任务或项目的大多数属性旨在由插件或构建脚本配置,以便它们可以为该构建使用特定值。
例如,指定编译任务输出目录的属性可能始于插件指定的值。然后构建脚本可能将该值更改为某个自定义位置,然后任务在运行时使用该值。但是,一旦任务开始运行,我们希望阻止进一步的属性更改。这样可以避免由于不同的消费者(例如任务动作、Gradle 的最新检查、构建缓存或其他任务)对属性使用不同值而导致的错误。
惰性属性提供了几种方法,可用于在值配置后禁止更改其值。finalizeValue() 方法计算属性的 最终 值,并防止对属性进行进一步更改。
libVersioning.version.finalizeValue()
当属性的值来自一个 Provider
时,Gradle 会查询该 Provider 的当前值,结果将成为属性的最终值。这个最终值会取代 Provider,并且该属性不再跟踪 Provider 的值。调用此方法还会使属性实例不可修改,任何进一步更改属性值的尝试都将失败。当任务开始执行时,Gradle 会自动使任务的属性最终确定。
finalizeValueOnRead() 方法类似,区别在于属性的最终值直到查询属性值时才计算。
modifiedFiles.finalizeValueOnRead()
换句话说,此方法根据需要惰性地计算最终值,而 finalizeValue()
则会急切地计算最终值。当值计算成本较高或可能尚未配置时,可以使用此方法。您还需要确保属性的所有消费者在查询值时看到相同的值。
使用 Provider API
成功使用 Provider API 的指南
Provider Files API 参考
对 只读 值使用这些类型
- Provider<RegularFile>
-
磁盘上的文件
- Provider<Directory>
-
磁盘上的目录
- FileCollection
-
非结构化文件集合
- FileTree
-
文件层级结构
Property Files API 参考
对 可变 值使用这些类型
- RegularFileProperty
-
磁盘上的文件
- DirectoryProperty
-
磁盘上的目录
- ConfigurableFileCollection
-
非结构化文件集合
- ConfigurableFileTree
-
文件层级结构
- SourceDirectorySet
-
源目录层级结构
惰性对象 API 参考
对 只读 值使用这些类型
- Provider<T>
-
一个值是
T
实例的属性- 工厂方法
-
-
ProviderFactory.provider(Callable)。始终优先使用其他工厂方法之一,而不是此方法。
对 可变 值使用这些类型
- Property<T>
-
一个值是
T
实例的属性