隨著建置複雜度的增加,很難追蹤特定值何時何地設定。Gradle 提供多種方法來使用延遲設定管理這個問題。

writing tasks 4

了解 Lazy 屬性

Gradle 提供延遲屬性,會延遲計算屬性值,直到實際需要為止。

延遲屬性提供三個主要好處

  1. 延遲值解析:允許連接 Gradle 模型,而無需知道屬性值何時會知道。例如,您可能想根據擴充功能的來源目錄屬性設定工作輸入來源檔案,但擴充功能屬性值在建置指令碼或其他外掛設定它們之前並不知道。

  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

這些類型也都被視為延遲類型。

有更強類型化的模型用於表示檔案系統元素:目錄常規檔案。這些類型不應與標準 Java 檔案 類型混淆,因為它們用於告訴 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 會追蹤屬性的值和產生該值的任務,讓這項工作變得更輕鬆。

舉例來說,考慮以下包含產生任務和使用任務的外掛程式,它們會互相連接

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

在上面的範例中,任務輸出和輸入會在定義任何位置之前連接。設定器可以在任務執行前隨時呼叫,而變更會自動影響所有相關的輸入和輸出屬性。

此範例中另一個要注意的重要事項是沒有任何明確的任務相依性。使用 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 實例。

與其他屬性類型類似,MapPropertyset() 方法,您可以使用它來指定屬性的值。一些其他方法允許將具有延遲值的項目新增到地圖中。

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. 避免在程式碼中簡化類似 obj.getProperty().get()obj.getProperty().set(T) 的呼叫,方法是加入其他 getter 和 setter。

  3. 將外掛程式移轉至使用提供者時,請遵循下列準則

    • 如果它是新屬性,請使用單一 getter 將其公開為 PropertyProvider

    • 如果它正在孵化中,請變更為使用單一 getter 的 PropertyProvider

    • 如果它是穩定的屬性,請加入新的 PropertyProvider,並將舊的屬性標示為已不建議使用。您應適當地將舊的 getter/setter 連接到新的屬性。

延遲集合 API 參考

可變動值使用這些類型

延遲物件 API 參考

唯讀值使用這些類型

Provider<T>

屬性值為 T 執行個體

工廠

可變動值使用這些類型

Property<T>

屬性值為 T 執行個體