[Kotlin翻译]迁移到 Gradle Kotlin DSL - 基础

198 阅读10分钟

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

迁移的第一步

学习一门新编程语言的唯一方法就是用它编写程序

你是否也用同样的原则来学习一门新的编程语言?我是这样做的,而且我经常在我的演讲中引用这句话,鼓励人们通过练习或用Kotlin编程语言编写程序来学习Kotlin编程语言。下面我们就来看看如何运用同样的策略来学习和练习Gradle Kotlin DSL

对于我们来说,"Hello World"项目还不够复杂。相反,我决定将现有的 Gradle 项目从 Groovy 转换为 Kotlin。我找到的项目是一个用 Kotlin 编写的服务器端 JVM 应用程序。它有 16 个 Gradle 子项目,涵盖了足够多的现实生活中的非难边缘案例,因此相当有趣。

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

迁移计划

将一个大型 Gradle 项目转换为 Kotlin 时,该从何入手呢?首先,我不建议一次性转换整个项目。你很可能会被完全损坏的脚本卡在中间某个地方。这可能会耗费你太多时间去修复它们,以完成迁移。对你来说,这么快就学会这么多不同的 Gradle Kotlin DSL 功能可能也太难了,而且由于项目已损坏,你可能无法测试你的更改。

我建议将转换分成一系列小的增量变更。我们必须逐一检查并更新所有构建脚本文件。让我们从最小的项目文件开始,学习基本功能,然后慢慢过渡到最复杂的构建脚本文件。

测试编译脚本是否在任何小改动后被破坏是非常重要的。你可以运行任何任务来执行 Gradle configuration 阶段,这很可能会发现代码中的错误。为此,我更喜欢在根项目上运行 testClasses 任务。

对于较大的 Gradle 项目文件,甚至建议将其转换为较小的部分。Gradle 支持在同一个项目中混合使用 Kotlin 和 Groovy 脚本,因此 apply(from="file.gradle") 函数可以包含任何一种语言编写的脚本。

是时候开始迁移了!

迁移的第一步

让我们从最小的 Gradle 子项目开始迁移。对于每个项目,我们都需要将 build.gradle 项目文件重命名为 build.gradle.kts。不出意外,结果将是行不通的。文件会有很多错误。让我们先修复最常见的错误:

首先,我们需要用双引号替换单引号。Groovy 支持字符串的两个引号,而 Kotlin 只支持字符串的 "。在IntelliJ IDEA中,您可以使用 Convert long character literal to string(将长字符转换为字符串)快速解决方法、多重引号或标准的搜索和替换对话框。

字符串模板,如 "$foo.bar.baz" 在 Groovy 和 Kotlin 中的工作方式不同。在 Kotlin 中,调用方法或属性时需要在 $ 后加上大括号,例如 "${foo.bar.baz}"。Groovy 不支持单引号字符串模板,因此您可能也需要将 $ 符号转义为 \$

我们用 listOf 函数替换 Groovy 列表,例如 ["foo", "bar"] 被转换为 listOf("foo", "bar")

至此,我们的编译文件中的错误应该会少很多。让我们看看其他 Gradle 对象在 Kotlin 中是如何表示的。

资源库

Kotlin DSL 中的 "repositories{...}"块具有与知名版本库相同的功能,例如 "mavenCentral()"。您可能需要知道如何像下面这样转换自定义的 Maven 资源库定义:

repositories {
  maven { url 'https://dl.bintray.com/palantir/releases' }
}

Kotlin DSL 中也定义了 maven{..} 函数。生成器具有 url 属性,但其类型是 java.net.URI 而不是 String。我不喜欢在 Gradle 脚本中手动创建 URI 类实例。有一个重载函数叫做 maven(),它接收一个名为 urlString 参数,用于表示 URL。因此,Kotlin 代码看起来像这样:

repositories {
  maven(url = "https://dl.bintray.com/palantir/releases")
}

依赖关系

例如,我们在 Groovy 中定义了一个依赖关系:

dependencies {
  implementation 'this.library:name:1.0.0'
}

在 Kotlin DSL 中,dependencies{...} 块有所不同。我们需要在配置名称后添加额外的括号,并为字符串添加双引号:

dependencies {
  implementation("this.library:name:1.0.0")
}

我建议使用multiple cursorsIntelliJ IDEA中同时修复所有依赖项。

Kotlin DSL 为已注册的项目配置名称提供了生成的辅助函数,包括 compiletestCompileapitestImplementation。我遇到过几种情况,脚本中可能没有生成的辅助函数。Gradle 不包含 这些辅助函数到你用 apply(from="some-file.gradle.kts") 函数包含的脚本文件中。您也不能将这些函数添加到buildSrc项目中。别担心,我有几种解决方法。

第一种变通方法是使用配置名称作为字符串,例如

dependencies {
  "implementation"("this.library:name:1.0.0")
}

更好的方法是通过委托属性来引用配置,下面的声明解决了这个问题:

val implementation by configurations
dependencies {
  implementation("this.library:name:1.0.0")
}

如果配置不存在,代码就会失败。最后一种解决方法是用相同的名称和值声明 String 变量,例如 val implementation = "implementation"。现在,代码也可以在 build.gradle.kts 文件中使用了。

Gradle 插件

Gradle 中的 plugins{..} 块与 Kotlin 中的类似。您应该在代码中的插件 ID 周围添加括号,例如

plugins {
  id("plugin.id") version "1.2.4"
}

Gradle 源中的 org.gradle.plugin.use.PluginDependenciesSpec 类中的 Javadoc 注释会误导 Kotlin DSL 用户。它可能是为 Groovy DSL 编写的,并已被重新使用。你可以使用上面代码段中的代码。

