本節將引導您將基於 Groovy 的 Gradle 建置腳本轉換為 Kotlin。

Gradle 較新的 Kotlin DSL 在受支援的 IDE 中提供愉快的編輯體驗:內容輔助、重構、文件等。

IntelliJ IDEA and Android Studio

請也閱讀 Gradle Kotlin DSL 入門 以了解 Gradle Kotlin DSL 的具體內容、限制和用法。

使用者手冊的其餘部分包含建置腳本摘錄,展示了 Groovy DSL 和 Kotlin DSL。這是找到如何執行此操作以及使用每個 DSL 的最佳位置;它涵蓋了從 使用外掛自訂相依性解析行為 的所有 Gradle 功能。

開始移轉前

請閱讀:在您移轉之前了解以下重要資訊很有幫助

  • 使用最新版本的 Gradle、套用外掛和您的 IDE 應該是您的第一步。

  • Intellij IDEA 和 Android Studio 完全支援 Kotlin DSL。其他 IDE,例如 Eclipse 或 NetBeans,尚未提供有用的工具來編輯 Gradle Kotlin DSL 檔案,但是匯入和使用基於 Kotlin DSL 的建置與平常一樣。

  • 在 IntelliJ IDEA 中,您必須 從 Gradle 模型匯入專案 才能取得 Kotlin DSL 腳本的內容輔助和重構工具。

  • 在某些情況下,Kotlin DSL 會較慢。例如,首次使用、在乾淨的結帳或短暫的 CI 代理上,已知會較慢。當 buildSrc 目錄中的某些內容發生變更,導致建置腳本快取失效時,也會發生相同的情況。建置設定時間較慢可能會影響 IDE 的回應速度,請查看 Gradle 效能文件

  • 您必須執行 Java 8 或更高版本的 Gradle。不支援 Java 7。

  • 已知嵌入式 Kotlin 編譯器可在 x86-64 架構的 Linux、macOS、Windows、Cygwin、FreeBSD 和 Solaris 上執行。

  • 了解 Kotlin 語法和基本語言功能非常有幫助。Kotlin 參考文件Kotlin Koans 對您應該有幫助。

  • 使用 plugins {} 區塊來宣告 Gradle 外掛可大幅改善編輯體驗,強烈建議您使用。在將 Groovy 建置指令碼轉換為 Kotlin 之前,請考慮在您的 Groovy 建置指令碼中採用此區塊。

  • Kotlin DSL 不會支援 model {} 元素。這是 已停用的 Gradle 軟體模型 的一部分。

  • 不建議啟用孵化中依需求配置功能,因為它可能會導致難以診斷的問題。

Gradle Kotlin DSL 入門 中閱讀更多資訊。

如果您遇到問題或疑似錯誤,請利用 gradle/gradle 問題追蹤器

您不必一次全部移轉! 基於 Groovy 和 Kotlin 的建置指令碼都可以 apply 任何一種語言的其他指令碼。您可以在 Kotlin DSL 範例 中找到未涵蓋在 Gradle 功能中的任何靈感。

準備您的 Groovy 指令碼

一些簡單的 Kotlin 和 Groovy 語言差異可能會讓指令碼轉換變得繁瑣

  • Groovy 字串可以用單引號 '字串' 或雙引號 "字串" 引用,而 Kotlin 則需要雙引號 "字串"

  • Groovy 允許在呼叫函式時省略括號,而 Kotlin 則總是需要括號。

  • Gradle Groovy DSL 允許在指定屬性時省略 = 指定運算子,而 Kotlin 則總是需要指定運算子。

建議您透過以下方式準備您的 Groovy 建置指令碼,作為第一個移轉步驟

  • 使用雙引號統一引號,

  • 區分函式呼叫和屬性指定(分別使用括號和指定運算子)。

前者可以透過搜尋 ' 並替換為 " 輕鬆完成。例如,

group 'com.acme'
dependencies {
    implementation 'com.acme:example:1.0'
}

變成

group "com.acme"
dependencies {
    implementation "com.acme:example:1.0"
}

