[Kotlin翻译]迁移到 Gradle Kotlin DSL - Groovy Closure

286 阅读6分钟

本文由 简悦 SimpRead转码, 原文地址 jonnyzzz.com

Groovy Closures 和 Gradle Kotlin DSL

在将实际项目的 Gradle 构建从 Groovy 迁移到 Kotlin 的过程中,我收集了一些有用的建议、代码片段和解释。在本系列文章中,我们将一起学习如何更快、更轻松地转换为 Gradle Kotlin DSL。

刚接触 Gradle Kotlin DSL?请查看 第一篇文章,了解从 Groovy 迁移到 Kotlin 构建脚本的实用建议。在第二篇文章中,我们将介绍在 Gradle Kotlin DSL 脚本上设置 Kotlin 任务。第三篇文章介绍了我对 "buildSrc"、插件和扩展的更多发现。

在那篇文章中,我将分享我将 Groovy Closure 移植到 Gradle Kotlin DSL 的发现。在那篇文章中,你将学习如何一步步将使用 Groovy 闭包的 Gradle 脚本迁移到 Kotlin DSL。

依赖关系

在不同的 Gradle 项目中,我曾多次看到有人尝试在根项目的某个文件中列出所有依赖库,并将所有使用过的库的版本固定在一个地方,这样在依赖库中只使用一个库名,而其版本在整个项目中只写一次。这有助于避免在多项目的 Gradle 项目中出现意外的库版本冲突。例如,你的项目中有多少个不同版本的 OkHttpJackson?当然,同一依赖关系的另一个版本可能来自传递依赖关系,这是另一回事,我们不会在这篇博文中涉及。

我采用了类似的模式来列出根 Gradle 项目中的所有依赖关系。让我们看看我是如何将 Groovy 解决方案迁移到 Kotlin 的。

Groovy 脚本依赖关系

假设我们需要在一个子项目中添加对 OkHttp 库的依赖。我们希望在所有子项目中使用通用的库定义。我发现在根项目中为这样的定义创建一个 Groovy 闭包是很优雅的:

ext {
  dependency_okhttp = { Project project ->
     project.dependencies { 
        compile 'com.squareup.okhttp3:okhttp:3.12.1'
     }  
  }
}

不要问我为什么要在该函数/闭包中加入 Project 参数。从技术上讲,它是不需要的,但我懒得从代码中删除它。现在是时候把它删除了(并转换为 Kotlin DSL)!

同时,Groovy 中的用法也很简单明了:

ext.dependency_okhttp(project)

Gradle Kotlin DSL

要遵循 逐一迁移策略,我们需要从 Kotlin DSL 调用相同代码的方法。我找到了以下代码:

(project.extensions.extraProperties["dependency_okhttp"] as Closure<*>).call(project)

代码确实有问题。当我将 dependency_okhttp 属性定义迁移到 Kotlin 时,我将不得不修正它的每种用法,并将向 Closure<*> 的转换替换为,例如,向 Function1<Project,*> 或其他不同的转换。

将 ext 移至 buildSrc 和 Kotlin

在 Kotlin 中(也在 Groovy 中),所有扩展的更好位置是 buildSrc。在上一篇文章中,我们已经介绍过它。让我们将 dependency_okhttp 函数的定义移到 Kotlin 代码中,并将代码放在 buildSrc 文件夹下。为此,我创建了一个 Kotlin 文件,并在其中添加了以下函数:

package x.y.z
fun DependencyHandlerScope.dependency_okhttp() {
  "implementation"("com.squareup.okhttp3:okhttp:3.12.1")
}

现在,我们可以直接在 Gradle/Kotlin 的 dependencies{..} 块中使用该函数:

import x.y.z.*
dependencies {
  dependency_okhttp()
}

现在的用法简洁明了,非常不错。唯一的代价是 import语句。我们需要导入包,在包中声明所有的 build.gradle.kts 项目文件。我希望可以告诉 Gradle 在 Kotlin DSL 脚本执行上下文中隐含导入更多的包。

DependencyHandlerScope "类型是 "dependencies{}"函数后面的 lambda 的接收器类型。还有一个问题--我们不能在 buildSrc 代码中的 dependencies{} 块内使用 implementation。相反,我们可以使用 "implementation" 字符串。我正在寻找答案。implementation "函数似乎是由 Gradle 的 Kotlin 脚本运行时即时生成的,它并不包含在 buildSrc 评估环境中。

