可操作任務描述 Gradle 中的工作。這些任務具有動作。在 Gradle 核心中,compileJava 任務編譯 Java 原始碼。JarZip 任務將檔案壓縮到封存檔中。

writing tasks 3

自訂可操作任務可以透過擴展 DefaultTask 類別並定義輸入、輸出和動作來建立。

任務輸入和輸出

可操作任務具有輸入和輸出。輸入和輸出可以是檔案、目錄或變數。

在可操作任務中

  • 輸入包含檔案、資料夾和/或配置資料的集合。
    例如,javaCompile 任務接受諸如 Java 原始碼檔案和建置腳本配置(如 Java 版本)之類的輸入。

  • 輸出指的是一個或多個檔案或資料夾。
    例如,javaCompile 產生類別檔案作為輸出。

然後,jar 任務將這些類別檔案作為輸入,並產生 JAR 封存檔。

明確定義的任務輸入和輸出有兩個目的

  1. 它們告知 Gradle 任務相依性。
    例如,如果 Gradle 了解 compileJava 任務的輸出作為 jar 任務的輸入,它將優先執行 compileJava

  2. 它們促進增量建置。
    例如,假設 Gradle 識別到任務的輸入和輸出保持不變。在這種情況下,它可以利用先前建置執行的結果或建置快取,從而避免完全重新執行任務動作。

當您應用諸如 java-library 外掛之類的外掛時,Gradle 將自動註冊某些任務並使用預設值配置它們。

讓我們定義一個任務,該任務將 JAR 和啟動腳本封裝到一個虛構範例專案的封存檔中

gradle-project
├── app
│   ├── build.gradle.kts    // app build logic
│   ├── run.sh              // script file
│   └── ...                 // some java code
├── settings.gradle.kts     // includes app subproject
├── gradle
├── gradlew
└── gradlew.bat
gradle-project
├── app
│   ├── build.gradle    // app build logic
│   ├── run.sh          // script file
│   └── ...             // some java code
├── settings.gradle     // includes app subproject
├── gradle
├── gradlew
└── gradlew.bat

run.sh 腳本可以從建置執行 Java 應用程式(一旦封裝為 JAR)

app/run.sh
java -cp 'libs/*' gradle.project.app.App

讓我們使用 task.register() 註冊一個名為 packageApp 的新任務

app/build.gradle.kts
tasks.register<Zip>("packageApp") {

}
app/build.gradle
tasks.register(Zip, "packageApp") {

}

我們使用了 Gradle 核心的現有實作,它是 Zip 任務實作(即 DefaultTask 的子類別)。由於我們在此處註冊了一個新任務,因此它未預先配置。我們需要配置輸入和輸出。

定義輸入和輸出是使任務成為可操作任務的原因。

對於 Zip 任務類型,我們可以使用 from() 方法將檔案新增到輸入中。在我們的例子中,我們新增了執行腳本。

如果輸入是我們直接建立或編輯的檔案,例如執行檔案或 Java 原始碼,它通常位於我們的專案目錄中的某個位置。為了確保我們使用正確的位置,我們使用 layout.projectDirectory 並定義相對於專案目錄根目錄的路徑。

我們提供 jar 任務的輸出以及所有相依性的 JAR(使用 configurations.runtimeClasspath)作為額外輸入。

對於輸出,我們需要定義兩個屬性。

首先,目的地目錄,它應該是建置資料夾內的目錄。我們可以透過 layout 存取它。

其次,我們需要為 zip 檔案指定一個名稱,我們稱之為 myApplication.zip

以下是完整任務的外觀

app/build.gradle.kts
val packageApp = tasks.register<Zip>("packageApp") {
    from(layout.projectDirectory.file("run.sh"))                // input - run.sh file
    from(tasks.jar) {                                           // input - jar task output
        into("libs")
    }
    from(configurations.runtimeClasspath) {                     // input - jar of dependencies
        into("libs")
    }
    destinationDirectory.set(layout.buildDirectory.dir("dist")) // output - location of the zip file
    archiveFileName.set("myApplication.zip")                    // output - name of the zip file
}
app/build.gradle
def packageApp = tasks.register(Zip, 'packageApp') {
    from layout.projectDirectory.file('run.sh')                 // input - run.sh file
    from tasks.jar {                                            // input - jar task output
        into 'libs'
    }
    from configurations.runtimeClasspath {                      // input - jar of dependencies
        into 'libs'
    }
    destinationDirectory.set(layout.buildDirectory.dir('dist')) // output - location of the zip file
    archiveFileName.set('myApplication.zip')                    // output - name of the zip file
}

