以下为:https://kotlinlang.org/docs/whatsnew20.html 文章译文

Kotlin 2.0.0 版本已经发布,新的Kotlin K2编译器是稳定的!此外,以下是一些其他亮点:

  • 新的Compose编译器Gradle插件
  • 使用invokedynamic生成lambda函数
  • kotlinx-metadata-jvm库现在很稳定
  • 在苹果平台上使用路标监控Kotlin/Native中的GC性能
  • 使用Objective-C方法解决Kotlin/Native中的冲突
  • 支持Kotlin/Wasm中的命名导出
  • 在Kotlin/Wasm中使用@JsExport的函数中支持无符号原始类型
  • 默认使用Binaryen优化生产构建
  • 用于多平台项目中编译器选项的新Gradle DSL
  • 稳定替换枚举类值通用函数
  • 稳定的AutoCloseable接口

IDE 支持

支持Kotlin 2.0.0的Kotlin插件捆绑在最新的IntelliJ IDEA和Android Studio中。您无需在IDE中更新Kotlin插件。您只需在构建脚本中将Kotlin版本更改为Kotlin 2.0.0。

  • 有关IntelliJ IDEA支持Kotlin K2编译器的详细信息,请参阅IDE中的支持。
  • 有关IntelliJ IDEA支持Kotlin的更多详细信息,请参阅Kotlin版本。

Kotlin K2编译器

通往K2编译器的道路是漫长的,但现在JetBrains团队已准备好宣布其稳定。在Kotlin 2.0.0中,默认使用新的Kotlin K2编译器,它对所有目标平台都是稳定的:JVM、Native、Wasm和JS。新的编译器带来了重大的性能改进,加快了新语言功能的开发,统一了Kotlin支持的所有平台,并为多平台项目提供了更好的架构。

JetBrains团队通过从选定的用户和内部项目中成功编译1000万行代码,确保了新编译器的质量。18,000名开发人员和80,000个项目参与了稳定过程,在他们的项目中尝试了新的K2编译器,并报告了他们发现的任何问题。

为了帮助使迁移到新编译器的过程尽可能顺利,我们创建了一个K2编译器迁移指南。本指南解释了编译器的许多好处,突出了您可能遇到的任何更改,并描述了如何在必要时回滚到以前的版本。

我们在一篇博客文章中探讨了K2编译器在不同项目中的性能。如果您想查看有关K2编译器如何运行的真实数据,并找到有关如何从您自己的项目中收集性能基准的说明,请查看它。

当前的K2编译器限制

在Gradle项目中启用K2具有某些限制,在以下情况下,这些限制可能会影响使用低于8.3的Gradle版本的项目:

  • buildSrc 编译源代码。
  • 编译包含的构建中的Gradle插件。
  • 编译其他Gradle插件,如果它们用于Gradle版本低于8.3的项目。
  • 构建Gradle插件依赖项。

如果您遇到上述任何问题,您可以采取以下步骤来解决它们:

  • 设置 buildSrc 、任何Gradle插件及其依赖项的语言版本:
kotlin {
    compilerOptions {
        languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
        apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
    }
}

如果您为特定任务配置语言和API版本,这些值将覆盖 compilerOptions 扩展设置的值。在这种情况下,语言和API版本不应高于1.9。

  • 将项目中的Gradle版本更新到8.3或更高版本。

智能转换改进

在特定情况下,Kotlin编译器可以自动将对象转换为类型,从而为您节省了自己显式转换对象的麻烦。这被称为智能铸造。Kotlin K2编译器现在在比以前更多的场景中执行智能转换。

在Kotlin 2.0.0中,我们在以下领域对智能转换进行了改进:

  • 局部变量和进一步的范围
  • 使用逻辑或运算符进行类型检查
  • 内联功能
  • 具有函数类型的属性
  • 异常处理
  • 增量和减量运营商

局部变量和进一步的范围

以前,如果变量在 if 条件内被评估为非 null ,则该变量将是智能转换的。然后,有关该变量的信息将在 if 代码块的范围内进一步共享。

但是,如果您在 if 条件 外部 声明了变量,则在 if 条件中将没有关于该变量的信息,因此它不能是智能转换的。在 when 表达式和 while 循环中也可以看到这种行为。

从Kotlin 2.0.0开始,如果您在 ifwhenwhile 条件下使用变量之前声明变量,那么编译器收集的有关变量的任何信息都可以在相应的块中访问,以便进行智能转换。

当您想将布尔条件提取到变量中时,这可能很有用。然后,您可以给变量一个有意义的名称,这将提高您的代码可读性,并有可能在稍后的代码中重用该变量。例如:

class Cat {
    fun purr() {
        println("Purr purr")
    }
}

fun petAnimal(animal: Any) {
    val isCat = animal is Cat
    if (isCat) {
        // 在 Kotlin 2.0.0 中,编译器可以访问有关 Cat 的信息,因此它知道animal被智能转换为 Cat 类型。
        // 因此,可以调用 purr() 函数。
        // 在 Kotlin 1.9.20 中,编译器不知道智能强制转换,因此调用 purr() 函数会出错。
        animal.purr()
    }
}

fun main() {
    val kitty = Cat()
    petAnimal(kitty)
    // Purr purr
}

使用逻辑或运算符进行类型检查

在Kotlin 2.0.0中,如果您将对象的类型检查与 or 运算符( || )相结合,则智能转换到它们最接近的通用超类型。在这一变化之前,智能演员总是对 Any 类型进行。

在这种情况下,您仍然需要手动检查对象类型,然后才能访问其任何属性或调用其函数。例如:

interface Status {
    fun signal() {}
}

interface Ok : Status
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {
    if (signalStatus is Postponed || signalStatus is Declined) {
        // signalStatus是智能投射到常见的超类型状态
        signalStatus.signal()
        // 在Kotlin 2.0.0之前,signalStatus被智能强制转换为Any类型,因此调用signal()函数会触发一个Unresolved引用错误。
		// signal()函数只能在另一次类型检查后才能成功调用:
        // check(signalStatus is Status)
        // signalStatus.signal()
    }
}

常见的超型是并集类型的近似值。Kotlin不支持联合类型。

内联方法

在Kotlin 2.0.0中,K2编译器以不同的方式处理内联函数,允许它与其他编译器分析相结合,确定智能转换是否安全。

具体来说,内联函数现在被视为具有隐式 callsInPlace 合同。这意味着传递给内联函数的任何lambda函数都会被调用。由于lambda函数是就地调用的,编译器知道lambda函数不能泄露对其函数主体中包含的任何变量的引用。

编译器使用这些知识以及其他编译器分析来决定智能转换任何捕获的变量是否安全。例如:

interface Processor {
    fun process()
}

inline fun inlineAction(f: () -> Unit) = f()

fun nextProcessor(): Processor? = null

fun runProcessor(): Processor? {
    var processor: Processor? = null
    inlineAction {
        // 在Kotlin 2.0.0中,编译器知道processor是一个局部变量,而inlineAction()是一个内联函数,
        // 所以对处理器的引用不能泄露。因此,它是安全的
        if (processor != null) {
            // 编译器知道处理器不是空的,所以不需要安全调用
            processor.process()

            // 在Kotlin 1.9.20中,你必须执行一个安全调用:
            // processor?.process()
        }

        processor = nextProcessor()
    }

    return processor
}

