Gradle的Kotlin DSL提供了传统Groovy DSL的替代语法,在受支持的IDE中提供了增强的编辑体验,并具有出色的内容辅助,重构,文档等功能. 本章详细介绍了Kotlin DSL的主要结构,以及如何使用它与Gradle API进行交互.

💡

如果您有兴趣将现有的Gradle版本迁移到Kotlin DSL,也请查看专用的迁移指南 .

Prerequisites

  • 嵌入式Kotlin编译器可在x86-64体系结构的Linux,macOS,Windows,Cygwin,FreeBSD和Solaris上运行.

  • 了解Kotlin语法和基本语言功能非常有帮助. Kotlin参考文档Kotlin Koans将帮助您学习基础知识.

  • 使用plugins {}块来声明Gradle插件可以大大改善编辑体验,因此强烈建议您使用.

IDE support

IntelliJ IDEA和Android Studio完全支持Kotlin DSL. 其他IDE尚未提供用于编辑Kotlin DSL文件的有用工具,但是您仍然可以导入基于Kotlin-DSL的内部版本并照常使用它们.

表1. IDE支持矩阵
构建导入 语法突出显示1 语义编辑器2

IntelliJ IDEA

Android Studio

Eclipse IDE

CLion

Apache NetBeans

Visual Studio代码(LSP)

视觉工作室

1 Kotlin syntax highlighting in Gradle Kotlin DSL scripts
2 code completion, navigation to sources, documentation, refactorings etc…​ in Gradle Kotlin DSL scripts

如限制中所述,您必须从Gradle模型导入项目,以在IntelliJ IDEA中获得Kotlin DSL脚本的内容辅助和重构工具.

另外,在编辑Gradle脚本时,IntelliJ IDEA和Android Studio可能会产生多达3个Gradle守护程序-每种脚本类型一个:构建脚本,设置文件和初始化脚本. 配置时间缓慢的版本可能会影响IDE的响应速度,因此请查看性能指南以帮助解决此类问题.

Automatic build import vs. automatic reloading of script dependencies

IntelliJ IDEA和从IntelliJ IDEA派生的Android Studio都将检测到您何时对构建逻辑进行更改,并提供两个建议:

  1. 再次导入整个构建

    IntelliJ IDEA
    IntelliJ IDEA
  2. 编辑构建脚本时重新加载脚本依赖项

    Reload script dependencies

我们建议您禁用自动生成导入 ,但是启用脚本依赖项的自动重新加载 . 这样,您可以在编辑Gradle脚本时获得早期反馈,并控制整个构建设置何时与IDE同步.

Troubleshooting

IDE支持由两个组件提供:

  • IntelliJ IDEA / Android Studio使用的Kotlin插件

  • Gradle

支持级别取决于每个版本.

如果遇到麻烦,首先应该尝试./gradlew tasks运行./gradlew tasks ,以查看问题是否仅限于IDE. 如果您在命令行遇到相同的问题,则问题在于构建而不是IDE集成.

如果可以从命令行成功运行该构建,但是脚本编辑器在抱怨,则应尝试重新启动IDE并使它的缓存无效.

如果上述方法不起作用,并且您怀疑Kotlin DSL脚本编辑器存在问题,则可以:

  • 运行./gradle tasks以获取更多详细信息

  • 检查以下位置之一中的日志:

    • Mac OS X上的$HOME/Library/Logs/gradle-kotlin-dsl

    • 在Linux上的$HOME/.gradle-kotlin-dsl/logs

    • $HOME/AppData/Local/gradle-kotlin-dsl/log on Windows

  • Gradle问题追踪器上打开问题,包括尽可能多的详细信息.

从5.1版开始,将自动清除日志目录. 定期检查(最多每24小时检查一次),如果7天内未使用日志文件,则将其删除.

对于Kotlin DSL脚本编辑器之外的IDE问题,请在相应的IDE的问题跟踪器中打开问题:

最后,如果您遇到Gradle本身或Kotlin DSL的问题,请在Gradle问题跟踪器上打开问题.

Kotlin DSL scripts

就像基于Groovy的同类产品一样,Kotlin DSL是在Gradle的Java API之上实现的. 您在Kotlin DSL脚本中可以读取的所有内容都是Gradle编译和执行的Kotlin代码. 您在构建脚本中使用的许多对象,函数和属性都来自Gradle API和所应用插件的API.

Script file names

Groovy DSL脚本文件使用.gradle文件扩展名.

Kotlin DSL脚本文件使用.gradle.kts文件扩展名.

要激活Kotlin DSL,只需将.gradle.kts扩展名用于构建脚本来代替.gradle . 这也适用于设置文件 (例如settings.gradle.kts )和初始化脚本 .

请注意,您可以将Groovy DSL构建脚本与Kotlin DSL脚本混合使用,即Kotlin DSL构建脚本可以应用Groovy DSL,而在多项目构建中的每个项目都可以使用其中之一.