如果我們執行 packageApp 任務,則會產生 myApplication.zip

$./gradlew :app:packageApp

> Task :app:compileJava
> Task :app:processResources NO-SOURCE
> Task :app:classes
> Task :app:jar
> Task :app:packageApp

BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed

Gradle 執行了許多建置 JAR 檔案所需的任務,其中包括 app 專案的程式碼編譯和程式碼相依性的編譯。

查看新建立的 ZIP 檔案,我們可以看到它包含執行 Java 應用程式所需的一切

> unzip -l ./app/build/dist/myApplication.zip

Archive:  ./app/build/dist/myApplication.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       42  01-31-2024 14:16   run.sh
        0  01-31-2024 14:22   libs/
      847  01-31-2024 14:22   libs/app.jar
  3041591  01-29-2024 14:20   libs/guava-32.1.2-jre.jar
     4617  01-29-2024 14:15   libs/failureaccess-1.0.1.jar
     2199  01-29-2024 14:15   libs/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
    19936  01-29-2024 14:15   libs/jsr305-3.0.2.jar
   223979  01-31-2024 14:16   libs/checker-qual-3.33.0.jar
    16017  01-31-2024 14:16   libs/error_prone_annotations-2.18.0.jar
---------                     -------
  3309228                     9 files

可操作任務應連接到生命週期任務,以便開發人員只需要執行生命週期任務。

到目前為止,我們直接呼叫了我們的新任務。讓我們將其連接到生命週期任務。

以下內容已新增到建置腳本中,以便使用 dependsOn()packageApp 可操作任務連接到 build 生命周期任務

app/build.gradle.kts
tasks.build {
    dependsOn(packageApp)
}
app/build.gradle
tasks.build {
    dependsOn(packageApp)
}

我們看到執行 :build 也會執行 :packageApp

$ ./gradlew :app:build

> Task :app:compileJava UP-TO-DATE
> Task :app:processResources NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:jar UP-TO-DATE
> Task :app:startScripts
> Task :app:distTar
> Task :app:distZip
> Task :app:assemble
> Task :app:compileTestJava
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses
> Task :app:test
> Task :app:check
> Task :app:packageApp
> Task :app:build

BUILD SUCCESSFUL in 1s
8 actionable tasks: 6 executed, 2 up-to-date

您可以根據需要定義自己的生命週期任務。

透過擴展 DefaultTask 實作任務

為了滿足更個別的需求,並且如果沒有現有的外掛程式提供您需要的建置功能,您可以建立自己的任務實作。

實作類別意味著建立自訂類別(即類型),這是透過子類別化 DefaultTask 來完成的

讓我們從 Gradle init 為簡單 Java 應用程式建置的範例開始,原始碼位於 app 子專案中,通用建置邏輯位於 buildSrc

gradle-project
├── app
│   ├── build.gradle.kts
│   └── src                 // some java code
│       └── ...
├── buildSrc
│   ├── build.gradle.kts
│   ├── settings.gradle.kts
│   └── src                 // common build logic
│       └── ...
├── settings.gradle.kts
├── gradle
├── gradlew
└── gradlew.bat
gradle-project
├── app
│   ├── build.gradle
│   └── src             // some java code
│       └── ...
├── buildSrc
│   ├── build.gradle
│   ├── settings.gradle
│   └── src             // common build logic
│       └── ...
├── settings.gradle
├── gradle
├── gradlew
└── gradlew.bat

我們在 ./buildSrc/src/main/kotlin/GenerateReportTask.kt./buildSrc/src/main/groovy/GenerateReportTask.groovy 中建立一個名為 GenerateReportTask 的類別。

為了讓 Gradle 知道我們正在實作任務,我們擴展了 Gradle 隨附的 DefaultTask 類別。使我們的任務類別成為 abstract 也是有益的,因為 Gradle 將自動處理許多事情

buildSrc/src/main/kotlin/GenerateReportTask.kt
import org.gradle.api.DefaultTask

public abstract class GenerateReportTask : DefaultTask() {

}
buildSrc/src/main/groovy/GenerateReportTask.groovy
import org.gradle.api.DefaultTask

public abstract class GenerateReportTask extends DefaultTask {

}