具有函数类型的属性

在以前版本的Kotlin中,有一个错误,这意味着具有函数类型的类属性不是智能转换的。我们在Kotlin 2.0.0和K2编译器中修复了此行为。例如:

class Holder(val provider: (() -> Unit)?) {
    fun process() {
        // 在Kotlin 2.0.0中,如果provider不为空,那么provider将被智能强制转换
        if (provider != null) {
            // 编译器知道provider不为空
            provider()

            // 在1.9.20中,编译器不知道provider不是null,所以它触发了一个错误:
            // Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
        }
    }
}

如果您重载 invoke 运算符,此更改也适用。例如:

interface Provider {
    operator fun invoke()
}

interface Processor : () -> String

class Holder(val provider: Provider?, val processor: Processor?) {
    fun process() {
        if (provider != null) {
            provider()
            // 在1.9.20中,编译器触发了一个错误:
            // Reference has a nullable type 'Provider?' use explicit '?.invoke()' to make a function-like call instead
        }
    }
}

异常处理

在Kotlin 2.0.0中,我们改进了异常处理,以便智能转换信息可以传递到 catchfinally 代码块。此更改使您的代码更安全,因为编译器会跟踪您的对象是否具有可空的类型。例如:

fun testString() {
    var stringInput: String? = null
    // stringInput是智能转换到String类型
    stringInput = ""
    try {
        // 编译器知道stringInput不是空的
        println(stringInput.length)
        // 0

        // 编译器拒绝stringInput之前的智能强制转换信息。现在stringInput有String?类型。
        stringInput = null

        // 触发异常
        if (2 > 1) throw Exception()
        stringInput = ""
    } catch (exception: Exception) {
        // 在Kotlin 2.0.0中,编译器知道stringInput可以为空,因此stringInput保持为空。
        println(stringInput?.length)
        // null

        // 在Kotlin 1.9.20中,编译器说不需要安全调用,但这是不正确的。
    }
}

increment 和 decrement 操作符

在Kotlin 2.0.0之前,编译器不明白在使用增量或递减运算符后,对象的类型可以更改。由于编译器无法准确跟踪对象类型,您的代码可能会导致未解决的引用错误。在Kotlin 2.0.0中,这已被修复:

interface Rho {
    operator fun inc(): Sigma = TODO()
}

interface Sigma : Rho {
    fun sigma() = Unit
}

interface Tau {
    fun tau() = Unit
}

fun main(input: Rho) {
    var unknownObject: Rho = input

    // 检查未知对象是否从Tau接口继承
    if (unknownObject is Tau) {

        // 使用来自接口 Rho 的重载 inc() 运算符,
        // 智能将未知对象的类型投射到 Sigma。
        ++unknownObject

        // 在Kotlin 2.0.0中,编译器知道 unknownObject 具有 Sigma 类型,因此可以成功调用 sigma() 函数。
        unknownObject.sigma()

        // 在Kotlin 1.9.20中,编译器认为 unknownObject 的类型是 Tau,因此不允许调用 sigma() 函数。
        
        // 在Kotlin 2.0.0中,编译器知道 unknownObject 的类型是 Sigma,因此不允许调用 tau() 函数。
        unknownObject.tau()
        // 未解决的引用 'tau'

        // 在Kotlin 1.9.20中,编译器错误地认为 unknownObject 的类型是 Tau, tau() 函数可以成功调用。
    }
}

Kotlin多平台改进

在Kotlin 2.0.0中,我们在以下领域对与Kotlin Multiplatform相关的K2编译器进行了改进:

  • 编译过程中 公共平台源 的分离
  • 预期和实际申报的不同可见性级别

编译过程中公共和平台源的分离

以前,Kotlin编译器的设计使其无法在编译时将公共源集和平台源集分开。因此,公共代码可以访问平台代码,这导致平台之间的不同行为。此外,一些编译器设置和常见代码的依赖项用于泄漏到平台代码中。

在Kotlin 2.0.0中,我们对新Kotlin K2编译器的实现包括重新设计编译方案,以确保在公共源集和平台源集之间严格分离。当您使用预期和实际功能时,这种变化最为明显。以前,通用代码中的函数调用可以解析为平台代码中的函数。例如:

通用代码

fun foo(x: Any) = println("common foo")

fun exampleFunction() {
    foo(42)
}

平台代码

// JVM
fun foo(x: Int) = println("platform foo")

// JavaScript
// JavaScript平台上没有 foo() 函数重载

在本例中,根据其运行平台的不同,通用代码具有不同的行为:

  • 在JVM平台上,在通用代码中调用 foo() 函数会导致平台代码中的 foo() 函数被调用为平台 foo
  • 在JavaScript平台上,在公共代码中调用foo()函数会导致公共代码中的 foo() 函数被调用为 公共foo,因为平台代码中没有此类函数。

在Kotlin 2.0.0中,公共代码无法访问平台代码,因此两个平台都成功将 foo() 函数解析为公共代码中的 foo() 函数:common foo

除了提高跨平台行为的一致性外,我们还努力修复IntelliJ IDEA或Android Studio与编译器之间行为冲突的情况。例如,当您使用 expected 和 actual 类时,会发生以下情况:

通用代码

expect class Identity {
    fun confirmIdentity(): String
}

fun common() {
    // 在2.0.0之前,它会触发一个IDE-only错误
    Identity().confirmIdentity()
    // RESOLUTION_TO_CLASSIFIER : Expected class
    // Identity 没有默认构造函数。
}

平台代码

actual class Identity {
    actual fun confirmIdentity() = "expect class fun: jvm"
}

在本例中,预期的类 Identity 没有默认构造函数,因此无法在通用代码中成功调用它。以前,IDE只报告错误,但代码仍然在JVM上成功编译。然而,现在编译器正确地报告了一个错误:

Expected class 'expect class Identity : Any' does not have default constructor

当解决行为没有改变时

我们仍在迁移到新的编译方案中,因此当您调用不在同一源集内的函数时,解析行为仍然相同。当您在通用代码中使用来自多平台库的重载时,您会注意到这种差异。

假设你有一个库,它有两个具有不同签名的 whichFun() 函数:

// Example library

// MODULE: common
fun whichFun(x: Any) = println("common function")

// MODULE: JVM
fun whichFun(x: Int) = println("platform function")

如果您在通用代码中调用 whichFun() 函数,则库中具有最相关参数类型的函数将被解析:

// 一个使用JVM目标示例库的项目

// MODULE: common
fun main() {
    whichFun(2)
    // platform function
}

相比之下,如果您在同一源集中声明 whichFun() 的重载,则公共代码中的函数将得到解决,因为您的代码无法访问特定于平台的版本:

// 没有使用示例库

// MODULE: common
fun whichFun(x: Any) = println("common function")

fun main() {
    whichFun(2)
    // 通用方法
}

// MODULE: JVM
fun whichFun(x: Int) = println("platform function")

与多平台库类似,由于 commonTest 模块位于一个单独的源集中,它仍然可以访问特定于平台的代码。因此,在 commonTest 模块中解析对函数的调用表现出与旧编译方案相同的行为。

将来,这些剩余的情况将与新的编译方案更加一致。

预期和实际申报的不同可见性级别

在Kotlin 2.0.0之前,如果您在Kotlin多平台项目中使用预期和实际声明,它们必须具有相同的可见性级别。Kotlin 2.0.0现在也支持不同的可见性级别,但前提是实际声明比预期声明更宽松。例如:

