文章

Gradle Task进阶

Gradle Task进阶

Task 的输入和输出

Task outcomes (Task 产出)

Task Outcome Task 结果标识有 5 种,从名字上能大概看出它们的含义: image.png|700

(no label) or EXECUTED

  • (no label) or EXECUTED 表示 Task 执行了 action,常见。
    • Task 有 action 且 Gradle 执行了
    • Task 没有 action 且有部分 dependencies 执行了

UP-TO-DATE

  • UP-TO-DATE 表示 Task 的输出(outputs)没有改变。
    • Task 有 outputs 和 inputs 且没有 changed;See Incremental Build.
    • Task 有 action,但是 task 告诉 Gradle 它的 outputs 没有 changed
    • Task 没有 action, 但有部分 dependencies 是 update-to-date,skipped 或 from cache;See also Lifecycle Tasks.
    • Task 没有 action 且没有 dependencies

FROM-CACHE

  • FROM-CACHE 可以从缓存中复用上一次的执行结果 (Task 的 outputs 从上一次构建找到)
    • Task 有 outputs 从 build cache 找到  See Build Cache.

SKIPPED

1
2
3
4
5
6
7
tasks.register('customTask') {  
    onlyIf {  
		
    }
    enabled = false  
}
// onlyIf和enabled都可以控制Task执行条件,如果其结果是false,那这个Task就不需要被执行

SkipOnlyIfTaskExecuter 就是用来判断这个的,Task 的 SKIPPED 标识就是这里处理的。

NO-SOURCE

  • NO-SOURCE Task 不需要执行它的 actions

Task 输入输出

一般情况下,任务需要一些输入并生成一些输出。我们可以将 Java 编译过程视为 Task 的一个示例,Java 源文件作为 Task 的输入,而生成的类文件,即编译的结果,是 Task 的输出。

![Example task inputs and outputs500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/taskInputsOutputs.png)

输入的一个重要特征是,它会影响一个或多个输出,如上图,根据源文件的内容和您要运行代码的 Java 运行时的最低版本,会生成不同的字节码。

编写 Task 时,需要告诉 Gradle 哪些 Task 属性是输入,哪些是输出。 如果 Task 属性影响输出,请务必将其注册为输入,否则当它不是时,Task 将被视为最新状态。相反,如果属性不影响输出,请不要将属性注册为输入,否则 Task 可能会在不需要时执行。还要注意可能为完全相同的输入生成不同输出的非确定性 Task,这些 Task 不应配置为增量构建,因为 UP-TO-DATE 检查将不起作用。

Gradle Task Incremental 增量更新

Advanced Tasks

Task 增量更新概述

增量构建 是当 Task 的输入和输出没有变化时,跳过 action 的执行,当 Task 输入或输出发生变化时,在 action 中只对发生变化的输入或输出进行处理,这样就可以避免一个没有变化的 Task 被反复构建,当 Task 发生变化时也只处理变化部分,这样可以提高 Gradle 的构建效率,缩短构建时间。

任何构建工具的一个重要部分是能够避免做已经完成的工作。编译源文件后,除非发生影响输出的某些变化,例如修改源文件或删除输出文件,否则无需重新编译它们。编译可能需要大量时间,因此在不需要时跳过步骤可以节省大量时间。

Gradle 提供这种开箱即用的增量构建的功能,当你在编译时,Task 在控制台输出中标记为 UP-TO-DATE,这意味着增量构建正在工作。

Task 增量构建的两种形式

  1. Task 完全可以复用,输入和输出都没有任何变化,即 UP-TO-DATE,默认支持
  2. 第二种,有部分变化,只需要针对变化的部分进行操作;

完全复用

示例 :编写一个复制文件的 Task,不支持增量编译的

在编写 Task 的时候,我们需要使用注解来声明输入和输出。@InputXXX 表示输入,@OutputXXX 表示输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
abstract class CopyTask : DefaultTask() {  
    // 指定输入  
//    @InputFile  
    @InputFiles  
    var from: FileCollection? = null  
  