接下來,我們使用屬性和註解定義輸入和輸出。在此上下文中,Gradle 中的屬性充當對其後實際值的參考,允許 Gradle 追蹤任務之間的輸入和輸出。

對於我們任務的輸入,我們使用 Gradle 中的 DirectoryProperty。我們使用 @InputDirectory 註解它,以指示它是任務的輸入

buildSrc/src/main/kotlin/GenerateReportTask.kt
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory

public abstract class GenerateReportTask : DefaultTask() {

    @get:InputDirectory
    abstract var sourceDirectory: DirectoryProperty

}
buildSrc/src/main/groovy/GenerateReportTask.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory

public abstract class GenerateReportTask extends DefaultTask {

    @InputDirectory
    DirectoryProperty sourceDirectory

}

同樣,對於輸出,我們使用 RegularFileProperty 並使用 @OutputFile 註解它。

buildSrc/src/main/kotlin/GenerateReportTask.kt
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputFile

public abstract class GenerateReportTask : DefaultTask() {

    @get:InputDirectory
    abstract var sourceDirectory: DirectoryProperty

    @get:OutputFile
    abstract var reportFile: File

}
buildSrc/src/main/groovy/GenerateReportTask.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputFile

public abstract class GenerateReportTask extends DefaultTask {

    @InputDirectory
    DirectoryProperty sourceDirectory

    @OutputFile
    File reportFile

}

定義了輸入和輸出後,剩下的唯一事情是實際的任務動作,它在使用 @TaskAction 註解的方法中實作。在此方法內,我們編寫使用 Gradle 特定 API 存取輸入和輸出的程式碼

buildSrc/src/main/kotlin/GenerateReportTask.kt
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

public abstract class GenerateReportTask : DefaultTask() {

    @get:InputDirectory
    abstract var sourceDirectory: DirectoryProperty

    @get:OutputFile
    abstract var reportFile: File

    @TaskAction
    fun generateReport() {
        val fileCount = sourceDirectory.listFiles().count { it.isFile }
        val directoryCount = sourceDirectory.listFiles().count { it.isDirectory }

        val reportContent = """
            |Report for directory: ${sourceDirectory.absolutePath}
            |------------------------------
            |Number of files: $fileCount
            |Number of subdirectories: $directoryCount
        """.trimMargin()

        reportFile.writeText(reportContent)
        println("Report generated at: ${reportFile.absolutePath}")
    }
}
buildSrc/src/main/groovy/GenerateReportTask.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

public abstract class GenerateReportTask extends DefaultTask {

    @InputDirectory
    DirectoryProperty sourceDirectory

    @OutputFile
    File reportFile

    @TaskAction
    void generateReport() {
        def fileCount = sourceDirectory.listFiles().count { it.isFile() }
        def directoryCount = sourceDirectory.listFiles().count { it.isDirectory() }

        def reportContent = """
            Report for directory: ${sourceDirectory.absolutePath}
            ------------------------------
            Number of files: $fileCount
            Number of subdirectories: $directoryCount
        """.trim()

        reportFile.text = reportContent
        println("Report generated at: ${reportFile.absolutePath}")
    }
}

任務動作產生 sourceDirectory 中檔案的報告。

在應用程式建置檔案中,我們使用 task.register() 註冊類型為 GenerateReportTask 的任務,並將其命名為 generateReport。同時,我們配置任務的輸入和輸出

app/build.gradle.kts
tasks.register<GenerateReportTask>("generateReport") {
    sourceDirectory.set(layout.projectDirectory.dir("src/main"))
    reportFile.set(layout.buildDirectory.file("reports/directoryReport.txt"))
}

tasks.build {
    dependsOn("generateReport")
}
app/build.gradle
tasks.register(GenerateReportTask, "generateReport") {
    sourceDirectory = layout.projectDirectory.dir("src/main").asFile
    reportFile = layout.buildDirectory.file("reports/directoryReport.txt").asFile
}

tasks.build.dependsOn("generateReport")

generateReport 任務已連接到 build 任務。

透過執行建置,我們觀察到我們的啟動腳本產生任務已執行,並且在後續建置中為 UP-TO-DATE。Gradle 的增量建置和快取機制與自訂任務無縫協作

