將建置邏輯從 Groovy 遷移至 Kotlin
本節將引導您完成將基於 Groovy 的 Gradle 建置腳本轉換為 Kotlin 的過程。
Gradle 較新的 Kotlin DSL 在支援的 IDE 中提供令人愉悅的編輯體驗:內容輔助、重構、文件等等。

另請閱讀 Gradle Kotlin DSL 入門,以了解 Gradle Kotlin DSL 的特殊性、限制和用法。 |
開始遷移之前
請閱讀: 在您遷移之前,了解以下重要資訊很有幫助
-
使用最新版本的 Gradle、已套用的外掛程式和您的 IDE 應該是您的第一步。
-
Kotlin DSL 在 Intellij IDEA 和 Android Studio 中受到完全支援。其他 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 編譯器可在 Linux、macOS、Windows、Cygwin、FreeBSD 和 Solaris 的 x86-64 架構上運作。
-
了解 Kotlin 語法和基本語言功能非常有幫助。Kotlin 參考文件和Kotlin Koans 對您應該很有用。
-
使用
plugins {}
區塊宣告 Gradle 外掛程式可以顯著改善編輯體驗,強烈建議使用。在將 Groovy 建置腳本轉換為 Kotlin 之前,請考慮採用它。 -
Kotlin DSL 不支援
model {}
元素。這是已終止的 Gradle 軟體模型的一部分。 -
不建議啟用孵化中的依需配置功能,因為它可能會導致難以診斷的問題。
在Gradle Kotlin DSL 入門中閱讀更多資訊。
如果您遇到問題或疑似錯誤,請利用 gradle/gradle
問題追蹤器。
您不必一次遷移所有內容! 基於 Groovy 和 Kotlin 的建置腳本都可以 apply
其他任何語言的腳本。您可以從 Kotlin DSL 範例 中找到任何 Gradle 功能的靈感,這些範例未涵蓋在內。
準備您的 Groovy 腳本
一些簡單的 Kotlin 和 Groovy 語言差異可能會使轉換腳本變得乏味
-
Groovy 字串可以使用單引號
'string'
或雙引號"string"
引起來,而 Kotlin 則需要雙引號"string"
。 -
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 {}
區塊的範例
plugins {
java
jacoco
`maven-publish`
id("org.springframework.boot") version "2.7.8"
}
plugins {
id 'java'
id 'jacoco'
id 'maven-publish'
id 'org.springframework.boot' version '2.7.8'
}
Kotlin DSL 為所有 Gradle 核心外掛程式 提供屬性擴充功能,如上所示,使用 java
、jacoco
或 maven-publish
宣告。
第三方外掛程式可以與 Groovy DSL 以相同的方式套用。除了雙引號和括號。您也可以使用該樣式套用核心外掛程式。但建議使用靜態類型存取器,因為它們是類型安全的,並且會由您的 IDE 自動完成。
您也可以使用命令式 apply
語法,但隨後必須將非核心外掛程式包含在建置腳本的類別路徑中
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")
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 {}
區塊套用的,則會提供 Kotlin 擴充功能函數來配置其擴充功能,與 Groovy 中的方式相同。以下範例示範了 Jacoco 外掛程式的運作方式。
plugins {
jacoco
}
jacoco {
toolVersion = "0.8.1"
}
plugins {
id 'jacoco'
}
jacoco {
toolVersion = '0.8.1'
}
相反地,如果您使用命令式 apply()
函數來套用外掛程式,則必須使用 configure<T>()
函數來配置該外掛程式。以下範例示範了 Checkstyle 外掛程式的運作方式,方法是在 configure<T>()
函數中明確宣告外掛程式的擴充功能類別 — CheckstyleExtension
—
apply(plugin = "checkstyle")
configure<CheckstyleExtension> {
maxErrors = 10
}
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 和新 Gradle 工作 API 之間的主要差異之一是 Gradle 是否花費時間來建立
Task
實例並執行配置程式碼。新的 API 允許 Gradle 延遲或完全避免配置永遠不會在建置中執行的工作。例如,在編譯程式碼時,Gradle 不需要配置執行測試的工作。
請參閱 改進 Gradle API 以減少配置時間 部落格文章和使用者手冊中的 避免工作配置 章節,以取得更多資訊。
Gradle Kotlin DSL 透過使類型安全模型存取器利用新的 API 並提供 DSL 建構來使其更易於使用,從而擁抱避免配置。請放心,整個 Gradle API 仍然可用。
配置工作
配置工作的語法是 Groovy 和 Kotlin DSL 開始顯著不同的地方。
tasks.jar {
archiveFileName = "foo.jar"
}
tasks.jar {
archiveFileName = 'foo.jar'
}
請注意,在 Kotlin 中,tasks.jar {}
表示法利用了避免配置 API 並延遲了 jar
工作的配置。
如果類型安全工作存取器 tasks.jar
不可用,請參閱上面的 配置外掛程式 章節,您可以回退到使用 tasks
容器 API。以下範例的 Kotlin 風格與上面使用類型安全存取器的範例完全相同
tasks.named<Jar>("jar") {
archiveFileName = "foo.jar"
}
tasks.named('jar') {
archiveFileName = 'foo.jar'
}
請注意,由於 Kotlin 是一種靜態類型語言,因此有必要明確指定工作的類型。否則,腳本將無法編譯,因為推斷的類型將是 Task
,而不是 Jar
,而 archiveName
屬性是 Jar
工作類型特有的。
如果避免配置妨礙了您的遷移,並且您想要像 Groovy 一樣急切地配置工作,您可以透過在 tasks
容器上使用急切配置 API 來實現
tasks.getByName<Jar>("jar") {
archiveFileName = "foo.jar"
}
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 專案的 bootJar
和 bootRun
工作
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")
}
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
工作確定 bootJar
和 bootRun
工作的類型
❯ ./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)
現在我們知道這兩個工作的類型,我們可以匯入相關類型 — BootJar
和 BootRun
— 並根據需要配置工作。請注意,IDE 可以協助我們完成所需的匯入,因此我們只需要簡單的名稱,即不包含完整套件。以下是產生的建置腳本,包含匯入
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")
}
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(…)
的腳本頂層函數來完成建立工作
task("greeting") {
doLast { println("Hello, World!") }
}
task greeting {
doLast { println 'Hello, World!' }
}
請注意,以上內容使用 Groovy 和 Kotlin DSL 都急切地配置了建立的工作。
註冊或建立工作也可以在 tasks
容器上完成,分別使用 register(…)
和 create(…)
函數,如下所示
tasks.register("greeting") {
doLast { println("Hello, World!") }
}
tasks.register('greeting') {
doLast { println('Hello, World!') }
}
tasks.create("greeting") {
doLast { println("Hello, World!") }
}
tasks.create('greeting') {
doLast { println('Hello, World!') }
}
上面的範例建立未類型化的臨時工作,但您更常希望建立特定類型的工作。這也可以使用相同的 register()
和 create()
方法來完成。以下範例建立了一個新的 Zip
類型的工作
tasks.register<Zip>("docZip") {
archiveFileName = "doc.zip"
from("doc")
}
tasks.register('docZip', Zip) {
archiveFileName = 'doc.zip'
from 'doc'
}
tasks.create<Zip>("docZip") {
archiveFileName = "doc.zip"
from("doc")
}
tasks.create(name: 'docZip', type: Zip) {
archiveFileName = 'doc.zip'
from 'doc'
}
配置和相依性
在現有配置中宣告相依性與在 Groovy 建置腳本中完成的方式類似,您可以在此範例中看到
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")
}
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 支援
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")
}
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
配置,我們在其中新增了不同的相依性
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")
}
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 {}
區塊中使用字串文字來參考它們。以下範例示範了這兩種方法
// 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 建置結構範例
根據您的建置結構,您可能對以下使用者手冊章節感興趣
-
撰寫建置腳本章節示範了使用
apply(from = "")
來模組化建置腳本。 -
多專案建置章節示範了各種多專案建置結構。
-
開發自訂 Gradle 外掛程式和 Gradle Kotlin DSL 入門章節示範了如何開發自訂 Gradle 外掛程式。
-
組合建置章節示範了如何使用複合建置。
互通性
當在您的建置邏輯中混合語言時,您可能必須跨越語言界線。一個極端的例子是,一個建置使用 Java、Groovy 和 Kotlin 實作的工作和外掛程式,同時也使用 Kotlin DSL 和 Groovy DSL 建置腳本。
引用 Kotlin 參考文件
Kotlin 在設計時就考慮到了 Java 互通性。現有的 Java 程式碼可以以自然的方式從 Kotlin 中調用,並且 Kotlin 程式碼也可以從 Java 中相當順利地使用。
在 Kotlin 參考文件中,從 Kotlin 調用 Java 和 從 Java 調用 Kotlin 都得到了很好的涵蓋。
這同樣主要適用於與 Groovy 程式碼的互通性。此外,Kotlin DSL 提供了幾種選擇加入 Groovy 語意的方法。
請在 Gradle Kotlin DSL 入門的互通性章節中找到詳細文件。