随着构建的复杂性增加,很难跟踪何时何地配置了特定值。Gradle 提供了几种方法来使用惰性配置管理此问题。

writing tasks 4

了解惰性属性

Gradle 提供惰性属性,它会延迟计算属性的值,直到实际需要它为止。

惰性属性提供三个主要好处

  1. 延迟值解析:允许连接 Gradle 模型,而无需知道何时会知道属性的值。例如,你可能希望根据扩展的 sourceDirectories 属性设置任务的输入源文件,但扩展属性值在构建脚本或其他插件配置它们之前是未知的。

  2. 自动任务依赖管理:将一个任务的输出连接到另一个任务的输入,自动确定任务依赖关系。属性实例会携带有关哪个任务(如果有)生成其值的信息。构建作者不必担心使任务依赖关系与配置更改保持同步。

  3. 改进的构建性能:避免在配置期间进行资源密集型工作,从而对构建性能产生积极影响。例如,当配置值来自解析文件但仅在运行功能测试时才使用时,使用属性实例来捕获此值意味着仅在运行功能测试时才解析文件(例如,而不是在运行 clean 时)。

Gradle 使用两个接口表示惰性属性

Provider

表示只能查询而不能更改的值。

  • 具有这些类型的属性是只读的。

  • 方法 Provider.get() 返回属性的当前值。

  • 可以使用 Provider.map(Transformer) 从另一个 Provider 创建 Provider

  • 许多其他类型扩展了 Provider,可以在任何需要 Provider 的地方使用。

Property

表示可以查询和更改的值。

  • 具有这些类型的属性是可配置的。

  • Property 扩展了 Provider 接口。

  • 方法 Property.set(T) 为属性指定一个值,覆盖可能存在的任何值。

  • 方法 Property.set(Provider) 为属性的值指定一个 Provider,覆盖可能存在的任何值。这允许你在配置值之前将 ProviderProperty 实例连接在一起。

  • 可以通过工厂方法 ObjectFactory.property(Class) 创建 Property

惰性属性旨在传递并仅在需要时查询。这通常发生在 执行阶段

以下演示了一个具有可配置 greeting 属性和只读 message 属性的任务

build.gradle.kts
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)
}
build.gradle
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> 的属性来表示计算出的只读消息。消息 Provider 是使用 map() 方法从问候语 Property 创建的;随着问候语属性值的更改,它的值保持最新。

创建 Property 或 Provider 实例

Provider 及其子类型(如 Property)不打算由构建脚本或插件实现。相反,Gradle 提供了工厂方法来创建这些类型的实例。

在前面的示例中,介绍了两个工厂方法

请参阅 快速参考 了解所有可用的类型和工厂。

还可以通过工厂方法 ProviderFactory.provider(Callable) 创建 Provider

没有使用 groovy.lang.Closure 创建提供程序的特定方法。

使用 Groovy 编写插件或构建脚本时,可以使用带有闭包的 map(Transformer) 方法,Groovy 会将闭包转换为 Transformer

类似地,使用 Kotlin 编写插件或构建脚本时,Kotlin 编译器会将 Kotlin 函数转换为 Transformer

连接属性

延迟属性的一个重要特性是它们可以连接在一起,以便对一个属性的更改自动反映在其他属性中。

这是一个将任务属性连接到项目扩展属性的示例

build.gradle.kts
// 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"
}
build.gradle
// 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() 创建的实现

使用文件

使用文件 中,我们介绍了四种用于类似 File 对象的集合类型

只读类型 可配置类型

FileCollection

ConfigurableFileCollection

FileTree

ConfigurableFileTree

所有这些类型也被视为惰性类型。

有更多强类型模型用于表示文件系统元素:DirectoryRegularFile。这些类型不应与标准 Java File 类型混淆,因为它们用于告诉 Gradle 您期望更具体的值,例如目录或非目录、常规文件。

Gradle 为处理这些类型的变量提供了两个专门的 Property 子类型:RegularFilePropertyDirectoryPropertyObjectFactory 具有创建这些类型的方法:ObjectFactory.fileProperty()ObjectFactory.directoryProperty()

DirectoryProperty 还可以用于通过 DirectoryProperty.dir(String)DirectoryProperty.file(String) 分别为 DirectoryRegularFile 创建惰性计算的 Provider。这些方法创建的提供程序的值相对于创建它们的 DirectoryProperty 的位置计算。从这些提供程序返回的值将反映对 DirectoryProperty 的更改。

build.gradle.kts
// 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")
build.gradle
// 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() 创建表示项目和构建目录中的位置的提供程序。

为了闭合循环,请注意 DirectoryProperty 或简单的 Directory 可以转换为 FileTree,允许使用 DirectoryProperty.getAsFileTree()Directory.getAsFileTree() 查询目录中包含的文件和目录。从 DirectoryPropertyDirectory,您可以使用 DirectoryProperty.files(Object...)Directory.files(Object...) 创建包含目录中包含的文件的 FileCollection 实例。