./gradlew :app:build
> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors
> Task :buildSrc:compileKotlin UP-TO-DATE
> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources NO-SOURCE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :app:compileJava UP-TO-DATE
> Task :app:processResources NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:jar UP-TO-DATE
> Task :app:startScripts UP-TO-DATE
> Task :app:distTar UP-TO-DATE
> Task :app:distZip UP-TO-DATE
> Task :app:assemble UP-TO-DATE
> Task :app:compileTestJava UP-TO-DATE
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses UP-TO-DATE
> Task :app:test UP-TO-DATE
> Task :app:check UP-TO-DATE

> Task :app:generateReport
Report generated at: ./app/build/reports/directoryReport.txt

> Task :app:packageApp
> Task :app:build

BUILD SUCCESSFUL in 1s
13 actionable tasks: 10 executed, 3 up-to-date

任務動作

任務動作是實作任務正在執行的程式碼,如上一節所示。例如,javaCompile 任務動作呼叫 Java 編譯器將原始碼轉換為位元組碼。

可以動態修改已註冊任務的任務動作。這對於測試、修補或修改核心建置邏輯很有用。

讓我們看一個簡單 Gradle 建置的範例,其中一個 app 子專案構成一個 Java 應用程式 – 包含一個 Java 類別並使用 Gradle 的 application 外掛。該專案在 buildSrc 資料夾中具有通用建置邏輯,其中駐留著 my-convention-plugin

app/build.gradle.kts
plugins {
    id("my-convention-plugin")
}

version = "1.0"

application {
    mainClass = "org.example.app.App"
}
app/build.gradle
plugins {
    id 'my-convention-plugin'
}

version = '1.0'

application {
    mainClass = 'org.example.app.App'
}

我們在 app 的建置檔案中定義一個名為 printVersion 的任務

buildSrc/src/main/kotlin/PrintVersion.kt
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction

abstract class PrintVersion : DefaultTask() {

    // Configuration code
    @get:Input
    abstract val version: Property<String>

    // Execution code
    @TaskAction
    fun print() {
        println("Version: ${version.get()}")
    }
}
buildSrc/src/main/groovy/PrintVersion.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction

abstract class PrintVersion extends DefaultTask {

    // Configuration code
    @Input
    abstract Property<String> getVersion()

    // Execution code
    @TaskAction
    void printVersion() {
        println("Version: ${getVersion().get()}")
    }
}

此任務做一件簡單的事情:它將專案的版本列印到命令列。

該類別擴展了 DefaultTask,並且它有一個 @Input,其類型為 Property<String>。它有一個使用 @TaskAction 註解的方法,該方法列印版本。

請注意,任務實作清楚地區分了「配置程式碼」和「執行程式碼」。

配置程式碼在 Gradle 的配置階段執行。它在記憶體中建置專案模型,以便 Gradle 知道它需要為特定建置調用做什麼。任務動作周圍的所有內容,例如輸入或輸出屬性,都是此配置程式碼的一部分。

任務動作方法內的程式碼是執行程式碼,它執行實際工作。如果任務是任務圖的一部分,並且如果無法跳過它,因為它是 UP-TO-DATE 或從快取取得,則它會存取輸入和輸出以執行某些工作。

一旦任務實作完成,就可以在建置設定中使用它。在我們的慣例外掛 my-convention-plugin 中,我們可以註冊一個使用新任務實作的新任務

app/build.gradle.kts
tasks.register<PrintVersion>("printVersion") {

    // Configuration code
    version = project.version as String
}
app/build.gradle
tasks.register(PrintVersion, "printVersion") {

    // Configuration code
    version = project.version.toString()
}

在任務的配置區塊內,我們可以編寫配置階段程式碼,該程式碼修改任務的輸入和輸出屬性的值。此處未以任何方式提及任務動作。

可以以更緊湊的方式直接在建置腳本中編寫像這樣的簡單任務,而無需為任務建立單獨的類別。

讓我們註冊另一個任務,並將其稱為 printVersionDynamic

這次,我們沒有為任務定義類型,這意味著任務將是通用類型 DefaultTask。此通用類型未定義任何任務動作,這表示它沒有使用 @TaskAction 註解的方法。此類型對於定義「生命週期任務」很有用

app/build.gradle.kts
tasks.register("printVersionDynamic") {

}
app/build.gradle
tasks.register("printVersionDynamic") {

}

但是,預設任務類型也可以用於動態定義具有自訂動作的任務,而無需額外的類別。這是透過使用 doFirst{}doLast{} 結構來完成的。類似於定義方法並註解此 @TaskAction,這會將動作新增到任務。