从 Groovy 调用 Kotlin buildSrc

首先,不要忘记使用 new 操作符在 Groovy 中创建对象。在使用 Kotlin 之后,这很容易被忘记。我为此花了十几分钟进行调试。

这时,我们已经在 Kotlin DSL 的 buildSrc 下声明了所有扩展函数 DependencyHandlerScope.dependency_okhttp 。让我们看看如何从 build.gradle 文件和 Groovy 中调用该函数。换句话说,在所有原始 gradle 脚本中重新实现较早的 Groovy ext 闭包是一项挑战。

我们的目标是从 Groovy 中调用 DependencyHandlerScope.dependency_okhttp 函数。我们只需要一个 DependencyHandlerScope 实例(该实例仅用于 Kotlin DSL)来调用我们的 Kotlin 扩展函数。DependencyHandlerScope类的companion object中包含of工厂函数,但它不是一个 static 函数,因此缺少@JvmStatic注解!

Kotlin 与 JVM 的互操作

全局函数(如 dependency_okhttp)会被 Kotlin/JVM 编译器编译成 FilenameKt 类的静态成员函数。当从 Groovy 或 Java 调用 Kotlin 函数时,我们需要 "import static "该类。这是个简短的解决方案,但我花了几十分钟才弄明白。

为了简化,我们可以在 Groovy 脚本中添加一个全局的 import static <package>.<KotlinFileName>Kt.*; ,然后调用我们所有的 Kotlin 全局函数,无需限定符。这同样适用于buildSrc下的dependency_okhttp()函数。

还有一点,dependency_okhttp() 函数是一个_扩展函数_。我们如何从 Groovy 中调用它呢?

在 JVM 字节码级别,Kotlin 扩展函数的第一个参数是接收器参数。我们可以从 Groovy 或 Java 中调用扩展函数,将接收器对象实例作为普通方法的第一个参数。

一般来说,从 "对象 "或 "伴随对象 "访问函数比较麻烦。使用以下语法从 Groovy 访问 Kotlin 声明:

  • 存在 @JvmStatic 注解 - 作为静态函数调用
  • Kotlin object - 使用TypeName.INSTANCE.functionName
  • Kotlin 同伴对象 - 使用TypeName.@Companion.functionName

你可以在 Kotlin Object 和 Companion Object 的 JVM 字节码 声明帖子中找到更多细节。老实说,我很高兴能在 Groovy 中编写 @Companion

从 Groovy 调用 Kotlin

在我的代码库中,"buildSrc "项目下的 "dependencies.kt "文件中声明了 "dependency_okhttp "函数。Groovy 将其视为 DependenciesKt 类的成员。我们使用 .@Companion. 技巧访问 Gradle 中 DependencyHandlerScope 类的 .of 函数。我们使用以下 Groovy 代码从 Groovy 中调用在 Kotlin 中声明的扩展函数:

import static x.y.z.DependenciesKt.*
ext {
  dependency_okhttp = { Project project ->
    dependency_okhttp(DependencyHandlerScope.@Companion.of(project.dependencies))
  }
}

这段代码是稳定的,如果我们修改了 buildSrc 下的代码,它也不太可能崩溃。Kotlin 版本很容易从 Kotlin DSL 中使用,而 Groovy 版本只在从 Gradle 迁移到 Kotlin 时才需要。我们已经在本系列的第一篇文章中介绍了迁移计划。

结论

在这篇文章中,我们了解了如何在 Kotlin 上处理 Groovy 闭包,以及如何从 ext 属性迁移到在 buildSrc 中声明的 Kotlin 扩展函数。

Kotlin作为一种静态类型编程语言,在编写Gradle构建脚本时似乎能发挥很好的作用。得益于静态类型推断,Kotlin 编译器能更早地检测到错误,并显示有用的编译错误信息和警告。集成开发环境和编译器都会使用类型信息来推断给定作用域中的可用函数和属性,即使是在带有接收器的第五层嵌套 lambda 中也是如此。

我将在接下来的文章中介绍更多内容,敬请期待!查看

评论由