    //    输出  
    @OutputDirectory  
    var to: Directory? = null  
    
    @TaskAction  
	fun execute() {  
	    val fromInput = from ?: return  
	    val toOutput = to ?: return  
	    val file = fromInput.singleFile  
	    if (file.isDirectory) {  
	        fromInput.asFileTree.forEach {  
	            println("===============>>>> copy dir file=$it")  
	            copyFileToDir(it, toOutput)  
	        }  
	    } else {  
	        println("------------------>>>>>> copy file file=$file")  
	        copyFileToDir(file, toOutput)  
	    }  
	}
    private fun copyFileToDir(src: File, dir: Directory) {  
        val destFile = File(dir.asFile.path, src.name)  
        if (!destFile.exists()) {  
            destFile.createNewFile()  
        }  
        destFile.outputStream().use { output ->  
            src.inputStream().use { input ->  
                input.copyTo(output)  
            }  
        }    }  
}
// 使用
tasks.register<CopyTask>("myCopyTask") {  
    group = "test"  
//    from = fileTree("src") // Expected directory 'src' to contain exactly one file, however, it contains more than one file.  
    from = files("from")  
    to = layout.projectDirectory.dir("to")  
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 当前目录
├── app
│   ├── from
│   │   └── txt1.txt

# 测试
./gradlew CopyTask --info

# 此时的目录结构:
├── app
│   ├── from
│   │   └── txt1.txt
│   └── to
│       └── txt1.txt

第 1 次执行的日志:

![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240311135132.png)

第 2 次执行的日志:

![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240311135322.png)

Task 的执行结果已经由 executed 变为 up-to-date 了,说明我们的增量构建已经生效了。

虽然说此时增量构建已经生效了,但完成度还不够,还需要有颗粒度更细的处理。

部分增量构建

场景:基于上面的场景,在 from 文件夹下增加一个 text2.txt 文件,并支持增量构建。

text1.txt 复制后,再执行 task,两个 text 又复制了一遍

![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240311192746.png)

增加一个 txt2.txt 文件再次执行上面的命令时,会发现 text1.txt 文件被再次复制了一遍。

这是因为我们的输入有了变化,CopyTask 的 Action 就会全量构建,而我们想要的效果是只复制 text2.txt 文件就好了。只对新增或修改的文件做复制操作,没有变化的文件不进行复制。 而要实现这种效果,就得让 Action 方法支持增量构建。

给 action 方法增加一个 InputChanges 参数,带 InputChanges 类型参数的 Action 方法表示这是一个增量任务操作方法,该参数告诉 Gradle,该 Action 方法仅需要处理更改的输入,此外,Task 还需要通过使用 @Incremental@SkipWhenEmpty 来指定至少一个增量文件输入属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
abstract class CopyTask : DefaultTask() {  
  
    // 指定增量输入属性  
    @Incremental  
    @InputFiles  
    var from: FileCollection? = null  
  
    //    输出  
    @OutputDirectory  
    var to: Directory? = null  
  
    @TaskAction  
    fun execute(inputChanges: InputChanges) {  
        val fromInput = from ?: return  
        val toOutput = to ?: return  
  
        val incremental = inputChanges.isIncremental  
        println("isIncremental = $incremental")  
  
        inputChanges.getFileChanges(fromInput).forEach {  
            println("file change file= ${it.file}, isDirectory=${it.file.isDirectory}, Type = ${it.changeType}")  
            if (it.fileType != FileType.DIRECTORY) {  
                println("file= ${it.file}, fileType = ${it.fileType}")  
                if (it.changeType != ChangeType.REMOVED) {  
                    copyFileToDir(it.file, toOutput)  
                }  
            } else {  
                println("file= ${it.file}, fileType = ${it.fileType}, DIRECTORY?")  
            }  
        }  
    }  
  
    private fun copyFileToDir(src: File, dir: Directory) {  
        val destFile = File(dir.asFile.path, src.name)  
        if (!destFile.exists()) {  
            destFile.createNewFile()  
        }  
        destFile.outputStream().use { output ->  
            src.inputStream().use { input ->  
                input.copyTo(output)  
            }  
        }    }  
}
![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240311213551.png)

ChangeType 的几种类型:

  • ADDED:表示文件是新增的;
  • MODIFIED:表示文件是修改的;
  • REMOVED:表示文件被删除;

全量编译的情况(增量编译失效的情况)

Task 并不是每次执行都是增量构建,我们可以通过 InputChangesisIncremental 方法判断本次构建是否是增量构建,不过有以下几种情况会全量构建:

  • 该 Task 是第一次执行;
  • 该 Task 只有输入没有输出;
  • 该 Task 的 upToDateWhen 条件返回了 false;
  • 自上次构建以来,该 Task 的某个输出文件已更改;
  • 自上次构建以来,该 Task 的某个属性输入发生了变化,例如一些基本类型的属性;
  • 自上次构建以来,该 Task 的某个非增量文件输入发生了变化,非增量文件输入是指没有使用 @Incremental@SkipWhenEmpty 注解的文件输入.

当 Task 处于全量构建时,即 InputChanges 的 isIncremental 方法返回 false 时,通过 InputChanges 的 getFileChanges 方法能获取到所有的输入文件,并且每个文件的 ChangeType 都为 ADDED,当 Task 处于增量构建时,即 InputChanges 的 isIncremental 方法返回 true 时,通过 InputChanges 的 getFileChanges 方法能获取到只发生变化的输入文件。

常用的注解类型

Incremental build # Declaring inputs and outputs via annotations

注解类型含义
@Input任何 Serializable 类型或依赖性解析结果类型一个简单的输入值或依赖关系解析结果
@InputFileFile单个输入文件(不是目录)
@InputDirectoryFile单个输入目录(不是文件)
@InputFilesIterable可迭代的输入文件和目录
@OutputFileFile单个输出文件(不是目录)
@OutputDirectoryFile单个输出目录(不是文件)
@OutputFilesMap<String, File>Iterable输出文件的可迭代或映射。使用文件树会关闭任务的缓存。
@OutputDirectoriesMap<String, File>Iterable输出目录的可迭代。使用文件树会关闭任务的缓存。
@Nested任何自定义类型自定义类型,可能无法实现 Serializable,但至少有一个字段或属性标记了此表中的注释之一。它甚至可能是另一个@Nested。
@Internal任何类型表示该属性在内部使用,但既不是输入也不是输出。
@SkipWhenEmptyFileIterable与@InputFiles 或@InputDirectory 一起使用,告诉 Gradle 在相应的文件或目录为空时跳过任务,以及使用此注释声明的所有其他输入文件。由于声明此注释为空的所有输入文件而跳过的任务将导致明显的 “ 无源 “ 结果。例如,NO-SOURCE 将在控制台输出中发出。暗示@Incremental。
@Incremental任何类型与@InputFiles 或@InputDirectory 一起使用,指示 Gradle 跟踪对带注释的文件属性的更改,因此可以通过 ` @InputChanges.getFileChanges() ` 查询更改。增量任务需要。
@Optional任何类型与可选 API 文档中列出的任何属性类型注释一起使用。此注释禁用对相应属性的验证检查。有关更多详细信息 请参阅 验证部分

增量构建原理

在首次执行 Task 之前,Gradle 会获取输入的指纹,此指纹包含输入文件的路径和每个文件内容的散列。然后执行 Task,如果 Task 成功完成,Gradle 会获取输出的指纹,此指纹包含一组输出文件和每个文件内容的散列,Gradle 会在下次执行 Task 时保留两个指纹。

后续每次在执行 Task 之前,Gradle 都会对输入和输出进行新的指纹识别,如果新指纹与之前的指纹相同,Gradle 假设输出是最新的,并跳过 Task,如果它们不一样,Gradle 会执行 Task。Gradle 会在下次执行 Task 时保留两个指纹。

如果文件的统计信息(即 lastModified 和 size)没有改变,Gradle 将重复使用上次运行的文件指纹,即当文件的统计信息没有变化时,Gradle 不会检测到更改。

Gradle 还将 Task 的代码视为任务输入的一部分,当 Task、Action 或其依赖项在执行之间发生变化时,Gradle 认为该 Task 是过时的。

Gradle 了解文件属性(例如持有 Java 类路径的属性)是否对顺序敏感,当比较此类属性的指纹时,即使文件顺序发生变化,也会导致 Task 过时。

请注意,如果 Task 指定了输出目录,则自上次执行以来添加到该目录的任何文件都会被忽略,并且不会导致 Task 过时,如此不相关的 Task 可能会共享一个输出目录,而不会相互干扰,如果出于某种原因这不是你想要的行为,请考虑使用 TaskOutputs.upToDateWhen(groovy.lang.Closure)

另请注意,更改不可用文件的可用性(例如,将损坏的符号链接的目标修改为有效文件,反之亦然),将通过最新检查进行检测和处理。

Task 的输入还用于计算启用时用于加载 Task 输出的构建缓存密钥。

Gradle Task Incremental 示例

上传 apk 到 pgyer

PgyUploadApkTask 功能:

  • 输入为 build 成功后的 apk
  • 输出为 build/output.txt,存储 apk 上传成功后 apk 文件的 md5
  • 调用 pgyer upload http v2 接口上传 apk
  • 上传 apk 成功后,获取该文件的 md5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// ...
import org.gradle.api.DefaultTask  
import org.gradle.api.file.RegularFileProperty  
import org.gradle.api.tasks.InputFile  
import org.gradle.api.tasks.OutputFile  
import org.gradle.api.tasks.TaskAction  
import java.io.File  
  
abstract class PgyUploadApkTask : DefaultTask() {

    @get:InputFile
    abstract val apkFileProperty: RegularFileProperty

    @get:OutputFile
    abstract val outputFilProperty: RegularFileProperty

    init {
        group = Constants.GROUP
        description = "上传apk到蒲公英"
    }

    @Suppress("CheckResult")
    @TaskAction
    fun doTaskAction() {
        val apkFile = apkFileProperty.get().asFile
        println("PgyUploadApkTask doTaskAction apkFile=$apkFile")

        if (apkFile == null) {
            println("apkFile is null")
            throw IllegalArgumentException("apkFile is null")
        }
        if (!apkFile.exists()) {
            println("apkFile($apkFile) not exists")
            throw IllegalArgumentException("apkFile not exists")
        }

        // 上传apk到蒲公英
        val extension = Extension.getExtension(project)
        println("pgyerApiKey=${extension?.pgyerApiKey}")

        val apiKey = extension?.pgyerApiKey
        val changeLog = extension?.changeLog

        // 上传成功后,将返回的下载地址写入output文件
        val req1 = COSTokenRequest1(apiKey ?: "")

        val output = outputFilProperty.get().asFile
        if (!output.exists()) {
            output.createNewFile()
        }

        val apkFileMd5Hash = MessageDigest.getInstance("MD5")
            .digest(Files.readAllBytes(apkFile.toPath()))
            .joinToString("") { hash -%3E "%02x".format(hash) }
        println("uploadApk apkFile=${apkFile}, apkFileMd5Hash=$apkFileMd5Hash")

        UploadApkToPgyerHelper.uploadApk(req1, apkFile)
            .subscribe({
                val s1 = System.currentTimeMillis()

                val md5Hash = MessageDigest.getInstance("MD5")
                    .digest(Files.readAllBytes(apkFile.toPath()))
                    .joinToString("") { hash -> "%02x".format(hash) }

                val cost = System.currentTimeMillis() - s1
                println(
                    "uploadApk success it=$it\noutput=$output(${output.exists()})\n" +
                            "MD5 hash of ${output.name} is $md5Hash\ncost=${cost}ms"
                )
                output.writeText(md5Hash)
            }, {
                println("uploadApk error e=$it, output=$output")
                it.printStackTrace()
                output.writeText("error: $it")
                throw it
            })
        Utils.log(" ${this.name} task end. ")
    }
}
  • 首次运行:./gradlew :app:uploadPgyerDebug,成功上传 apk 后
![image.png300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240401211848.png)
  • 二次运行
![image.png300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240401211914.png)

Lazy Task Configuration(8.x)

Lazy Configuration

![image.png600](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240320150456.png)

Gradle 提供了 lazy 属性,属性值的计算延迟到需要的才计算,Configuration 不会计算,到 Execute 阶段才去计算。

Provider 和 Property

Gradle 提供了 2 种 lazy 属性

  • Provider:代表一个可读不可写的属性
    • 该属性只可读
    • Provider.get() 返回当前属性值
    • 一个 Provider 能从另外一个 Provider 创建,通过 Provider.map(Transformer) 方法
    • 其他类型可以继承 Provider
  • Property:代表一个可读可写的属性

示例:

Property 和 Provider 的属性,不会在 Configuration 阶段执行,在 execute 需要时才执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
abstract class Greeting : DefaultTask() {  
    @Input  
    val fileText = getFileTextStr()  // Configruation阶段就执行
  
    @get:Input  
    abstract val greeting: Property<String> // Configruation阶段不执行,Execute阶段执行
  
    private fun getFileTextStr(): String {  
        println("===================>>> HELLO FROM MY TASK, I AM HACKET")  
        return "===================>>> HELLO FROM MY TASK, I AM HACKET"  
    }  
  
    @Internal  
    val message: Provider<String> = greeting.map {  // Configruation阶段不执行,Execute阶段执行
        println("===================>>> message map $it")  
        "===================>>> $it from Gradle"    }  
  
    @TaskAction  
    fun printMessage() {  
        logger.quiet(message.get())  
    }  
}  
  
tasks.register<Greeting>("greeting") {  
    group = "custom"  
  
//    可读可写的属性  
    greeting.set("Hi")  
    greeting = "Hi"  
  
    // 报错:Provider属性只读,不可更改  
//    message = "message"  
}  
  
//> Configure project :app  
//===================>>> HELLO FROM MY TASK, I AM HACKET  
//> Task :app:greeting  
//===================>>> message map Hi  
//===================>>> Hi from Gradle

创建 Property or Provider 实例:

  • ` Project.getObjects().property(Class) `
  • Provider.map (Transformer) 创建 Provider 从 ProviderProperty

属性相连,一个属性引用另外一个属性:message 通过 Property.map{} 引用 greeting 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 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"
}