方法稱為 doFirst{}doLast{},因為任務可以有多個動作。如果任務已定義動作,您可以使用此區別來決定您的其他動作應在現有動作之前還是之後執行

app/build.gradle.kts
tasks.register("printVersionDynamic") {
    doFirst {
        // Task action = Execution code
        // Run before exiting actions
    }
    doLast {
        // Task action = Execution code
        // Run after existing actions
    }
}
app/build.gradle
tasks.register("printVersionDynamic") {
    doFirst {
        // Task action = Execution code
        // Run before exiting actions
    }
    doLast {
        // Task action = Execution code
        // Run after existing actions
    }
}

如果您只有一個動作,這裡是這種情況,因為我們從空任務開始,我們通常使用 doLast{} 方法。

在任務中,我們首先動態宣告我們要列印的版本作為輸入。我們沒有宣告屬性並使用 @Input 註解它,而是使用所有任務都具有的通用輸入屬性。然後,我們在 doLast{} 方法內新增動作程式碼,即 println() 陳述式

app/build.gradle.kts
tasks.register("printVersionDynamic") {
    inputs.property("version", project.version.toString())
    doLast {
        println("Version: ${inputs.properties["version"]}")
    }
}
app/build.gradle
tasks.register("printVersionDynamic") {
    inputs.property("version", project.version)
    doLast {
        println("Version: ${inputs.properties["version"]}")
    }
}

我們看到了兩種實作 Gradle 中自訂任務的替代方法。

動態設定使其更緊湊。但是,在編寫動態任務時,很容易混合配置和執行時間狀態。您還可以發現「輸入」在動態任務中是未輸入的,這可能會導致問題。當您將自訂任務實作為類別時,您可以將輸入清楚地定義為具有專用類型的屬性。

動態修改任務動作可以為已註冊的任務提供價值,但由於某些原因您需要修改這些任務。

讓我們以 compileJava 任務為例。

註冊任務後,您無法移除它。相反,您可以清除其動作

app/build.gradle.kts
tasks.compileJava {
    // Clear existing actions
    actions.clear()

    // Add a new action
    doLast {
        println("Custom action: Compiling Java classes...")
    }
}
app/build.gradle
tasks.compileJava {
    // Clear existing actions
    actions.clear()

    // Add a new action
    doLast {
        println("Custom action: Compiling Java classes...")
    }
}

也很難,並且在某些情況下不可能移除您正在使用的外掛程式已設定的某些任務相依性。相反,您可以修改其行為

app/build.gradle.kts
tasks.compileJava {
    // Modify the task behavior
    doLast {
        val outputDir = File("$buildDir/compiledClasses")
        outputDir.mkdirs()

        val compiledFiles = sourceSets["main"].output.files
        compiledFiles.forEach { compiledFile ->
            val destinationFile = File(outputDir, compiledFile.name)
            compiledFile.copyTo(destinationFile, true)
        }

        println("Java compilation completed. Compiled classes copied to: ${outputDir.absolutePath}")
    }
}
app/build.gradle
tasks.compileJava {
    // Modify the task behavior
    doLast {
        def outputDir = file("$buildDir/compiledClasses")
        outputDir.mkdirs()

        def compiledFiles = sourceSets["main"].output.files
        compiledFiles.each { compiledFile ->
            def destinationFile = new File(outputDir, compiledFile.name)
            compiledFile.copyTo(destinationFile)
        }

        println("Java compilation completed. Compiled classes copied to: ${outputDir.absolutePath}")
    }
}

了解輸入和輸出

任務輸入和輸出對於以下方面很重要

  1. 最新檢查 - Gradle 的增量建置功能透過查看任務輸入和輸出的變更,協助您的建置避免多次執行相同的工作。

  2. 連結任務輸入和輸出 - 當一個任務的輸出連結到另一個任務的輸入時,Gradle 可以自動建立任務相依性。

  3. 使用相依性配置 - 任務輸出可用於告知 Gradle 任務產生的 Artifact 應新增到特定配置。

宣告輸入和輸出

您可以透過兩種主要方式配置任務的輸入和輸出

  1. 靜態配置:直接在任務類別內定義輸入和輸出。這些輸入和輸出將始終應用於任務,無論何時執行。

  2. 動態配置:動態將輸入和輸出新增到任務,這表示您可以根據特定條件或需求自訂任務每次執行的輸入和輸出。這種方法可以更靈活地精細控制任務的行為。

abstract class ConfigurableTask : DefaultTask() {