我们建议您应用以下约定以获得更好的IDE支持:

  • 根据模式*.settings.gradle.kts命名设置脚本(或由Gradle Settings对象支持的任何脚本)-这包括从设置脚本中应用的脚本插件

  • 根据模式*.init.gradle.kts或简单地init.gradle.kts命名初始化脚本 .

这样,IDE就能知道是哪种类型的对象"支持"脚本,无论是ProjectSettings还是Gradle .

Implicit imports

All Kotlin DSL build scripts have implicit imports consisting of:

  • The default Gradle API imports

  • Kotlin DSL API,目前是org.gradle.kotlin.dslorg.gradle.kotlin.dsl.plugins.dsl软件包中的所有类型

避免使用内部Kotlin DSL API

当Gradle或插件更改时,在插件和构建脚本中使用内部Kotlin DSL API可能会破坏构建. Kotlin DSL API使用org.gradle.kotlin.dslorg.gradle.kotlin.dsl.plugins.dsl包(但不是这些子包)中相应API文档中列出的类型扩展了Gradle公共API .

Type-safe model accessors

Groovy DSL允许您按名称引用构建模型的许多元素,即使它们是在运行时定义的也是如此. 考虑命名配置,命名源集,等等. 例如,您可以通过configurations.implementation来掌握implementation configurations.implementation .

Kotlin DSL用类型安全的模型访问器代替了这种动态解析,该类型访问器可处理由插件贡献的模型元素.

Understanding when type-safe model accessors are available

Kotlin DSL当前支持由插件贡献的以下任何类型的类型安全模型访问器:

  • 依赖关系和工件配置(例如implementationruntimeOnlyruntimeOnly由Java插件提供)

  • 项目扩展名和约定(例如sourceSets

  • tasksconfigurations容器中的元素

  • 项目扩展容器中的元素(例如,由Java插件贡献的源集已添加到sourceSets容器中)

  • 以上每个方面的扩展

❗️

只有主项目构建脚本和预编译的项目脚本插件才具有类型安全的模型访问器. 初始化脚本,设置脚本,脚本插件没有. 这些限制将在将来的Gradle版本中删除.

可以在评估脚本主体之前,即在plugins {}块之后立即计算可用的类型安全模型访问器的集合. 此后提供的任何模型元素均不适用于类型安全的模型访问器. 例如,这包括您可能在自己的构建脚本中定义的任何配置. 但是,这种方法确实意味着您可以对父项目应用的插件贡献的任何模型元素使用类型安全的访问器.

以下项目构建脚本演示了如何使用类型安全的访问器访问各种配置,扩展名和其他元素:

例子1.使用类型安全的模型访问器
build.gradle.kts
plugins {
    `java-library`
}

dependencies {                              // (1)
    api("junit:junit:4.12")
    implementation("junit:junit:4.12")
    testImplementation("junit:junit:4.12")
}

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
    }
}
  1. Java库插件贡献的apiimplementationtestImplementation依赖项配置使用类型安全的访问器

  2. 使用访问器来配置sourceSets项目扩展

  3. 使用访问器配置main源集

  4. 使用访问器为main源集配置java

  5. 使用访问器配置test任务

💡

您的IDE知道类型安全访问器,因此它将在建议中包括它们. 这将在构建脚本的顶级(大多数插件扩展都添加到Project对象)以及配置扩展的块中发生.