处理文件目录的 Lazy property

处理文件和目录的 Property:

  • RegularFileProperty 单个 File
  • DirectoryProperty 目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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")

其他 Task 的 output 作为另一个 Task 的 input

示例:Producer 的 output 做为 Consumer 的 input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
abstract class Producer : DefaultTask() {
    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun produce() {
        val message = "Hello, World 1!"
        val output = outputFile.get().asFile
        output.writeText(message)
        logger.quiet("Producer 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("Consumer Read '${message}' from $input")
    }
}

val producer = tasks.register<Producer>("producer")
val consumer = tasks.register<Consumer>("consumer")

consumer {
    group = "custom"
    // 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 {
    group = "custom"
    // 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")
//> Task :app:producer  
//Producer Wrote 'Hello, World 1!' to /Users/xxx/WorkSpace/GradlePluginDemos/GradlePlugin8.0-Kotlin-New/app/output/file.txt  
//  
//> Task :app:consumer  
//Consumer Read 'Hello, World 1!' from /Users/xxx/WorkSpace/GradlePluginDemos/GradlePlugin8.0-Kotlin-New/app/output/file.txt

上面会自动将 Consumer Task 依赖 Producer Task,隐式的依赖关系也可以不用 File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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() }
}
//$ gradle consumer
//> Task :producer
//Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/build/file.txt
//
//> Task :consumer
//Hello, World!