下一步會稍微複雜一點,因為在 Groovy 指令碼中區分函式呼叫和屬性指定可能並非易事。一個好的策略是先將所有不明確的陳述指定為屬性指定,然後透過將失敗的陳述轉換為函式呼叫來修正建置。

例如,

group "com.acme"
dependencies {
    implementation "com.acme:example:1.0"
}

變成

group = "com.acme"                          (1)
dependencies {
    implementation("com.acme:example:1.0")  (2)
}
1 屬性指派
2 函數呼叫

雖然仍然是有效的 Groovy,但現在它沒有歧義且接近 Kotlin 語法,這使得將腳本重新命名為 Gradle Kotlin DSL 腳本變得更容易。

重要的是要注意,雖然可以使用物件的 ext 屬性修改 Groovy 額外屬性,但在 Kotlin 中,它們是使用 extra 屬性修改的。重要的是查看每個物件並相應地更新建置腳本。

您可以在 使用者指南 中找到範例。

腳本檔案命名

Groovy DSL 腳本檔案使用 .gradle 檔案名稱副檔名。Kotlin DSL 腳本檔案使用 .gradle.kts 檔案名稱副檔名。

若要使用 Kotlin DSL,只需將檔案命名為 build.gradle.kts,而不是 build.gradle

設定檔 settings.gradle 也可重新命名為 settings.gradle.kts

在多專案建置中,您可以使用 Groovy DSL(搭配 build.gradle)使用某些模組,並使用 Kotlin DSL(搭配 build.gradle.kts)使用其他模組。

此外,套用以下慣例以獲得更好的 IDE 支援

  • 根據模式 *.settings.gradle.kts 命名套用至 Settings 的腳本,

  • 根據模式 *.init.gradle.kts 命名 初始化腳本

套用外掛

就像 Groovy DSL 一樣,有兩種方法可以套用 Gradle 外掛

以下是使用宣告式 plugins {} 區塊的範例

build.gradle.kts
plugins {
    java
    jacoco
    `maven-publish`
    id("org.springframework.boot") version "2.7.8"
}
build.gradle
plugins {
    id 'java'
    id 'jacoco'
    id 'maven-publish'
    id 'org.springframework.boot' version '2.7.8'
}

Kotlin DSL 為所有 Gradle 核心外掛 提供屬性擴充,如上所示,其中包含 javajacocomaven-publish 宣告。

第三方外掛可以與 Groovy DSL 以相同的方式套用。除了雙引號和括弧之外。您也可以使用該樣式套用核心外掛。但建議使用靜態型別存取器,因為它們是類型安全的,並且會由您的 IDE 自動完成。

您也可以使用命令式 apply 語法,但非核心外掛必須包含在建置腳本的類別路徑中

build.gradle.kts
buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.7.8")
    }
}

apply(plugin = "java")
apply(plugin = "jacoco")
apply(plugin = "org.springframework.boot")
build.gradle
buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath('org.springframework.boot:spring-boot-gradle-plugin:2.7.8')
    }
}

apply plugin: 'java'
apply plugin: 'jacoco'
apply plugin: 'org.springframework.boot'

我們強烈建議您使用 plugins {} 區塊,而不是 apply() 函數。

plugins {} 區塊的宣告性質讓 Kotlin DSL 能夠提供類型安全的存取器,以存取已套用的外掛程式所提供的擴充功能、組態和其他功能,這讓 IDE 容易找出外掛程式模型的詳細資料,並讓這些資料容易組態。
請參閱 Gradle 使用者手冊中的 plugins {} 區塊文件 以取得更多資訊。

組態外掛程式

許多外掛程式都附有擴充功能以供組態。如果這些外掛程式是使用宣告式的 plugins {} 區塊套用的,那麼 Kotlin 擴充功能函式就會提供給您,以組態其擴充功能,就像在 Groovy 中一樣。下列範例顯示這對 Jacoco 外掛程式的運作方式。

build.gradle.kts
plugins {
    jacoco
}

jacoco {
    toolVersion = "0.8.1"
}
build.gradle
plugins {
    id 'jacoco'
}