请注意,容器元素(例如configurationstaskssourceSets利用Gradle的避免配置API . 例如,在tasks它们的类型为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()

对于tasks以外的所有其他容器,元素的访问器的类型为NamedDomainObjectProvider<T>并提供相同的行为.

Understanding what to do when type-safe model accessors are not available

考虑上面显示的样本构建脚本,该脚本演示了类型安全访问器的用法. 除了使用apply()方法来应用插件之外,以下示例完全相同. 在这种情况下,构建脚本不能使用类型安全的访问器,因为apply()调用发生在构建脚本的主体中. 您必须改用其他技术,如此处所示:

例子2.配置没有类型安全访问器的插件
build.gradle.kts
apply(plugin = "java-library")

dependencies {
    "api"("junit:junit:4.12")
    "implementation"("junit:junit:4.12")
    "testImplementation"("junit:junit:4.12")
}

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(plugin = "id")

  • 项目构建脚本

  • 脚本插件,通过apply(from = "script-plugin.gradle.kts")

  • 通过跨项目配置应用的插件

您还不能在Kotlin中实现的Binary Gradle插件中使用类型安全的访问器.

如果找不到类型安全的访问器,则退回到对相应类型使用常规API的方式. 为此,您需要知道已配置模型元素的名称和/或类型. 现在,我们将向您展示如何通过仔细查看上述脚本来发现这些内容.

Artifact configurations

以下样本演示了如何在没有类型访问器的情况下引用和配置工件配置:

例子3.工件配置
build.gradle.kts
apply(plugin = "java-library")

dependencies {
    "api"("junit:junit:4.12")
    "implementation"("junit:junit:4.12")
    "testImplementation"("junit:junit:4.12")
}

configurations {
    "implementation" {
        resolutionStrategy.failOnVersionConflict()
    }
}

该代码看起来与类型安全访问器的代码相似,只是在这种情况下,配置名称是字符串文字. 您可以在依赖项声明中以及configurations {}块中使用字符串文字作为配置名称.

在这种情况下,IDE将无法帮助您发现可用的配置,但是您可以在相应插件的文档中或通过运行gradle dependencies来查找它们.

Project extensions and conventions

项目扩展名和约定都具有名称和唯一类型,但是Kotlin DSL只需要知道类型即可进行配置. 如以下示例针对原始示例构建脚本中的sourceSets {}java {}块所示,可以使用具有相应类型的configure<T>()函数来执行此操作:

例子4.项目扩展和约定
build.gradle.kts
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类型SourceSetContainerjava是一个扩展Project类型JavaPluginExtension .

您可以通过查看所应用插件的文档或运行gradle kotlinDslAccessorsReport来发现可用的扩展名和约定,该gradle kotlinDslAccessorsReport打印出访问所有已应用插件贡献的模型元素所需的Kotlin代码. 该报告提供名称和类型. 作为最后的选择,您还可以检查插件的源代码,但是在大多数情况下,这不是必需的.

请注意,如果只需要对扩展名或约定的引用而不进行配置,或者想要执行单行配置,也可以使用the<T>()函数,例如:

the<SourceSetContainer>()["main"].srcDir("src/core/java")

上面的代码段还演示了一种配置作为容器的项目扩展的元素的方法.

Elements in project-extension containers

基于容器的项目扩展,例如SourceSetContainer ,还允许您配置它们所包含的元素. 在示例构建脚本中,我们想要在源集容器中配置名为main的源集,这可以通过使用named()方法代替访问器来完成,如下所示:

例子5.作为容器的项目扩展的元素
build.gradle.kts
apply(plugin = "java-library")

configure<SourceSetContainer> {
    named("main") {
        java.srcDir("src/core/java")
    }
}

基于容器的项目扩展中的所有元素都有一个名称,因此您可以在所有这种情况下使用此技术.

至于项目扩展和约定本身,您可以通过查看所应用插件的文档或运行gradle kotlinDslAccessorsReport来发现任何容器中存在哪些元素. 最后,您也许可以查看插件的源代码以了解其功能,但是在大多数情况下,这不是必需的.

Tasks

任务不是通过基于容器的项目扩展来管理的,但是它们是行为类似的容器的一部分. 这意味着您可以按照与源集相同的方式配置任务,如本例所示:

例子6.任务
build.gradle.kts
apply(plugin = "java-library")

tasks {
    named<Test>("test") {
        testLogging.showExceptions = true
    }
}

我们使用Gradle API通过名称和类型来引用任务,而不是使用访问器. 请注意,有必要明确指定任务的类型,否则脚本将无法编译,因为推断的类型将是Task ,而不是Test ,并且testLogging属性特定于Test任务类型. 但是,如果只需要配置属性或调用所有任务通用的方法,即在Task接口上声明它们,则可以省略类型.

通过运行gradle tasks可以发现可用的gradle tasks . 然后,您可以通过运行gradle help --task <taskName>来找到给定任务的类型,如下所示:

❯ ./gradlew help --task test
...
Type
     Test (org.gradle.api.tasks.testing.Test)

请注意,IDE可以帮助您进行所需的导入,因此您只需要类型的简单名称,即不需要包名称部分. 在这种情况下,无需导入Test任务类型,因为它是Gradle API的一部分,因此是隐式导入的 .

About conventions

一些Gradle核心插件借助所谓的Conventional对象公开了可配置性. 它们具有与扩展类似的用途,并且现在已被扩展取代. 编写新插件时,请避免使用约定对象. 长期计划是迁移所有Gradle核心插件以使用扩展并完全删除约定对象.

如上所示,Kotlin DSL仅为Project上的约定对象提供访问器. 在某些情况下,您需要与使用其他类型约定对象的Gradle插件进行交互. Kotlin DSL提供了withConvention(T::class) {}扩展功能来执行此操作:

示例7.配置源集约定
build.gradle.kts
plugins {
    groovy
}

sourceSets {
    main {
        withConvention(GroovySourceSet::class) {
            groovy.srcDir("src/core/groovy")
        }
    }
}

对于由Java插件以外的其他语言插件(例如Groovy插件和Scala插件)​​添加的源集,最通常需要此技术. 您可以在SourceSet参考文档中查看哪些插件为源集添加了哪些属性.

Multi-project builds

与单项目构建一样,您应该尝试在多项目构建中使用plugins {}块,以便可以使用类型安全的访问器. 多项目构建的另一个注意事项是,在根构建脚本中配置子项目或在项目之间使用其他形式的交叉配置时,将无法使用类型安全的访问器. 在以下各节中,我们将更详细地讨论这两个主题.

Applying plugins

您可以在应用插件的子项目中声明您的插件,但是我们建议您也在根项目构建脚本中声明它们. 这样可以更轻松地使构建版本中各个项目的插件版本保持一致. 该方法还提高了构建的性能.

" 使用Gradle插件"一章介绍了如何在根项目构建脚本中使用版本声明插件,然后将其应用于适当的子项目的构建脚本. 以下是使用三个子项目和三个插件的这种方法的示例. 请注意,由于Java库插件与您使用的Gradle版本有关,因此根构建脚本仅声明社区插件.

例子8.使用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.5.4" 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 {
    mainClassName = "example.App"
}