    @Input
    val inputProperty = project.objects.property(String::class.java)

    @OutputFile
    val outputFile = project.objects.fileProperty()

    // Static Configuration: Inputs and Outputs defined in the task class
    init {
        group = "custom"
        description = "A configurable task example"
    }

    @TaskAction
    fun executeTask() {
        println("Executing task with input: ${inputProperty.get()} and output: ${outputFile.get()}")
    }

}

// Dynamic Configuration: Adding inputs and outputs to a task instance
tasks.register("dynamicTask", ConfigurableTask::class) {
    // Set the input property dynamically
    inputProperty.set("dynamic input value")

    // Set the output file dynamically
    outputFile.set(layout.buildDirectory.file("dynamicOutput.txt"))
}
abstract class ConfigurableTask extends DefaultTask {

    @Input
    def inputProperty = project.objects.property(String)

    @OutputFile
    def outputFile = project.objects.fileProperty()

    // Static Configuration: Inputs and Outputs defined in the task class
    {
        group = 'custom'
        description = 'A configurable task example'
    }

    @TaskAction
    void executeTask() {
        println "Executing task with input: ${inputProperty.get()} and output: ${outputFile.get()}"
    }

}

// Dynamic Configuration: Adding inputs and outputs to a task instance
tasks.register('dynamicTask', ConfigurableTask) {
    // Set the input property dynamically
    inputProperty.set('dynamic input value')

    // Set the output file dynamically
    outputFile.set(layout.buildDirectory.file('dynamicOutput.txt'))
}

建立延遲輸入和輸出

Gradle 具有延遲配置的概念,它允許在實際設定任務輸入和輸出之前參考它們。這是透過 Property 類別類型完成的。

abstract class MyTask : DefaultTask() {
    // Avoid Java Bean properties
    @Input
    var myEagerProperty: String = "default value"

    // Use Gradle managed properties instead
    @Input
    val myLazyProperty: Property<String> = project.objects.property(String::class.java)

    @TaskAction
    fun myAction() {
        println("Use ${myLazyProperty.get()} and do NOT use $myEagerProperty")
    }
}
abstract class MyTask extends DefaultTask {
    // Avoid Java Bean properties
    @Input
    String myEagerProperty = "default value"

    // Use Gradle managed properties instead
    @Input
    Property<String> myLazyProperty = project.objects.property(String)

    @TaskAction
    void myAction() {
        println("Use ${myLazyProperty.get()} and do NOT use $myEagerProperty")
    }
}

此機制的一個優點是,您可以將一個任務的輸出檔案連結到另一個任務的輸入檔案,所有這些都在尚未確定檔案名稱之前完成。Property 類別也知道它連結到哪個任務,因此以這種方式使用輸入和輸出使 Gradle 能夠自動新增所需的任務相依性。

writing tasks 7

為了確保正確的延遲配置,您應該避免使用 Java Bean 類型。讓我們探索 Gradle 延遲類型可以宣告為任務輸入和輸出的常見選項

延遲 Gradle 類型 Java Bean 類型 輸入 輸出

Property<String>

String

X

RegularFileProperty

File

X

X

檔案的可迭代項 (Property<Iterable<File> 例如 ConfigurableFileCollectionConfigurableFileTree)

檔案的可迭代項 (Iterable<File> 例如 FileTreeFileCollection)

X

X

檔案的 Map (MapProperty<String, File>)

檔案的 Map (Map<String, File>)

X

DirectoryProperty

Directory

X

X

目錄的可迭代項 (Property<Iterable<File>)

目錄的可迭代項 (Iterable<File>)

X

目錄的 Map (MapProperty<String, File>)

目錄的 Map (Map<String, File>)

X

注意事項

  • 字串僅支援任務輸入,不支援輸出。這些通常用於配置選項,例如 compileJava 任務類型的 sourceCompatibility

  • 任務輸出只能是檔案或目錄。

  • 請勿使用 StringBooleanString 和其他標準類型,而應使用 Property<T>

  • 請勿使用 List<T>,而應使用 ListProperty<T>

  • 請勿使用 Set<T>,而應使用 SetProperty<T>

註解輸入和輸出

良好的做法是為您的自訂任務建立任務類別。該類別封裝了任務動作邏輯,但也應宣告任務期望的任何輸入和輸出。為此,我們使用註解。