处理 Collection 的 Lazy property

Gradle 提供了 2 种方式:

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
abstract class Producer1 : DefaultTask() {  
    @get:OutputFile  
    abstract val outputFile: RegularFileProperty  
  
    @TaskAction  
    fun produce() {  
        val message = "Hello, World!"  
        val output = outputFile.get().asFile  
        output.writeText(message)  
        logger.quiet("Producer1 Wrote '${message}' to ${output}")  
    }  
}  
  
abstract class Consumer1 : 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("Consumer1 Read '${message}' from ${input}")  
        }  
    }  
}  
  
val producerOne = tasks.register<Producer1>("producerOne")  
val producerTwo = tasks.register<Producer1>("producerTwo")  
tasks.register<Consumer1>("consumer1") {  
    group = "custom"  
    // 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 {  
    group = "custom"  
    outputFile = layout.buildDirectory.file("one.txt")  
}  
producerTwo {  
    group = "custom"  
    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")  
// 首次运行./gradlew consumer1  
//> Task :app:producerOne  
//Producer1 Wrote 'Hello, World!' to /Users/xxx/WorkSpace/GradlePluginDemos/GradlePlugin8.0-Kotlin-New/app/output/one.txt  
//  
//> Task :app:producerTwo  
//Producer1 Wrote 'Hello, World!' to /Users/xxx/WorkSpace/GradlePluginDemos/GradlePlugin8.0-Kotlin-New/app/output/two.txt  
//  
//> Task :app:consumer1  
//Consumer1 Read 'Hello, World!' from /Users/xxx/WorkSpace/GradlePluginDemos/GradlePlugin8.0-Kotlin-New/app/output/one.txt  
//Consumer1 Read 'Hello, World!' from /Users/xxx/WorkSpace/GradlePluginDemos/GradlePlugin8.0-Kotlin-New/app/output/two.txt

处理 map 的 lazy property

Gradle 提供了 MapProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Map Configuration for Task Properties  
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") {  
    group = "custom"  
    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) })  
}