ratpack.baseDir = file("src/ratpack/baseDir")

如果您的版本在Gradle插件门户网站顶部需要其他插件存储库,则应在settings.gradle.kts文件的pluginManagement {}块中声明它们,如下所示:

示例9.声明其他插件存储库
settings.gradle.kts
pluginManagement {
    repositories {
        jcenter()
        gradlePluginPortal()
    }
}

如果从Gradle插件门户网站以外的其他来源获取的plugins {}插件标记工件一起发布,则只能通过plugins {}块声明.

在撰写本文时, google()信息库中存在的所有适用于Gradle的Android插件(最高3.2.0 google()版本都没有插件标记工件.

如果缺少这些工件,那么您将无法使用plugins {}块. 相反,您必须使用根项目构建脚本中的buildscript {}块来声明插件依赖性. 这是为Android插件执行此操作的示例:

例子10.使用buildscript {}块在根构建脚本中声明插件依赖性
settings.gradle.kts
include("lib", "app")
build.gradle.kts
buildscript {
    repositories {
        google()
        gradlePluginPortal()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:3.2.0")
    }
}
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 projects

跨项目配置是一种机制,通过该机制可以从另一个项目的构建脚本中配置一个项目. 一个常见的示例是在根项目构建脚本中配置子项目时.

采用这种方法意味着您将无法对插件贡献的模型元素使用类型安全的访问器. 相反,您将不得不依赖字符串文字和标准的Gradle API.

作为示例,让我们修改Java / Ratpack示例构建以从根项目构建脚本完全配置其子项目:

例子11.交叉配置项目
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.5.4" 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")
    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<ApplicationPluginConvention> {
        mainClassName = "example.App"
    }
    ratpack.baseDir = file("src/ratpack/baseDir")
}

注意我们如何使用apply()方法来应用插件,因为plugins {}块在这种情况下不起作用. 我们还使用标准API而不是类型安全的访问器来配置任务,扩展名和约定-我们已在其他地方更详细地讨论了这种方法.

When you can’t use the plugins {} block

Gradle插件门户网站以外的其他来源获取的插件可能会或可能无法与plugins {}块一起使用. 这取决于它们的发布方式,尤其取决于它们是否已使用必要的插件标记工件发布 .

例如,未将Gradle的Android插件发布到Gradle插件门户,并且-至少在该插件的3.2.0版本之前-解决给定插件标识符的工件所需的元数据没有发布到Google存储库.

如果您的构建是一个多项目构建和你不需要这样的插件应用到您的项目,然后您可以利用该技术克服这个问题, 如上所述 . 对于其他情况,请继续阅读.

💡

发布插件时,请使用Gradle内置的Gradle插件开发插件 . 它自动发布必要的元数据,以使您的插件可用于plugins {}块.

我们将在本节中向您展示如何将Android插件应用于单项目构建或多项目构建的根项目. 目的是指导您的构建如何将com.android.application插件标识符映射到可解决的工件. 这分两个步骤完成:

  • 将插件存储库添加到构建的设置脚本

  • 将插件ID映射到相应的工件坐标

您可以通过在构建的设置脚本中配置pluginManagement {}块来完成两个步骤. 为了演示,以下示例将google()存储库(发布Android插件的位置google()添加到存储库搜索列表中,并使用resolutionStrategy {}块将com.android.application插件ID映射到com.android.tools.build:gradle:<version> google()存储库中可用的com.android.tools.build:gradle:<version>工件:

例子12.将插件ID映射到依赖项坐标
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 "3.2.0"
}

android {
    // ...
}

实际上,以上示例适用于指定模块提供的所有com.android.*插件. 这是因为打包的模块包含使用" 编写自定义插件"一章中描述的属性文件机制,将哪些插件ID映射到哪个插件实现类的详细信息.

有关pluginManagement {}块及其用途的更多信息,请参见Gradle用户手册的" 插件管理"部分.

Working with container objects

Gradle构建模型大量使用了容器对象(或只是"容器"). 例如, configurationstasks都是容器对象,分别包含ConfigurationTask对象. 社区插件还提供了容器,例如Android插件提供的android.buildTypes容器.

Kotlin DSL为构建作者与容器交互提供了几种方法. 接下来,以tasks容器为例,介绍每种方式.

💡

请注意,如果要在支持的容器上配置现有元素,则可以利用另一节中描述的类型安全访问器. 该部分还描述了哪些容器支持类型安全的访问器.

Using the container API

Gradle中的所有容器都实现NamedDomainObjectContainer <DomainObjectType> . 其中一些可以包含不同类型的对象并实现PolymorphicDomainObjectContainer <BaseType> . 与容器交互的最简单方法是通过这些接口.

