随着构建复杂性的增加,知道何时何地配置特定值可能变得难以推理. Gradle提供了几种使用惰性配置来管理这种复杂性的方法.

Lazy properties

Gradle提供了惰性属性,这延迟了对属性值的计算,直到真正需要它为止. 这些为构建脚本和插件作者提供了三个主要好处:

  1. 构建作者可以将Gradle模型连接在一起,而不必担心何时知道某个特定属性的值. 例如,您可能想基于扩展的源目录属性设置任务的输入源文件,但是直到构建脚本或某些其他插件对其进行配置之前,扩展属性的值才是未知的.

  2. 构建作者可以将任务的输出属性连接到其他任务的输入属性,Gradle会基于此连接自动确定任务依赖性. 属性实例携带有关哪个任务(如果有)产生其价值的信息. 构建作者无需担心使任务依赖项与配置更改保持同步.

  3. 构建作者可以避免在配置阶段花费大量资源,这可能会对构建性能产生重大影响. 例如,当配置值来自解析文件但仅在运行功能测试时使用时,使用属性实例捕获这意味着仅在运行功能测试时才解析文件,例如, clean运行.

Gradle通过两个接口表示惰性属性:

  • 提供者表示只能查询而不能更改的值.

    • 这些类型的属性是只读的.

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

    • Provider可以从另一个被创建Provider使用Provider.map(变压器) .

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

  • 属性表示可以查询和更改的值.

    • 这些类型的属性是可配置的.

    • Property扩展了Provider接口.

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

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

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

惰性属性旨在传递,仅在需要时才查询. 通常,这将在执行阶段发生. 有关Gradle构建阶段的更多信息,请参见构建生命周期 .

以下内容演示了一个任务,该任务具有可配置的greeting属性和从该属性派生的只读message属性:

示例1.使用只读和可配置属性
build.gradle
// A task that displays a greeting
class Greeting extends DefaultTask {
    // A configurable greeting
    @Input
    final Property<String> greeting = project.objects.property(String)

    // Read-only property calculated from the greeting
    @Internal
    final Provider<String> message = greeting.map { it + ' from Gradle' }

    @TaskAction
    void printMessage() {
        logger.quiet(message.get())
    }
}

task greeting(type: Greeting) {
    // Configure the greeting
    greeting.set('Hi')

    // Note that an assignment statement can be used instead of calling Property.set()
    greeting = 'Hi'
}
build.gradle.kts
// A task that displays a greeting
open class Greeting : DefaultTask() {
    // Configurable by the user
    @Input
    val greeting: Property<String> = project.objects.property()

    // Read-only property calculated from the greeting
    @Internal
    val message: Provider<String> = greeting.map { it + " from Gradle" }

    @TaskAction
    fun printMessage() {
        logger.quiet(message.get())
    }
}

tasks.register<Greeting>("greeting") {
    // Configure the greeting
    greeting.set("Hi")
}
gradle greeting输出
$ gradle greeting

> Task :greeting
Hi from Gradle

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Greeting任务的类型为Property<String>的属性表示可配置的问候语,而类型为Provider<String>的属性表示计算的只读消息. 消息Provider是使用map()方法从greeting Property创建的,因此,当greeting属性的值更改时,其值将保持最新.

请注意,Gradle Groovy DSL为任务实现中的每个Property typed属性生成setter方法. 这些设置方法使您可以方便地使用赋值( = )运算符配置属性.

Kotlin DSL便利性将在将来的版本中添加.

Creating a Property or Provider instance