jacoco {
    toolVersion = '0.8.1'
}

相反地,如果您使用命令式的 apply() 函式套用外掛程式,那麼您就必須使用 configure<T>() 函式來組態該外掛程式。下列範例顯示這對 Checkstyle 外掛程式的運作方式,方法是在 configure<T>() 函式中明確宣告外掛程式的擴充功能類別 — CheckstyleExtension

build.gradle.kts
apply(plugin = "checkstyle")

configure<CheckstyleExtension> {
    maxErrors = 10
}
build.gradle
apply plugin: "checkstyle"

checkstyle {
    maxErrors = 10
}

再次強調,我們強烈建議您透過 plugins {} 區塊宣告式地套用外掛程式。

了解有哪些外掛程式提供的擴充功能可用

由於您的 IDE 知道外掛程式提供的組態元素,因此當您要求 IDE 提供建議時,它會包含那些元素。這會發生在建置指令碼的最上層 — 大多數外掛程式擴充功能都會新增到 Project 物件 — 以及擴充功能的組態區塊內。

您也可以執行 :kotlinDslAccessorsReport 任務,以了解所有已套用外掛程式所提供的擴充功能。它會列印您可以用來存取那些擴充功能的 Kotlin 程式碼,並提供存取器方法的名稱和類型。

如果您要組態的外掛程式在其方法簽章中依賴 groovy.lang.Closure,或使用其他動態 Groovy 語意,那麼從 Kotlin DSL 建置指令碼組態該外掛程式將需要更多工作。請參閱 Gradle Kotlin DSL 文件的 互通性區段,以取得更多關於如何從 Kotlin 程式碼呼叫 Groovy 程式碼,或將該外掛程式的組態保留在 Groovy 指令碼中的資訊。

外掛程式也會提供您可能想要直接組態的任務。此主題在下列的 組態任務 區段中說明。

保持建置指令碼宣告式

若要充分利用 Gradle Kotlin DSL,您應該努力保持建置指令碼宣告式。這裡要記住的主要事項是,為了取得類型安全的存取器,必須在建置指令碼主體之前套用外掛程式。

強烈建議您在 Gradle 使用者手冊中閱讀關於使用 Gradle Kotlin DSL 組態外掛程式 的說明。

如果您的建置是多專案建置,例如大多數 Android 建置,請也閱讀後續關於 多專案建置 的區段。

最後,有一些策略可以 使用 plugins {} 區塊搭配未發佈正確元資料的外掛程式,例如 Android Gradle 外掛程式。

配置迴避

Gradle 4.9 引進了一個新的 API,用於在建置腳本和外掛中建立和設定工作。此新 API 的用意是最終取代現有的 API。

現有 Gradle 工作 API 和新 API 之間的主要差異之一,在於 Gradle 是否花時間建立 `Task` 實例並執行設定程式碼。新 API 允許 Gradle 延遲或完全避免設定在建置中永遠不會執行的工作。例如,在編譯程式碼時,Gradle 不需要設定執行測試的工作。

請參閱 Evolving the Gradle API to reduce configuration time 部落格文章和使用者手冊中的 工作設定迴避 章節,以取得更多資訊。

Gradle Kotlin DSL 透過讓類型安全模型存取器利用新 API,並提供 DSL 建構來讓它們更容易使用,來採用設定迴避。請放心,整個 Gradle API 仍然可用。

設定工作

設定工作的語法是 Groovy 和 Kotlin DSL 開始有顯著差異的地方。

build.gradle.kts
tasks.jar {
    archiveFileName = "foo.jar"
}
build.gradle
tasks.jar {
    archiveFileName = 'foo.jar'
}

請注意,在 Kotlin 中,`tasks.jar {}` 標記利用設定迴避 API 並延遲 `jar` 工作的設定。

如果類型安全工作存取器 `tasks.jar` 不可用,請參閱上述 設定外掛 區段,您可以使用 `tasks` 容器 API。以下範例的 Kotlin 風格與使用上述類型安全存取器的範例完全相同