The following sample demonstrates how you can use the named() method to configure existing tasks and the register() method to create new ones.

Example 13. Using the container API
build.gradle.kts
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. 获取Task类型对现有名为check Task的引用

  2. 注册名为myTask1的新无类型任务

  3. 获取对JavaCompile类型的名为compileJava的现有任务的引用

  4. 注册一个新的任务名为myCopy1类型的Copy

  5. 获取对现有(未类型化)任务的引用,名为assemble并对其进行配置-您只能使用以下语法配置Task上可用的属性和方法

  6. 注册一个名为myTask2的新无类型任务并进行配置-在这种情况下,您只能配置Task上可用的属性和方法

  7. 获取对名为test类型的Test的现有任务的引用并进行配置-在这种情况下,您可以访问指定类型的属性和方法

  8. Registers a new task named myCopy2 of type Copy and configures it

上面的示例依赖于配置避免API. 如果您需要或希望急于配置或注册容器元素,只需将named()替换named() getByName()并将register()替换named() create() .

Using Kotlin delegated properties

与容器交互的另一种方法是通过Kotlin委托的属性. 如果您需要引用可在构建中其他位置使用的容器元素,则这些选项特别有用. 此外,可以通过IDE重构轻松重命名Kotlin委托的属性.

下面的示例执行与上一部分完全相同的操作,但是它使用委托的属性并重用这些引用代替字符串字面的任务路径:

例子14.使用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)  // (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() .

Configuring multiple container elements together

在配置容器的多个元素时,可以将交互分组在一个块中,以避免在每次交互时重复容器的名称. 以下示例结合使用类型安全访问器,容器API和Kotlin委托属性:

例子15.容器范围
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 */ }
    }
}

Working with runtime properties

Gradle在运行时定义了两个主要的属性来源: 项目属性Extra属性 . Kotlin DSL提供了用于处理这些类型的属性的特定语法,我们将在以下部分中进行介绍.

Project properties

Kotlin DSL允许您通过Kotlin委托属性绑定项目属性来访问它们. 这是一个示例片段,演示了几个项目属性的技术,其中必须定义其中一个:

build.gradle.kts
val myProperty: String by project  // (1)
val myNullableProperty: String? by project // (2)
  1. 通过myProperty委托属性使myProperty项目属性可用-在这种情况下,该项目属性必须存在,否则当构建脚本尝试使用myProperty值时,构建将失败

  2. myNullableProperty项目属性执行相同的myNullableProperty ,但是只要您检查是否为空,构建就不会在使用myNullableProperty值时失败(适用于空安全性的标准Kotlin规则

同样的方法适用于两种设置和初始化脚本,除非您使用by settings ,并by gradle分别代替by project .

Extra properties

在实现ExtensionAware接口的任何对象上都可以使用其他属性. Kotlin DSL允许您使用以下示例中演示的任何by extra形式访问附加属性并通过委托属性创建新属性:

build.gradle.kts
val myNewProperty by extra("initial value")  // (1)
val myOtherNewProperty by extra { "calculated initial value" }  // (2)

val myProperty: String by extra  // (3)
val myNullableProperty: String? by extra  // (4)
  1. 在当前上下文(在本例中为项目)中创建一个名为myNewProperty的新额外属性,并使用值"initial value"对其进行初始化,这也决定了该属性的类型

  2. 创建一个新的额外属性,其初始值由提供的lambda计算

  3. 将当前上下文(在本例中为项目)中现有的额外属性绑定到myProperty引用

  4. 与上一行相同,但允许属性具有空值

该方法适用于所有Gradle脚本:项目构建脚本,脚本插件,设置脚本和初始化脚本.

您还可以使用以下语法从子项目访问根项目的其他属性:

my-sub-project/build.gradle.kts
val myNewProperty: String by rootProject.extra  // (1)
  1. 将根项目的myNewProperty额外属性绑定到同名引用

额外的属性不仅限于项目. 例如, Task扩展了ExtensionAware ,因此您也可以将附加属性附加到任务. 这是一个在test任务上定义新的myNewTaskProperty ,然后使用该属性初始化另一个任务的示例:

build.gradle.kts
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.set(reportType)
        from(test.get().reports.html.destination)
    }
}
  1. test任务上创建一个新的reportType额外属性

  2. 使test任务的reportType额外属性可用于配置archiveTestReports任务

如果您乐意使用紧急配置而不是配置回避API,则可以对报告类型使用单个"全局"属性,如下所示:

build.gradle.kts
tasks.test.doLast { ... }

val testReportType by tasks.test.get().extra("dev")  // (1)

tasks.create<Zip>("archiveTestReports") {
    archiveAppendix.set(testReportType)  // (2)
    from(test.get().reports.html.destination)
}
  1. 创建并初始化test任务的额外属性,将其绑定到"全局"属性

  2. 使用"全局"属性初始化archiveTestReports任务