對於任務輸入,我們可以使用 @Input@InputFile@InputDirectory@InputFiles@Classpath@CompileClasspath。對於任務輸出,我們有 @OutputFile@OutputDirectory@OutputFiles@OutputDirectories

以下是所有可用註解的詳細資訊

註解 用法

@Input

屬性是任務的輸入值

@InputFile

屬性是任務的輸入檔案

@InputFiles

屬性是一個或多個任務的輸入檔案

@InputDirectory

屬性是任務的輸入目錄

@Classpath

屬性是一個或多個表示 Java 類別路徑的輸入檔案或目錄

@CompileClasspath

屬性是一個或多個表示 Java 編譯類別路徑的輸入檔案或目錄

@OutputFile

屬性是任務的輸出檔案[1]

@OutputFiles

屬性是一個或多個任務的輸出檔案

@OutputDirectory

屬性是任務的輸出目錄

@OutputDirectories

屬性是一個或多個任務的輸出目錄

@Destroys

屬性是任務破壞(刪除/移除)的一個或多個檔案或目錄(來自其他任務)[2]

@LocalState

屬性是任務的本機狀態

@Nested

屬性是巢狀 Bean,應檢查其他註解

@Console

屬性不是輸入或輸出,不應在最新檢查中考慮在內

@ReplacedBy

屬性在內部使用,不應在最新檢查中考慮在內

@SkipWhenEmpty

屬性是檔案或目錄,當屬性的值為空時,應跳過任務

@Incremental

屬性是檔案或目錄,可以透過 @InputChanges.getFileChanges() 查詢對它的變更

@Optional

屬性是任何類型,其值不必指定,並且會停用驗證檢查

@PathSensitive

屬性是一個或多個檔案,只有檔案路徑的給定部分很重要

@IgnoreEmptyDirectories

@InputFiles@InputDirectory 一起使用,以指示 Gradle 僅追蹤對目錄內容的變更,而不追蹤目錄本身的差異。

@NormalizeLineEndings

@InputFiles@InputDirectory@Classpath 一起使用,以指示 Gradle 在計算最新檢查或建置快取金鑰時,正規化行尾符

請注意,在 Kotlin 中,註解以 get: 作為前綴,因此 @InputFile 變為 @get:InputFile

build.gradle.kts
abstract class AllTypes : DefaultTask() {

    //inputs
    @get:Input
    abstract val inputString: Property<String>
    @get:InputFile
    abstract val inputFile: RegularFileProperty
    @get:InputDirectory
    abstract val inputDirectory: DirectoryProperty
    @get:InputFiles
    abstract val inputFileCollection: ListProperty<RegularFile>
    @get:Classpath
    abstract val inputClasspath: ListProperty<RegularFile>

    // outputs
    @get:OutputFile
    abstract val outputFile: RegularFileProperty
    @get:OutputDirectory
    abstract val outputDirectory: DirectoryProperty
    @get:OutputFiles
    abstract val outputFiles: ListProperty<RegularFile>
    @get:OutputDirectories
    abstract val outputDirectories: ListProperty<Directory>
}
build.gradle
abstract class AllTypes extends DefaultTask {

    //inputs
    @Input
    final abstract Property<String> inputString = ""
    @InputFile
    final abstract RegularFileProperty inputFile = ""
    @InputDirectory
    final abstract DirectoryProperty inputDirectory = ""
    @InputFiles
    final abstract ConfigurableFileCollection inputFileCollection = ""
    @Classpath
    final abstract ConfigurableFileCollection inputClasspath = ""

    // outputs
    @OutputFile
    final abstract RegularFileProperty outputFile = ""
    @OutputDirectory
    final abstract DirectoryProperty outputDirectory = ""
    @OutputFiles
    final abstract ConfigurableFileCollection outputFiles = ""
    @OutputDirectories
    final abstract ConfigurableFileCollection outputDirectories = ""
}

1. 此標記用於宣告任務可能會產生特定的輸出檔案。此註解通常應用於任務類別中表示輸出檔案的屬性。當執行具有 @OutputFile 註解屬性的任務時,Gradle 將檢查指定的輸出檔案是否存在。如果檔案不存在,或者如果任務的輸入(由 @Input 註解定義)已變更,Gradle 將認為任務已過時,並將執行它以重新產生輸出檔案。
2. 此標記用於指定任務將始終刪除的位置/檔案,而這些位置/檔案通常屬於其他任務。此註解對於清理其他任務(例如 clean)之後的任務很有用。