expect internal class Attribute // 可见性是 internal
actual class Attribute          // 默认可见级别是 public
                                // 哪个更宽松

同样,如果您在实际声明中使用类型别名,则基础类型的可见性应与预期的声明相同或更宽松。例如:

expect internal class Attribute                 // 可见性是 internal
internal actual typealias Attribute = Expanded

class Expanded                                  // 默认情况下,可见性是 public
                                                // 哪个更宽松

编译插件支持

目前,Kotlin K2编译器支持以下Kotlin编译器插件:

此外,Kotlin K2编译器支持:

如果您使用任何其他编译器插件,请查看其文档,看看它们是否与K2兼容。

实验性Kotlin Power-assert编译器插件

Kotlin Power-assert插件是实验性的。它可以随时发生改变。

Kotlin 2.0.0引入了一个实验性的Power-assert编译器插件。该插件通过在故障消息中包含上下文信息来改善编写测试的体验,使调试更简单、更高效。

开发人员通常需要使用复杂的断言库来编写有效的测试。Power-assert插件通过自动生成包含断言表达式中间值的失败消息来简化此过程。这有助于开发人员快速了解测试失败的原因。

当断言在测试中失败时,改进的错误消息会显示断言中所有变量和子表达式的值,明确条件的哪一部分导致了失败。这对于检查多个条件的复杂断言特别有用。

要在项目中启用插件,请在 build.gradle(.kts) 文件中配置它:

Kotlin

plugins {
    kotlin("multiplatform") version "2.0.0"
    kotlin("plugin.power-assert") version "2.0.0"
}

powerAssert {
    functions = listOf("kotlin.assert", "kotlin.test.assertTrue")
}

Groovy

plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '2.0.0'
    id 'org.jetbrains.kotlin.plugin.power-assert' version '2.0.0'
}

powerAssert {
    functions = ["kotlin.assert", "kotlin.test.assertTrue"]
}

在文档中了解有关 Kotlin Power-assert 插件的更多信息。

How to enable the Kotlin K2 compiler

从Kotlin 2.0.0开始,Kotlin K2编译器默认启用。无需采取其他行动。

在Kotlin Playground中尝试Kotlin K2编译器

Kotlin Playground支持2.0.0版本。看看吧

IDE中的支持

默认情况下,IntelliJ IDEA和Android Studio仍然使用以前的编译器进行代码分析、代码完成、高亮显示和其他IDE相关功能。要在IDE中获得完整的Kotlin 2.0体验,请启用K2 Kotlin模式。

在您的IDE中,转到 Settings | Languages & Frameworks | Kotlin,然后选择 Enable the K2-based Kotlin plugin 选项。IDE将使用其K2 Kotlin模式分析您的代码。

K2 Kotlin模式为Alpha,从2024.1开始可用。代码高亮显示和代码完成的性能和稳定性已显著提高,但尚未支持所有IDE功能。

启用K2模式后,您可能会注意到由于编译器行为的变化而导致IDE分析的差异。在我们的迁移指南中了解新的K2编译器与之前的编译器有何不同。

  • 我们的博客中了解有关K2
    Kotlin模式的更多信息。
  • 我们正在积极收集有关K2 Kotlin模式的反馈。请在我们的公共Slack频道上分享您的想法。

留下您对新K2编译器的反馈

如果您有任何反馈,我们将不胜感激!

Kotlin/JVM

此版本带来了以下变化:

  • 使用invokedynamic生成lambda函数
  • kotlinx-metadata-jvm库现在很稳定

使用invokedynamic生成lambda函数

Kotlin 2.0.0引入了一种新的默认方法,用于使用 invokedynamic 生成lambda函数。与传统的匿名类生成相比,这一变化减少了应用程序的二进制大小。

自第一个版本以来,Kotlin将lambdas生成为匿名类。然而,从Kotlin 1.5.0开始,通过使用 -Xlambdas=indy 编译器选项,可以使用invokedynamic 生成选项。在Kotlin 2.0.0中,invokedynamic 已成为lambda生成的默认方法。这种方法产生更轻的二进制文件,并使Kotlin与JVM优化保持一致,确保应用程序从JVM性能的持续和未来改进中受益。

目前,与普通lambda编译相比,它有三个局限性:

  • 编译成 invokedynamic 的lambda是不可序列化的。
  • Experimental reflect() API不支持 invokedynamic 生成的lambdas。
  • 在这样的lambda上调用.toString()会产生一个不太可读的字符串表示:
fun main() {
    println({})

    // 使用Kotlin 1.9.24和反射,返回
    // () -> kotlin.Unit

    // 使用Kotlin 2.0.0,返回
    // FileKt$$Lambda$13/0x00007f88a0004608@506e1b77
}

要保留生成lambda函数的遗留行为,您可以:

  • @JvmSerializableLambda 注释特定的lambda。
  • 使用编译器选项 -Xlambdas=class 使用遗留方法生成模块中的所有lambdas。

kotlinx-metadata-jvm库是稳定的

在Kotlin 2.0.0中,kotlinx-metadata-jvm 库变得稳定。现在库已更改为kotlin包和坐标,您可以将其作为 kotlin-metadata-jvm(不含“x”)找到它。

以前,kotlinx-metadata-jvm 库有自己的发布方案和版本。现在,我们将构建和发布 kotlin-metadata-jvm 更新,作为Kotlin发布周期的一部分,并具有与Kotlin标准库相同的向后兼容性保证。

kotlin-metadata-jvm 库提供了一个API来读取和修改Kotlin/JVM编译器生成的二进制文件的元数据。

Kotlin/Native

此版本带来了以下变化:

  • 使用路标监控GC性能
  • 解决Objective-C方法的冲突
  • 更改了Kotlin/Native中编译器参数的日志级别
  • 明确向Kotlin/Native添加了标准库和平台依赖项
  • Gradle配置缓存中的任务错误

使用苹果平台上的路标监控GC性能

以前,只能通过查看日志来监控Kotlin/Native的垃圾收集器(GC)的性能。然而,这些日志没有与Xcode Instruments集成,Xcode Instruments是一个用于调查iOS应用程序性能问题的流行工具包。

自Kotlin 2.0.0以来,GC报告与仪器中可用的路标一起暂停。Signposts允许在您的应用程序中进行自定义日志记录,因此现在,在调试iOS应用程序性能时,您可以检查GC暂停是否对应于应用程序冻结。

文档中了解有关GC性能分析的更多信息。

解决Objective-C方法的冲突

Objective-C方法可以有不同的名称,但参数的数量和类型相同。例如,locationManager:didEnterRegion:locationManager:didExitRegion:。在Kotlin中,这些方法具有相同的签名,因此尝试使用它们会触发冲突的重载错误。

以前,您必须手动抑制冲突的重载,以避免此编译错误。为了提高Kotlin与Objective-C的互操作性,Kotlin 2.0.0引入了新的@ObjCSignatureOverride 注解。

注解指示Kotlin编译器忽略冲突的重载,以防从Objective-C类继承了具有相同参数类型但不同参数名称的多个函数。

应用此注解也比一般的错误抑制更安全。此注解仅可用于覆盖支持和测试的Objective-C方法的情况,而一般抑制可能会隐藏重要错误并导致代码无声损坏。