conversion

默认值,如果显示设置了值,conversion 再设置就无效了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
tasks.register("show") {  
    group = "test"  
    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())  
    }  
}  
//> Configure project :app  
//value = convention 1  
//value = convention 2  
//> Task :app:show  
//value = explicit value

Provider 使用指南

  • 需要配置的属性,暴露 Property 的 getter;不需要配置的属性,暴露 Provider 的 getter
  • 避免在代码中简单调用 obj.getProperty().get() 和 obj.getProperty().set(T) 新增 getter 和 setter
  • 升级你的插件用 Providers,遵照以下指南:
    • 如果是新的属性,暴露一个 Property 或 Provider 的 getter
    • 如果还在开发中,更改为 Property 或 Provider 的 getter
    • 如果已经是稳定的了,添加新的 Property 或 Provider 的 getter 属性,deprecate 旧的;将旧的 getter/setter 连接到新的属性

Provider Files API

Provider<RegularFile>

Provider < RegularFile > File on disk

Factories

Provider<Directory>

Directory on disk

Factories

FileCollection

FileCollection

Unstructured collection of files

Factories

FileTree

Hierarchy of files

Factories

Property Files API

Configuring Tasks Lazily

  • RegularFileProperty
  • DirectoryProperty
  • ConfigurableFileCollection
  • ConfigurableFileTree
  • SourceDirectorySet

Lazy Collections API Reference

Configuring Tasks Lazily

  • ListProperty<T>
  • ListProperty<T>

Lazy Objects API Reference

Configuring Tasks Lazily

Parallel Tasks

Developing Parallel Tasks

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240322112155.png)
本文由作者按照 CC BY 4.0 进行授权