Provider或其子类型(例如Property都不打算由构建脚本或插件作者实现. Gradle提供了工厂方法来创建这些类型的实例. 有关所有类型和工厂的信息,请参阅快速参考 . 在前面的示例中,我们看到了两种工厂方法:

Provider也可以通过工厂方法ProviderFactory.provider(Callable)创建 . 您应该更喜欢使用map() ,因为它具有一些有用的好处,我们将在后面看到.

没有使用groovy.lang.Closure创建提供程序的特定方法. 用Groovy编写插件或构建脚本时,可以将map(Transformer)方法与闭包一起使用,Groovy将负责将闭包转换为Transformer . 您可以在前面的示例中看到这一点.

同样,当使用Kotlin编写插件或构建脚本时,Kotlin编译器将负责将Kotlin函数转换为Transformer .

Connecting properties together

惰性属性的一个重要特征是它们可以连接在一起,以便对一个属性的更改会自动反映在其他属性中. 这是一个示例,其中任务的属性连接到项目扩展的属性:

示例2.将属性连接在一起
build.gradle
// A project extension
class MessageExtension {
    // A configurable greeting
    final Property<String> greeting

    @javax.inject.Inject
    MessageExtension(ObjectFactory objects) {
        greeting = objects.property(String)
    }
}

// A task that displays a greeting
class Greeting extends DefaultTask {
    // A configurable greeting
    @Input
    final Property<String> greeting = project.objects.property(String)

    // 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
task greeting(type: Greeting) {
    // Attach the greeting from the project extension
    // Note that the values of the project extension have not been configured yet
    greeting.set(project.messages.greeting)

    // Note that an assignment statement can be used instead of calling Property.set()
    greeting = project.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'
}
build.gradle.kts
// A project extension
open class MessageExtension(objects: ObjectFactory) {
    // A configurable greeting
    val greeting: Property<String> = objects.property()
}

// A task that displays a greeting
open class Greeting : DefaultTask() {
    // Configurable by the user
    @Input
    val greeting: Property<String> = project.objects.property()

    // 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("messages", MessageExtension::class)

// 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)
}

configure<MessageExtension> {
    // 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")
}
gradle greeting输出
$ 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()创建的实现.

Working with files

"使用文件"中 ,我们为类File对象引入了四种收集类型:

表1.文件概要回顾
只读类型 可配置类型

FileCollection

ConfigurableFileCollection

FileTree

ConfigurableFileTree

所有这些类型也被认为是惰性类型.

在本节中,我们将介绍更强类型的模型类型来表示文件系统的元素: DirectoryRegularFile . 这些类型不应与标准Java File类型混淆,因为它们用于告诉Gradle和其他人您期望更具体的值,例如目录或非目录常规文件.

Gradle提供了两个专用的Property子类型来处理这些类型的值: RegularFilePropertyDirectoryProperty . ObjectFactory具有创建这些方法的方法: ObjectFactory.fileProperty()ObjectFactory.directoryProperty() .

DirectoryProperty还可以用于RegularFile通过DirectoryProperty.dir(String)DirectoryProperty.file(String)DirectoryRegularFile创建延迟评估的Provider . 这些方法创建提供程序,其值是根据创建它们的DirectoryProperty的位置计算得出的. 这些提供程序返回的值将反映DirectoryProperty更改.

例子3.使用文件和目录属性
build.gradle
// A task that generates a source file and writes the result to an output directory
class GenerateSource extends DefaultTask {
    // The configuration file to use to generate the source file
    @InputFile
    final RegularFileProperty configFile = project.objects.fileProperty()

    // The directory to write source files to
    @OutputDirectory
    final DirectoryProperty outputDir = project.objects.directoryProperty()

    @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
task generate(type: GenerateSource) {
    // Configure the locations, relative to the project and build directories
    configFile = project.layout.projectDirectory.file('src/main/config.txt')
    outputDir = project.layout.buildDirectory.dir('generated-source')

    // Note that a `File` instance can be used as a convenience to set a location
    configFile = file('src/config.txt')
}

// Change the build directory
// Don't need to reconfigure the task properties. These are automatically updated as the build directory changes
buildDir = 'output'
build.gradle.kts
// A task that generates a source file and writes the result to an output directory
open class GenerateSource @javax.inject.Inject constructor(objects: ObjectFactory): DefaultTask() {
    @InputFile
    val configFile: RegularFileProperty = objects.fileProperty()

    @OutputDirectory
    val outputDir: DirectoryProperty = objects.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(project.layout.projectDirectory.file("src/config.txt"))
    outputDir.set(project.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
buildDir = file("output")
gradle print输出
$ gradle print

> Task :generate
configuration file = /home/user/gradle/samples/groovy/src/config.txt
output dir = /home/user/gradle/samples/groovy/output/generated-source

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
gradle print输出
$ gradle print

> 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 ,该文件FileTree允许使用DirectoryProperty.getAsFileTree()Directory.getAsFileTree()查询目录中包含的文件和目录. 此外,还可以从DirectoryPropertyDirectory ,使用DirectoryProperty.files(Object ...)Directory.files(Object ...)创建FileCollection实例,其中包含目录中包含的一组文件.

Working with task inputs and outputs

许多内部版本将多个任务连接在一起,其中一个任务将另一个任务的输出作为输入. 为了使这项工作有效,我们将需要配置每个任务以知道在哪里寻找其输入并放置其输出,确保生产和消费任务配置在相同的位置,并在任务之间附加任务依赖性. 如果这些值中的任何一个可由用户配置或由多个插件配置,则这将很麻烦且脆弱,因为需要按正确的顺序配置任务属性,并且随着值的更改,位置和任务相关性必须保持同步.

Property API通过不仅跟踪我们已经看到的属性值,而且还跟踪产生该值的任务,从而使此操作变得更加容易,因此您也不必指定它. 例如,考虑以下带有生产者和消费者任务的插件,它们相互连接在一起:

例子4.隐式任务输入文件的依赖性
build.gradle
class Producer extends DefaultTask {
    @OutputFile
    final RegularFileProperty outputFile = project.objects.fileProperty()

    @TaskAction
    void produce() {
        String message = 'Hello, World!'
        def output = outputFile.get().asFile
        output.text = message
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

class Consumer extends DefaultTask {
    @InputFile
    final RegularFileProperty inputFile = project.objects.fileProperty()

    @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)

// 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
consumer.configure {
    inputFile = producer.flatMap { it.outputFile }
}

// Set values for the producer lazily
// Don't need to update the consumer.inputFile property. This is automatically updated as producer.outputFile changes
producer.configure {
    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
buildDir = 'output'
build.gradle.kts
open class Producer : DefaultTask() {
    @OutputFile
    val outputFile: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun produce() {
        val message = "Hello, World!"
        val output = outputFile.get().asFile
        output.writeText( message)
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

open class Consumer : DefaultTask() {
    @InputFile
    val inputFile: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun consume() {
        val input = inputFile.get().asFile
        val message = input.readText()
        logger.quiet("Read '${message}' from ${input}")
    }
}

val producer by tasks.registering(Producer::class)
val consumer by tasks.registering(Consumer::class)

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.set(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.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
buildDir = file("output")
gradle consumergradle consumer
$ gradle consumer

> Task :producer
Wrote 'Hello, World!' to /home/user/gradle/samples/groovy/output/file.txt

> Task :consumer
Read 'Hello, World!' from /home/user/gradle/samples/groovy/output/file.txt

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
gradle consumergradle 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

在上面的示例中,在定义任何位置之前已连接任务输出和输入. 可以在执行任务之前的任何时间调用设置器,更改将自动影响所有相关的输入和输出属性.

在此示例中要注意的另一件重要事情是,没有任何明确的任务依赖性. 使用Providers表示的任务输出会跟踪哪个任务产生其值,并将它们用作任务输入将隐式添加正确的任务依赖性.

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

例子5.隐式任务输入依赖
build.gradle
class Producer extends DefaultTask {
    @OutputFile
    final RegularFileProperty outputFile = project.objects.fileProperty()

    @TaskAction
    void produce() {
        String message = 'Hello, World!'
        def output = outputFile.get().asFile
        output.text = message
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

class Consumer extends DefaultTask {
    @Input
    final Property<String> message = project.objects.property(String)

    @TaskAction
    void consume() {
        logger.quiet(message.get())
    }
}

task producer(type: Producer)
task consumer(type: 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
consumer.message = producer.outputFile.map { it.asFile.text }

// Set values for the producer lazily
producer.outputFile = layout.buildDirectory.file('file.txt')
build.gradle.kts
open class Producer : DefaultTask() {
    @OutputFile
    val outputFile: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun produce() {
        val message = "Hello, World!"
        val output = outputFile.get().asFile
        output.writeText( message)
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

open class Consumer : DefaultTask() {
    @Input
    val message: Property<String> = project.objects.property(String::class)

    @TaskAction
    fun consume() {
        logger.quiet(message.get())
    }
}

val producer by tasks.registering(Producer::class) {
    // 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"))
}
val consumer by tasks.registering(Consumer::class) {
    // 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.map { it.outputFile.get().asFile.readText() })
}
gradle consumergradle consumer
$ gradle consumer

> Task :producer
Wrote 'Hello, World!' to /home/user/gradle/samples/groovy/build/file.txt

> Task :consumer
Hello, World!

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
gradle consumergradle 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提供了两种惰性属性类型,以帮助配置Collection属性. 这些文件的工作原理与任何其他Provider完全一样,并且就像文件提供程序一样,它们具有其他建模功能:

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

就像每个Provider ,在调用Provider.get()时计算集合. 以下示例显示了运行中的ListProperty

例子6.列表属性
build.gradle
class Producer extends DefaultTask {
    @OutputFile
    final RegularFileProperty outputFile = project.objects.fileProperty()

    @TaskAction
    void produce() {
        String message = 'Hello, World!'
        def output = outputFile.get().asFile
        output.text = message
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

class Consumer extends DefaultTask {
    @InputFiles
    final ListProperty<RegularFile> inputFiles = project.objects.listProperty(RegularFile)

    @TaskAction
    void consume() {
        inputFiles.get().each { inputFile ->
            def input = inputFile.asFile
            def message = input.text
            logger.quiet("Read '${message}' from ${input}")
        }
    }
}

task producerOne(type: Producer)
task producerTwo(type: Producer)
task consumer(type: 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
consumer.inputFiles.add(producerOne.outputFile)
consumer.inputFiles.add(producerTwo.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
buildDir = 'output'
build.gradle.kts
open class Producer : DefaultTask() {
    @OutputFile
    val outputFile: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun produce() {
        val message = "Hello, World!"
        val output = outputFile.get().asFile
        output.writeText( message)
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

open class Consumer : DefaultTask() {
    @InputFiles
    val inputFiles: ListProperty<RegularFile> = project.objects.listProperty(RegularFile::class)

    @TaskAction
    fun consume() {
        inputFiles.get().forEach { inputFile ->
            val input = inputFile.asFile
            val message = input.readText()
            logger.quiet("Read '${message}' from ${input}")
        }
    }
}

val producerOne by tasks.registering(Producer::class)
val producerTwo by tasks.registering(Producer::class)
val consumer by tasks.registering(Consumer::class) {
    // 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
buildDir = file("output")
gradle consumergradle consumer
$ gradle consumer

> Task :producerOne
Wrote 'Hello, World!' to /home/user/gradle/samples/groovy/output/one.txt

> Task :producerTwo
Wrote 'Hello, World!' to /home/user/gradle/samples/groovy/output/two.txt

> Task :consumer
Read 'Hello, World!' from /home/user/gradle/samples/groovy/output/one.txt
Read 'Hello, World!' from /home/user/gradle/samples/groovy/output/two.txt

BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 executed
Output of 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提供了一种惰性MapProperty类型,以允许配置Map值. 您可以使用ObjectFactory.mapProperty(Class,Class)创建MapProperty实例.

与其他属性类型类似, MapProperty具有set()方法,可用于指定属性的值. 还有一些其他方法可以将具有惰性值的条目添加到映射中.

例子7.地图属性
build.gradle
class Generator extends DefaultTask {
    @Input
    final MapProperty<String, Integer> properties = project.objects.mapProperty(String, Integer)

    @TaskAction
    void generate() {
        properties.get().each { key, value ->
            logger.quiet("${key} = ${value}")
        }
    }
}

// Some values to be configured later
def b = 0
def c = 0

task generate(type: 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
build.gradle.kts
open class Generator: DefaultTask() {
    @Input
    val properties: MapProperty<String, Int> = project.objects.mapProperty(String::class, Int::class)

    @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
gradle consumergradle 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

如果尚未为该属性配置任何值,通常您希望将某些约定或默认值应用于该属性. 您可以为此使用convention()方法. 此方法接受值或Provider ,并将其用作值,直到配置了其他值.

例子8.属性约定
build.gradle
task show {
    doLast {
        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("value")

        // Once a value is set, the convention is ignored
        property.convention("ignored convention")
        println("value = " + property.get())
    }
}
build.gradle.kts
tasks.register("show") {
    doLast {
        val property = objects.property(String::class)

        property.convention("convention 1")
        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

任务或项目的大多数属性旨在由插件或构建脚本配置,然后将所得的值用于做一些有用的事情. 例如,为编译任务指定输出目录的属性可以从插件指定的值开始,然后构建脚本可能将其配置为某个自定义位置,然后任务在运行时使用该值. 但是,一旦任务开始运行,我们希望防止对该属性进行任何进一步的更改. 这样,我们可以避免使用不同属性的值来避免由不同使用者产生的错误,例如任务操作或Gradle的最新检查或构建缓存或其他任务.

惰性属性提供了finalizeValue()方法来使之明确. 从那时起,调用此方法将使属性实例不可修改,并且任何进一步尝试更改属性值的尝试都会失败. 当任务开始执行时,Gradle会自动使任务的属性最终化.

Guidelines

本节将介绍成功使用Provider API的准则. 要查看这些指导原则,请查看gradle-site-plugin ,这是一个Gradle插件,演示了已建立的插件开发技术和实践.

  • 属性提供者类型具有查询或配置值所需的所有重载. 因此,您应该遵循以下准则:

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

    • 对于不可配置的属性,请直接通过单个getter公开提供程序 .

  • 避免通过引入其他getter和setter来简化obj.getProperty().get()obj.getProperty().set(T)类的调用.

  • 在将插件迁移为使用提供程序时,请遵循以下准则:

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

    • If it’s incubating, change it to use a Property or Provider using a single getter.

    • 如果它是稳定的属性,请添加新的属性提供者,并弃用旧的属性提供者 . 您应将旧的吸气剂/装料器适当地连接到新的属性中.

Future development

展望未来,新属性将使用提供者API. Groovy Gradle DSL添加了便捷方法,以使在构建脚本中对提供程序的使用几乎是透明的. 现有任务将根据需要并以向后兼容的方式将其现有的"原始"属性替换为提供程序. 新任务将使用提供程序API设计.

Property Files API Reference

将这些类型用于可变值:

Lazy Collections API Reference

将这些类型用于可变值:

ListProperty<T>

一个值为List<T>的属性

SetProperty<T>

一个值为Set<T>的属性

Lazy Objects API Reference

将这些类型用于只读值:

Provider<T>

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

Factories

将这些类型用于可变值:

Property<T>

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