build.gradle.kts
tasks.named<Jar>("jar") {
    archiveFileName = "foo.jar"
}
build.gradle
tasks.named('jar') {
    archiveFileName = 'foo.jar'
}

請注意,由於 Kotlin 是一種靜態類型語言,因此必須明確指定工作的類型。否則,腳本將無法編譯,因為推斷出的類型將是 `Task`,而不是 `Jar`,而 `archiveName` 屬性是特定於 `Jar` 工作類型。

如果設定迴避妨礙您進行遷移,而且您想要急切設定一個任務,就像 Groovy 一樣,您可以使用 tasks 容器上的急切設定 API 來執行此操作

build.gradle.kts
tasks.getByName<Jar>("jar") {
    archiveFileName = "foo.jar"
}
build.gradle
tasks.getByName('jar') {
    archiveFileName = 'foo.jar'
}

在 Gradle Kotlin DSL 中使用容器的詳細資訊在此處說明

瞭解任務的類型

如果您不知道任務的類型,則您可以透過內建的 help 任務找出該資訊。只要使用 --task 選項傳遞您有興趣使用的任務名稱,如下所示

❯ ./gradlew help --task jar
...
Type
     Jar (org.gradle.api.tasks.bundling.Jar)

讓我們透過執行一個快速的工作範例來彙整所有這些內容,該範例會設定 Spring Boot 專案的 bootJarbootRun 任務

build.gradle.kts
plugins {
    java
    id("org.springframework.boot") version "2.7.8"
}

tasks.bootJar {
    archiveFileName = "app.jar"
    mainClass = "com.example.demo.Demo"
}

tasks.bootRun {
    mainClass = "com.example.demo.Demo"
    args("--spring.profiles.active=demo")
}
build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.8'
}

tasks.bootJar {
    archiveFileName = 'app.jar'
    mainClass = 'com.example.demo.Demo'
}

tasks.bootRun {
    mainClass = 'com.example.demo.Demo'
    args '--spring.profiles.active=demo'
}

這相當不言自明。主要差異在於,在使用 Kotlin DSL 存取器時,任務設定會自動變為延遲執行。

現在,為了範例,讓我們來看看使用 API 套用相同的設定,而不是類型安全存取器,這些存取器可能無法使用,具體取決於建置邏輯結構,請參閱 Gradle 使用者手冊中對應的文件以取得更多資訊。

我們首先透過 help 任務來確定 bootJarbootRun 任務的類型

❯ ./gradlew help --task bootJar
...
Type
     BootJar (org.springframework.boot.gradle.tasks.bundling.BootJar)
❯ ./gradlew help --task bootRun
...
Type
     BootRun (org.springframework.boot.gradle.tasks.run.BootRun)

現在我們知道了這兩個任務的類型,我們可以匯入相關類型 — BootJarBootRun — 並根據需要設定任務。請注意,IDE 可以協助我們進行必要的匯入,因此我們只需要簡單名稱,也就是說,不需要完整的套件。以下是結果建置指令碼,包含匯入

build.gradle.kts
import org.springframework.boot.gradle.tasks.bundling.BootJar
import org.springframework.boot.gradle.tasks.run.BootRun

// TODO:Finalize Upload Removal - Issue #21439
plugins {
    java
    id("org.springframework.boot") version "2.7.8"
}

tasks.named<BootJar>("bootJar") {
    archiveFileName = "app.jar"
    mainClass = "com.example.demo.Demo"
}

tasks.named<BootRun>("bootRun") {
    mainClass = "com.example.demo.Demo"
    args("--spring.profiles.active=demo")
}
build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.8'
}

tasks.named('bootJar') {
    archiveFileName = 'app.jar'
    mainClass = 'com.example.demo.Demo'
}

tasks.named('bootRun') {
    mainClass = 'com.example.demo.Demo'
    args '--spring.profiles.active=demo'
}

建立任務

可以使用名為 task(…​) 的指令碼頂層函數來建立任務

build.gradle.kts
task("greeting") {
    doLast { println("Hello, World!") }
}
build.gradle
task greeting {
    doLast { println 'Hello, World!' }
}