我们应该介绍额外属性的最后一种语法,一种将extra属性视为地图. 我们建议您不要使用它,因为这样会失去Kotlin的类型检查的好处,并且会阻止IDE提供尽可能多的支持. 但是,它比委派属性语法更简洁,如果您只需要设置额外属性的值而无需稍后引用,则可以合理地使用它.

这是一个简单的示例,演示如何使用map语法设置和读取额外的属性:

build.gradle.kts
extra["myNewProperty"] = "initial value"  // (1)

tasks.create("myTask") {
    doLast {
        println("Property: ${project.extra["myNewProperty"]}")  // (2)
    }
}
  1. 创建一个名为myNewProperty的新项目额外属性,并设置其值

  2. 从我们创建的项目额外属性中读取值-请注意project.extra[…​]上的限定符,否则Gradle将假定我们要从任务中读取一个额外的属性

The Kotlin DSL Plugin

Kotlin DSL插件提供了一种方便的方法来开发基于Kotlin的项目,这些项目有助于构建逻辑. 其中包括buildSrc项目包括内部版本Gradle插件 .

该插件通过执行以下操作来实现此目的:

  • Applies the Kotlin Plugin, which adds support for compiling Kotlin source files.

  • kotlin-stdlib-jdk8kotlin-reflectgradleKotlinDsl()依赖项添加到compileOnlytestImplementation配置中,使您可以在Kotlin代码中使用这些Kotlin库和Gradle API.

  • 使用与Kotlin DSL脚本相同的设置来配置Kotlin编译器,以确保构建逻辑和这些脚本之间的一致性.

  • 启用对预编译脚本插件的支持.

避免为kotlin-dsl插件指定版本

Each Gradle release is meant to be used with a specific version of the kotlin-dsl plugin and compatibility between arbitrary Gradle releases and kotlin-dsl plugin versions is not guaranteed. Using an unexpected version of the kotlin-dsl plugin in a build will emit a warning and can cause hard to diagnose problems.

这是使用插件所需的基本配置:

例子16.将Kotlin DSL插件应用于buildSrc项目
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.
    jcenter()
}

请注意,Kotlin DSL插件会启用实验性Kotlin编译器功能. 有关更多信息,请参见下面的Kotlin编译器参数部分.

默认情况下,该插件会警告您使用Kotlin编译器的实验功能. 您可以通过将kotlinDslPluginOptions扩展的experimentalWarning属性设置为false来使警告静音,如下所示:

例子17.禁用关于使用实验性Kotlin编译器功能的警告
buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

kotlinDslPluginOptions {
    experimentalWarning.set(false)
}

Precompiled script plugins

除了src/main/kotlin位于src/main/kotlin下的普通Kotlin源文件之外,Kotlin DSL插件还允许您将构建逻辑作为预编译脚本插件提供. 您可以将它们作为*.gradle.kts文件写入同一src/main/kotlin目录中.

预编译脚本插件是Kotlin DSL脚本,它们作为常规Kotlin源集的一部分进行编译,然后放置在构建类路径中或打包为二进制插件,具体取决于它们所处的项目类型.出于所有意图和目的,它们二进制插件,尤其是可以通过插件ID应用的二进制插件,就像普通插件一样. 实际上,由于与Gradle Plugin Development Plugin集成,Kotlin DSL插件会为其生成插件元数据.

Gradle 6.0内置的预编译脚本插件不能与Gradle的早期版本一起使用. 在Gradle的未来版本中将取消此限制.

因此,要应用预编译的脚本插件,您需要知道其ID. 这是从其文件名(减去.gradle.kts扩展名)及其(可选)程序包声明中派生的.

例如,脚本src/main/kotlin/java-library-convention.gradle.kts的插件ID为java-library-convention (假设它没有包声明). 同样, src/main/kotlin/my/java-library-convention.gradle.kts的软件包声明为my ,其插件ID为my.java-library-convention .

为了演示如何实现和使用预编译的脚本插件,让我们buildSrc一个基于buildSrc项目的示例.

首先,您需要一个应用Kotlin DSL插件的buildSrc/build.gradle.kts文件:

例子18.将Kotlin DSL插件应用到buildSrc项目
buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    jcenter()
}

我们建议您还创建一个buildSrc/settings.gradle.kts文件,您可以将其保留为空.

接下来,在buildSrc/src/main/kotlin目录中创建一个新的java-library-convention.gradle.kts文件,并将其内容设置为以下内容:

例子19.创建一个简单的脚本插件
buildSrc/src/main/kotlin/java-library-convention.gradle.kts
plugins {
    `java-library`
    checkstyle
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

checkstyle {
    maxWarnings = 0
    // ...
}

tasks.withType<JavaCompile> {
    options.isWarnings = true
    // ...
}

dependencies {
    testImplementation("junit:junit:4.12")
    // ...
}

该脚本插件仅应用Java库和Checkstyle插件并对其进行配置. 请注意,这实际上会将插件应用于主项目,即,将预编译脚本插件应用于主项目的插件.

最后,将脚本插件应用于根项目,如下所示:

例子20.将预编译的脚本插件应用到主项目
build.gradle.kts
plugins {
    `java-library-convention`
}

The embedded Kotlin

Gradle嵌入Kotlin是为了为基于Kotlin的脚本提供支持.

Kotlin versions

Gradle随附了kotlin-compiler-embeddable以及匹配版本的kotlin-stdlibkotlin-reflect库. 例如,Gradle 4.3附带了Kotlin DSL v0.12.1,其中包括这些模块的Kotlin 1.1.51版本. 这些模块的kotlin包可通过Gradle类路径看到.

Kotlin提供的兼容性保证适用于向后和向前兼容性.

Backward compatibility

我们的方法是仅在主要Gradle版本上进行向后的Kotlin升级. 在主要版本发布之前,我们将始终清楚地记录我们所发行的Kotlin版本,并宣布升级计划.

想要与较早的Gradle版本保持兼容的插件作者,必须将其API使用限制为与这些旧版本兼容的子集. 它与Gradle中的任何其他新API并没有真正的不同. 例如,如果我们引入一个新的API来解决依赖关系,而一个插件想使用该API,那么他们要么需要放弃对较旧的Gradle版本的支持,要么需要对它们的代码进行一些巧妙的组织以仅在较新的代码上执行新的代码路径版本.

Forward compatibility

最大的问题是Gradle随附的外部kotlin-gradle-plugin版本与kotlin-stdlib版本之间的兼容性. 更一般而言,在任何依赖于kotlin-stdlib插件与其Gradle附带的版本之间. 只要组合兼容,一切都应该起作用. 随着语言的成熟,这将不再是一个问题.

Kotlin compiler arguments

这些是Kotlin编译器参数,用于在应用了kotlin-dsl插件的项目中编译Kotlin DSL脚本以及Kotlin源和脚本:

-jvm-target=1.8

将生成的JVM字节码的目标版本设置为1.8 .

-Xjsr305=strict

设置Kotlin的Java互操作性以严格遵循JSR-305注释,以提高null安全性. 有关更多信息,请参阅Kotlin文档中的从Kotlin调用Java代码 .

-XX:NewInference

启用实验性Kotlin编译器推理引擎(对于Kotlin函数的SAM转换是必需的).

-XX:SamConversionForKotlinFunctions

为Kotlin函数启用SAM(单一抽象方法)转换,以允许Kotlin构建逻辑公开和使用基于org.gradle.api.Action<T>的API. 然后可以从Kotlin和Groovy DSL统一使用此类API.

As an example, given the following hypothetical Kotlin function with a Java SAM parameter type:

fun kotlinFunctionWithJavaSam(action: org.gradle.api.Action<Any>) = TODO()

SAM转换为Kotlin函数可实现该函数的以下用法:

kotlinFunctionWithJavaSam {
    // ...
}

如果没有Kotlin函数的SAM转换,则必须显式转换传递的lambda:

kotlinFunctionWithJavaSam(Action {
    // ...
})

Interoperability

在构建逻辑中混合语言时,可能必须跨越语言边界. 一个极端的例子是使用Java,Groovy和Kotlin中实现的任务和插件的构建,同时还使用Kotlin DSL和Groovy DSL构建脚本.

引用Kotlin参考文档:

Kotlin在设计时就考虑了Java互操作性. 现有的Java代码可以自然地从Kotlin调用,而Kotlin代码也可以从Java相当顺畅地使用.

Kotlin参考文档非常详细地介绍了从Kotlin调用Java从Java调用Kotlin .

这同样适用于与Groovy代码的互操作性. 另外,Kotlin DSL提供了几种选择Groovy语义的方法,我们将在后面讨论.

Static extensions

Groovy和Kotlin语言都支持通过Groovy Extension模块Kotlin扩展来扩展现有的类.

要从Groovy调用Kotlin扩展函数,请将其称为静态函数,并将接收器作为第一个参数传递:

例子21.从Groovy调用Kotlin扩展
build.gradle
TheTargetTypeKt.kotlinExtensionFunction(receiver, "parameters", 42, aReference)

Kotlin扩展功能是程序包级功能,您可以在Kotlin参考文档的" 程序包级功能"部分中学习如何查找声明给定Kotlin扩展名的类型的名称.

要从Kotlin调用Groovy扩展方法,可以使用相同的方法:将其作为静态函数调用,并将接收者作为第一个参数. 这是一个例子:

例子22.从Kotlin调用Groovy扩展
build.gradle.kts
TheTargetTypeGroovyExtension.groovyExtensionMethod(receiver, "parameters", 42, aReference)

Named parameters and default arguments

Groovy和Kotlin语言都支持命名函数参数和默认参数,尽管它们的实现方式非常不同. 如Kotlin语言参考中命名实参和默认实参所述,Kotlin对这两者均提供了完善的支持. Groovy基于Map<String, ?>参数以非类型安全的方式实现命名参数,这意味着它们不能与默认参数组合. 换句话说,对于任何给定的方法,您只能在Groovy中使用一个或另一个.

Calling Kotlin from Groovy

要调用从Groovy命名参数的Kotlin函数,只需使用带有位置参数的普通方法调用即可. 无法通过参数名称提供值.

要调用具有Groovy的默认参数的Kotlin函数,请始终传递所有函数参数的值.

Calling Groovy from Kotlin

要从Kotlin用命名参数调用Groovy函数,您需要传递Map<String, ?> ,如以下示例所示:

例子23.用来自Kotlin的命名参数调用Groovy函数
build.gradle.kts
groovyNamedArgumentTakingMethod(mapOf(
    "parameterName" to "value",
    "other" to 42,
    "and" to aReference))

要使用Kotlin的默认参数调用Groovy函数,请始终传递所有参数的值.

Groovy closures from Kotlin

有时您可能必须调用从Kotlin代码获取Closure参数的Groovy方法. 例如,一些用Groovy编写的第三方插件需要闭包参数.

用任何语言编写的Gradle插件都应该首选Action<T>类型代替闭包. Groovy闭包和Kotlin lambda会自动映射到该类型的参数.

为了提供一种在保留Kotlin强类型的同时构造闭包的方法,存在两种辅助方法:

  • closureOf<T> {}

  • delegateClosureOf<T> {}

这两种方法在不同的情况下都很有用,并且取决于您将Closure实例传递给的方法.

一些插件期望使用简单的闭包,例如Bintray插件:

例子24.使用closureOf<T> {}
build.gradle.kts
bintray {
    pkg(closureOf<PackageConfig> {
        // Config for the package here
    })
}

在其他情况下,例如在配置服务器场时使用Gretty插件 ,该插件需要委托关闭:

例子25.使用delegateClosureOf<T> {}
build.gradle.kts
dependencies {
    implementation("group:artifact:1.2.3") {
        artifact(delegateClosureOf<DependencyArtifact> {
            // configuration for the artifact
            name = "artifact-name"
        })
    }
}

通过查看源代码,有时没有一个很好的方法来告诉您使用哪个版本. 通常,如果您获得带有closureOf<T> {}NullPointerException ,则使用delegateClosureOf<T> {} closureOf<T> {} delegateClosureOf<T> {}将解决此问题.

这两个实用程序功能对于配置闭包很有用,但是某些插件可能期望Groovy闭包用于其他目的. 从KotlinClosure0KotlinClosure2类型,可以使Kotlin函数适应Groovy闭包,具有更大的灵活性.

例子26.使用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"
    }))
}