处理任务输入和输出

许多构建将多个任务连接在一起,其中一个任务将另一个任务的输出作为输入使用。

为了实现此功能,我们需要配置每个任务以了解在哪里查找其输入以及在哪里放置其输出。确保生成任务和使用任务配置为相同的位置,并在任务之间附加任务依赖关系。如果这些值中的任何一个都可以由用户配置或由多个插件配置,这可能会很麻烦且脆弱,因为任务属性需要按正确的顺序和位置进行配置,并且任务依赖关系需要在值更改时保持同步。

Property API 通过跟踪属性值和生成该值的 task 来简化此过程。

例如,考虑以下插件,其中包含连接在一起的生产者和使用者任务

build.gradle.kts
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")
build.gradle
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 表示的任务输出会跟踪哪个任务生成其值,并且将它们用作任务输入将隐式添加正确任务依赖关系。

隐式任务依赖关系也适用于不是文件的输入属性

build.gradle.kts
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() }
}
build.gradle
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 完全相同,并且就像文件提供程序一样,它们周围有额外的建模

这种类型的属性允许您使用 HasMultipleValues.set(Iterable)HasMultipleValues.set(Provider) 覆盖整个集合值,或通过各种 add 方法添加新元素

与每个 Provider 一样,当调用 Provider.get() 时,将计算集合。以下示例展示了 ListProperty 的实际应用

build.gradle.kts
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")
build.gradle
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

使用映射

Gradle 提供了一个延迟的 MapProperty 类型,以允许配置 Map 值。你可以使用 ObjectFactory.mapProperty(Class, Class) 创建 MapProperty 实例。

与其他属性类型类似,MapProperty 具有 set() 方法,你可以使用该方法指定属性的值。一些其他方法允许将具有延迟值的条目添加到映射中。

build.gradle.kts
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
build.gradle
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,并且在配置其他值之前,这将用作该值。

build.gradle.kts
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())
    }
}
build.gradle
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

使属性不可修改

任务或项目的大多数属性都旨在由插件或构建脚本进行配置,以便它们可以使用该构建的特定值。

例如,指定编译任务输出目录的属性可能从插件指定的值开始。然后,构建脚本可能会将值更改为某个自定义位置,然后任务在运行时使用此值。但是,一旦任务开始运行,我们希望防止进一步的属性更改。这样,我们可以避免由于不同的使用者(例如任务操作、Gradle 的最新检查、构建缓存或其他任务)对属性使用不同的值而导致的错误。

惰性属性提供了多种方法,您可以在配置值后使用这些方法禁止更改其值。finalizeValue() 方法计算属性的最终值,并防止进一步更改属性。

libVersioning.version.finalizeValue()

当属性的值来自 Provider 时,会查询提供程序以获取其当前值,结果将成为属性的最终值。此最终值替换提供程序,属性不再跟踪提供程序的值。调用此方法还会使属性实例不可修改,并且任何进一步尝试更改属性值的操作都将失败。当任务开始执行时,Gradle 会自动使任务的属性变为最终属性。

finalizeValueOnRead() 方法类似,不同之处在于,只有在查询属性值时才会计算属性的最终值。

modifiedFiles.finalizeValueOnRead()

换句话说,此方法根据需要延迟计算最终值,而 finalizeValue() 则急切地计算最终值。当计算值代价高昂或尚未配置值时,可以使用此方法。您还希望确保属性的所有使用者在查询值时看到相同的值。

使用 Provider API

成功使用 Provider API 的指南

  1. PropertyProvider 类型具有查询或配置值所需的所有重载。因此,您应遵循以下指南

    • 对于可配置属性,直接通过单个 getter 公开 Property

    • 对于不可配置属性,直接通过单个 getter 公开 Provider

  2. 避免通过引入其他 getter 和 setter 来简化代码中的 obj.getProperty().get()obj.getProperty().set(T) 之类的调用。

  3. 在将插件迁移到使用提供程序时,请遵循以下指南

    • 如果是新属性,则使用单个 getter 将其公开为 PropertyProvider

    • 如果是孵化属性,则将其更改为使用单个 getter 的 PropertyProvider

    • 如果是稳定属性,则添加新的 PropertyProvider,并将旧属性弃用。您应该根据需要将旧的 getter/setter 连接到新属性。

Lazy 集合 API 参考

可变值使用这些类型

ListProperty<T>

其值为 List<T> 的属性

SetProperty<T>

其值为 Set<T> 的属性

惰性对象 API 参考

只读值使用这些类型

Provider<T>

一个属性,其值为 T 的实例

工厂

可变值使用这些类型

Property<T>

一个属性,其值为 T 的实例