Что нового в Kotlin 2.0.0
Вышел Kotlin 2.0.0, а новый компилятор Kotlin K2 получил статус стабильного! Кроме того, вот ещё несколько ключевых нововведений:
Мониторинг производительности GC в Kotlin/Native с помощью signpost на платформах Apple
Разрешение конфликтов с методами Objective-C в Kotlin/Native
Поддержка беззнаковых примитивных типов в функциях с @JsExport в Kotlin/Wasm
Оптимизация production-сборок по умолчанию с помощью Binaryen
Новый Gradle DSL для параметров компилятора в мультиплатформенных проектах
Kotlin 2.0 — важная веха для команды JetBrains. Этот выпуск стал центральной темой KotlinConf 2024. Посмотрите вступительный доклад, в котором мы объявили о новых интересных обновлениях и рассказали о недавней работе над языком Kotlin:
Поддержка в IDE
Плагины Kotlin с поддержкой Kotlin 2.0.0 входят в состав последних версий IntelliJ IDEA и Android Studio. Обновлять плагин Kotlin в IDE не нужно. Достаточно изменить версию Kotlin на Kotlin 2.0.0 в сценариях сборки.
Подробную информацию о поддержке компилятора Kotlin K2 в IntelliJ IDEA см. в разделе Поддержка в IDE.
Дополнительную информацию о поддержке Kotlin в IntelliJ IDEA см. в разделе Выпуски Kotlin.
Компилятор Kotlin K2
Путь к компилятору K2 был долгим, но теперь команда JetBrains наконец готова объявить о его стабилизации. В Kotlin 2.0.0 новый компилятор Kotlin K2 используется по умолчанию и имеет статус стабильного для всех целевых платформ: JVM, Native, Wasm и JS. Новый компилятор обеспечивает значительное повышение производительности, ускоряет разработку новых языковых функций, объединяет все поддерживаемые Kotlin платформы и предоставляет более совершенную архитектуру для мультиплатформенных проектов.
Команда JetBrains проверила качество нового компилятора, успешно скомпилировав 10 миллионов строк кода из отобранных пользовательских и внутренних проектов. В процессе стабилизации участвовали 18 000 разработчиков: они протестировали новый компилятор K2 в общей сложности на 80 000 проектах и сообщили обо всех обнаруженных проблемах.
Чтобы переход на новый компилятор прошёл как можно проще, мы подготовили руководство по переходу на компилятор K2. В нём описаны многочисленные преимущества компилятора, возможные изменения, с которыми вы можете столкнуться, а также способы при необходимости вернуться к предыдущей версии.
В публикации в блоге мы изучили производительность компилятора K2 в различных проектах. Ознакомьтесь с ней, если хотите увидеть реальные данные о производительности компилятора K2 и узнать, как собирать показатели производительности собственных проектов.
Также можно посмотреть доклад с KotlinConf 2024, в котором ведущий языковой дизайнер Michail Zarečenskij рассказывает об эволюции функций Kotlin и компиляторе K2:
Текущие ограничения компилятора K2
Включение K2 в проекте Gradle связано с некоторыми ограничениями, которые могут затронуть проекты с версиями Gradle ниже 8.3 в следующих случаях:
Компиляция исходного кода из
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) } } Обновите версию Gradle в проекте до 8.3 или новее.
Улучшения умного приведения типов
В определённых случаях компилятор Kotlin может автоматически привести объект к типу, избавляя вас от необходимости делать это явно. Это называется умным приведением типов. Теперь компилятор Kotlin K2 выполняет умные приведения типов в ещё большем количестве случаев.
В Kotlin 2.0.0 мы улучшили умные приведения типов в следующих областях:
Локальные переменные и последующие области видимости
Ранее, если при проверке условия if переменная получала значение, отличное от null, для неё выполнялось умное приведение типа. Информация об этой переменной затем становилась доступна в дальнейшей части области видимости блока if.
Однако если вы объявляли переменную вне условия if, информация о ней внутри условия if была недоступна, поэтому умное приведение типа не выполнялось. Такое поведение наблюдалось также с выражениями when и циклами while.
Начиная с Kotlin 2.0.0, если вы объявите переменную до её использования в условии if, when или while, собранная компилятором информация о переменной будет доступна в соответствующем блоке для умного приведения типа.
Это может быть полезно, например, если вы хотите вынести логические условия в переменные. Тогда переменной можно дать понятное имя, что улучшит читаемость кода и позволит повторно использовать её позднее. Например:
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// In Kotlin 2.0.0, the compiler can access
// information about isCat, so it knows that
// animal was smart-cast to the type Cat.
// Therefore, the purr() function can be called.
// In Kotlin 1.9.20, the compiler doesn't know
// about the smart cast, so calling the purr()
// function triggers an error.
animal.purr()
}
}
fun main() {
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
Проверки типа с логическим оператором or
В 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 is smart-cast to a common supertype Status
signalStatus.signal()
// Prior to Kotlin 2.0.0, signalStatus is smart cast
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
Встроенные функции
В Kotlin 2.0.0 компилятор K2 иначе обрабатывает встроенные функции и может определить, безопасно ли выполнять умное приведение типов, совместно с другими анализами компилятора.
В частности, теперь встроенные функции считаются неявно имеющими контракт callsInPlace. Это означает, что все лямбда-функции, переданные встроенной функции, вызываются на месте. Поскольку лямбда-функции вызываются на месте, компилятор знает, что лямбда-функция не может передать ссылки на переменные, содержащиеся в её теле.
Компилятор использует эти сведения вместе с результатами других анализов, чтобы определить, безопасно ли выполнять умное приведение захваченных переменных. Например:
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// In Kotlin 2.0.0, the compiler knows that processor
// is a local variable, and inlineAction() is an inline function, so
// references to processor can't be leaked. Therefore, it's safe
// to smart-cast processor.
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
// In Kotlin 1.9.20, you have to perform a safe call:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
Свойства с функциональными типами
В предыдущих версиях Kotlin была ошибка, из-за которой для свойств класса с функциональным типом не выполнялось умное приведение. В Kotlin 2.0.0 и компиляторе K2 это поведение исправлено. Например:
class Holder(val provider: (() -> Unit)?) {
fun process() {
// In Kotlin 2.0.0, if provider isn't null, then
// provider is smart-cast
if (provider != null) {
// The compiler knows that provider isn't null
provider()
// In 1.9.20, the compiler doesn't know that provider isn't
// null, so it triggers an error:
// 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()
// In 1.9.20, the compiler triggers an error:
// Reference has a nullable type 'Provider?' use explicit '?.invoke()' to make a function-like call instead
}
}
}
Обработка исключений
В Kotlin 2.0.0 мы улучшили обработку исключений, чтобы информация об умном приведении типа передавалась в блоки catch и finally. Это изменение делает код безопаснее, поскольку компилятор отслеживает, имеет ли объект nullable-тип. Например:
//sampleStart
fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0
// The compiler rejects previous smart cast information for
// stringInput. Now stringInput has the String? type.
stringInput = null
// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
// In Kotlin 1.9.20, the compiler says that a safe call isn't
// needed, but this is incorrect.
}
}
//sampleEnd
fun main() {
testString()
}
Операторы инкремента и декремента
До 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
// Check if unknownObject inherits from the Tau interface
// Note, it's possible that unknownObject inherits from both
// Rho and Tau interfaces.
if (unknownObject is Tau) {
// Use the overloaded inc() operator from interface Rho.
// In Kotlin 2.0.0, the type of unknownObject is smart-cast to
// Sigma.
++unknownObject
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so the sigma() function can be called successfully.
unknownObject.sigma()
// In Kotlin 1.9.20, the compiler doesn't perform a smart cast
// when inc() is called so the compiler still thinks that
// unknownObject has type Tau. Calling the sigma() function
// throws a compile-time error.
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so calling the tau() function throws a compile-time
// error.
unknownObject.tau()
// Unresolved reference 'tau'
// In Kotlin 1.9.20, since the compiler mistakenly thinks that
// unknownObject has type Tau, the tau() function can be called,
// but it throws a ClassCastException.
}
}
Улучшения Kotlin Multiplatform
В Kotlin 2.0.0 мы улучшили компилятор K2 в следующих областях, связанных с Kotlin Multiplatform:
Разделение общих и платформенных исходников при компиляции
Ранее устройство компилятора Kotlin не позволяло ему разделять общие и платформенные наборы исходников во время компиляции. В результате общий код мог обращаться к платформенному коду, что приводило к разному поведению на разных платформах. Кроме того, некоторые параметры компилятора и зависимости из общего кода могли попадать в платформенный код.
В Kotlin 2.0.0 реализация нового компилятора Kotlin K2 включила переработку схемы компиляции, обеспечивающую строгое разделение общих и платформенных наборов исходников. Это изменение особенно заметно при использовании функций expected и actual. Ранее вызов функции в общем коде мог разрешаться в пользу функции из платформенного кода. Например:
Общий код |
Платформенный код |
|---|---|
fun foo(x: Any) = println("common foo")
fun exampleFunction() {
foo(42)
}
|
// JVM
fun foo(x: Int) = println("platform foo")
// JavaScript
// There is no foo() function overload
// on the JavaScript platform
|
В этом примере поведение общего кода зависит от платформы, на которой он выполняется:
На платформе JVM вызов функции
foo()в общем коде приводит к вызову функцииfoo()из платформенного кода какplatform foo.На платформе JavaScript вызов функции
foo()в общем коде приводит к вызову функцииfoo()из общего кода какcommon foo, поскольку в платформенном коде такой функции нет.
В Kotlin 2.0.0 общий код не имеет доступа к платформенному коду, поэтому на обеих платформах функция foo() успешно разрешается в функцию foo() из общего кода: common foo.
Помимо повышения согласованности поведения на разных платформах, мы приложили немало усилий, чтобы устранить случаи, когда поведение IntelliJ IDEA или Android Studio расходилось с поведением компилятора. Например, при использовании классов expected и actual происходило следующее:
Общий код |
Платформенный код |
|---|---|
expect class Identity {
fun confirmIdentity(): String
}
fun common() {
// Before 2.0.0,
// it triggers an IDE-only error
Identity().confirmIdentity()
// RESOLUTION_TO_CLASSIFIER : Expected class
// Identity has no default constructor.
}
|
actual class Identity {
actual fun confirmIdentity() = "expect class fun: jvm"
}
|
В этом примере у класса expected 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() в общем коде, будет выбрана функция из библиотеки с наиболее подходящим типом аргумента:
// A project that uses the example library for the JVM target
// MODULE: common
fun main() {
whichFun(2)
// platform function
}
Если же объявить перегруженные функции для whichFun() в одном наборе исходников, будет выбрана функция из общего кода, поскольку у вашего кода нет доступа к платформенной версии:
// Example library isn't used
// MODULE: common
fun whichFun(x: Any) = println("common function")
fun main() {
whichFun(2)
// common function
}
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
Как и мультиплатформенные библиотеки, модуль commonTest находится в отдельном наборе исходников, поэтому он по-прежнему имеет доступ к платформенному коду. Следовательно, вызовы функций в модуле commonTest разрешаются так же, как и в старой схеме компиляции.
В будущем эти оставшиеся случаи будут лучше соответствовать новой схеме компиляции.
Разные уровни видимости объявлений expected и actual
До Kotlin 2.0.0 при использовании объявлений expected и actual в проекте Kotlin Multiplatform они должны были иметь одинаковый уровень видимости. Теперь Kotlin 2.0.0 также поддерживает разные уровни видимости, но только если объявление actual имеет более свободный уровень доступа, чем объявление expected. Например:
expect internal class Attribute // Visibility is internal
actual class Attribute // Visibility is public by default,
// which is more permissive
Аналогично, если в объявлении actual используется псевдоним типа, видимость базового типа должна быть такой же или более свободной, чем у объявления expected. Например:
expect internal class Attribute // Visibility is internal
internal actual typealias Attribute = Expanded
class Expanded // Visibility is public by default,
// which is more permissive
Поддержка плагинов компилятора
В настоящее время компилятор Kotlin K2 поддерживает следующие плагины компилятора Kotlin:
Кроме того, компилятор Kotlin K2 поддерживает:
Плагин компилятора Jetpack Compose версии 2.0.0, который был перенесён в репозиторий Kotlin.
Плагин обработки символов Kotlin (KSP), начиная с версии KSP2.
Экспериментальный плагин компилятора Kotlin Power-assert
В Kotlin 2.0.0 представлен экспериментальный плагин компилятора Power-assert. Этот плагин упрощает написание тестов, добавляя в сообщения об ошибках контекстную информацию, благодаря чему отладка становится проще и эффективнее.
Разработчикам часто приходится использовать сложные библиотеки утверждений для написания эффективных тестов. Плагин Power-assert упрощает эту задачу, автоматически формируя сообщения об ошибках с промежуточными значениями выражения утверждения. Это помогает разработчикам быстро понять, почему тест завершился с ошибкой.
Если утверждение в тесте не выполняется, улучшенное сообщение об ошибке показывает значения всех переменных и подвыражений в утверждении, помогая определить, какая часть условия вызвала ошибку. Это особенно полезно для сложных утверждений, проверяющих несколько условий.
Чтобы включить плагин в проекте, настройте его в файле build.gradle(.kts):
plugins {
kotlin("multiplatform") version "2.0.0"
kotlin("plugin.power-assert") version "2.0.0"
}
powerAssert {
functions = listOf("kotlin.assert", "kotlin.test.assertTrue")
}
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 можно узнать из документации.
Как включить компилятор Kotlin K2
Начиная с Kotlin 2.0.0, компилятор Kotlin K2 включён по умолчанию. Дополнительные действия не требуются.
Попробуйте компилятор Kotlin K2 в Kotlin Playground
Kotlin Playground поддерживает выпуск 2.0.0. Попробуйте!
Поддержка в IDE
По умолчанию IntelliJ IDEA и Android Studio по-прежнему используют предыдущий компилятор для анализа кода, автодополнения, подсветки и других функций IDE. Чтобы получить все возможности Kotlin 2.0 в IDE, включите режим K2.
В IDE перейдите в раздел Настройки | Языки и платформы | Kotlin и выберите параметр Включить режим K2. IDE будет анализировать код в режиме K2.
После включения режима K2 вы можете заметить различия в анализе IDE, обусловленные изменениями в поведении компилятора. Узнайте, чем новый компилятор K2 отличается от предыдущего, из нашего руководства по переходу.
Подробнее о режиме K2 читайте в нашем блоге.
Мы активно собираем отзывы о режиме K2, поэтому поделитесь своим мнением в нашем открытом канале Slack.
Оставьте отзыв о новом компиляторе K2
Мы будем рады вашим отзывам!
Сообщайте о любых проблемах с новым компилятором K2 в нашем трекере задач.
Включите параметр «Отправлять статистику использования», чтобы разрешить JetBrains собирать анонимные данные об использовании K2.
Kotlin/JVM
Начиная с версии 2.0.0, компилятор может генерировать классы, содержащие байт-код Java 22. В этой версии также появились следующие изменения:
Генерация лямбда-функций с помощью invokedynamic
В Kotlin 2.0.0 появился новый способ генерации лямбда-функций по умолчанию с помощью invokedynamic. Это изменение уменьшает размер бинарных файлов приложений по сравнению с традиционной генерацией анонимных классов.
С самого первого релиза Kotlin генерировал лямбды в виде анонимных классов. Однако начиная с Kotlin 1.5.0 можно было включить генерацию invokedynamic с помощью параметра компилятора -Xlambdas=indy. В Kotlin 2.0.0 invokedynamic стал способом генерации лямбд по умолчанию. Этот способ позволяет создавать более компактные бинарные файлы и согласуется с оптимизациями JVM, благодаря чему приложения могут использовать текущие и будущие улучшения производительности JVM.
В настоящее время у этого способа есть три ограничения по сравнению с обычной компиляцией лямбд:
Лямбда, скомпилированная в
invokedynamic, не сериализуема.Экспериментальный API
reflect()не поддерживает лямбды, сгенерированные с помощьюinvokedynamic.Вызов
.toString()для такой лямбды приводит к менее удобному для чтения строковому представлению:
fun main() {
println({})
// With Kotlin 1.9.24 and reflection, returns
// () -> kotlin.Unit
// With Kotlin 2.0.0, returns
// FileKt$$Lambda$13/0x00007f88a0004608@506e1b77
}
Чтобы сохранить прежнее поведение при генерации лямбда-функций, можно:
Добавить аннотацию
@JvmSerializableLambdaк отдельным лямбдам.Использовать параметр компилятора
-Xlambdas=class, чтобы генерировать все лямбды в модуле прежним способом.
Библиотека 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 с помощью signposts на платформах Apple
Изменен уровень логирования аргументов компилятора в Kotlin/Native
В Kotlin/Native явно добавлены зависимости от стандартной библиотеки и платформы
Мониторинг производительности GC с помощью signposts на платформах Apple
Ранее отслеживать производительность сборщика мусора Kotlin/Native (GC) можно было только по журналам. Однако эти журналы не интегрировались с Xcode Instruments — популярным набором инструментов для поиска проблем с производительностью приложений для iOS.
Начиная с Kotlin 2.0.0, GC сообщает о паузах с помощью signposts, которые доступны в Instruments. 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 — такой сценарий поддерживается и протестирован. Общее подавление ошибок может скрыть важные ошибки и привести к незаметно нарушенной работе кода.
Изменен уровень логирования аргументов компилятора
В этом выпуске уровень логирования аргументов компилятора в задачах Gradle для Kotlin/Native, таких как compile, link и cinterop, изменен с 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.
Эта ошибка появляется в таких задачах, как NativeDistributionCommonizerTask и KotlinNativeCompile.
Однако эта ошибка является ложным срабатыванием. На самом деле проблема вызвана наличием задач, несовместимых с кэшем конфигурации Gradle, например задачи publish*.
Это несоответствие может быть не сразу очевидно, поскольку сообщение об ошибке указывает на другую первопричину.
Поскольку в отчете об ошибке не указана точная причина, команда Gradle уже работает над устранением этой проблемы с отчетами.
Kotlin/Wasm
В Kotlin 2.0.0 улучшены производительность и взаимодействие с JavaScript:
Оптимизация production-сборок по умолчанию с помощью Binaryen
Поддержка беззнаковых примитивных типов в функциях с
@JsExportДобавлена возможность использовать новое предложение по обработке исключений
Оптимизация production-сборок по умолчанию с помощью Binaryen
Теперь цепочка инструментов Kotlin/Wasm использует инструмент Binaryen при компиляции production-сборок всех проектов — в отличие от прежнего подхода с ручной настройкой. По нашим оценкам, это должно повысить производительность во время выполнения и уменьшить размер бинарного файла проекта.
Поддержка именованного экспорта
Ранее все экспортированные объявления из Kotlin/Wasm импортировались в JavaScript с помощью экспорта по умолчанию:
//JavaScript: import Module from "./index.mjs" Module.add()
Теперь каждое объявление Kotlin, помеченное аннотацией @JsExport, можно импортировать по имени:
// 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 читайте в документации.
Генерация файлов объявлений TypeScript в Kotlin/Wasm
В Kotlin 2.0.0 компилятор Kotlin/Wasm научился генерировать определения TypeScript для любых объявлений @JsExport в коде Kotlin. Эти определения могут использоваться IDE и инструментами JavaScript для автодополнения кода, проверки типов и упрощения включения кода Kotlin в JavaScript.
Компилятор Kotlin/Wasm собирает все функции верхнего уровня, помеченные аннотацией @JsExport, и автоматически генерирует определения TypeScript в файле .d.ts.
Чтобы сгенерировать определения TypeScript, добавьте функцию generateTypeScriptDefinitions() в блок wasmJs {} файла build.gradle(.kts):
kotlin {
wasmJs {
binaries.executable()
browser {
}
generateTypeScriptDefinitions()
}
}
Поддержка перехвата исключений JavaScript
Ранее код Kotlin/Wasm не мог перехватывать исключения JavaScript, поэтому обработка ошибок, возникающих на стороне JavaScript программы, была затруднена.
В Kotlin 2.0.0 мы добавили поддержку перехвата исключений JavaScript в Kotlin/Wasm. Теперь для правильной обработки таких ошибок можно использовать блоки try-catch с определенными типами, такими как Throwable или JsException.
Кроме того, блоки finally, которые позволяют выполнять код независимо от возникновения исключений, также работают правильно. Несмотря на добавление поддержки перехвата исключений 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:
Новая цель компиляции
В Kotlin 2.0.0 мы добавляем новую цель компиляции для Kotlin/JS — es2015. Это новый способ сразу включить все поддерживаемые в Kotlin возможности ES2015.
Её можно настроить в файле build.gradle(.kts) следующим образом:
kotlin {
js {
compilerOptions {
target.set("es2015")
}
}
}
Новая цель автоматически включает поддержку классов и модулей ES, а также недавно добавленную поддержку генераторов ES.
Suspend-функции в виде генераторов ES2015
В этом выпуске появилась экспериментальная поддержка генераторов ES2015 для компиляции suspend-функций.
Использование генераторов вместо конечных автоматов должно уменьшить размер итогового пакета вашего проекта. Например, команде JetBrains удалось уменьшить размер пакета проекта Space на 20% благодаря использованию генераторов ES2015.
Подробнее о ES2015 (ECMAScript 2015, ES6) читайте в официальной документации.
Передача аргументов главной функции
Начиная с Kotlin 2.0.0 можно указать источник для args функции main(). Эта возможность упрощает работу с командной строкой и передачу аргументов.
Для этого определите блок js {} с помощью новой функции passAsArgumentToMainFunction(), которая возвращает массив строк:
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. Чтобы включить режим покомпиляционной обработки файлов:
-
Добавьте в файл сборки функцию
useEsModules(), чтобы включить поддержку модулей ECMAScript:// build.gradle.kts kotlin { js(IR) { useEsModules() // Enables ES2015 modules browser() } }Для этого также можно использовать новую
es2015цель компиляции. -
Примените параметр компилятора
-Xir-per-fileили обновите файлgradle.properties, добавив:# gradle.properties kotlin.js.ir.output.granularity=per-file // `per-module` is the default
Улучшенное взаимодействие с коллекциями
Начиная с Kotlin 2.0.0 можно экспортировать в JavaScript (и TypeScript) объявления, в сигнатурах которых используются типы коллекций Kotlin. Это относится к типам коллекций Set, Map и List, а также к их изменяемым вариантам.
Чтобы использовать коллекции Kotlin в JavaScript, сначала пометьте нужные объявления аннотацией @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']
Поддержка createInstance()
Начиная с Kotlin 2.0.0, в целевом окружении Kotlin/JS можно использовать функцию createInstance(). Ранее она была доступна только в JVM.
Эта функция интерфейса KClass создаёт новый экземпляр указанного класса, что удобно для получения ссылки на класс Kotlin во время выполнения.
Поддержка типобезопасных простых объектов JavaScript
Чтобы упростить работу с API JavaScript, в 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() взаимодействует с API JavaScript, используя внешние интерфейсы для описания структуры объектов JavaScript:
import kotlinx.js.JsPlainObject
@JsPlainObject
external interface FetchOptions {
val body: String?
val method: String
}
// A wrapper for Window.fetch
suspend fun fetch(url: String, options: FetchOptions? = null) = TODO("Add your custom behavior here")
// A compile-time error is triggered as "metod" is not recognized
// as method
fetch("https://google.com", options = FetchOptions(metod = "POST"))
// A compile-time error is triggered as method is required
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")
// No error is triggered. As "metod" is not recognized, the wrong method
// (GET) is used.
fetch("https://google.com", options = js("{ metod: 'POST' }"))
// By default, the GET method is used. A runtime error is triggered as
// body shouldn't be present.
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) следующее:
plugins {
kotlin("plugin.js-plain-objects") version "2.0.0"
}
plugins {
id "org.jetbrains.kotlin.plugin.js-plain-objects" version "2.0.0"
}
Поддержка менеджера пакетов npm
Ранее плагин Kotlin Multiplatform Gradle мог использовать для загрузки и установки зависимостей npm только менеджер пакетов Yarn. Начиная с Kotlin 2.0.0 вместо него можно использовать npm. Использование npm в качестве менеджера пакетов означает, что при настройке проекта нужно управлять на один инструмент меньше.
Для обеспечения обратной совместимости Yarn по-прежнему используется по умолчанию. Чтобы использовать npm в качестве менеджера пакетов, задайте следующее свойство в файле gradle.properties:
kotlin.js.yarn = false
Изменения задач компиляции
Ранее задачи компиляции webpack и distributeResources обрабатывали одни и те же каталоги. Кроме того, задача distribution также объявляла dist в качестве выходного каталога. Это приводило к пересечению выходных данных и вызывало предупреждение компилятора.
Поэтому, начиная с Kotlin 2.0.0, мы внесли следующие изменения:
Теперь задача
webpackобрабатывает отдельную папку.Задача
distributeResourcesполностью удалена.Теперь задача
distributionимеет типCopyи обрабатывает папкуdist.
Отказ от устаревших артефактов JAR для Kotlin/JS
Начиная с Kotlin 2.0.0, дистрибутив Kotlin больше не содержит устаревшие артефакты Kotlin/JS с расширением .jar. Эти артефакты использовались в неподдерживаемом старом компиляторе Kotlin/JS и не требуются компилятору IR, который использует формат klib.
Улучшения Gradle
Kotlin 2.0.0 полностью совместим с Gradle версий от 6.8.3 до 8.5. Вы также можете использовать более поздние версии Gradle, вплоть до последнего выпуска, но имейте в виду, что при этом могут появиться предупреждения об устаревших функциях или некоторые новые возможности Gradle могут не работать.
В этой версии появились следующие изменения:
Новый DSL Gradle для параметров компилятора в мультиплатформенных проектах
Новый атрибут для различения опубликованных библиотек JVM и Android
Улучшена обработка зависимостей Gradle для CInteropProcess в Kotlin/Native
Новое свойство Gradle для опробования последней версии языка
Конфигурации kapt наследуют процессоры аннотаций из родительских конфигураций
Плагин Kotlin Gradle больше не использует устаревшие соглашения Gradle
Новый DSL Gradle для параметров компилятора в мультиплатформенных проектах
До Kotlin 2.0.0 настроить параметры компилятора в мультиплатформенном проекте с Gradle можно было только на низком уровне — например, для отдельной задачи, компиляции или набора исходного кода. Чтобы упростить настройку параметров компилятора для проектов в целом, в Kotlin 2.0.0 появился новый DSL Gradle.
С помощью нового DSL можно настраивать параметры компилятора на уровне расширения для всех целевых платформ и общих наборов исходного кода, таких как commonMain, а также на уровне целевой платформы для отдельной целевой платформы:
kotlin {
compilerOptions {
// Extension-level common compiler options that are used as defaults
// for all targets and shared source sets
allWarningsAsErrors.set(true)
}
jvm {
compilerOptions {
// Target-level JVM compiler options that are used as defaults
// for all compilations in this target
noJdk.set(true)
}
}
}
Теперь конфигурация всего проекта состоит из трех уровней. Самый высокий — уровень расширения, затем идет уровень целевой платформы, а самый низкий — единица компиляции (обычно это задача компиляции):
Параметры, заданные на более высоком уровне, используются в качестве соглашения (значения по умолчанию) для нижнего уровня:
Значения параметров компилятора расширения являются значениями по умолчанию для параметров компилятора целевой платформы, в том числе для общих наборов исходного кода, таких как
commonMain,nativeMainиcommonTest.Значения параметров компилятора целевой платформы используются по умолчанию для параметров компилятора единицы компиляции (задачи), например задач
compileKotlinJvmиcompileTestKotlinJvm.
В свою очередь, конфигурации, заданные на более низком уровне, переопределяют соответствующие настройки более высокого уровня:
Параметры компилятора на уровне задачи переопределяют соответствующие конфигурации на уровне целевой платформы или расширения.
Параметры компилятора на уровне целевой платформы переопределяют соответствующие конфигурации на уровне расширения.
Настраивая проект, имейте в виду, что некоторые старые способы задания параметров компилятора устарели.
Мы рекомендуем опробовать новый DSL в ваших мультиплатформенных проектах и оставить отзыв в YouTrack, поскольку мы планируем сделать этот DSL рекомендуемым способом настройки параметров компилятора.
Новый плагин Gradle для компилятора Compose
Компилятор Jetpack Compose, который преобразует composable-функции в код Kotlin, теперь перенесен в репозиторий Kotlin. Это поможет перевести проекты Compose на Kotlin 2.0.0, поскольку компилятор Compose всегда будет выпускаться одновременно с Kotlin. Кроме того, версия компилятора Compose повышена до 2.0.0.
Чтобы использовать новый компилятор Compose в своих проектах, примените плагин Gradle org.jetbrains.kotlin.plugin.compose в файле build.gradle(.kts) и укажите для него версию, равную Kotlin 2.0.0.
Подробнее об этом изменении и инструкциях по миграции см. в документации компилятора Compose.
Новый атрибут для различения опубликованных библиотек JVM и Android
Начиная с Kotlin 2.0.0, атрибут Gradle org.gradle.jvm.environment публикуется по умолчанию для всех вариантов Kotlin.
Этот атрибут помогает различать варианты JVM и Android библиотек Kotlin Multiplatform. Он указывает, что определенный вариант библиотеки лучше подходит для конкретной среды JVM. Целевой средой может быть "android", "standard-jvm" или "no-jvm".
Публикация этого атрибута также должна повысить надежность использования библиотек Kotlin Multiplatform с целевыми платформами JVM и Android из немультиплатформенных клиентов, например проектов только на Java.
При необходимости публикацию атрибута можно отключить. Для этого добавьте следующий параметр Gradle в файл gradle.properties:
kotlin.publishJvmEnvironmentAttribute=false
Улучшена обработка зависимостей Gradle для CInteropProcess в Kotlin/Native
В этом выпуске мы улучшили обработку свойства defFile, чтобы обеспечить более эффективное управление зависимостями задач Gradle в проектах Kotlin/Native.
До этого обновления сборки Gradle могли завершаться с ошибкой, если свойство defFile было указано в качестве выхода другой задачи, которая еще не была выполнена. Обходным решением этой проблемы было добавление зависимости от этой задачи:
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 вместо defFile и defFileProperty:
kotlin {
macosArm64("native") {
compilations.getByName("main") {
cinterops {
val cinterop by creating {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}
kotlin {
macosArm64("native") {
compilations.main {
cinterops {
cinterop {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}
Изменения видимости в Gradle
В Kotlin 2.0.0 мы изменили плагин Kotlin Gradle, чтобы обеспечить больший контроль и безопасность в сценариях сборки. Ранее некоторые функции и свойства Kotlin DSL, предназначенные для определенного контекста DSL, случайно становились доступными и в других контекстах DSL. Это могло привести к использованию неверных параметров компилятора, многократному применению настроек и другим ошибкам конфигурации:
kotlin {
// Target DSL couldn't access methods and properties defined in the
// kotlin{} extension DSL
jvm {
// Compilation DSL couldn't access methods and properties defined
// in the kotlin{} extension DSL and Kotlin jvm{} target DSL
compilations.configureEach {
// Compilation task DSLs couldn't access methods and
// properties defined in the kotlin{} extension, Kotlin jvm{}
// target or Kotlin compilation DSL
compileTaskProvider.configure {
// For example:
explicitApi()
// ERROR as it is defined in the kotlin{} extension DSL
mavenPublication {}
// ERROR as it is defined in the Kotlin jvm{} target DSL
defaultSourceSet {}
// ERROR as it is defined in the Kotlin compilation DSL
}
}
}
}
Чтобы устранить эту проблему, мы добавили аннотацию @KotlinGradlePluginDsl, которая не позволяет функциям и свойствам DSL плагина Kotlin Gradle становиться доступными на тех уровнях, для которых они не предназначены. Следующие уровни теперь отделены друг от друга:
Расширение 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.
Мы будем признательны за ваши отзывы об этом изменении! Поделитесь своими комментариями напрямую с разработчиками Kotlin в нашем канале #gradle в Slack. Получите приглашение в Slack.
Новый каталог для данных Kotlin в проектах Gradle
В 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 |
Описание |
|---|---|
|
Задает расположение для хранения данных на уровне проекта. По умолчанию: |
|
Логическое значение, определяющее, отключена ли запись данных Kotlin в каталог |
Чтобы эти свойства начали действовать, добавьте их в файл gradle.properties ваших проектов.
Загрузка компилятора Kotlin/Native по мере необходимости
До Kotlin 2.0.0, если в сценарии сборки Gradle вашего мультиплатформенного проекта была настроена целевая платформа Kotlin/Native, Gradle всегда загружал компилятор Kotlin/Native на этапе конфигурации.
Это происходило даже в том случае, если не было задач для компиляции кода целевой платформы Kotlin/Native, которые должны были выполняться на этапе выполнения. Такая загрузка компилятора Kotlin/Native была особенно неэффективна для пользователей, которым требовалось проверять в проектах только код JVM или JavaScript, например запускать тесты или проверки в рамках CI-процесса для проекта Kotlin.
В Kotlin 2.0.0 мы изменили это поведение в плагине Kotlin Gradle: теперь компилятор Kotlin/Native загружается на этапе выполнения и только при запросе компиляции для целевой платформы Kotlin/Native.
В свою очередь, зависимости компилятора Kotlin/Native теперь загружаются не вместе с компилятором, а также на этапе выполнения.
Если вы столкнетесь с проблемами из-за нового поведения, можно временно вернуться к прежнему, добавив следующее свойство Gradle в файл gradle.properties:
kotlin.native.toolchain.enabled=false
Начиная с Kotlin 1.9.20-Beta, дистрибутив Kotlin/Native публикуется в Maven Central и в CDN.
Это позволило нам изменить способ поиска и загрузки необходимых артефактов Kotlin. Теперь по умолчанию вместо CDN используются репозитории Maven, указанные в блоке repositories {} вашего проекта.
Вы можете временно вернуть прежнее поведение, задав следующее свойство Gradle в файле gradle.properties:
kotlin.native.distribution.downloadFromMaven=false
Сообщайте о любых проблемах в нашем трекере задач YouTrack. Оба этих свойства Gradle, изменяющие поведение по умолчанию, являются временными и будут удалены в будущих выпусках.
Устаревшие способы задания параметров компилятора
В этом выпуске мы продолжаем совершенствовать настройку параметров компилятора. Это должно устранить неоднозначность между различными способами настройки и упростить конфигурацию проекта.
Начиная с Kotlin 2.0.0, следующие DSL для указания параметров компилятора устарели:
DSL
kotlinOptionsиз интерфейсаKotlinCompile, реализуемого всеми задачами компиляции Kotlin. Вместо него используйтеKotlinCompilationTask<CompilerOptions>.-
Свойство
compilerOptionsс типомHasCompilerOptionsиз интерфейсаKotlinCompilation. Этот DSL не соответствовал другим DSL и настраивал тот же объектKotlinCommonCompilerOptions, что иcompilerOptionsвнутри задачи компиляцииKotlinCompilation.compileTaskProvider, что вызывало путаницу.Вместо него мы рекомендуем использовать свойство
compilerOptionsиз задачи компиляции Kotlin:kotlinCompilation.compileTaskProvider.configure { compilerOptions { ... } }Например:
kotlin { js(IR) { compilations.all { compileTaskProvider.configure { compilerOptions.freeCompilerArgs.add("-Xir-minimized-member-names=false") } } } } DSL
kotlinOptionsиз интерфейсаKotlinCompilation.DSL
kotlinOptionsиз интерфейсаKotlinNativeArtifactConfig, классаKotlinNativeLinkи классаKotlinNativeLinkArtifactTask. Вместо него используйте DSLtoolOptions.DSL
dceOptionsиз интерфейсаKotlinJsDce. Вместо него используйте DSLtoolOptions.
Подробнее о том, как указывать параметры компилятора в плагине Kotlin Gradle, см. в разделе Как задавать параметры.
Повышена минимальная поддерживаемая версия AGP
Начиная с Kotlin 2.0.0, минимальная поддерживаемая версия Android Gradle Plugin — 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 // The directory to store your build reports 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 наследуют процессоры аннотаций из родительских конфигураций
До 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")
}
В этом примере конфигурация Gradle commonAnnotationProcessors является общей конфигурацией обработки аннотаций, которую вы хотите использовать во всех проектах. С помощью метода extendsFrom() вы добавляете commonAnnotationProcessors в качестве родительской конфигурации. kapt обнаруживает, что конфигурация Gradle commonAnnotationProcessors зависит от процессора аннотаций Dagger. Поэтому kapt включает процессор аннотаций Dagger в конфигурацию для обработки аннотаций.
Благодарим Christoph Loy за реализацию!
Плагин Kotlin Gradle больше не использует устаревшие соглашения Gradle
До Kotlin 2.0.0 при использовании Gradle 8.2 или более поздней версии плагин Kotlin Gradle некорректно использовал соглашения Gradle, объявленные устаревшими в Gradle 8.2. Из-за этого Gradle сообщал об устаревших возможностях сборки. В Kotlin 2.0.0 плагин Kotlin Gradle обновлен и больше не вызывает предупреждения об устаревших возможностях при использовании Gradle 8.2 или более поздней версии.
Стандартная библиотека
Этот выпуск повышает стабильность стандартной библиотеки Kotlin и делает ещё больше существующих функций общими для всех платформ:
Стабильная замена универсальной функции values для enum-классов
В Kotlin 2.0.0 функция enumEntries<T>() становится стабильной. Функция enumEntries<T>() заменяет универсальную функцию enumValues<T>(). Новая функция возвращает список всех элементов перечисления для заданного типа перечисления T. Свойство entries для enum-классов было представлено ранее и также стало стабильным, заменив синтетическую функцию values(). Подробнее о свойстве entries см. в разделе Что нового в Kotlin 1.8.20.
Например:
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") }
}
}
}
}
}
Общее защищённое свойство AbstractMutableList.modCount
В этом выпуске свойство protected modCount интерфейса AbstractMutableList становится общим. Ранее свойство modCount было доступно на каждой платформе, но не для общей цели. Теперь вы можете создавать собственные реализации AbstractMutableList и обращаться к этому свойству в общем коде.
Свойство отслеживает количество структурных изменений коллекции. К ним относятся операции, изменяющие размер коллекции или список таким образом, что выполняющиеся итерации могут возвращать неверные результаты.
При реализации собственного списка вы можете использовать свойство modCount для регистрации и обнаружения изменений, выполненных одновременно.
Общая защищённая функция AbstractMutableList.removeRange
В этом выпуске функция protected интерфейса AbstractMutableList removeRange() становится общей. Ранее она была доступна на каждой платформе, но не для общей цели. Теперь вы можете создавать собственные реализации 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)
// Convert the string and store it in the 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 в сценариях сборки.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew20.html