Gradle Kotlin DSL 入門
Gradle 的 Kotlin DSL 提供了一種替代傳統 Groovy DSL 的方案,在支援的 IDE 中提供增強的編輯體驗,包括更好的內容輔助、重構和文件。
本章探討了關鍵的 Kotlin DSL 結構,並示範如何使用它們與 Gradle API 互動。
如果您有興趣將現有的 Gradle 建置遷移到 Kotlin DSL,請查看專門的遷移頁面。 |
先決條件
-
嵌入式 Kotlin 編譯器可在 Linux、macOS、Windows、Cygwin、FreeBSD 和 Solaris 的 x86-64 架構上運作。
-
建議熟悉 Kotlin 語法和基本語言特性。請參考 Kotlin 文件 和 Kotlin Koans 學習基礎知識。
-
強烈建議使用
plugins {}
區塊來宣告 Gradle 外掛,因為它可以顯著改善編輯體驗。
IDE 支援
IntelliJ IDEA 和 Android Studio 完全支援 Kotlin DSL。雖然其他 IDE 缺少用於編輯 Kotlin DSL 檔案的進階工具,但您仍然可以匯入基於 Kotlin-DSL 的建置,並像往常一樣使用它們。
建置匯入 | 語法高亮 1 | 語義編輯器 2 | |
---|---|---|---|
IntelliJ IDEA |
✓ |
✓ |
✓ |
Android Studio |
✓ |
✓ |
✓ |
Eclipse IDE |
✓ |
✓ |
✖ |
CLion |
✓ |
✓ |
✖ |
Apache NetBeans |
✓ |
✓ |
✖ |
Visual Studio Code (LSP) |
✓ |
✓ |
✖ |
Visual Studio |
✓ |
✖ |
✖ |
1 Gradle Kotlin DSL 腳本中的 Kotlin 語法 高亮
2 Gradle Kotlin DSL 腳本中的 程式碼 完成、 導航到 來源、 文件、 重構 等等…
如限制中所述,您必須使用 Gradle 模型匯入您的專案,才能在 IntelliJ IDEA 中為 Kotlin DSL 腳本啟用內容輔助和重構工具。
配置時間較慢的建置可能會影響 IDE 的響應速度,因此請查看效能章節以幫助解決此類問題。
自動建置匯入與自動重新載入腳本相依性
當您更改建置邏輯時,IntelliJ IDEA 和 Android Studio 都會偵測到並提供兩個建議
-
再次匯入整個建置
-
在編輯建置腳本時重新載入腳本相依性
我們建議停用自動建置匯入,同時啟用自動重新載入腳本相依性。這種方法在編輯 Gradle 腳本時提供早期回饋,同時讓您可以控制整個建置設定何時與您的 IDE 同步。
請參閱疑難排解章節以了解更多資訊。
Kotlin DSL 腳本
就像其基於 Groovy 的對應物一樣,Kotlin DSL 建構於 Gradle 的 Java API 之上。Kotlin DSL 腳本中的所有內容都是 Kotlin 程式碼,由 Gradle 編譯和執行。您的建置腳本中的許多物件、函數和屬性都來自 Gradle API 和應用外掛的 API。
使用Kotlin DSL 參考搜尋來探索可用的成員。 |
腳本檔案名稱
-
Groovy DSL 腳本檔案使用
.gradle
檔案名稱副檔名。 -
Kotlin DSL 腳本檔案使用
.gradle.kts
檔案名稱副檔名。
您可以在同一個建置中混合使用 Groovy DSL 和 Kotlin DSL 腳本。例如,Kotlin DSL 建置腳本可以應用 Groovy DSL 腳本,並且多專案建置中的不同專案可以使用其中任何一種。
為了改善 IDE 支援,我們建議遵循以下慣例
-
使用
*.settings.gradle.kts
模式命名設定腳本(或任何由 GradleSettings
物件支援的腳本)。這包括從設定腳本應用程式的腳本外掛。 -
使用
*.init.gradle.kts
模式或僅僅init.gradle.kts
命名 初始化腳本。
隱式匯入
所有 Kotlin DSL 建置腳本都帶有隱式匯入,包括
-
Kotlin DSL API,其中包括來自以下套件的類型
-
org.gradle.kotlin.dsl
-
org.gradle.kotlin.dsl.plugins.dsl
-
org.gradle.kotlin.dsl.precompile
-
避免使用內部 Kotlin DSL API
在外掛和建置腳本中使用內部 Kotlin DSL API 可能會在更新 Gradle 或外掛時破壞建置。
Kotlin DSL API 使用 相應 API 文件 中列出的類型(但不包括其子套件中的類型)擴展了公共 Gradle API。
編譯警告
Gradle Kotlin DSL 腳本在建置的配置階段由 Gradle 編譯。
Kotlin 編譯器找到的棄用警告會在編譯腳本時在控制台上報告
> Configure project :
w: build.gradle.kts:4:5: 'getter for uploadTaskName: String!' is deprecated. Deprecated in Java
可以配置您的建置,以便在腳本編譯期間發出的任何警告都失敗,方法是設定 org.gradle.kotlin.dsl.allWarningsAsErrors
Gradle 屬性為 true
org.gradle.kotlin.dsl.allWarningsAsErrors=true
類型安全模型存取器
Groovy DSL 允許您按名稱引用許多建置模型元素,即使它們是在執行時期定義的,例如命名的配置或來源集。
例如,當應用 Java
外掛時,您可以透過 configurations.implementation
存取 implementation
配置。
Kotlin DSL 使用類型安全模型存取器取代了這種動態解析,類型安全模型存取器適用於外掛貢獻的模型元素。
了解何時可以使用類型安全模型存取器
Kotlin DSL 目前提供各種類型安全模型存取器集,每個集合都針對不同的範圍量身定制。
對於主專案建置腳本和預先編譯的專案腳本外掛
類型安全模型存取器 | 範例 |
---|---|
相依性和成品配置 |
|
專案擴展和慣例,以及它們的擴展 |
|
|
|
|
|
專案擴展容器中的元素 |
Java 外掛貢獻並添加到 |
對於主專案設定腳本和預先編譯的設定腳本外掛
類型安全模型存取器 | 範例 |
---|---|
|
|
初始化腳本和腳本外掛沒有類型安全模型存取器。這些限制將在未來的 Gradle 版本中移除。 |
可用類型安全模型存取器的集合在評估腳本主體之前立即確定,緊接在 plugins {}
區塊之後。在此之後貢獻的模型元素,例如在您的建置腳本中定義的配置,將無法與類型安全模型存取器一起使用
// Applies the Java plugin
plugins {
id("java")
}
repositories {
mavenCentral()
}
// Access to 'implementation' (contributed by the Java plugin) works here:
dependencies {
implementation("org.apache.commons:commons-lang3:3.12.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") // Add this if needed for runtime
}
// Add a custom configuration
configurations.create("customConfiguration")
// Type-safe accessors for 'customConfiguration' will NOT be available because it was created after the plugins block
dependencies {
customConfiguration("com.google.guava:guava:32.1.2-jre") // ❌ Error: No type-safe accessor for 'customConfiguration'
}
但是,這表示您可以將類型安全存取器用於父專案應用程式的外掛貢獻的任何模型元素。
以下專案建置腳本示範了如何使用類型安全存取器存取各種配置、擴展和其他元素
plugins {
`java-library`
}
dependencies { (1)
api("junit:junit:4.13")
implementation("junit:junit:4.13")
testImplementation("junit:junit:4.13")
}
configurations { (1)
implementation {
resolutionStrategy.failOnVersionConflict()
}
}
sourceSets { (2)
main { (3)
java.srcDir("src/core/java")
}
}
java { (4)
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks {
test { (5)
testLogging.showExceptions = true
useJUnit()
}
}
1 | 使用類型安全存取器來存取 Java 函式庫外掛 貢獻的 api 、implementation 和 testImplementation 相依性配置 |
2 | 使用存取器來配置 sourceSets 專案擴展 |
3 | 使用存取器來配置 main 來源集 |
4 | 使用存取器來配置 main 來源集的 java 來源 |
5 | 使用存取器來配置 test 任務 |
您的 IDE 知道類型安全存取器,並會將它們包含在其建議中。 這同時適用於建置腳本的頂層(大多數外掛擴展都添加到 |
請注意,容器元素(例如 configurations
、tasks
和 sourceSets
)的存取器利用了 Gradle 的 配置避免 API。例如,在 tasks
上,存取器的類型為 TaskProvider<T>
,並提供基礎任務的延遲參考和延遲配置。
以下是一些範例,說明何時應用配置避免
tasks.test {
// lazy configuration
useJUnitPlatform()
}
// Lazy reference
val testProvider: TaskProvider<Test> = tasks.test
testProvider {
// lazy configuration
}
// Eagerly realized Test task, defeats configuration avoidance if done out of a lazy context
val test: Test = tasks.test.get()
對於所有其他容器,元素的存取器類型為 NamedDomainObjectProvider<T>
,提供相同的行為
val mainSourceSetProvider: NamedDomainObjectProvider<SourceSet> = sourceSets.named("main")
了解在類型安全模型存取器不可用時該怎麼做
考慮上面顯示的範例建置腳本,它示範了類型安全存取器的使用。以下範例是相同的,只是它使用 apply()
方法來應用外掛。
在這種情況下,建置腳本無法使用類型安全存取器,因為 apply()
呼叫發生在建置腳本的主體中。您必須改用其他技術,如此處所示
apply(plugin = "java-library")
dependencies {
"api"("junit:junit:4.13")
"implementation"("junit:junit:4.13")
"testImplementation"("junit:junit:4.13")
}
configurations {
"implementation" {
resolutionStrategy.failOnVersionConflict()
}
}
configure<SourceSetContainer> {
named("main") {
java.srcDir("src/core/java")
}
}
configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks {
named<Test>("test") {
testLogging.showExceptions = true
}
}
類型安全存取器對於以下項目貢獻的模型元素不可用
-
透過
apply(plugin = "id")
方法應用程式的外掛。 -
專案建置腳本。
-
腳本外掛,透過
apply(from = "script-plugin.gradle.kts")
。 -
透過跨專案配置應用程式的外掛。
您無法在 Kotlin 中實作的 二進制 Gradle 外掛 中使用類型安全存取器。
如果您找不到類型安全存取器,請退回到使用對應類型的正常 API。為此,您需要知道已配置模型元素的名稱和/或類型。我們現在將向您展示如何透過詳細檢查腳本來發現這些內容。
成品配置
以下範例示範如何在沒有類型安全存取器的情況下參考和配置成品配置
apply(plugin = "java-library")
dependencies {
"api"("junit:junit:4.13")
"implementation"("junit:junit:4.13")
"testImplementation"("junit:junit:4.13")
}
configurations {
"implementation" {
resolutionStrategy.failOnVersionConflict()
}
}
程式碼看起來與類型安全存取器的程式碼類似,只是配置名稱是字串文字。您可以在相依性宣告和 configurations {}
區塊中使用字串文字作為配置名稱。
雖然 IDE 無法幫助您發現可用的配置,但您可以查看相應外掛的文件或執行 ./gradlew dependencies
來查找它們。
專案擴展和慣例
專案擴展和慣例既有名稱又有唯一類型。但是,Kotlin DSL 只需要知道類型即可配置它們。
以下範例顯示了原始範例建置腳本中的 sourceSets {}
和 java {}
區塊。configure<T>()
函數與相應類型一起使用
apply(plugin = "java-library")
configure<SourceSetContainer> {
named("main") {
java.srcDir("src/core/java")
}
}
configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
請注意,sourceSets
是 Project
上類型為 SourceSetContainer
的 Gradle 擴展,而 java
是 Project
上類型為 JavaPluginExtension
的擴展。
您可以透過查看已應用外掛的文件或執行 ./gradlew kotlinDslAccessorsReport
來發現可用的擴展和慣例。該報告生成存取已應用外掛貢獻的模型元素所需的 Kotlin 程式碼,同時提供名稱和類型。
作為最後的手段,您可以檢查外掛的原始程式碼,儘管在大多數情況下這應該是不必要的。
如果您只需要參考擴展或慣例而不配置它,或者如果您想要執行單行配置,您也可以使用 the<T>()
函數
the<SourceSetContainer>()["main"].java.srcDir("src/main/java")
上面的程式碼片段也示範了一種配置專案擴展元素的方法,該擴展是一個容器。
專案擴展容器中的元素
基於容器的專案擴展,例如 SourceSetContainer
,允許您配置它們擁有的元素。
在我們的範例建置腳本中,我們想要在來源集容器中配置一個名為 main
的來源集。我們可以透過使用 named() 方法而不是存取器來做到這一點
apply(plugin = "java-library")
configure<SourceSetContainer> {
named("main") {
java.srcDir("src/core/java")
}
}
基於容器的專案擴展中的所有元素都有一個名稱,因此您可以在所有此類情況下使用此技術。
對於專案擴展和慣例,您可以透過檢查已應用外掛的文件或執行 ./gradlew kotlinDslAccessorsReport
來發現任何容器中存在的元素。
作為最後的手段,您也可以查看外掛的原始程式碼以找出它的作用。
任務
任務不是透過基於容器的專案擴展來管理的,但它們是容器的一部分,其行為方式類似。
這表示您可以像配置來源集一樣配置任務。以下範例說明了這種方法
apply(plugin = "java-library")
tasks {
named<Test>("test") {
testLogging.showExceptions = true
}
}
我們正在使用 Gradle API 按名稱和類型引用任務,而不是使用存取器。
請注意,必須明確指定任務的類型。如果您不這樣做,腳本將無法編譯,因為推斷的類型將是 Task
,而不是 Test
,並且 testLogging
屬性是 Test
任務類型特有的。
但是,如果您只需要配置所有任務共有的屬性或呼叫方法,即在 Task
介面上宣告的那些,則可以省略類型。
您可以透過執行 ./gradlew tasks
來發現可用的任務。
要找出給定任務的類型,請執行 ./gradlew help --task <taskName>
,如此處所示
❯ ./gradlew help --task test
...
Type
Test (org.gradle.api.tasks.testing.Test)
IDE 可以協助您完成所需的匯入,因此您只需要類型的簡單名稱,而無需套件名稱部分。在這種情況下,無需匯入 Test
任務類型,因為它是 Gradle API 的一部分,因此是隱式匯入的。
關於慣例
一些 Gradle 核心外掛藉助所謂的慣例物件來公開可配置性。這些物件的作用與擴展類似,並且現在已被擴展取代。慣例已被棄用,因此在編寫新外掛時請避免使用慣例物件。
如上所述,Kotlin DSL 僅為 Project
上的慣例物件提供存取器。但是,在某些情況下,您可能需要與 Gradle 外掛互動,該外掛在其他類型上使用慣例物件。Kotlin DSL 為此提供了 withConvention(T::class) {}
擴展函數
sourceSets {
main {
withConvention(CustomSourceSetConvention::class) {
someOption = "some value"
}
}
}
此技術主要適用於語言外掛添加的來源集,這些來源集尚未遷移到擴展。
使用容器物件
Gradle 建置模型廣泛使用容器物件(或簡稱為「容器」)。
例如,configurations
和 tasks
是分別保存 Configuration
和 Task
物件的容器。社群外掛也會貢獻容器,例如 Android 外掛貢獻的 android.buildTypes
容器。
Kotlin DSL 為建置作者提供了多種與容器互動的方式。我們將使用 tasks
容器作為範例來探索這些方法中的每一種。
在配置支援容器上的現有元素時,您可以利用 另一個章節中描述的類型安全存取器。該章節也說明了哪些容器支援類型安全存取器。 |
使用容器 API
Gradle 中的所有容器都實作 NamedDomainObjectContainer<DomainObjectType>。某些容器可以保存不同類型的物件,並實作 PolymorphicDomainObjectContainer<BaseType>。與容器互動的最簡單方法是透過這些介面。
以下範例示範了如何使用 named() 方法來配置現有任務,以及使用 register() 方法來建立新任務
tasks.named("check") (1)
tasks.register("myTask1") (2)
tasks.named<JavaCompile>("compileJava") (3)
tasks.register<Copy>("myCopy1") (4)
tasks.named("assemble") { (5)
dependsOn(":myTask1")
}
tasks.register("myTask2") { (6)
description = "Some meaningful words"
}
tasks.named<Test>("test") { (7)
testLogging.showStackTraces = true
}
tasks.register<Copy>("myCopy2") { (8)
from("source")
into("destination")
}
1 | 取得對現有任務(名為 check )的類型為 Task 的參考 |
2 | 註冊一個名為 myTask1 的新未類型化任務 |
3 | 取得對現有任務(名為 compileJava )的類型為 JavaCompile 的參考 |
4 | 註冊一個名為 myCopy1 的新任務,類型為 Copy |
5 | 取得對現有(未類型化)任務(名為 assemble )的參考並配置它 — 使用此語法,您只能配置 Task 上可用的屬性和方法 |
6 | 註冊一個名為 myTask2 的新未類型化任務並配置它 — 在這種情況下,您只能配置 Task 上可用的屬性和方法 |
7 | 取得對現有任務(名為 test )的類型為 Test 的參考並配置它 — 在這種情況下,您可以存取指定類型的屬性和方法 |
8 | 註冊一個名為 myCopy2 的新任務,類型為 Copy 並配置它 |
上面的範例依賴於配置避免 API。如果您需要或想要急切地配置或註冊容器元素,只需將 named() 替換為 getByName() ,並將 register() 替換為 create() 。 |
使用 Kotlin 委託屬性
與容器互動的另一種方式是透過 Kotlin 委託屬性。如果您需要容器元素的參考,以便可以在建置中的其他地方使用,這些屬性特別有用。此外,Kotlin 委託屬性可以透過 IDE 重構輕鬆重新命名。
以下範例達成了與上一節範例相同的結果,但它使用了委託屬性並重複使用了這些參考,而不是字串文字任務路徑
val check by tasks.existing
val myTask1 by tasks.registering
val compileJava by tasks.existing(JavaCompile::class)
val myCopy1 by tasks.registering(Copy::class)
val assemble by tasks.existing {
dependsOn(myTask1) (1)
}
val myTask2 by tasks.registering {
description = "Some meaningful words"
}
val test by tasks.existing(Test::class) {
testLogging.showStackTraces = true
}
val myCopy2 by tasks.registering(Copy::class) {
from("source")
into("destination")
}
1 | 使用對 myTask1 任務的參考,而不是任務路徑 |
上面的範例依賴於配置避免 API。如果您需要或想要急切地配置或註冊容器元素,只需將 existing() 替換為 getting() ,並將 registering() 替換為 creating() 。 |
一起配置多個容器元素
在配置容器的多個元素時,您可以將互動分組在一個區塊中,以避免在每次互動時重複容器的名稱。
以下範例示範了類型安全存取器、容器 API 和 Kotlin 委託屬性的組合
tasks {
test {
testLogging.showStackTraces = true
}
val myCheck by registering {
doLast { /* assert on something meaningful */ }
}
check {
dependsOn(myCheck)
}
register("myHelp") {
doLast { /* do something helpful */ }
}
}
使用執行時期屬性
Kotlin DSL 提供了用於處理這些屬性類型的特定語法,我們將在以下章節中進行探索。
專案屬性
Kotlin DSL 允許您透過 Kotlin 委託屬性綁定來存取專案屬性。
以下程式碼片段示範了針對幾個專案屬性的此技術,其中一個專案屬性必須定義
val myProperty: String by project (1)
val myNullableProperty: String? by project (2)
1 | 透過 myProperty 委託屬性使 myProperty 專案屬性可用 — 在這種情況下,專案屬性必須存在,否則當建置腳本嘗試使用 myProperty 值時,建置將會失敗 |
2 | 對 myNullableProperty 專案屬性執行相同的操作,但只要您檢查 null(適用於 null 安全性的標準 Kotlin 規則),建置就不會在使用 myNullableProperty 值時失敗 |
相同的方法適用於設定和初始化腳本,只是您分別使用 by settings
和 by gradle
來代替 by project
。
額外屬性
額外屬性在任何實作 ExtensionAware 介面的物件上都可用。
在 Kotlin DSL 中,您可以使用 by extra
語法透過委託屬性存取和建立額外屬性,如以下範例所示
val myNewProperty by extra("initial value") (1)
val myOtherNewProperty by extra { "calculated initial value" } (2)
val myExtraProperty: String by extra (3)
val myExtraNullableProperty: String? by extra (4)
1 | 在目前上下文(在本例中為專案)中建立一個名為 myNewProperty 的新額外屬性,並使用值 "initial value" 初始化它,這也決定了屬性的類型 |
2 | 建立一個新的額外屬性,其初始值由提供的 lambda 計算 |
3 | 將目前上下文(在本例中為專案)中的現有額外屬性綁定到 myProperty 參考 |
4 | 與上一行執行相同的操作,但允許屬性具有 null 值 |
此方法適用於所有 Gradle 腳本:專案建置腳本、腳本外掛、設定腳本和初始化腳本。
您也可以使用以下語法從子專案存取根專案上的額外屬性
val myNewProperty: String by rootProject.extra (1)
1 | 將根專案的 myNewProperty 額外屬性綁定到同名的參考 |
額外屬性不僅限於專案。例如,Task
擴展了 ExtensionAware
,因此您也可以將額外屬性附加到任務。
以下範例在 test
任務上定義了一個新的 myNewTaskProperty
,然後使用該屬性來初始化另一個任務
tasks {
test {
val reportType by extra("dev") (1)
doLast {
// Use 'suffix' for post-processing of reports
}
}
register<Zip>("archiveTestReports") {
val reportType: String by test.get().extra (2)
archiveAppendix = reportType
from(test.get().reports.html.outputLocation)
}
}
1 | 在 test 任務上建立一個新的 reportType 額外屬性 |
2 | 使 test 任務的 reportType 額外屬性可用於配置 archiveTestReports 任務 |
如果您樂於使用急切配置而不是配置避免 API,則可以為報告類型使用單個「全域」屬性,如下所示
tasks.test {
doLast { /* ... */ }
}
val testReportType by tasks.test.get().extra("dev") (1)
tasks.create<Zip>("archiveTestsReports") {
archiveAppendix = testReportType (2)
from(test.reports.html.outputLocation)
}
1 | 在 test 任務上建立並初始化一個額外屬性,並將其綁定到「全域」屬性 |
2 | 使用「全域」屬性來初始化 archiveTestReports 任務 |
額外屬性還有一種最後的語法,它將 extra
視為地圖。我們通常建議不要使用它,因為它繞過了 Kotlin 的類型檢查並限制了 IDE 支援。但是,它比委託屬性語法更簡潔,如果您只需要設定一個額外屬性而無需稍後參考它,則可以使用它。
以下是一個簡單的範例,示範如何使用地圖語法設定和讀取額外屬性
extra["myNewProperty"] = "initial value" (1)
tasks.register("myTask") {
doLast {
println("Property: ${project.extra["myNewProperty"]}") (2)
}
}
1 | 建立一個名為 myNewProperty 的新專案額外屬性並設定其值 |
2 | 從我們建立的專案額外屬性中讀取值 — 請注意 extra[…] 上的 project. 限定詞,否則 Gradle 會假設我們要從任務中讀取額外屬性 |
使用 Gradle 類型
Property
、Provider
和 NamedDomainObjectProvider
是類型,表示值和物件的延遲和延遲評估。Kotlin DSL 提供了用於處理這些類型的專用語法。
使用 Property
屬性表示可以延遲設定和讀取的值
-
設定值:
property.set(value)
或property = value
-
存取值:
property.get()
-
使用委託語法:
val propValue: String by property
val myProperty: Property<String> = project.objects.property(String::class.java)
myProperty.set("Hello, Gradle!") // Set the value
println(myProperty.get()) // Access the value
// Using delegate syntax
val propValue: String by myProperty
println(propValue)
// Using lazy syntax
myProperty = "Hi, Gradle!" // Set the value
println(myProperty.get()) // Access the value
使用 Provider
Provider 代表一個唯讀、延遲求值的值
-
存取值:
provider.get()
-
鏈式調用:
provider.map { transform(it) }
val versionProvider: Provider<String> = project.provider { "1.0.0" }
println(versionProvider.get()) // Access the value
// Chaining transformations
val majorVersion: Provider<String> = versionProvider.map { it.split(".")[0] }
println(majorVersion.get()) // Prints: "1"
使用 NamedDomainObjectProvider
具名網域物件 Provider 代表來自 Gradle 容器(例如 tasks 或 extensions)的延遲求值具名物件
-
存取物件:
namedObjectProvider.get()
-
設定物件:
namedObjectProvider.configure { … }
val myTaskProvider: NamedDomainObjectProvider<Task> = tasks.named("build")
// Configuring the task
myTaskProvider.configure {
doLast {
println("Build task completed!")
}
}
// Accessing the task
val myTask: Task = myTaskProvider.get()
延遲屬性賦值
Gradle 的 Kotlin DSL 支援使用 =
運算子進行延遲屬性賦值。
當使用延遲屬性時,延遲屬性賦值可以減少冗長程度。它適用於公開視為 final
(沒有 setter)且類型為 Property
或 ConfigurableFileCollection
的屬性。由於屬性必須是 final
,我們通常建議避免為延遲類型的屬性使用自訂 setter,並且在可能的情況下,透過抽象 getter 實作此類屬性。
在 Kotlin DSL 中,使用 =
運算子是呼叫 set()
的建議方式
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
abstract class WriteJavaVersionTask : DefaultTask() {
@get:Input
abstract val javaVersion: Property<String>
@get:OutputFile
abstract val output: RegularFileProperty
@TaskAction
fun execute() {
output.get().asFile.writeText("Java version: ${javaVersion.get()}")
}
}
tasks.register<WriteJavaVersionTask>("writeJavaVersion") {
javaVersion.set("17") (1)
javaVersion = "17" (2)
javaVersion = java.toolchain.languageVersion.map { it.toString() } (3)
output = layout.buildDirectory.file("writeJavaVersion/javaVersion.txt")
}
1 | 使用 .set() 方法設定值 |
2 | 使用 = 運算子,透過延遲屬性賦值設定值 |
3 | = 運算子也可以用於賦值延遲值 |
IDE 支援
IntelliJ 2022.3 和 Android Studio Giraffe 以上版本支援延遲屬性賦值。
Kotlin DSL 外掛程式
Kotlin DSL 外掛程式提供了一種便捷的方式來開發貢獻建置邏輯的基於 Kotlin 的專案。這包括 buildSrc 專案、包含的建置和 Gradle 外掛程式。
此外掛程式透過執行以下操作來達成此目的
-
套用 Kotlin 外掛程式,該外掛程式增加了對編譯 Kotlin 原始檔的支援。
-
將
kotlin-stdlib
、kotlin-reflect
和gradleKotlinDsl()
依賴項新增到compileOnly
和testImplementation
組態,從而在您的 Kotlin 程式碼中啟用這些 Kotlin 程式庫和 Gradle API 的使用。 -
使用與 Kotlin DSL 腳本相同的設定來設定 Kotlin 編譯器,確保您的建置邏輯與這些腳本之間的一致性
-
啟用對預編譯腳本外掛程式的支援。
每個 Gradle 版本都旨在與特定版本的 kotlin-dsl
外掛程式一起使用。任意 Gradle 版本與 kotlin-dsl
外掛程式版本之間的相容性無法保證。使用意外版本的 kotlin-dsl
外掛程式將發出警告,並可能導致難以診斷的問題。
這是使用此外掛程式所需的基本組態
plugins {
`kotlin-dsl`
}
repositories {
// The org.jetbrains.kotlin.jvm plugin requires a repository
// where to download the Kotlin compiler dependencies from.
mavenCentral()
}
Kotlin DSL 外掛程式利用 Java 工具鏈。預設情況下,程式碼將以 Java 8 為目標。您可以透過定義專案要使用的 Java 工具鏈來變更此設定
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
嵌入式 Kotlin
Gradle 嵌入了 Kotlin,以便為基於 Kotlin 的腳本提供支援。
Kotlin 版本
Gradle 隨附 kotlin-compiler-embeddable
以及匹配版本的 kotlin-stdlib
和 kotlin-reflect
程式庫。如需詳細資訊,請參閱 Gradle 相容性矩陣的 Kotlin 章節。這些模組中的 kotlin
套件可透過 Gradle 類別路徑看見。
相容性 Kotlin 提供的保證適用於向後和向前相容性。
向後相容性
我們的方法是僅在 Gradle 主要版本中進行向後不相容的 Kotlin 升級。我們清楚地記錄每個版本隨附的 Kotlin 版本,並在主要版本發布前宣布升級計畫。
旨在維持與舊版 Gradle 版本相容性的外掛程式作者,必須將其 API 使用限制為這些版本支援的內容。這與在 Gradle 中使用任何新 API 沒有什麼不同。例如,如果引入了依賴項解析的新 API,外掛程式必須放棄對舊版 Gradle 版本的支援,或者組織其程式碼以在相容版本上條件式執行新的程式碼路徑。
向前相容性
主要相容性問題存在於外部 kotlin-gradle-plugin
版本與 Gradle 隨附的 kotlin-stdlib
版本之間。更廣泛地說,這適用於任何以可轉移方式依賴 kotlin-stdlib
及其 Gradle 提供的版本的外掛程式。只要版本相容,一切都應該按預期運作。隨著 Kotlin 語言的成熟,此問題將會減少。
Kotlin 編譯器引數
以下 Kotlin 編譯器引數用於編譯 Kotlin DSL 腳本,以及套用 kotlin-dsl
外掛程式的專案中的 Kotlin 原始碼和腳本
-java-parameters
-
產生用於 Java >= 1.8 反射的方法參數的元資料。如需更多資訊,請參閱 Kotlin 文件中的 Kotlin/JVM 編譯器選項。
-Xjvm-default=all
-
使 Kotlin 介面的所有非抽象成員成為實作它們的 Java 類別的預設成員。這是為了為以 Kotlin 撰寫的外掛程式提供與 Java 和 Groovy 更好的互通性。如需更多資訊,請參閱 Kotlin 文件中的 介面中的預設方法。
-Xsam-conversions=class
-
設定 SAM(單一抽象方法)轉換的實作策略,以始終產生匿名類別,而不是使用
invokedynamic
JVM 指令。這是為了為組態快取和增量建置提供更好的支援。如需更多資訊,請參閱 Kotlin 問題追蹤器中的 KT-44912。 -Xjsr305=strict
-
設定 Kotlin 的 Java 互通性,以嚴格遵循 JSR-305 註解以提高空值安全性。如需更多資訊,請參閱 Kotlin 文件中的 從 Kotlin 呼叫 Java 程式碼。
互通性
當在您的建置邏輯中混合語言時,您可能必須跨越語言邊界。一個極端的例子是建置使用以 Java、Groovy 和 Kotlin 實作的 tasks 和外掛程式,同時也使用 Kotlin DSL 和 Groovy DSL 建置腳本。
Kotlin 在設計時就考慮到 Java 互通性。現有的 Java 程式碼可以以自然的方式從 Kotlin 呼叫,而 Kotlin 程式碼也可以相當順暢地從 Java 使用。
從 Kotlin 呼叫 Java 和 從 Java 呼叫 Kotlin 在 Kotlin 參考文件中都有非常詳細的說明。
同樣的道理也大多適用於與 Groovy 程式碼的互通性。此外,Kotlin DSL 提供了幾種選擇加入 Groovy 語意的方法,我們將在接下來探討。
靜態擴充功能
Groovy 和 Kotlin 語言都支援透過 Groovy 擴充功能模組和 Kotlin 擴充功能來擴充現有的類別。
若要從 Groovy 呼叫 Kotlin 擴充功能函式,請將其作為靜態函式呼叫,並將接收器作為第一個參數傳遞
TheTargetTypeKt.kotlinExtensionFunction(receiver, "parameters", 42, aReference)
Kotlin 擴充功能函式是套件層級函式。您可以在 Kotlin 參考文件的 套件層級函式 章節中,了解如何找到宣告給定 Kotlin 擴充功能的類型名稱。
若要從 Kotlin 呼叫 Groovy 擴充功能方法,同樣的方法也適用:將其作為靜態函式呼叫,並將接收器作為第一個參數傳遞
TheTargetTypeGroovyExtension.groovyExtensionMethod(receiver, "parameters", 42, aReference)
具名參數和預設引數
Groovy 和 Kotlin 語言都支援具名函式參數和預設引數,儘管它們的實作方式非常不同。Kotlin 完全支援這兩者,如 Kotlin 語言參考中的 具名引數 和 預設引數 所述。Groovy 以非類型安全的方式基於 Map<String, ?>
參數實作 具名引數,這表示它們不能與 預設引數 結合使用。換句話說,對於任何給定的方法,您在 Groovy 中只能使用其中一種。
從 Groovy 呼叫 Kotlin
若要從 Groovy 呼叫具有具名引數的 Kotlin 函式,只需使用帶有位置參數的普通方法呼叫即可
kotlinFunction("value1", "value2", 42)
沒有辦法透過引數名稱提供值。
若要從 Groovy 呼叫具有預設引數的 Kotlin 函式,請始終為所有函式參數傳遞值。
從 Kotlin 呼叫 Groovy
若要從 Kotlin 呼叫具有具名引數的 Groovy 函式,您需要傳遞 Map<String, ?>
,如此範例所示
groovyNamedArgumentTakingMethod(mapOf(
"parameterName" to "value",
"other" to 42,
"and" to aReference))
若要從 Kotlin 呼叫具有預設引數的 Groovy 函式,請始終為所有參數傳遞值。
來自 Kotlin 的 Groovy 閉包
有時您可能必須從 Kotlin 程式碼呼叫接受 Closure 引數的 Groovy 方法。例如,一些以 Groovy 撰寫的協力廠商外掛程式預期使用閉包引數。
以任何語言撰寫的 Gradle 外掛程式都應優先使用 Action<T> 類型來代替閉包。Groovy 閉包和 Kotlin lambda 會自動對應到該類型的引數。 |
為了提供一種在保留 Kotlin 強型別的同時建構閉包的方法,存在兩種輔助方法
-
closureOf<T> {}
-
delegateClosureOf<T> {}
這兩種方法在不同的情況下都很有用,並且取決於您要將 Closure
執行個體傳遞給的方法。
某些外掛程式期望簡單的閉包,例如 Bintray 外掛程式
bintray {
pkg(closureOf<PackageConfig> {
// Config for the package here
})
}
在其他情況下,例如使用 Gretty 外掛程式 設定 farm 時,此外掛程式期望使用委派閉包
farms {
farm("OldCoreWar", delegateClosureOf<FarmExtension> {
// Config for the war here
})
}
有時從查看原始碼中無法很好地判斷要使用哪個版本。通常,如果您使用 closureOf<T> {}
遇到 NullPointerException
,則使用 delegateClosureOf<T> {}
將會解決問題。
這兩個公用程式函式對於組態閉包很有用,但某些外掛程式可能會期望 Groovy 閉包用於其他目的。KotlinClosure0
到 KotlinClosure2
類型允許使用更大的彈性將 Kotlin 函式調整為 Groovy 閉包
somePlugin {
// Adapt parameter-less function
takingParameterLessClosure(KotlinClosure0({
"result"
}))
// Adapt unary function
takingUnaryClosure(KotlinClosure1<String, String>({
"result from single parameter $this"
}))
// Adapt binary function
takingBinaryClosure(KotlinClosure2<String, String, String>({ a, b ->
"result from parameters $a and $b"
}))
}
Kotlin DSL Groovy Builder
如果某些外掛程式大量使用 Groovy 元程式設計,那麼從 Kotlin 或 Java 或任何靜態編譯語言中使用它可能會非常麻煩。
Kotlin DSL 提供了 withGroovyBuilder {}
公用程式擴充功能,可將 Groovy 元程式設計語意附加到 Any
類型的物件。
以下範例示範了物件 target
上方法的幾個功能
target.withGroovyBuilder { (1)
// GroovyObject methods available (2)
if (hasProperty("foo")) { /*...*/ }
val foo = getProperty("foo")
setProperty("foo", "bar")
invokeMethod("name", arrayOf("parameters", 42, aReference))
// Kotlin DSL utilities
"name"("parameters", 42, aReference) (3)
"blockName" { (4)
// Same Groovy Builder semantics on `blockName`
}
"another"("name" to "example", "url" to "https://example.com/") (5)
}
1 | 接收器是 GroovyObject 並提供 Kotlin 輔助程式 |
2 | GroovyObject API 可用 |
3 | 叫用 methodName 方法,傳遞一些參數 |
4 | 設定 blockName 屬性,對應到接受方法叫用的 Closure |
5 | 叫用 another 方法,該方法接受具名引數,對應到接受方法叫用的 Groovy 具名引數 Map<String, ?> |
使用 Groovy 腳本
處理假設 Groovy DSL 建置腳本的有問題的外掛程式時,另一種選擇是在從主要 Kotlin DSL 建置腳本套用的 Groovy DSL 建置腳本中設定它們
native { (1)
dynamic {
groovy as Usual
}
}
plugins {
id("dynamic-groovy-plugin") version "1.0" (2)
}
apply(from = "dynamic-groovy-plugin-configuration.gradle") (3)
1 | Groovy 腳本使用動態 Groovy 設定外掛程式 |
2 | Kotlin 建置腳本請求並套用外掛程式 |
3 | Kotlin 建置腳本套用 Groovy 腳本 |
疑難排解
IDE 支援由兩個元件提供
-
Kotlin 外掛程式(由 IntelliJ IDEA/Android Studio 使用)。
-
Gradle。
支援程度因每個版本的不同而異。
如果您遇到問題,請先從命令列執行 ./gradlew tasks
,以判斷問題是否特定於 IDE。如果問題在命令列中仍然存在,則很可能源自建置本身,而不是 IDE 整合。
但是,如果建置在命令列中成功執行,但您的腳本編輯器報告錯誤,請嘗試重新啟動 IDE 並使其快取失效。
如果問題仍然存在,並且您懷疑 Kotlin DSL 腳本編輯器有問題,請嘗試以下操作
-
執行
./gradlew tasks
以收集更多詳細資訊。 -
查看以下位置之一的日誌
-
macOS 上的
$HOME/Library/Logs/gradle-kotlin-dsl
-
Linux 上的
$HOME/.gradle-kotlin-dsl/log
-
Windows 上的
$HOME/AppData/Local/gradle-kotlin-dsl/log
-
-
在 Gradle 問題追蹤器上報告問題,並儘可能包含詳細資訊。
從 5.1 版開始,日誌目錄會自動清除。日誌會定期檢查(最多每 24 小時一次),如果檔案 7 天未使用,則會刪除。
如果這無法幫助您找出問題,您可以在 IDE 中啟用 org.gradle.kotlin.dsl.logging.tapi
系統屬性。這會導致 Gradle Daemon 將其他詳細資訊記錄在其位於 $HOME/.gradle/daemon
的日誌檔中。
在 IntelliJ IDEA 中,導覽至 Help > Edit Custom VM Options…
並新增:-Dorg.gradle.kotlin.dsl.logging.tapi=true
來啟用此屬性。
對於 Kotlin DSL 腳本編輯器之外的 IDE 問題,請在對應 IDE 的問題追蹤器中開啟問題
最後,如果您遇到 Gradle 本身或 Kotlin DSL 的問題,請在 Gradle 問題追蹤器上開啟問題。
限制
-
眾所周知,Kotlin DSL 在首次使用時(例如使用全新簽出或在臨時持續整合代理程式上)比 Groovy DSL 慢。變更 buildSrc 目錄中的內容也會產生影響,因為它會使建置腳本快取失效。主要原因是 Kotlin DSL 的腳本編譯速度較慢。
-
在 IntelliJ IDEA 中,您必須從 Gradle 模型匯入您的專案,才能為您的 Kotlin DSL 建置腳本取得內容輔助和重構支援。
-
Kotlin DSL 腳本編譯避免存在已知問題。如果您遇到問題,可以透過設定
org.gradle.kotlin.dsl.scriptCompilationAvoidance
系統屬性為false
來停用它。 -
Kotlin DSL 將不支援
model {}
區塊,它是 已停止使用的 Gradle 軟體模型 的一部分。
如果您遇到問題或發現疑似錯誤,請在 Gradle 問題追蹤器中報告問題。