請注意,上述內容會使用 Groovy 和 Kotlin DSL 急切設定已建立的任務。

也可以在 tasks 容器上註冊或建立任務,分別使用 register(…​)create(…​) 函數,如下所示

build.gradle.kts
tasks.register("greeting") {
    doLast { println("Hello, World!") }
}
build.gradle
tasks.register('greeting') {
    doLast { println('Hello, World!') }
}
build.gradle.kts
tasks.create("greeting") {
    doLast { println("Hello, World!") }
}
build.gradle
tasks.create('greeting') {
    doLast { println('Hello, World!') }
}

上述範例會建立未分型的臨時任務,但您通常會想要建立特定類型的任務。也可以使用相同的 register()create() 方法來執行此操作。以下是一個建立 Zip 類型新任務的範例

build.gradle.kts
tasks.register<Zip>("docZip") {
    archiveFileName = "doc.zip"
    from("doc")
}
build.gradle
tasks.register('docZip', Zip) {
    archiveFileName = 'doc.zip'
    from 'doc'
}
build.gradle.kts
tasks.create<Zip>("docZip") {
    archiveFileName = "doc.zip"
    from("doc")
}
build.gradle
tasks.create(name: 'docZip', type: Zip) {
    archiveFileName = 'doc.zip'
    from 'doc'
}

設定和相依性

在現有的設定中宣告相依性與在 Groovy 建置指令碼中所做的方式類似,如下面的範例所示

build.gradle.kts
plugins {
    `java-library`
}
dependencies {
    implementation("com.example:lib:1.1")
    runtimeOnly("com.example:runtime:1.0")
    testImplementation("com.example:test-support:1.3") {
        exclude(module = "junit")
    }
    testRuntimeOnly("com.example:test-junit-jupiter-runtime:1.3")
}
build.gradle
plugins {
    id 'java-library'
}
dependencies {
    implementation 'com.example:lib:1.1'
    runtimeOnly 'com.example:runtime:1.0'
    testImplementation('com.example:test-support:1.3') {
        exclude(module: 'junit')
    }
    testRuntimeOnly 'com.example:test-junit-jupiter-runtime:1.3'
}

應用外掛所提供的每個設定也可用作 configurations 容器的成員,因此您可以像參考任何其他設定一樣參考它。

了解有哪些設定可用

找出有哪些設定可用的最簡單方法是詢問您的 IDE 在 configurations 容器中提供建議。

您也可以使用 :kotlinDslAccessorsReport 任務,它會列印用於存取由應用外掛所提供的設定的 Kotlin 程式碼,並提供所有這些存取器的名稱。

請注意,如果您沒有使用 plugins {} 區塊來應用您的外掛,則您將無法以一般的方式設定由這些外掛提供的相依性設定。相反地,您必須使用設定名稱的字串常數,這表示您不會獲得 IDE 支援

build.gradle.kts
apply(plugin = "java-library")
dependencies {
    "implementation"("com.example:lib:1.1")
    "runtimeOnly"("com.example:runtime:1.0")
    "testImplementation"("com.example:test-support:1.3") {
        exclude(module = "junit")
    }
    "testRuntimeOnly"("com.example:test-junit-jupiter-runtime:1.3")
}
build.gradle
apply plugin: 'java-library'
dependencies {
    implementation 'com.example:lib:1.1'
    runtimeOnly 'com.example:runtime:1.0'
    testImplementation('com.example:test-support:1.3') {
        exclude(module: 'junit')
    }
    testRuntimeOnly 'com.example:test-junit-jupiter-runtime:1.3'
}

這是只要您能就使用 plugins {} 區塊的另一個原因!

自訂設定和相依性

有時您需要建立自己的設定並將相依性附加到它們。以下範例宣告兩個新的設定

  • db,我們會將 PostgreSQL 相依性加入其中

  • integTestImplementation,設定為延伸 testImplementation 設定,我們會將不同的相依性加入其中

build.gradle.kts
val db by configurations.creating
val integTestImplementation by configurations.creating {
    extendsFrom(configurations["testImplementation"])
}