将 Gradle 插件应用于子项目

有两种方法可以将 Gradle 插件应用到子工程。第一种是在构建脚本文件中添加 apply(plugin = "plugin.id") 调用。在 subprojects{..} 块中使用该函数为子工程启用插件。

应用插件的第二种语法是使用较短版本的 plugins{..} 块,例如

plugins {
  id("plugin.id")
}

subprojects{..}allproject{..} 块中不允许使用此语法。

第三个选项是调用 apply<PluginClassName>() 函数,其中 PluginClassName 是插件主类的类名。apply<>()函数对临时插件很有效。对于外部插件,我不喜欢这种方法,因为插件类名(与插件 ID 不同)是插件实现细节的一部分,插件作者将来可能会更改。

我发现了一个奇怪的副作用,它是由 Gradle Kotlin DSL 引擎的辅助函数为应用的插件生成的。帮助声明可能不可见,这取决于你应用插件的方式。

Kotlin Multiplatform插件(例如 1.3.21)会将 kotlin{..} 函数注册到应用它的项目中。让我们考虑两个子项目定义,其中一个不起作用:

project-a/build.gradle.kts文件中的 项目A

apply(plugin = "org.jetbrains.kotlin.multiplatform")

kotlin { }

Project Bproject-b/build.gradle.kts 文件中:

plugins { 
  id("org.jetbrains.kotlin.multiplatform") 
}

kotlin { }

Project B 可以正常工作,kotlin{...}块也已解析,但Project A 由于未解析kotlin函数而无法工作。

我们应该使用 plugins{..} 块在 Gradle 中生成访问器。

配置任务

任务配置在 Groovy 中很容易完成,对于众所周知的任务,可以使用任务名称作为快捷语法。Application Gradle 插件中的 run 任务可以在 Groovy 中简单配置为

run {
  args = "--foo", "bar
}

同样的方法在 Kotlin 中不起作用。首先,我们需要从 tasks 容器中找到任务。然后,需要显式任务类型来设置任务参数。

tasks 对象中有很多函数可以定义或查找任务。我使用 reified generic 函数 tasks.getByName<T>(name: String, action: T.() -> Unit)来查找任务,并通过相同的调用对其进行配置。

任务的类型是什么?您可以查看插件文档、插件源代码,或者在 Groovy 脚本中添加类似 println(tasks.task_name) 的调试行来查找。

由于 Gradle Application 插件中的 run 任务的类型是 JavaExec,我们可以使用下面的 Kotlin 代码来设置它:

tasks.getByName<JavaExec>("run") {
  args = listOf("--foo", "bar)
}

我们应该使用 "plugins{...}"块语法来使插件拥有所有生成的可用访问器。因此,我们可以使用以下代码:

plugins {
  application
}

application {
  mainClassName = "org.jonnyzzz.example.MainKt`
}

(tasks.run) {
  args = listOf("--foo", "bar)
}

在示例中,Kotlin run 函数与生成的 run 任务访问器的 invoke 重载操作符之间存在冲突。为了解决这个问题,我添加了括号。其他任务不需要这种变通方法,因为在这些任务中不会出现这种名称冲突。

故障排除

编译脚本时很容易卡住。我们可能不清楚为什么有些东西不起作用。我有几个有用的小窍门可以解决这个复杂的问题。

尝试在更改中找到问题的根源。一个文件中的微小改动可能会导致另一个文件出错。使用版本控制或本地历史检查最近的更改,尝试还原它们,看看是否有帮助。

在 Gradle 文档中搜索关键字或任务名称。有几个 Gradle Kotlin DSL 的链接,我经常用它们来找到解决问题的有用提示:

阅读 Gradle 或您使用的第三方插件的源代码。最简单的方法是从IntelliJ IDEA导航到有问题的声明,然后直接找到 Gradle 或插件的源代码。我通过检查源代码解决了一些问题。希望对你也有帮助。

除此之外,你还可以在 GitHub 上找到 Gradle 的源代码。大多数 Gradle 插件的源代码都发布在 GitHub 或其他地方。

尝试调试 Gradle。最后一个也是最难的一个小技巧就是解决脚本问题。老式的调试技术 "println() "语句在这里通常非常有效,它可以打印类型名称或任务名称,以帮助浏览源代码和 Javadoc 注释。是否需要多次运行才能调试出问题的根本原因?JVM 调试器可能是更快的选择。IntelliJ IDEA 支持 Gradle 调试,点击任务上的 Debug ... 操作即可。

IntelliJ项目中的Gradle源代码

Gradle Wrapper 默认不下载 Gradle 源代码。你可能会在IntelliJ IDEA中注意到一个黄色警告,建议你也修改一下。请确保项目中的 gradle/wrapper/gradle-wrapper.properties 文件的下载 URL 后缀为 -all.zip

distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip

Gradle wrapper 任务有一个选项,默认情况下首选完整的软件包。你可以对其进行配置,以确保下次 Gradle 升级时不会意外切换到默认包。下面的 Kotlin 代码在根 Gradle 项目中进行了设置:

tasks.wrapper {
    distributionType = Wrapper.DistributionType.ALL
}

结论

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

在这篇文章中,我们介绍了迁移到 Kotlin 的第一步。我们定义了迁移策略,并列出了快速从 Groovy 迁移到 Kotlin 的一系列建议。我将在接下来的文章中介绍更多内容,敬请期待!

感谢 Hadi、Paul 和 David 的帮助、时间和反馈!

查看

评论由