延遲配置 Task
隨著建置複雜度的增加,要知道特定值在何時何地配置變得難以追蹤。 Gradle 提供了幾種方法來使用延遲配置來管理此問題。

理解延遲屬性
Gradle 提供了延遲屬性,它會延遲計算屬性的值,直到實際需要時才計算。
延遲屬性提供三個主要優點
-
延遲值解析: 允許連接 Gradle 模型,而無需知道何時會知道屬性的值。 例如,您可能想要根據擴充功能的來源目錄屬性來設定 task 的輸入原始檔,但在建置腳本或某些其他外掛配置它們之前,擴充功能屬性值是未知的。
-
自動 Task 相依性管理: 將一個 task 的輸出連接到另一個 task 的輸入,自動判斷 task 相依性。 屬性實例攜帶有關哪個 task(如果有的話)產生其值的資訊。 建置作者無需擔心使 task 相依性與配置變更保持同步。
-
改善建置效能: 避免在配置期間進行資源密集型工作,從而正面影響建置效能。 例如,當配置值來自解析檔案,但僅在執行功能測試時使用時,使用屬性實例來捕獲此值意味著僅在執行功能測試時(而不是在執行
clean
時,例如)才解析檔案。
Gradle 使用兩個介面來表示延遲屬性
- Provider
-
表示只能查詢而不能更改的值。
-
具有這些類型的屬性是唯讀的。
-
方法 Provider.get() 傳回屬性的目前值。
-
可以使用 Provider.map(Transformer) 從另一個
Provider
建立Provider
。 -
許多其他類型擴充了
Provider
,並且可以在需要Provider
的任何地方使用。
-
- Property
-
表示可以查詢和更改的值。
-
具有這些類型的屬性是可配置的。
-
Property
擴充了Provider
介面。 -
方法 Property.set(T) 指定屬性的值,覆寫可能存在的任何值。
-
方法 Property.set(Provider) 為屬性的值指定
Provider
,覆寫可能存在的任何值。 這可讓您在配置值之前將Provider
和Property
實例連接在一起。 -
Property
可以由工廠方法 ObjectFactory.property(Class) 建立。
-
延遲屬性旨在傳遞,並且僅在需要時查詢。 這通常發生在執行階段期間。
以下示範了一個具有可配置 greeting
屬性和唯讀 message
屬性的 task
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 | 顯示問候語的 task |
2 | 可配置的問候語 |
3 | 從問候語計算的唯讀屬性 |
4 | 配置問候語 |
5 | 呼叫 Property.set() 的替代表示法 |
$ gradle greeting > Task :greeting Hi from Gradle BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
Greeting
task 具有 Property<String>
類型的屬性來表示可配置的問候語,以及 Provider<String>
類型的屬性來表示計算的唯讀訊息。 訊息 Provider
是使用 map()
方法從問候語 Property
建立的; 當問候語屬性的值變更時,其值會保持最新。
建立 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 函數轉換為 |
連接屬性
延遲屬性的一個重要功能是它們可以連接在一起,以便對一個屬性的變更會自動反映在其他屬性中。
以下範例說明如何將 task 的屬性連接到專案擴充功能的屬性
// 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()
建立的實作
使用檔案
在使用檔案中,我們介紹了 File
類別物件的四種集合類型
唯讀類型 | 可配置類型 |
---|---|
所有這些類型也被視為延遲類型。
有更強型別的模型用於表示檔案系統的元素: 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
,您可以建立 FileCollection
實例,其中包含一組使用 DirectoryProperty.files(Object...) 或 Directory.files(Object...) 的目錄中包含的檔案。
使用 task 輸入和輸出
許多建置都有多個 task 連接在一起,其中一個 task 使用另一個 task 的輸出作為輸入。
為了使此工作正常運作,我們需要配置每個 task 以知道在哪裡尋找其輸入以及在哪裡放置其輸出。 確保生產 task 和消費 task 配置為相同的位置,並在 task 之間附加 task 相依性。 如果任何這些值可由使用者配置或由多個外掛配置,則這可能會很麻煩且脆弱,因為需要以正確的順序和位置配置 task 屬性,並且 task 相依性會隨著值的變更而保持同步。
Property
API 透過追蹤屬性的值和產生該值的 task 來簡化此操作。
作為範例,請考慮以下具有生產者和消費者 task 的外掛,它們連接在一起
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
在上面的範例中,task 輸出和輸入是在定義任何位置之前連接的。 setter 可以在執行 task 之前的任何時間呼叫,並且變更將自動影響所有相關的輸入和輸出屬性。
此範例中另一個需要注意的重要事項是缺少任何明確的 task 相依性。 使用 Providers
表示的 Task 輸出會追蹤哪個 task 產生它們的值,並且將它們用作 task 輸入會隱含地新增正確的 task 相依性。
隱含的 task 相依性也適用於非檔案的輸入屬性
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()
方法配置延遲屬性的慣例,同時執行定義屬性的 task 或擴充功能的初步配置。 這對於常規外掛程式(旨在分發並在野外使用)和內部 慣例外掛程式(通常以統一的方式為整個建置配置第三方外掛程式定義的屬性)都非常有效。
// 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")
}
}
}
從建置腳本
建置工程師可以從共享建置邏輯配置延遲屬性的慣例,該邏輯以標準方式為整個建置配置 task(例如,來自第三方外掛程式)。
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(…)
),而不是慣例,因為慣例僅用於定義預設值。
從 task 初始化
task 作者可以從 task 建構子或(如果在 Kotlin 中)初始化程式區塊配置延遲屬性的慣例。 此方法適用於具有簡單預設值的屬性,但如果需要額外內容(task 實作外部)才能設定合適的預設值,則不適用。
// 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")
}
在屬性宣告旁邊
您可以配置延遲屬性的慣例,使其與宣告屬性的位置相鄰。 請注意,此選項不適用於受管理屬性,並且具有與從 task 建構子配置慣例相同的注意事項。
// 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")
將屬性設為不可修改
task 或專案的大多數屬性都旨在由外掛程式或建置腳本配置,以便它們可以使用該建置的特定值。
例如,指定編譯 task 的輸出目錄的屬性可能會從外掛程式指定的值開始。 然後,建置腳本可能會將值變更為某些自訂位置,然後當 task 執行時,task 會使用此值。 但是,一旦 task 開始執行,我們就希望防止進一步的屬性變更。 這樣,我們可以避免因不同的消費者(例如 task 動作、Gradle 的最新檢查、建置快取或其他 task)使用不同的屬性值而導致的錯誤。
延遲屬性提供了幾種方法,您可以使用這些方法在配置值後禁止變更其值。 finalizeValue() 方法計算屬性的最終值,並防止進一步變更屬性。
libVersioning.version.finalizeValue()
當屬性的值來自 Provider
時,provider 會被查詢其目前值,並且結果會成為屬性的最終值。 此最終值會取代 provider,並且屬性不再追蹤 provider 的值。 呼叫此方法也會使屬性實例變成不可修改,並且任何進一步嘗試變更屬性值的操作都會失敗。 當 task 開始執行時,Gradle 會自動使 task 的屬性成為最終屬性。
finalizeValueOnRead() 方法類似,不同之處在於屬性的最終值要等到查詢屬性的值時才會計算。
modifiedFiles.finalizeValueOnRead()
換句話說,此方法會在需要時延遲計算最終值,而 finalizeValue()
會積極計算最終值。 當值可能難以計算或可能尚未配置時,可以使用此方法。 您也希望確保屬性的所有消費者在查詢值時看到相同的值。
使用 Provider API
使用 Provider API 取得成功的指南
Provider 檔案 API 參考
將這些類型用於唯讀值
- Provider<RegularFile>
-
磁碟上的檔案
- Provider<Directory>
-
磁碟上的目錄
- FileCollection
-
非結構化檔案集合
- FileTree
-
檔案階層
Property 檔案 API 參考
將這些類型用於可變值
- RegularFileProperty
-
磁碟上的檔案
- DirectoryProperty
-
磁碟上的目錄
- ConfigurableFileCollection
-
非結構化檔案集合
- ConfigurableFileTree
-
檔案階層
- SourceDirectorySet
-
原始碼目錄階層
延遲物件 API 參考
將這些類型用於唯讀值
- Provider<T>
-
其值為
T
實例的屬性- 工廠
-
-
ProviderFactory.provider(Callable)。 始終優先選擇其他工廠方法之一,而不是此方法。
將這些類型用於可變值
- Property<T>
-
其值為
T
實例的屬性