更简单的 Kotlin DSL 属性赋值

目录

引言

使 Kotlin DSL 成为 新 Gradle 构建的默认设置 的改进之一是提供了一种更简单的 惰性属性 赋值方式。

在这篇博文中,我们将解释什么是惰性属性赋值,以及如何在最新的 Gradle 版本中利用新的语法。

什么是惰性属性 API? #

惰性属性 API 是 Gradle 的一项功能,它会延迟属性值的计算,直到需要时才进行。这是编写扩展和任务属性的推荐方式。

提供的好处包括:

  • 解决配置顺序问题(不再需要 `Project.afterEvaluate()`)
  • 自动跟踪依赖项(无需 `Task.dependsOn()`)
  • 提供标准化访问(setter 或 getter 中无自定义行为)
  • 减少属性声明的样板代码(无需字段和 setter,只需 getter)

要在自定义任务中使用惰性属性 API,您需要用属性类型替换基本类型。例如:

abstract class MyTask : DefaultTask() {
    @get:Input
    var myProperty: String? = null

    @TaskAction
    fun run() {
        // Skipped for brevity
    }
}

变成

abstract class MyTask : DefaultTask() {
    @get:Input
    abstract val myProperty: Property<String>

    @TaskAction
    fun run() {
        // Skipped for brevity
    }
}

麻烦来了! #

尽管惰性属性提供了多项好处,但它们可能会使使用 Kotlin DSL 配置属性变得有些冗长。

在 Groovy DSL 中,可以使用简单的 `=` 进行惰性属性赋值

tasks.register("taskName", MyTask) {
    myProperty = "Constant string value"
}

然而,在 Kotlin DSL 中,为属性赋值需要使用冗长的 **set(...)** 方法

tasks.register<MyTask>("taskName") {
    myProperty.set("Constant string value")
}

用户显然想要一种更具声明性的简单语法。对 简洁且静态类型的 `Property` 赋值 的请求是 GitHub 上投票最多的问题之一。由于 Kotlin 语言不支持重载赋值运算符,因此改进这种情况的选择并不多。

由于冗长,一些插件作者发明了自定义 setter。这些 setter 以多种不同方式实现,进一步降低了用户体验。

我们不希望在使用惰性属性时强制使用更冗长的 DSL,也不希望阻止在所有插件中使用惰性属性。

一个简单赋值的解决方案 #

对于使用惰性属性类型(例如 `Property`)的任务或扩展,Gradle 需要一种简单的方式来赋值。

从 Gradle 8.2 开始,在 Kotlin DSL 脚本中,可以使用 `=` 运算符为 `Property` 类型赋值,作为 `set(T)` 方法的直接替代方案。

冗长的方式

tasks.register<MyTask>("taskName") {
    // Using the set() method call
    myProperty.set("Constant string value")
}

变成

tasks.register<MyTask>("taskName") {
    // Using lazy property assignment
    myProperty = "Constant string value"
}

此语法还支持赋值惰性值,例如 `set(Provider)` 方法

tasks.register<MyTask>("taskName") {
    // otherTask.map() returns lazy value `Provider<T>`
    myProperty = otherTask.map { it.otherProperty }
}

Kotlin DSL 现在看起来好多了。此外,这使得从 Groovy DSL 迁移到 Kotlin DSL 更加容易,因为它减少了两种 DSL 之间的不一致性。

惰性属性赋值适用于类型为 `Property` 或 `ConfigurableFileCollection` 的仅 getter 属性。我们 建议插件作者为这些类型的属性实现自定义 setter。

与 JetBrains 的合作成果 #

如果没有与 Kotlin 团队的密切合作,实现惰性属性赋值是不可能的。

我们共同实现了一个 Kotlin 编译器插件,该插件提供了一个选项来重载用于 final 属性的 `=` 赋值运算符。这与 Kotlin 语言支持的 其他运算符(如 `+=`)类似。由于编写此类插件需要修改 Kotlin 编译器,因此我们直到现在才能实现此更改。

在内部,Gradle 使用新的编译器插件,并为惰性类型提供重载 `=` 运算符的方法。

fun <T> Property<T>.assign(value: T?) {
    this.set(value)
}

由于该功能是作为 Kotlin 编译器插件编写的,我们还获得了 Kotlin 语言的所有优势,包括静态类型、可发现性和出色的 IDE 集成。

我们也可以自豪地说,这是第一个外部贡献的 Kotlin 语言功能!新插件直接随 Kotlin 一起发布。

感谢 JetBrains 团队在此功能上的支持与合作。

在 Gradle 8.2 中试用 #

Gradle 在 Gradle 8.1 中添加了惰性属性赋值支持,作为一项 opt-in 功能。Gradle 8.2 默认启用了此功能,因此您可以在 **\*.gradle.kts** 脚本中轻松使用它。

需要注意的是,该功能仍被标记为孵化状态。该功能可能会发生更改,尽管不太可能。我们不建议在稳定标记之前(可能在 Gradle 8.3 中)在已发布的插件中使用此功能。

惰性属性赋值受 IntelliJ 2022.3(推荐 2023.1.1 或更高版本)和 Android Studio Giraffe(或更高版本)支持。

IDE 集成存在三个已知问题:

KT-56941 和 KT-56221 已在 IntelliJ 2023.1.1 中修复。

KTIJ-24390 将在未来的版本中修复。

结论 #

惰性属性赋值

  • 降低了 Kotlin DSL 的冗长性
  • 标准化了任务和扩展属性的访问
  • 简化了惰性属性 API 的使用
  • 帮助用户从 Groovy 迁移到 Kotlin

如果您想了解更多关于 Kotlin 惰性属性赋值的信息,请查看 Gradle 的 Kotlin DSL 入门指南

如果您正在将现有项目从 Groovy DSL 迁移到 Kotlin DSL,请务必阅读 迁移指南,并考虑使用惰性属性 API。

加入 社区聊天(#kotlin-dsl 频道)和 论坛 来提问、分享经验并为 Kotlin DSL 的开发做出贡献。您的反馈和贡献对于塑造 Gradle 构建工具的未来至关重要,我们非常重视。

讨论