更改了编译器参数的日志级别

在此版本中,Kotlin/Native Gradle任务(如 compilelinkcinterop )中编译器参数的日志级别已从 info 更改为 debug

debug 为默认值,日志级别与其他Gradle编译任务一致,并提供详细的调试信息,包括所有编译器参数。

明确向Kotlin/Native添加了标准库和平台依赖项

Kotlin/Native编译器用于隐式解析标准库和平台依赖性,这导致Kotlin Gradle插件在Kotlin目标上的工作方式不一致。

现在,每个Kotlin/Native Gradle编译通过 compileDependencyFiles 编译参数在其编译时库路径中明确包含标准库和平台依赖项。

Gradle配置缓存中的任务错误

从Kotlin 2.0.0开始,您可能会遇到配置缓存错误,其中显示消息指出:invocation of Task.project at execution time is unsupported

此错误出现在 NativeDistributionCommonizerTaskKotlinNativeCompile 等任务中。

然而,这是一个假阳性错误。根本问题是存在与Gradle配置缓存不兼容的任务,例如 publish* 任务。

这种差异可能不会立即显现出来,因为错误消息表明了不同的根本原因。

由于错误报告中没有明确说明确切的原因,Gradle团队已经在解决这个问题,以修复报告

Kotlin/Wasm

Kotlin 2.0.0提高了与JavaScript的性能和互操作性:

  • 默认使用Binaryen优化生产构建
  • 支持命名导出
  • 支持 @JsExport 函数中的无符号原始类型
  • 在Kotlin/Wasm中生成TypeScript声明文件
  • 支持捕获JavaScript异常
  • 新的异常处理提案现在在选项下得到支持
  • withWasm() 函数分为 JS 和 WASI 变体

默认使用Binaryen优化生产构建

Kotlin/Wasm工具链现在在生产编译期间将Binaryen工具应用于所有项目,而不是之前的手动设置方法。根据我们的估计,它应该能提高您项目的运行时性能和二进制文件大小。

此更改仅影响生产编译。开发编译过程保持不变。

支持命名导出

以前,从Kotlin/Wasm导出的所有声明都使用默认导出导入JavaScript:

//JavaScript:
import Module from "./index.mjs"

Module.add()

现在,您可以导入每个以 @JsExport 为名称标记的Kotlin声明:

// Kotlin:
@JsExport
fun add(a: Int, b: Int) = a + b
// JavaScript:
import { add } from "./index.mjs"

命名导出使Kotlin和JavaScript模块之间共享代码变得更加容易。它们提高了可读性,并帮助您管理模块之间的依赖性。

支持@JsExport函数中的无符号原始类型

从Kotlin 2.0.0开始,您可以使用 @JsExport 注释在外部声明和函数中使用无符号的原始类型,使Kotlin/Wasm函数在JavaScript代码中可用。

这有助于缓解之前的限制,该限制阻止了未签名的原语直接在导出和外部声明中使用。现在,您可以将无符号原语的函数导出为返回或参数类型,并使用返回或使用无符号原语的外部声明。

有关Kotlin/Wasm与JavaScript互操作性的更多信息,请参阅文档

在Kotlin/Wasm中生成TypeScript声明文件

在Kotlin/Wasm中生成TypeScript声明文件是实验性的。它可能随时被丢弃或更改。

在Kotlin 2.0.0中,Kotlin/Wasm编译器现在能够从Kotlin代码中的任何 @JsExport 声明生成TypeScript定义。这些定义可以被IDE和JavaScript工具用于提供代码自动完成,帮助类型检查,并使在JavaScript中包含Kotlin代码更容易。

Kotlin/Wasm编译器收集任何标有@JsExport的顶级函数,并在 .d.ts 文件中自动生成TypeScript定义。

要生成TypeScript定义,请在 wasmJs {} 块的 build.gradle(.kts) 文件中,添加 generateTypeScriptDefinitions() 函数:

kotlin {
    wasmJs {
        binaries.executable()
        browser {
        }
        generateTypeScriptDefinitions()
    }
}

支持捕获JavaScript异常

以前,Kotlin/Wasm代码无法捕获JavaScript异常,因此很难处理来自程序JavaScript端的错误。

在Kotlin 2.0.0中,我们在Kotlin/Wasm中实现了对捕获JavaScript异常的支持。此实现允许您使用具有 ThrowableJsException 等特定类型的 try-catch 块来正确处理这些错误。

此外,无论是否存在异常,都有助于执行代码的最终块也能正常工作。虽然我们正在引入对捕获JavaScript异常的支持,但当发生JavaScript异常时,如调用堆栈,不会提供其他信息。然而,我们正在研究这些实施

新的异常处理提案现在在选项下得到支持

在这个版本中,我们在Kotlin/Wasm中引入了对新版本WebAssembly异常处理提案的支持。

此更新确保新提案与Kotlin要求一致,允许在仅支持最新版本提案的虚拟机上使用Kotlin/Wasm。

使用 -Xwasm-use-new-exception-proposal 编译器选项激活新的异常处理提案。默认情况下,它是关闭的。

withWasm() 函数分为 JS 和 WASI 变体

withWasm() 函数用于为层次结构模板提供Wasm目标,不建议使用专门的 withWasmJs()withWasmWasi() 函数。

现在,您可以在树定义中的不同组之间分离WASI和JS目标。

Kotlin/JS

除其他更改外,此版本为Kotlin带来了现代JS编译,支持ES2015标准的更多功能:

  • 新的编译目标
  • 作为ES2015生成器的 suspend 函数
  • 将参数传递给主函数
  • Kotlin/JS项目的每个文件编译
  • 改进了 collection 互操作性
  • 支持 createInstance()
  • 支持类型安全的纯JavaScript对象
  • 支持npm软件包管理器
  • 编译任务的更改
  • 停止遗留的Kotlin/JS JAR工件

新的编译目标

在Kotlin 2.0.0中,我们正在向Kotlin/JS,es2015 添加一个新的编译目标。这是您一次启用Kotlin中支持的所有ES2015功能的新方法。

您可以在 build.gradle(.kts) 文件中像这样设置它:

kotlin {
    js {
        compilerOptions {
            target.set("es2015")
        }
    }
}

新目标会自动打开ES类和模块以及新支持的ES生成器。

作为ES2015生成器的 suspend 函数

此版本引入了对ES2015生成器的实验支持,用于编译 suspend 函数。

使用生成器而不是状态机应该会提高项目的最终捆绑大小。例如,JetBrains团队通过使用ES2015生成器,设法将其Space项目的捆绑大小减少了20%。

在官方文档中了解有关ES2015(ECMAScript 2015,ES6)的更多信息。

将参数传递给主函数

从Kotlin 2.0.0开始,您可以为main()函数指定args的来源。此功能使使用命令行和传递参数变得更加容易。

为此,请使用新的 passAsArgumentToMainFunction() 函数定义 js {} 块,该函数返回一个字符串数组:

kotlin {
    js {
        binary.executable()
        passAsArgumentToMainFunction("Deno.args")
    }
}

该函数在运行时执行。它采用JavaScript表达式,并将其用作 args:Array<String> 参数,而不是 main() 函数调用。

此外,如果您使用Node.js运行时,您可以利用特殊的别名。它允许您将 process.argv 传递给 args 参数一次,而不是每次都手动添加它:

kotlin {
    js {
        binary.executable()
        nodejs {
            passProcessArgvToMainFunction()
        }
    }
}

Kotlin/JS项目的每个文件编译

Kotlin 2.0.0为Kotlin/JS项目输出引入了一个新的粒度选项。您现在可以设置每个文件的编译,每个Kotlin文件生成一个JavaScript文件。它有助于显著优化最终捆绑包的大小,并改善程序的加载时间。

以前,只有两个输出选项。Kotlin/JS编译器可以为整个项目生成单个.js文件。然而,这个文件可能太大,使用起来不方便。每当您想使用项目中的函数时,您必须将整个JavaScript文件作为依赖项。或者,您可以为每个项目模块配置单独的.js文件的编译。这仍然是默认选项。

由于模块文件也可能太大,使用Kotlin 2.0.0,我们添加了一个更精细的输出,为每个Kotlin文件生成一个(或两个,如果文件包含导出声明)JavaScript文件。要启用每个文件的编译模式:

  1. useEsModules() 函数添加到您的构建文件中,以支持ECMAScript模块:
// build.gradle.kts
kotlin {
    js(IR) {
        useEsModules() // 启用ES2015模块
        browser()
    }
}

您也可以为此使用新的es2015编译目标

  1. 应用 -Xir-per-file 编译器选项或使用以下方式更新您的 gradle.properties 文件:
# gradle.properties
kotlin.js.ir.output.granularity=per-file	// `per-module` 是默认的

改进了 collection 互操作性

从Kotlin 2.0.0开始,可以将签名内带有Kotlin集合类型的声明导出到JavaScript(和TypeScript)。这适用于 SetMapList 集合类型及其可变对应类型。

要在JavaScript中使用Kotlin集合,首先用 @JsExport 注释标记必要的声明:

// Kotlin
@JsExport
data class User(
    val name: String,
    val friends: List<User> = emptyList()
)

@JsExport
val me = User(
    name = "Me",
    friends = listOf(User(name = "Kodee"))
)

然后,您可以将它们从JavaScript中作为常规的JavaScript数组使用:

// JavaScript
import { User, me, KtList } from "my-module"

const allMyFriendNames = me.friends
    .asJsReadonlyArrayView()
    .map(x => x.name) // [‘Kodee']

不幸的是,从JavaScript创建Kotlin集合仍然不可用。我们计划在Kotlin 2.0.20中添加此功能。

支持 createInstance()

从Kotlin 2.0.0开始,您可以使用Kotlin/JS目标中的 createInstance() 函数。以前,它只在JVM上可用。

来自KClass接口的此函数创建了指定类的新实例,这对于获取Kotlin类的运行时引用非常有用。

支持类型安全的纯JavaScript对象

js-plain-objects插件是实验性的。它可能随时被丢弃或更改。js-plain-objects插件仅支持K2编译器。

为了更轻松地使用JavaScript API,在Kotlin 2.0.0中,我们提供了一个新的插件:js-plain-objects,您可以使用它来创建类型安全的普通JavaScript对象。该插件检查您的代码中是否有任何带有 @JsPlainObject 注释的外部接口,并添加:

  • 伴侣对象内的内敛 invoke 运算符函数,您可以将其用作构造函数。
  • 一个 .copy() 函数,您可以使用它来创建对象的副本,同时调整其一些属性。

例如:

import kotlinx.js.JsPlainObject

@JsPlainObject
external interface User {
    var name: String
    val age: Int
    val email: String?
}

fun main() {
    // Creates a JavaScript object
    val user = User(name = "Name", age = 10)
    // Copies the object and adds an email
    val copy = user.copy(age = 11, email = "some@user.com")

    println(JSON.stringify(user))
    // { "name": "Name", "age": 10 }
    println(JSON.stringify(copy))
    // { "name": "Name", "age": 11, "email": "some@user.com" }
}

使用这种方法创建的任何JavaScript对象都更安全,因为您不仅可以在运行时看到错误,还可以在编译时看到它们,甚至可以在IDE中突出显示。

考虑以下示例,它使用 fetch() 函数与JavaScript API交互,使用外部接口来描述JavaScript对象的形状:

import kotlinx.js.JsPlainObject

@JsPlainObject
external interface FetchOptions {
    val body: String?
    val method: String
}

// Window.fetch的包装器
suspend fun fetch(url: String, options: FetchOptions? = null) = TODO("Add your custom behavior here")

// 当“metod”未被识别为 method 时触发编译时错误
fetch("https://google.com", options = FetchOptions(metod = "POST"))
// 由于需要 method,会触发编译时错误
fetch("https://google.com", options = FetchOptions(body = "SOME STRING"))

相比之下,如果您使用 js() 函数来创建JavaScript对象,则仅在运行时发现错误或根本不会触发:

suspend fun fetch(url: String, options: FetchOptions? = null) = TODO("Add your custom behavior here")

// 没有触发错误。由于无法识别“metod”,因此使用了错误的方法(GET)。
fetch("https://google.com", options = js("{ metod: 'POST' }"))

// 默认情况下,使用GET方法。运行时错误被触发,因为 body 不应该存在。
fetch("https://google.com", options = js("{ body: 'SOME STRING' }"))
// TypeError: Window.fetch: HEAD or GET Request cannot have a body

要使用 js-plain-objects 插件,请将以下内容添加到您的 build.gradle(.kts) 文件中:

Kotlin

plugins {
    kotlin("plugin.js-plain-objects") version "2.0.0"
}

Groovy

plugins {
    id "org.jetbrains.kotlin.plugin.js-plain-objects" version "2.0.0"
}

支持npm软件包管理器

以前,只有Kotlin Multiplatform Gradle插件才能使用Yarn作为软件包管理器来下载和安装npm依赖项。从Kotlin 2.0.0开始,您可以使用npm作为软件包管理器。使用npm作为软件包管理器意味着您在设置期间少了一个要管理的工具。

为了向后兼容,Yarn仍然是默认的软件包管理器。要使用npm作为软件包管理器,请在 gradle.properties 文件中设置以下属性:

kotlin.js.yarn = false

编译任务的更改

以前,webpackdistributResources 编译任务都针对相同的目录。此外,分发任务也声明了 dist 作为其输出目录。这导致输出重叠,并产生编译警告。

因此,从Kotlin 2.0.0开始,我们实现了以下更改:

  • webpack 任务现在针对一个单独的文件夹。
  • distributeResources 任务被完全删除。
  • distribution 任务现在具有 Copy 类型,并针对 dist 文件夹。

停止遗留的Kotlin/JS JAR工件

从Kotlin 2.0.0开始,Kotlin发行版不再包含带有 .jar 扩展名的遗留Kotlin/JS工件。遗产工件用于不受支持的旧Kotlin/JS编译器,对于使用klib格式的IR编译器来说是不必要的。

Gradle改进

Kotlin 2.0.0与Gradle 6.8.3到8.5完全兼容。您还可以使用Gradle版本,直到最新的Gradle版本,但如果您这样做,请记住,您可能会遇到弃用警告,或者一些新的Gradle功能可能不起作用。