dependencies {
    db("org.postgresql:postgresql")
    integTestImplementation("com.example:integ-test-support:1.3")
}
build.gradle
configurations {
    db
    integTestImplementation {
        extendsFrom testImplementation
    }
}

dependencies {
    db 'org.postgresql:postgresql'
    integTestImplementation 'com.example:integ-test-support:1.3'
}

請注意,我們只能在上述範例的 dependencies {} 區塊中使用 db(…​)integTestImplementation(…​) 表示法,因為兩個設定都是透過 creating() 方法事先宣告為委派屬性。如果設定是在其他地方定義的,您只能透過先透過 configurations(與 configurations.creating() 相反)建立委派屬性,或透過在 dependencies {} 區塊中使用字串常數來參考它們。以下範例示範這兩種方法

build.gradle.kts
// get the existing 'testRuntimeOnly' configuration
val testRuntimeOnly by configurations

dependencies {
    testRuntimeOnly("com.example:test-junit-jupiter-runtime:1.3")
    "db"("org.postgresql:postgresql")
    "integTestImplementation"("com.example:integ-test-support:1.3")
}

遷移策略

如上所述,使用 Kotlin DSL 和使用 Groovy DSL 的指令碼都可以參與相同的建置。此外,來自 buildSrc 目錄、包含建置或外部位置的 Gradle 外掛可以使用任何 JVM 語言實作。這使得可以逐漸遷移建置,逐一進行,而不會阻礙您的團隊。

有兩種方法可以進行遷移

  • 將您現有的建置語法逐漸遷移到 Kotlin,同時保留結構,我們稱之為機械遷移

  • 將您的建置邏輯重新調整為 Gradle 最佳實務,並在其中切換到 Kotlin DSL

兩種方法都可行。機械遷移對於簡單的建置就足夠了。複雜且高度動態的建置可能無論如何都需要進行一些重新調整,因此在這種情況下,重新實作建置邏輯以遵循 Gradle 最佳實務是有意義的。

由於套用 Gradle 最佳實務將使您的建置更容易使用且速度更快,我們建議您最終以這種方式遷移所有專案,但專注於必須首先重新調整的專案以及從這些改進中受益最多的專案是有意義的。

還要考慮到您的建置邏輯中有多少部分依賴於 Groovy 的動態層面,從 Kotlin DSL 使用它們會變得更加困難。無論動態 Groovy 建置邏輯位於何處,您都可以在 Gradle Kotlin DSL 文件的互操作性區段 中找到如何從靜態 Kotlin 跨越動態邊界的範例。

有兩個關鍵的最佳實務,可以讓您更容易在 Kotlin DSL 的靜態環境中工作

  • 使用 plugins {} 區塊

  • 將本機建置邏輯放入建置的 buildSrc 目錄中

plugins {} 區塊 旨在讓您的建置指令碼保持宣告式,以充分利用 Kotlin DSL。

使用 buildSrc 專案 是將您的建置邏輯整理到可輕鬆測試且提供良好 IDE 支援的共用本機外掛和慣例中。

Kotlin DSL 建置結構範例

根據您的建置結構,您可能對下列使用者手冊章節感興趣

互操作性

在建置邏輯中混合語言時,您可能必須跨越語言界線。極端的範例是同時使用 Kotlin DSL 和 Groovy DSL 建置指令碼,並使用以 Java、Groovy 和 Kotlin 實作的任務和外掛的建置。

引用 Kotlin 參考文件

Kotlin 的設計考量了與 Java 的互操作性。現有的 Java 程式碼可以用自然的方式從 Kotlin 呼叫,而 Kotlin 程式碼也可以很順利地從 Java 使用。

Kotlin 參考文件中非常詳盡地說明了 從 Kotlin 呼叫 Java從 Java 呼叫 Kotlin 的方法。

與 Groovy 程式碼的互操作性也大多適用相同的方法。此外,Kotlin DSL 提供了多種選擇 Groovy 語意的方式。

關於 Gradle Kotlin DSL 和互操作性

請參閱 Gradle Kotlin DSL Primer 的互操作性區段 中的詳細文件。