更简洁的 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<T>
赋值 的请求是 GitHub 上投票最多的问题之一。由于 Kotlin 语言不支持重载赋值运算符,因此没有太多好的选择来改善这种情况。
由于冗长性,一些插件作者发明了自定义 setter。这些 setter 以许多不同的方式实现,进一步降低了用户体验。
我们不希望在使用惰性属性时强制使用更冗长的 DSL,也不希望不鼓励在所有插件中使用惰性属性。
简单赋值的解决方案 #
对于使用惰性属性类型(例如 Property
)的任务或扩展,Gradle 需要一种简单的方法来赋值。
从 Gradle 8.2 开始,现在可以使用 =
运算符为 Property
类型赋值,作为 Kotlin DSL 脚本中 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<T>)
方法
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 中添加了惰性属性赋值支持,作为 一项可选功能。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 构建工具的未来至关重要,我们非常重视。