此版本带来了以下变化:

  • 用于多平台项目中编译器选项的新Gradle DSL
  • 新的Compose编译器Gradle插件
  • Bumping 最低支持版本
  • 区分JVM和Android发布库的新属性
  • 改进了Kotlin/Native中CInteropProcess的Gradle依赖处理
  • Gradle的可见性变化
  • Gradle项目中Kotlin数据的新目录
  • 需要时下载 Kotlin/Native 编译器
  • 弃用定义编译器选项的旧方法
  • Bumping 最低AGP支持版本
  • 新的Gradle属性来尝试最新的语言版本
  • 构建报告的新JSON输出格式
  • kapt配置从super配置中继承注解处理器
  • Kotlin Gradle插件不再使用已弃用的Gradle约定

用于多平台项目中编译器选项的新Gradle DSL

这个功能是实验性的。它可能随时被丢弃或更改。仅将其用于评估目的。如果您能在YouTrack中对此进行反馈,我们将不胜感激。

在Kotlin 2.0.0之前,使用Gradle在多平台项目中配置编译器选项只能在低级别,例如每个任务、编译或源集。为了在您的项目中更轻松地更普遍地配置编译器选项,Kotlin 2.0.0附带了新的Gradle DSL。

有了这个新的DSL,您可以在扩展级别为所有目标和共享源集(如 commonMain )配置编译器选项,并在特定目标的目标级别配置编译器选项:

kotlin {
    compilerOptions {
        // 目标级JVM编译器选项,作为默认值用于此目标中的所有编译
        allWarningsAsErrors.set(true)
    }
    jvm {
        compilerOptions {
            // 目标级JVM编译器选项,作为默认值用于此目标中的所有编译
            noJdk.set(true)
        }
    }
}

整个项目配置现在有三层。最高是扩展级别,然后是目标级别,最低是编译单元(通常是编译任务):

扩展DSL

kotlin {
	compilerOptions {
		// common compiler options
	}
}

目标级别:目标扩展DSL

kotlin {
	jvm {
		compilerOptions {
			// common + JVM compiler options
		}
	}
}

编译单元级别:任务编译器选项DSL

task.named<KotlinJvmCompile>("compileKotlinJvm") {
	compilerOptions {
		// common + JVM compiler options
	}
}

更高级别的设置被用作较低级别的惯例(默认):

  • 扩展编译器选项的值是目标编译器选项的默认值,包括共享源集,如 commonMainnativeMaincommonTest
  • 目标编译器选项的值用作编译单元(任务)编译器选项的默认值,例如,compileKotlinJvmcompileTestKotlinJvm 任务。

反过来,在较低级别进行的配置会覆盖较高级别的相关设置:

  • 任务级编译器选项覆盖目标或扩展级别的相关配置。
  • 目标级编译器选项在扩展级别覆盖相关配置。

在配置项目时,请记住,一些设置编译器选项的旧方法已被弃用。

我们鼓励您在多平台项目中尝试新的DSL,并在YouTrack中留下反馈,因为我们计划将此DSL作为配置编译器选项的推荐方法。

新的Compose编译器Gradle插件

将可组合文件转换为Kotlin代码的Jetpack Compose编译器现已合并到Kotlin存储库中。这将有助于将Compose项目过渡到Kotlin 2.0.0,因为Compose编译器将始终与Kotlin同时发布。这也将Compose编译器版本提升到2.0.0。

要在项目中使用新的Compose编译器,请在 build.gradle(.kts) 文件中应用 org.jetbrains.kotlin.plugin.compose Gradle插件,并将其版本设置为Kotlin 2.0.0。

要了解有关此更改的更多信息并查看迁移说明,请参阅Compose编译器文档

区分JVM和Android发布库的新属性

从Kotlin 2.0.0开始,org.gradle.jvm.environment Gradle属性默认与所有Kotlin变体一起发布。

该属性有助于区分Kotlin多平台库的JVM和Android变体。它表明某个库变体更适合某个JVM环境。目标环境可以是"android"、
“standard-jvm"或"no-jvm”。

发布此属性应该使使用带有JVM和Android目标的Kotlin多平台库从非多平台客户端(如仅限Java的项目)中变得更加强大。

如有必要,您可以禁用属性发布。要做到这一点,请将以下Gradle选项添加到您的 gradle.properties 文件中:

kotlin.publishJvmEnvironmentAttribute=false

改进了Kotlin/Native中CInteropProcess的Gradle依赖处理

在此版本中,我们增强了 defFile 属性的处理,以确保在Kotlin/Native项目中更好的Gradle任务依赖性管理。

在此更新之前,如果 defFile 属性被指定为另一个尚未执行的任务的输出,Gradle构建可能会失败。这个问题的变通办法是添加对此任务的依赖性:

kotlin {
    macosArm64("native") {
        compilations.getByName("main") {
            cinterops {
                val cinterop by creating {
                    defFileProperty.set(createDefFileTask.flatMap { it.defFile.asFile })
                    project.tasks.named(interopProcessingTaskName).configure {
                        dependsOn(createDefFileTask)
                    }
                }
            }
        }
    }
}

为了解决这个问题,引入了一个新的 RegularFileProperty 属性叫做 definitionFile。现在,Gradle 在构建过程的后期,即连接任务运行后,懒惰地验证 definitionFile 属性是否存在。这种新的方法消除了对额外依赖的需求。

CInteropProcess 任务和 CInteropSettings 类使用 definitionFile 属性,而不是 defFiledefFileProperty

Kotlin

kotlin {
    macosArm64("native") {
        compilations.getByName("main") {
            cinterops {
                val cinterop by creating {
                    definitionFile.set(project.file("def-file.def"))
                }
            }
        }
    }
}

Groovy

kotlin {
    macosArm64("native") {
        compilations.main {
            cinterops {
                cinterop {
                    definitionFile.set(project.file("def-file.def"))
                }
            }
        }
    }
}

defFiledefFileProperty 参数已弃用。

Gradle的可见性变化

这一变化只影响Kotlin DSL用户。

在Kotlin 2.0.0中,我们修改了Kotlin Gradle插件,以更好地控制您的构建脚本并安全。以前,用于特定DSL上下文的某些Kotlin DSL函数和属性会无意中泄漏到其他DSL上下文中。这种泄漏可能导致使用不正确的编译器选项、多次应用设置以及其他错误配置:

kotlin {
    // 目标DSL无法访问 kotlin {} 扩展DSL中定义的方法和属性
    jvm {
        // 编译DSL无法访问 kotlin {} 扩展DSL和 kotlin jvm {} 目标DSL中定义的方法和属性
        compilations.configureEach {
            // 编译任务DSL无法访问 kotlin {} 扩展、kotlin jvm{} 目标或kotlin编译DSL中定义的方法和属性
            compileTaskProvider.configure {
                // 例如:
                explicitApi()
                // 错误,因为它在 kotlin {} 扩展DSL中定义
                mavenPublication {}
                // 错误,因为它在 kotlin jvm {} 目标DSL中定义
                defaultSourceSet {}
                // 错误,因为它在 kotlin 编译DSL中定义
            }
        }
    }
}

为了解决这个问题,我们添加了 @KotlinGradlePluginDsl 注释,防止Kotlin Gradle插件DSL功能和属性暴露在不打算可用的级别。以下级别是相互分开的:

  • Kotlin 扩展
  • Kotlin 目标
  • Kotlin 编译
  • Kotlin 编译任务

对于最流行的情况,我们添加了编译器警告,并建议如何在构建脚本配置不正确时修复它们。例如:

