Gradle的Kotlin DSL提供了一种替代传统Groovy DSL的语法,它在受支持的ide中增强了编辑体验,具有更好的内容辅助、重构、文档等功能。本章详细介绍了主要的Kotlin DSL结构,以及如何使用它与Gradle API进行交互。
如果您有兴趣将现有的Gradle构建迁移到Kotlin DSL,也请参阅专用迁移部分。
预备知识
众所周知,嵌入式Kotlin编译器可以在Linux、macOS、Windows、Cygwin、FreeBSD和x86-64架构的Solaris上工作。
了解Kotlin语法和基本语言特性非常有帮助。Kotlin参考文档和Kotlin Koans将帮助您学习基本知识。
使用plugins{}块声明Gradle插件可以显著改善编辑体验,强烈推荐使用。
IDE支持
IntelliJ IDEA和Android Studio完全支持Kotlin DSL。其他ide还没有提供用于编辑Kotlin DSL文件的有用工具,但是您仍然可以导入基于Kotlin DSL的构建,并像往常一样使用它们。
Build import | Syntax highlighting 1 | Semantic editor 2 | |
---|---|---|---|
IntelliJ IDEA | ✓ | ✓ | ✓ |
Android Studio | ✓ | ✓ | ✓ |
Eclipse IDE | ✓ | ✓ | ✖ |
CLion | ✓ | ✓ | ✖ |
Apache NetBeans | ✓ | ✓ | ✖ |
Visual Studio Code (LSP) | ✓ | ✓ | ✖ |
Visual Studio | ✓ | ✖ | ✖ |
-
在Gradle Kotlin DSL脚本中突出显示Kotlin语法
-
在Gradle Kotlin DSL脚本中的代码补全,源代码导航,文档,重构等等
正如在限制中提到的,你必须从Gradle模型中导入你的项目,才能在IntelliJ IDEA中获得Kotlin DSL脚本的内容辅助和重构工具。
此外,IntelliJ IDEA和Android Studio在编辑Gradle脚本时可能会生成多达3个Gradle守护进程——每种类型的脚本都有一个:构建脚本、设置文件和初始化脚本。配置时间较慢的构建版本可能会影响IDE的响应性,因此请查看性能部分以帮助解决此类问题。
自动构建导入vs.自动重新加载脚本依赖项
IntelliJ IDEA和Android Studio(源自IntelliJ IDEA)都会在你修改构建逻辑时进行检测,并提供两个建议:
1
2
我们建议您禁用自动构建导入,但启用脚本依赖项的自动重新加载。这样你就可以在编辑Gradle脚本的时候得到早期的反馈,并控制整个构建设置何时与你的IDE同步。
疑难解答
IDE支持由两个组件提供:
-
IntelliJ IDEA/Android Studio使用的Kotlin插件
-
Gradle
支持级别根据每个版本的不同而不同。
如果遇到问题,您应该尝试的第一件事是从命令行运行./gradlew tasks,以查看问题是否仅限于IDE。如果您在命令行中遇到同样的问题,那么问题在于构建,而不是IDE集成。
如果您可以从命令行成功运行构建,但脚本编辑器出现故障,那么您应该尝试重新启动IDE并使其缓存失效。
如果上述方法不起作用,并且您怀疑Kotlin DSL脚本编辑器存在问题,您可以:
-
执行./gradle tasks来获取更多信息
-
检查下述位置信息
-
$HOME/Library/Log/gradle-kotlin-dsl Mac OS X
-
$HOME/.gradle-kotlin-dsl/log Linux
-
$HOME/AppData/Local/gradle-kotlin-dsl/log Windows
在Gradle issue上提更多你的详细信息
-
从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 issue tracker中反馈。
Kotlin DSL脚本
就像基于groovy的DSL一样,Kotlin DSL是在Gradle的Java API之上实现的。你在Kotlin DSL脚本中读到的所有东西都是由Gradle编译和执行的Kotlin代码。你在构建脚本中使用的许多对象、函数和属性都来自于Gradle API和应用插件的API。
要激活Kotlin DSL,只需使用.gradle.kts扩展为您的构建脚本代替.gradle。这也适用于设置文件(例如settings.gradle.kts)和初始化脚本。
注意,您可以将Groovy DSL构建脚本与Kotlin DSL构建脚本混合使用,例如,Kotlin DSL构建脚本可以应用Groovy DSL脚本,多项目构建中的每个项目都可以使用其中任何一个。
我们建议您应用以下约定以获得更好的IDE支持:
根据模式*.settings.gradle.kts命名设置脚本(或任何由Gradle设置对象支持的脚本)。这包括从设置脚本中应用的脚本插件
根据模式*.init.gradle.kts为初始化脚本命名。或者简单的init.gradle.kts。
这样IDE就知道什么类型的对象“支持”脚本,是Project、Settings还是Gradle
隐式导入
所有Kotlin DSL构建脚本都有隐式导入,包括:
-
Kotlin DSL API,它是org.gradle.kotlin.dsl和org.gradle.kotlin.dsl.plugins. DSL包中的所有类型
避免使用内部Kotlin DSL api
在插件和构建脚本中使用内部Kotlin DSL api可能会在Gradle或插件发生变化时破坏构建。Kotlin DSL API使用org.gradle.kotlin.dsl包或org.gradle.kotlin.dsl.plugins. DSL包(但不是它们的子包)中相应API文档中列出的类型扩展了Gradle公共API。
类型安全模型访问器
Groovy DSL允许您通过名称引用构建模型的许多元素,即使它们是在运行时定义的。考虑命名配置、命名源集等等。例如,你可以通过configurations.implementation获得实现配置。
Kotlin DSL用使用插件提供的模型元素的类型安全的模型访问器取代了这种动态解析。
了解类型安全模型访问器何时可用
Kotlin DSL目前支持由插件提供的类型安全模型访问器:
-
Dependency和artifact configurations(比如由Java Plugin提供的implementation和runtimeOnly)
-
Projetc扩展和约定(比如sourceSets)
-
任务和配置容器中的元素
-
项目扩展容器中的元素(例如添加到sourceSets容器中的由Java Plugin贡献的源集)
-
以上每一项的扩展
只有主项目构建脚本和预编译的项目脚本插件具有类型安全的模型访问器。初始化脚本,设置脚本,脚本插件不需要。这些限制将在未来的Gradle发行版中被移除。
只有主项目构建脚本和预编译的项目脚本插件具有类型安全的模型访问器。初始化脚本,设置脚本,脚本插件不需要。这些限制将在未来的Gradle发行版中被移除。
下面的项目构建脚本演示了如何使用类型安全访问器访问各种配置、扩展和其他元素:
使用类型安全的模型访问器
plugins {
`java-library`
}
dependencies {
api("junit:junit:4.13")
implementation("junit:junit:4.13")
testImplementation("junit:junit:4.13")
}
configurations {
implementation {
resolutionStrategy.failOnVersionConflict()
}
}
sourceSets {
main {
java.srcDir("src/core/java")
}
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks {
test {
testLogging.showExceptions = true
}
}
Uses type-safe accessors for the api
, implementation
and testImplementation
dependency configurations contributed by the Java Library Plugin
Uses an accessor to configure the sourceSets
project extension
Uses an accessor to configure the main
source set
Uses an accessor to configure the java
source for the main
source set
Uses an accessor to configure the test
task
您的IDE知道类型安全访问器,所以它会将它们包含在建议中。在构建脚本的顶层(大多数插件扩展被添加到Project对象中)和配置扩展的块中都会发生这种情况。
注意,容器元素(如配置、任务和sourceSets)的访问器利用了Gradle的配置避免api。例如,在任务上,它们的类型是TaskProvider<T>,并提供底层任务的惰性引用和惰性配置。下面是一些例子,说明配置避免适用的情况:
tasks.test {
// lazy configuration
}
// Lazy reference
val testProvider: TaskProvider<Test> = tasks.test
testProvider {
// lazy configuration
}
// Eagerly realized Test task, defeat configuration avoidance if done out of a lazy context
val test: Test = tasks.test.get()
对于除任务之外的所有其他容器,元素的访问器类型为NamedDomainObjectProvider<T>,并提供相同的行为
了解当类型安全模型访问器不可用时应该做什么
考虑上面展示的示例构建脚本,该脚本演示了类型安全访问器的使用。下面的示例完全相同,只是使用了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<JavaPluginConvention> {
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。为此,您需要知道配置的模型元素的名称和/或类型。现在,我们将向您展示如何通过详细查看上面的脚本来发现它们。
Artifact configurations
下面的示例演示如何在不使用类型访问器的情况下引用和配置工件配置:
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无法帮助你发现可用的配置,但你可以在相应的插件文档或运行gradle依赖项中查找它们。
项目扩展和约定
项目扩展和约定都有名称和唯一的类型,但是Kotlin DSL只需要知道类型就可以配置它们。下面的示例显示了来自原始示例构建脚本的sourceSets{}和java{}块,你可以使用configure<T>()函数和相应的类型来实现:
apply(plugin = "java-library")
configure<SourceSetContainer> {
named("main") {
java.srcDir("src/core/java")
}
}
configure<JavaPluginConvention> {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
注意,sourceSets是Project上SourceSetContainer类型的Gradle扩展,而java是Project上JavaPluginExtension类型的扩展。
您可以通过查看应用插件的文档或运行gradle kotlinDslAccessorsReport来发现可用的扩展和约定,该报告打印了访问所有应用插件提供的模型元素所必需的Kotlin代码。报告提供了名称和类型。作为最后的手段,你也可以检查插件的源代码,但在大多数情况下这是不必要的。
如果您只需要对扩展或约定的引用而不需要配置它,或者如果您想执行一行配置,也可以使用<T>()函数,如下所示:
the<SourceSetContainer>()["main"].srcDir("src/core/java")
上面的代码片段还演示了配置作为容器的项目扩展的元素的一种方法。
项目扩展容器中的元素
基于容器的项目扩展(如SourceSetContainer)还允许您配置由它们持有的元素。在我们的示例构建脚本中,我们想要在源集容器中配置一个名为main的源集,我们可以通过使用named()方法来代替访问器来实现,如下所示:
作为容器的项目扩展的元素
apply(plugin = "java-library")
configure<SourceSetContainer> {
named("main") {
java.srcDir("src/core/java")
}
}
基于容器的项目扩展中的所有元素都有一个名称,因此您可以在所有此类情况下使用此技术。
至于项目扩展和约定本身,你可以通过查看应用插件的文档或运行gradle kotlinDslAccessorsReport来发现任何容器中都有哪些元素。作为最后的手段,你可以查看插件的源代码来了解它的功能,但在大多数情况下这是不必要的。
Tasks
任务不是通过基于容器的项目扩展来管理的,但它们是以类似方式运行的容器的一部分。这意味着你可以像配置源集一样配置任务,就像你在这个例子中看到的那样
Tasks
apply(plugin = "java-library")
tasks {
named<Test>("test") {
testLogging.showExceptions = true
}
}
我们使用Gradle API通过名称和类型来引用任务,而不是使用访问器。注意,有必要显式指定任务的类型,否则脚本将无法编译,因为推断的类型将是task,而不是Test,并且testLogging属性特定于Test任务类型。但是,如果你只需要配置属性或调用所有任务共有的方法,也就是说,它们是在Task接口上声明的,你可以忽略该类型。
你可以通过运行gradle tasks来发现哪些任务可用。然后,你可以通过运行gradle help——task <taskName>来查找给定任务的类型,如下所示:
❯ ./gradlew help --task test
...
Type
Test (org.gradle.api.tasks.testing.Test)
请注意,IDE可以帮助您进行所需的导入,因此您只需要类型的简单名称,即不需要包名称部分。在这种情况下,不需要导入Test任务类型,因为它是Gradle API的一部分,因此是隐式导入的。
关于约定
在所谓的约定对象的帮助下,一些Gradle核心插件公开了可配置性。它们的目的与扩展相似,但现在已被扩展所取代。在编写新插件时,请避免使用约定对象。长期的计划是迁移所有的Gradle核心插件来使用扩展,并完全移除约定对象。
如上所述,Kotlin DSL仅为项目上的约定对象提供访问器。在某些情况下,你需要与一个在其他类型上使用约定对象的Gradle插件进行交互。Kotlin DSL提供了withConvention(T::class){}扩展函数来实现:
配置源集约定
plugins {
groovy
}
sourceSets {
main {
withConvention(GroovySourceSet::class) {
groovy.srcDir("src/core/groovy")
}
}
}
这种技术通常是由Java插件以外的语言插件添加的源集所需要的,例如Groovy插件和Scala插件。您可以在SourceSet参考文档中查看哪些插件将哪些属性添加到源集。
多项目构建
和单项目构建一样,你应该尝试在多项目构建中使用plugins{}块,这样你就可以使用类型安全访问器。多项目构建的另一个需要考虑的问题是,当在根构建脚本中配置子项目或在项目之间使用其他形式的跨配置时,您将无法使用类型安全访问器。我们将在下面几节中更详细地讨论这两个主题。
应用插件
您可以在它们所应用的子项目中声明插件,但我们建议您也在根项目构建脚本中声明它们。这使得在构建中保持插件版本的一致性变得更加容易。该方法还提高了构建的性能。
使用Gradle插件一章解释了如何在根项目构建脚本中声明一个版本的插件,然后将它们应用到相应子项目的构建脚本中。下面是一个使用三个子项目和三个插件的方法示例。注意,根构建脚本只声明社区插件,因为Java库插件是绑定到你使用的Gradle版本的:
使用plugins{}块在根构建脚本中声明插件依赖项
settings.gradle.kts
rootProject.name = "multi-project-build"
include("domain", "infra", "http")
build.gradle.kts
plugins {
id("com.github.johnrengelman.shadow") version "4.0.1" apply false
id("io.ratpack.ratpack-java") version "1.8.2" apply false
}
domain/build.gradle.kts
plugins {
`java-library`
}
dependencies {
api("javax.measure:unit-api:1.0")
implementation("tec.units:unit-ri:1.0.3")
}
infra/build.gradle.kts
plugins {
`java-library`
id("com.github.johnrengelman.shadow")
}
shadow {
applicationDistribution.from("src/dist")
}
tasks.shadowJar {
minimize()
}
http/build.gradle.kts
plugins {
java
id("io.ratpack.ratpack-java")
}
dependencies {
implementation(project(":domain"))
implementation(project(":infra"))
implementation(ratpack.dependency("dropwizard-metrics"))
}
application {
mainClass.set("example.App")
}
ratpack.baseDir = file("src/ratpack/baseDir")
如果你的构建需要在Gradle plugin Portal的顶部添加额外的插件库,你应该在settings.gradle.kts文件的pluginManagement{}块中声明它们,像这样:
声明额外的插件库
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
从非Gradle Plugin Portal获取的插件只能通过Plugins{}块声明,如果它们和它们的插件标记工件一起发布的话。
在撰写本文时,google()库中所有版本的Android Plugin for Gradle在3.2.0之前都缺少插件标记artifacts。
如果这些artifacts丢失了,那么您就不能使用plugins{}块。你必须转而使用根项目构建脚本中的buildscript{}块来声明你的插件依赖项。下面是一个Android插件的例子:
settings.gradle.kts
include("lib", "app")
build.gradle.kts
buildscript {
repositories {
google()
gradlePluginPortal()
}
dependencies {
classpath("com.android.tools.build:gradle:4.1.2")
}
}
lib/build.gradle.kts
plugins {
id("com.android.library")
}
android {
// ...
}
app/build.gradle.kts
plugins {
id("com.android.application")
}
android {
// ...
}
这种技术与Android Studio在创建新版本时所产生的没有什么不同。主要的区别是上面例子中的子项目的构建脚本使用plugins{}块声明它们的插件。这意味着您可以对它们所提供的模型元素使用类型安全的访问器。
注意,如果您想将这样的插件应用于多项目构建的根项目构建脚本(而不是仅应用于其子项目)或单个项目构建,则不能使用这种技术。在这些情况下,您需要使用另一种不同的方法,我们将在另一节详细介绍。
Cross-configuring项目
跨项目配置是一种机制,通过这种机制,您可以从另一个项目的构建脚本配置一个项目。一个常见的例子是在根项目构建脚本中配置子项目。
采用这种方法意味着您将不能对插件提供的模型元素使用类型安全访问器。你将不得不依赖字符串字面量和标准的Gradle api。
作为一个例子,让我们修改Java/Ratpack示例构建,从根项目构建脚本完全配置它的子项目:
settings.gradle.kts
rootProject.name = "multi-project-build"
include("domain", "infra", "http")
build.gradle.kts
import com.github.jengelman.gradle.plugins.shadow.ShadowExtension
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import ratpack.gradle.RatpackExtension
plugins {
id("com.github.johnrengelman.shadow") version "4.0.1" apply false
id("io.ratpack.ratpack-java") version "1.8.2" apply false
}
project(":domain") {
apply(plugin = "java-library")
dependencies {
"api"("javax.measure:unit-api:1.0")
"implementation"("tec.units:unit-ri:1.0.3")
}
}
project(":infra") {
apply(plugin = "java-library")
apply(plugin = "com.github.johnrengelman.shadow")
configure<ShadowExtension> {
applicationDistribution.from("src/dist")
}
tasks.named<ShadowJar>("shadowJar") {
minimize()
}
}
project(":http") {
apply(plugin = "java")
apply(plugin = "io.ratpack.ratpack-java")
repositories { mavenCentral() }
val ratpack = the<RatpackExtension>()
dependencies {
"implementation"(project(":domain"))
"implementation"(project(":infra"))
"implementation"(ratpack.dependency("dropwizard-metrics"))
"runtimeOnly"("org.slf4j:slf4j-simple:1.7.25")
}
configure<JavaApplication> {
mainClass.set("example.App")
}
ratpack.baseDir = file("src/ratpack/baseDir")
}
注意我们是如何使用apply()方法来应用插件的,因为plugins{}块在这个上下文中不起作用。我们还使用标准api而不是类型安全访问器来配置任务、扩展和约定——我们在其他地方详细讨论过这种方法。
当你不能使用plugins{}块时
从非Gradle Plugin Portal获取的插件在Plugins{}块中可能可用,也可能不可用。这取决于它们是如何发布的,特别是,是否已经发布了必要的插件标记artifacts。
例如,Gradle的Android Plugin没有发布到Gradle Plugin Portal,并且——至少在插件3.2.0版本之前——解析给定插件标识符的构件所需的元数据没有发布到谷歌存储库。
如果你的构建是一个多项目构建,你不需要将这样的插件应用到你的根项目,那么你可以使用上面描述的技术绕过这个问题。如果是其他情况,请继续阅读。
发布插件时,请使用Gradle内置的Gradle Plugin Development Plugin。它通过plugins{}块自动发布所需的元数据,使您的插件可用。
在本节中,我们将向您展示如何将Android Plugin应用于单个项目构建或多项目构建的根项目。目的是指导构建如何将com.android.application插件标识符映射到可解析工件。这可以通过两个步骤完成:
-
向构建的设置脚本添加一个插件库
-
将插件ID映射到相应的工件坐标
您可以通过在构建的设置脚本中配置pluginManagement{}块来完成这两个步骤。为了演示,下面的示例将谷歌()存储库——Android插件发布的地方——添加到存储库搜索列表中,并使用resoltionstrategy{}块将com.android.application插件ID映射到com.android.tools。Build:gradle:<version> artifacts(在谷歌()存储库中可用):
您可以通过在构建的设置脚本中配置pluginManagement{}块来完成这两个步骤。为了演示,下面的示例将谷歌()存储库——Android插件发布的地方——添加到存储库搜索列表中,并使用resoltionstrategy{}块将com.android.application插件ID映射到com.android.tools。Build:gradle:
<version> artifacts(在谷歌()存储库中可用):
settings.gradle.kts
pluginManagement {
repositories {
google()
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
if(requested.id.namespace == "com.android") {
useModule("com.android.tools.build:gradle:${requested.version}")
}
}
}
}
build.gradle.kts
plugins {
id("com.android.application") version "4.1.2"
}
android {
// ...
}
事实上,上面的示例将适用于所有com.android。*由指定模块提供的插件。这是因为封装的模块使用编写自定义插件一章中描述的属性-文件机制,包含了插件ID映射到插件实现类的详细信息。
有关pluginManagement{}块的更多信息以及它的用途,请参阅Gradle用户手册中的Plugin Management部分。
使用容器对象
Gradle构建模型大量使用容器对象(或者仅仅是“容器”)。例如,配置和任务都是容器对象,分别包含Configuration和Task对象。社区插件也贡献了容器,比如android。buildTypes容器由Android Plugin贡献。
Kotlin DSL为构建作者提供了几种与容器交互的方法。下面我们将以任务容器为例,逐一介绍这些方法。
注意,如果在受支持的容器上配置现有元素,可以利用另一节中描述的类型安全访问器。该部分还描述了哪些容器支持类型安全访问器。
使用容器API
Gradle中的所有容器都实现NamedDomainObjectContainer<DomainObjectType>。其中一些可以包含不同类型的对象,并实现多态域objectcontainer <BaseType>。与容器交互的最简单方法是通过这些接口。
下面的示例演示如何使用named()方法配置现有任务,以及使用register()方法创建新任务。
使用容器API
build.gradle.kts
tasks.named("check")
tasks.register("myTask1")
tasks.named<JavaCompile>("compileJava")
tasks.register<Copy>("myCopy1")
tasks.named("assemble") {
dependsOn(":myTask1")
}
tasks.register("myTask2") {
description = "Some meaningful words"
}
tasks.named<Test>("test") {
testLogging.showStackTraces = true
}
tasks.register<Copy>("myCopy2") {
from("source")
into("destination")
}
获取Task类型的对名为check的现有任务的引用
注册一个名为myTask1的新非类型化任务
获取对类型为JavaCompile、名为compileJava的现有任务的引用
注册一个名为myCopy1的类型为Copy的新任务
获取对名为assemble的现有(非类型化)任务的引用并配置它—您只能使用此语法配置task上可用的属性和方法
注册一个名为myTask2的新非类型化任务并配置它——在这种情况下,您只能配置task上可用的属性和方法
获取对现有任务名称的引用
上面的示例依赖于配置而非api。如果您需要或希望直接配置或注册容器元素,只需将named()替换为getByName(),将register()替换为create()
使用Kotlin委托属性
另一种与容器交互的方法是通过Kotlin委托属性。如果您需要一个容器元素的引用,可以在构建的其他地方使用,那么这些参数特别有用。此外,Kotlin委托属性可以通过IDE重构轻松地重命名。
下面的示例做了与前一节完全相同的事情,但它使用了委托属性,并重用了这些引用来代替字符串-文字任务路径:
使用Kotlin委托属性
build.gradle.kts
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) // 使用对myTask1任务的引用,而不是任务路径
}
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")
}
上面的方法依赖于配置而不是api。如果需要主动配置或注册容器元素,只需用get()替换existing(),用create()替换register()。
一起配置多个容器元素
在配置容器的几个元素时,可以将交互分组到一个块中,以避免在每次交互中重复容器的名称。下面的示例使用了类型安全访问器、容器API和Kotlin委托属性的组合:
容器范围
build.gradle.kts
tasks {
test {
testLogging.showStackTraces = true
}
val myCheck by registering {
doLast { /* assert on something meaningful */ }
}
check {
dependsOn(myCheck)
}
register("myHelp") {
doLast { /* do something helpful */ }
}
}
使用运行时属性
Gradle有两个在运行时定义的属性来源:项目属性和额外属性。Kotlin DSL为处理这些类型的属性提供了特定的语法,我们将在下面几节中介绍这些语法
项目属性
通过Kotlin委托属性绑定项目属性,Kotlin DSL允许您访问项目属性。下面是一个示例片段,演示了两个项目属性的技术,其中一个必须定义:
build.gradle.kts
val myProperty: String by project
val myNullableProperty: String? by project
通过myProperty委托属性使myProperty项目属性可用-在这种情况下项目属性必须存在,否则当构建脚本尝试使用myProperty值时构建将失败
对myNullableProperty项目属性执行同样的操作,但是只要您检查null(适用于null安全的标准Kotlin规则),使用myNullableProperty值构建不会失败。
同样的方法在设置和初始化脚本中都可以工作,除了你分别使用by settings和by gradle来代替by project。
额外的任务属性
在任何实现ExtensionAware接口的对象上都可以使用额外的属性。Kotlin DSL允许您访问额外的属性,并通过委托属性创建新的属性,使用下面示例中演示的任何by extra表单:
build.gradle.kts
val myNewProperty by extra("initial value")
val myOtherNewProperty by extra { "calculated initial value" }
val myProperty: String by extra
val myNullableProperty: String? by extra
-
在当前上下文中(在本例中是项目)创建一个新的额外属性myNewProperty,并用“initial value”值初始化它,这也决定了属性的类型
-
创建一个新的额外属性,其初始值由提供的lambda计算
-
从当前上下文(本例中是项目)绑定一个现有的额外属性到一个myProperty引用
-
是否与前一行相同,但允许属性有一个空值
这种方法适用于所有的Gradle脚本:项目构建脚本、脚本插件、设置脚本和初始化脚本。
您还可以使用以下语法从子项目访问根项目上的额外属性:
my-sub-project/build.gradle.kts
val myNewProperty: String by rootProject.extra
将根项目的myNewProperty额外属性绑定到同名的引用
额外的财产并不仅限于项目。例如,Task扩展了ExtensionAware,因此您也可以为任务附加额外的属性。下面是一个例子,它在测试任务上定义了一个新的myNewTaskProperty,然后使用该属性初始化另一个任务:
build.gradle.kts
tasks {
test {
val reportType by extra("dev")
doLast {
// Use 'suffix' for post processing of reports
}
}
register<Zip>("archiveTestReports") {
val reportType: String by test.get().extra
archiveAppendix.set(reportType)
from(test.get().reports.html.destination)
}
}
在测试任务上创建一个新的reportType额外属性
使测试任务的reportType额外属性可用来配置archiveTestReports任务
如果你喜欢使用急切配置而不是配置避免api,你可以为报告类型使用一个单一的“全局”属性,像这样:
build.gradle.kts
tasks.test.doLast { ... }
val testReportType by tasks.test.get().extra("dev")
tasks.create<Zip>("archiveTestReports") {
archiveAppendix.set(testReportType)
from(test.get().reports.html.destination)
}
创建并初始化测试任务上的一个额外属性,将其绑定到一个“全局”属性
使用"global"属性初始化archiveTestReports任务
我们还应该介绍最后一种额外属性的语法,它将额外属性视为映射。我们通常不建议使用这种方法,因为这会失去Kotlin类型检查的好处,并且会阻止ide提供尽可能多的支持。但是,它比委托属性语法更简洁,如果您只需要设置额外属性的值,而不需要稍后引用它,则可以合理地使用它。
下面是一个简单的例子,演示了如何使用map语法设置和读取额外的属性:
build.gradle.kts
extra["myNewProperty"] = "initial value"
tasks.create("myTask") {
doLast {
println("Property: ${project.extra["myNewProperty"]}")
}
}
创建一个名为myNewProperty的新项目额外属性并设置其值
从我们创建的项目额外属性读取值-注意项目。否则,Gradle会假设我们想从task中读取一个额外的属性
Kotlin DSL插件
Kotlin DSL插件提供了一种方便的方法来开发基于Kotlin的项目,这些项目贡献了构建逻辑。这包括buildSrc项目,包括构建和Gradle插件。
这个插件通过以下步骤来实现:
-
应用Kotlin Plugin,增加了对编译Kotlin源文件的支持。
-
将Kotlin -stdlib-jdk8, Kotlin -reflect和gradleKotlinDsl()依赖项添加到compileOnly和testplementation配置中,这允许你在Kotlin代码中使用那些Kotlin库和Gradle API。
-
使用与Kotlin DSL脚本相同的设置配置Kotlin编译器,确保构建逻辑和那些脚本之间的一致性。
-
支持预编译脚本插件。
buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
// The org.jetbrains.kotlin.jvm plugin requires a repository
// where to download the Kotlin compiler dependencies from.
mavenCentral()
}
内置的kotlin
为了支持基于Kotlin的脚本,Gradle嵌入了Kotlin。
Kotlin版本
Gradle附带了kotlin编译器可嵌入库,以及kotlin-stdlib和kotlin-reflect库的匹配版本。例如,Gradle 4.3附带了Kotlin DSL v0.12.1,其中包括这些模块的Kotlin 1.1.51版本。这些模块中的kotlin包可以通过Gradle类路径看到。
Kotlin提供的兼容性保证适用于向后和向前的兼容性。
向后兼容
我们的方法是在一个主要的Gradle发行版上只做向后突破的Kotlin升级。我们总是会清楚地记录我们发布的Kotlin版本,并在主要版本发布前宣布升级计划。
插件作者想要兼容旧版本的Gradle,需要限制API的使用,使之与旧版本兼容。它与Gradle中的其他新API没有什么不同。例如,如果我们引入了一个新的API来解决依赖关系,而一个插件想要使用这个API,那么他们要么需要放弃对旧版本Gradle的支持,要么需要做一些事情。
向前兼容
最大的问题是外部的kotlin-gradle-plugin版本和Gradle附带的kotlin-stdlib版本之间的兼容性。更普遍地说,在任何过渡依赖于kotlin-stdlib的插件和它随Gradle发布的版本之间。只要组合是兼容的,一切都应该工作。随着语言的成熟,这将不再是一个问题
Kotlin的编译器参数
这些是在一个应用了Kotlin – DSL插件的项目中编译Kotlin DSL脚本和Kotlin源代码和脚本时使用的Kotlin编译器参数:
-jvm-target=1.8
将生成的JVM字节码的目标版本设置为1.8。-Xjsr305=strict
设置Kotlin的Java互操作性,严格遵循JSR-305注释,以提高空安全性。有关更多信息,请参阅Kotlin文档中的从Kotlin调用Java代码。
互用性
在构建逻辑中混合语言时,可能需要跨越语言边界。一个极端的例子是使用Java、Groovy和Kotlin实现的任务和插件的构建,同时也使用Kotlin DSL和Groovy DSL构建脚本。
引用Kotlin参考文档:
Kotlin在设计时考虑到了Java互操作性。现有的Java代码可以从Kotlin以一种自然的方式调用,Kotlin代码也可以在Java中相当顺畅地使用。
从Kotlin调用Java和从Java调用Kotlin在Kotlin参考文档中都有很好的介绍。
这同样适用于与Groovy代码的互操作性。此外,Kotlin DSL提供了几种选择Groovy语义的方法,我们接下来将讨论这一点
静态键入扩展
Groovy和Kotlin语言都支持通过Groovy Extension模块和Kotlin扩展现有类。
要从Groovy调用Kotlin扩展函数,将其作为静态函数调用,并将接收方作为第一个参数传递:
从Groovy调用Kotlin扩展
TheTargetTypeKt.kotlinExtensionFunction(receiver, "parameters", 42, aReference)
Kotlin扩展函数是包级函数,您可以在Kotlin参考文档的包级函数一节中了解如何定位声明给定Kotlin扩展的类型的名称。
要从Kotlin调用Groovy扩展方法,可以采用相同的方法:将其作为静态函数调用,将接收方作为第一个参数传递。这里有一个例子:
从Kotlin调用Groovy扩展
build.gradle.kts
TheTargetTypeGroovyExtension.groovyExtensionMethod(receiver, "parameters", 42, aReference)
命名参数和默认参数
Groovy和Kotlin语言都支持命名函数参数和默认参数,尽管它们的实现非常不同。Kotlin完全支持这两种参数,如Kotlin语言参考中关于命名参数和默认参数的描述。Groovy以一种基于Map<String, ?>形参的非类型安全方式实现命名实参,这意味着它们不能与默认实参组合。换句话说,对于任何给定的方法,在Groovy中只能使用其中一种。
从Groovy调用Kotlin
要调用具有来自Groovy的命名参数的Kotlin函数,只需使用带有位置参数的普通方法调用。没有办法通过参数名提供值。
要从Groovy调用具有默认参数的Kotlin函数,请始终传递所有函数参数的值
从Kotlin给Groovy打电话
要从Kotlin调用一个带命名参数的Groovy函数,需要传递一个Map<String, ?>,如下例所示:
调用带有Kotlin命名参数的Groovy函数
build.gradle.kts
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插件:
使用closureOf<T> {}
build.gradle.kts
bintray {
pkg(closureOf<PackageConfig> {
// Config for the package here
})
}
在其他情况下,如Gretty Plugin在配置farms时,插件期望一个delegate闭包:
使用delegateClosureOf<T> {}
build.gradle.kts
dependencies {
implementation("group:artifact:1.2.3") {
artifact(delegateClosureOf<DependencyArtifact> {
// configuration for the artifact
name = "artifact-name"
})
}
}
有时候,通过查看源代码,并不能很好地判断要使用哪个版本。通常,如果你用closureOf/{}得到一个NullPointerException,使用delegateClosureOf<T>{}将会解决这个问题。
这两个实用函数对于配置闭包很有用,但有些插件可能希望Groovy闭包用于其他目的。KotlinClosure0到KotlinClosure2类型允许将Kotlin函数更灵活地适应Groovy闭包。
使用kotlinClosureX类型
build.gradle.kts
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的对象上。下面的例子演示了目标对象上的方法的几个特性:
使用withGroovyBuilder {}
build.gradle.kts
target.withGroovyBuilder {
// GroovyObject methods available
val foo = getProperty("foo")
setProperty("foo", "bar")
invokeMethod("name", arrayOf("parameters", 42, aReference))
// Kotlin DSL utilities
"name"("parameters", 42, aReference)
"blockName" {
// Same Groovy Builder semantics on `blockName`
}
"another"("name" to "example", "url" to "https://example.com/")
}
接收器是一个GroovyObject,并提供Kotlin helper
GroovyObject API是可用的
调用methodName方法,并传递一些参数
配置blockName属性,映射到一个接受方法调用的Closure
调用另一个接受命名参数的方法,映射到一个Groovy命名参数Map<String, ?>接受方法调用
使用Groovy脚本
当处理有问题的插件时,另一种选择是在Groovy DSL构建脚本中配置它们,该脚本应用于主要的Kotlin DSL构建脚本:
使用Groovy脚本
build.gradle.kts
plugins {
id("dynamic-groovy-plugin") version "1.0"
}
apply(from = "dynamic-groovy-plugin-configuration.gradle")
dynamic-groovy-plugin-configuration.gradle
native {
dynamic {
groovy as Usual
}
}
Kotlin构建脚本请求并应用插件
Kotlin构建脚本应用Groovy脚本
Groovy脚本使用动态Groovy配置插件
局限性
-
众所周知,Kotlin DSL在第一次使用时比Groovy DSL慢,例如使用干净的签出或使用短暂的持续集成代理。更改buildSrc目录中的内容也会产生影响,因为它会使构建脚本缓存失效。主要原因是Kotlin DSL的脚本编译较慢。
-
在IntelliJ IDEA中,为了获得Kotlin DSL构建脚本的内容辅助和重构支持,你必须从Gradle模型中导入项目。
-
Kotlin DSL将不支持model{}块,这是已停止的Gradle软件模式的一部分
-
我们建议不要启用随需应变的孵化配置特性,因为它可能导致非常难以诊断的问题。
如果您遇到麻烦或发现可疑的bug,请在Gradle问题跟踪器中报告问题。
本文为从大数据到人工智能博主「xiaozhch5」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://lrting.top/backend/2156/
不错