另请参阅groovy-interop示例.

The Kotlin DSL Groovy Builder

If some plugin makes heavy use of Groovy metaprogramming, then using it from Kotlin or Java or any statically-compiled language can be very cumbersome.

Kotlin DSL提供了withGroovyBuilder {}实用程序扩展,该扩展将Groovy元编程语义附加到Any类型的对象上. 以下示例演示了对象target上该方法的几个功能:

例子27. withGroovyBuilder {}使用
build.gradle.kts
target.withGroovyBuilder {                                          // (1)

    // GroovyObject methods available                               // (2)
    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, ?>进行方法调用

maven-plugin示例演示了withGroovyBuilder()实用程序扩展的用法,该扩展用于配置uploadArchives任务, 使用Gradle的核心Maven Plugin将其部署到具有自定义POM 的Maven存储库中 . 请注意,推荐的Maven Publish插件提供了类型安全且Kotlin友好的DSL,使您无需使用withGroovyBuilder()即可轻松地完成相同且更多的操作 .

Using a Groovy script

处理假定Groovy DSL构建脚本的问题插件时,另一种选择是在主要Kotlin DSL构建脚本中应用的Groovy DSL构建脚本中配置它们:

例子28.使用一个Groovy脚本
build.gradle.kts
plugins {
    id("dynamic-groovy-plugin") version "1.0"               (1)
}
apply(from = "dynamic-groovy-plugin-configuration.gradle")  (2)
dynamic-groovy-plugin-configuration.gradle
native {                                                    (3)
    dynamic {
        groovy as Usual
    }
}
  1. Kotlin构建脚本请求并应用插件

  2. Kotlin构建脚本应用Groovy脚本

  3. Groovy脚本使用动态Groovy来配置插件

Limitations

  • 众所周知, Kotlin DSL首次使用时比Groovy DSL慢 ,例如使用干净的结帐或临时连续集成代理时. 更改buildSrc目录中的内容也会产生影响,因为它会使build-script缓存无效. 主要原因是Kotlin DSL的脚本编译速度较慢.

  • 在IntelliJ IDEA中,您必须从Gradle模型导入项目,以获得内容帮助和对Kotlin DSL构建脚本的重构支持.

  • Kotlin DSL将不支持model {}块,该块是已停产的Gradle Software Model的一部分 . 但是,您可以从脚本中应用模型规则-有关更多信息,请参见模型规则示例.

  • 我们建议您不要启用按需配置的孵化功能,因为它会导致很难诊断的问题.

如果您遇到麻烦或发现可疑的错误,请在Gradle问题跟踪器中报告该问题.