kotlin {
    jvm {
        sourceSets.getByName("jvmMain").dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
        }
    }
}

在这种情况下,sourceSets 的警告消息是:

[DEPRECATION] 'sourceSets: NamedDomainObjectContainer<KotlinSourceSet>' is deprecated.Accessing 'sourceSets' container on the Kotlin target level DSL is deprecated. Consider configuring 'sourceSets' on the Kotlin extension level.

如果您能对这一变化提供反馈,我们将不胜感激!在我们的 #gradle Slack channel 中直接与Kotlin开发人员分享您的评论。
获取Slack邀请

Gradle项目中Kotlin数据的新目录

通过此更改,您可能需要将 .kotlin 目录添加到项目的 .gitignore 文件中。

在Kotlin 1.8.20中,Kotlin Gradle插件切换到将其数据存储在Gradle项目缓存目录中:<project-root-directory>/.gradle/kotlin。然而,.gradle目录仅保留给Gradle,因此它不适用于未来。

为了解决这个问题,从Kotlin 2.0.0开始,我们将默认将Kotlin数据存储在您的 <project-root-directory>/.kotlin 中。为了向后兼容,我们将继续在 .gradle/kotlin 目录中存储一些数据。

您可以配置的新Gradle属性是:

Gradle propertyDescripption
kotlin.project.persistent.dir配置存储项目级数据的位置。默认:<project-root-directory>/.kotlin
kotlin.project.persistent.dir.gradle.disableWrite用于控制是否禁用将Kotlin数据写入 .gradle 目录。默认: false

将这些属性添加到项目中的 gradle.properties 文件中,以便它们生效。

需要时下载Kotlin/Native编译器

在Kotlin 2.0.0之前,如果您在多平台项目的Gradle构建脚本中配置了Kotlin/Native目标,Gradle将始终在配置阶段下载Kotlin/Native编译器。

即使没有为将在执行阶段运行的Kotlin/Native目标编译代码的任务,也会发生这种情况。对于只想在项目中检查JVM或JavaScript代码的用户来说,以这种方式下载Kotlin/Native编译器效率特别低。例如,作为CI流程的一部分,对他们的Kotlin项目进行测试或检查。

在Kotlin 2.0.0中,我们在Kotlin Gradle插件中更改了此行为,以便在执行阶段下载Kotlin/Native编译器,并且仅在为Kotlin/Native目标请求编译时下载。

反过来,Kotlin/Native编译器的依赖项现在不是作为编译器的一部分下载的,而是在执行阶段下载。

如果您在新行为中遇到任何问题,您可以通过在 gradle.properties 文件中添加以下Gradle属性来暂时切换回上一个行为:

kotlin.native.toolchain.enabled=false

从1.9.20-Beta版本开始,Kotlin/Native发行版与CDN一起发布到Maven Central。

这使我们能够改变Kotlin寻找和下载必要工件的方式。它现在默认使用您在项目的 repositories {} 代码块中指定的Maven存储库,而不是CDN。

您可以通过在 gradle.properties 文件中设置以下Gradle属性来暂时切换此行为:

kotlin.native.distribution.downloadFromMaven=false

请向我们的问题跟踪器YouTrack报告任何问题。改变默认行为的这两个Gradle属性都是临时的,将在未来的版本中删除。

弃用定义编译器选项的旧方法

在本版本中,我们继续完善您如何设置编译器选项。它应该解决不同方式之间的歧义,并使项目配置更直接。

自Kotlin 2.0.0以来,以下用于指定编译器选项的DSL已弃用:

  • 来自 KotlinCompile 接口的 kotlinOptions DSL实现了所有Kotlin编译任务。改用 KotlinCompilationTask<CompilerOptions>
  • KotlinCompiation 接口中带有 HasCompilerOptions 类型的 compilerOptions 属性。这个DSL与其他DSL不一致,并在KotlinCompilation.compileTaskProvider 编译任务中配置了与编译器选项相同的 KotlinCommonCompilerOptions 对象,这令人困惑。
    相反,我们建议使用Kotlin编译任务中的编译器选项属性:
kotlinCompilation.compileTaskProvider.configure {
    compilerOptions { ... }
}

例如:

kotlin {
    js(IR) {
        compilations.all {
            compileTaskProvider.configure {
                compilerOptions.freeCompilerArgs.add("-Xerror-tolerance-policy=SYNTAX")
            }
        }
    }
}
  • 来自 KotlinCompilation 接口的 kotlinOptions DSL。
  • 来自 KotlinNativeArtifactConfig 接口、KotlinNativeLink 类和 KotlinNativeLinkArtifactTask 类的 kotlinOptions DSL。改用 toolOptions DSL。
  • 来自 KotlinJsDce 接口的 dceOptions DSL。改用 toolOptions DSL。

有关如何在Kotlin Gradle插件中指定编译器选项的更多信息,请参阅如何定义选项

Bumped 最低支持的AGP版本

从Kotlin 2.0.0开始,最低支持的Android Gradle插件版本为7.1.3。

新的Gradle属性来尝试最新的语言版本

在Kotlin 2.0.0之前,我们有以下Gradle属性来尝试新的K2编译器:kotlin.experimental.tryK2。现在K2编译器在Kotlin 2.0.0中默认启用,我们决定将此属性发展为新的形式,您可以使用该形式在项目中尝试最新的语言版本:kotlin.experimental.tryNext。当您在gradle.properties 文件中使用此属性时,Kotlin Gradle插件会将语言版本增加到Kotlin版本的默认值之上。例如,在Kotlin 2.0.0中,默认语言版本是2.0,因此该属性配置了语言版本2.1。

这个新的Gradle属性在构建报告中生成与 kotlin.experimental.tryK2 类似的指标。配置的语言版本包含在输出中。例如:

##### 'kotlin.experimental.tryNext' results #####
:app:compileKotlin: 2.1 language version
:lib:compileKotlin: 2.1 language version
##### 100% (2/2) tasks have been compiled with Kotlin 2.1 #####

要了解有关如何启用构建报告及其内容的更多信息,请参阅构建报告

构建报告的新JSON输出格式

在Kotlin 1.7.0中,我们引入了构建报告来帮助跟踪编译器性能。随着时间的推移,我们添加了更多指标,使这些报告在调查性能问题时更加详细和有用。以前,本地文件的唯一输出格式是 *.txt 格式。在Kotlin 2.0.0中,我们支持JSON输出格式,以便更轻松地使用其他工具进行分析。

要为构建报告配置JSON输出格式,请在 gradle.properties 文件中声明以下属性:

kotlin.build.report.output=json

// 存储构建报告的目录
kotlin.build.report.json.directory="my/directory/path"

或者,您可以运行以下命令:

./gradlew assemble -Pkotlin.build.report.output=json -Pkotlin.build.report.json.directory="my/directory/path"

配置后,Gradle会在您指定的目录中生成构建报告:${project_name}-date-time-<sequence_number>.json

以下是带有JSON输出格式的构建报告的示例片段,其中包含构建指标和聚合指标:

"buildOperationRecord": [
    {
     "path": ":lib:compileKotlin",
      "classFqName": "org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated",
      "startTimeMs": 1714730820601,
      "totalTimeMs": 2724,
      "buildMetrics": {
        "buildTimes": {
          "buildTimesNs": {
            "CLEAR_OUTPUT": 713417,
            "SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 19699333,
            "IR_TRANSLATION": 281000000,
            "NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14088042,
            "CALCULATE_OUTPUT_SIZE": 1301500,
            "GRADLE_TASK": 2724000000,
            "COMPILER_INITIALIZATION": 263000000,
            "IR_GENERATION": 74000000,}
        }"aggregatedMetrics": {
    "buildTimes": {
      "buildTimesNs": {
        "CLEAR_OUTPUT": 782667,
        "SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 22031833,
        "IR_TRANSLATION": 333000000,
        "NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14890292,
        "CALCULATE_OUTPUT_SIZE": 2370750,
        "GRADLE_TASK": 3234000000,
        "COMPILER_INITIALIZATION": 292000000,
        "IR_GENERATION": 89000000,}
    }

kapt配置从super配置中继承注解处理

在Kotlin 2.0.0之前,如果您想在单独的Gradle配置中定义一组通用的注解处理器,并在子项目的特定于kapt的配置中扩展此配置,kapt将跳过注解处理,因为它找不到任何注解处理器。在Kotlin 2.0.0中,kapt可以成功检测您的注解处理器存在间接依赖性。

例如,对于使用 Dagger 的子项目,在您的 build.gradle(.kts) 文件中,使用以下配置:

val commonAnnotationProcessors by configurations.creating
configurations.named("kapt") { extendsFrom(commonAnnotationProcessors) }

dependencies {
    implementation("com.google.dagger:dagger:2.48.1")
    commonAnnotationProcessors("com.google.dagger:dagger-compiler:2.48.1")
}

在本例中,commonAnnotationProcessors Gradle配置是您想要用于所有项目的注解处理的“通用”配置。您可以使用 extendsFrom() 方法将“commonAnnotationProcessors”添加为超级配置。kapt看到 commonAnnotationProcessors Gradle配置依赖于Dagger注释处理器,并成功将其包含在其配置中以进行注释处理。

感谢Christoph Loy的实现

Kotlin Gradle插件不再使用已弃用的Gradle约定

在Kotlin 2.0.0之前,如果您使用Gradle 8.2或更高版本,Kotlin Gradle插件错误地使用了Gradle 8.2中已弃用的Gradle约定。这导致了Gradle报告构建弃用。在Kotlin 2.0.0中,Kotlin Gradle插件已更新,当您使用Gradle 8.2或更高版本时,不再触发这些弃用警告。

标准库

此版本为Kotlin标准库带来了进一步的稳定性,并使所有平台都具有更多现有功能:

  • 稳定替换 enum class value 通用函数
  • 稳定的 AutoCloseable 接口
  • 通用 protected 属性 AbstractMutableList.modCount
  • 通用 protected 函数AbstractMutableList.removeRange
  • 通用 String.toCharArray(destination)

稳定替换 enum class value 通用函数

在Kotlin 2.0.0中,enumEntries<T>() 函数变得稳定。enumEntries<T>() 函数是泛型 enumValues<T>() 函数的替代品。新函数返回给定枚举类型 T 的所有枚举条目的列表。之前引入了枚举类的 entries 属性,并进行了稳定,以替换合成 values() 函数。有关条目属性的更多信息,请参阅Kotlin 1.8.20的新功能

仍然支持 enumValues<T>() 函数,但我们建议您改用 enumEntries<T>() 函数,因为它对性能的影响较小。每次调用 enumValues<T>() 时,都会创建一个新数组,而每当您调用 enumEntries<T>() 时,每次都会返回相同的列表,这要高效得多。

例如:

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumEntries<T>().joinToString { it.name })
}

printAllValues<RGB>()
// RED, GREEN, BLUE

稳定的 AutoCloseable 接口

在Kotlin 2.0.0中,常见的 AutoCloseable 接口变得稳定。它允许您轻松关闭资源,并包括一些有用的功能:

  • use() 扩展函数,它在所选资源上执行给定的块函数,然后正确关闭它,无论是否抛出异常。
  • AutoCloseable() 构造函数,该函数创建 AutoCloseable 接口的实例。

在下面的示例中,我们定义了 XMLWriter 接口,并假设有一个资源可以实现它。例如,此资源可以是一个类,它打开一个文件,写入XML内容,然后关闭它:

interface XMLWriter {
    fun document(encoding: String, version: String, content: XMLWriter.() -> Unit)
    fun element(name: String, content: XMLWriter.() -> Unit)
    fun attribute(name: String, value: String)
    fun text(value: String)

    fun flushAndClose()
}

fun writeBooksTo(writer: XMLWriter) {
    val autoCloseable = AutoCloseable { writer.flushAndClose() }
    autoCloseable.use {
        writer.document(encoding = "UTF-8", version = "1.0") {
            element("bookstore") {
                element("book") {
                    attribute("category", "fiction")
                    element("title") { text("Harry Potter and the Prisoner of Azkaban") }
                    element("author") { text("J. K. Rowling") }
                    element("year") { text("1999") }
                    element("price") { text("29.99") }
                }
                element("book") {
                    attribute("category", "programming")
                    element("title") { text("Kotlin in Action") }
                    element("author") { text("Dmitry Jemerov") }
                    element("author") { text("Svetlana Isakova") }
                    element("year") { text("2017") }
                    element("price") { text("25.19") }
                }
            }
        }
    }
}

通用 protected 属性 AbstractMutableList.modCount

在此版本中,AbstractMutableList 接口的 modCount protected 属性变得常见。以前,modCount 属性在每个平台上都可用,但不适用于公共目标。现在,您可以创建 AbstractMutableList 的自定义实现,并在公共代码中访问该属性。

这个属性跟踪集合的结构修改次数。这包括改变集合大小或以可能导致正在进行的迭代返回不正确结果的方式修改列表的操作。

在实现自定义列表时,您可以使用 modCount 属性注册和检测并发修改。

通用 protected 函数AbstractMutableList.removeRange

在此版本中,AbstractMutableList 接口的 removeRange() protected 函数变得常见。以前,它在每个平台上都可用,但不适用于共同目标。现在,您可以创建 AbstractMutableList 的自定义实现,并覆盖公共代码中的函数。

该函数按照指定的范围从此列表中删除元素。通过覆盖此功能,您可以利用自定义实现并提高列表操作的性能。

通用 String.toCharArray(destination)

此版本引入了一个常见的 String.toCharArray(destination) 函数。以前,它只在JVM上可用。

让我们将其与现有的 String.toCharArray() 函数进行比较。它创建一个新的 CharArray,其中包含指定字符串中的字符。然而,新的通用String.toCharArray(destination) 函数将 String 字符移动到现有的目标 CharArray。如果您已经有一个要填充的缓冲区,这很有用:

fun main() {
    val myString = "Kotlin is awesome!"
    val destinationArray = CharArray(myString.length)

    // 转换字符串并将其存储在 destinationArray 中:
    myString.toCharArray(destinationArray)

    for (char in destinationArray) {
        print("$char ")
        // K o t l i n   i s   a w e s o m e ! 
    }
}

安装Kotlin 2.0.0

从IntelliJ IDEA 2023.3和Android Studio Iguana(2023.2.1)Canary 15开始,Kotlin插件作为IDE中包含的捆绑插件分发。这意味着您无法再从JetBrains Marketplace安装插件。

要更新到新的Kotlin版本,请在构建脚本中将Kotlin版本更改为2.0.0。

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