Мультиплатформенные проекты
Мультиплатформенные проекты - это новая экспериментальная возможность Kotlin 1.2. Все описываемые здесь возможности языка могут претерпеть изменения в последующих версиях Kotlin.
Мультиплатформенные проекты Kotlin позволяют вам компилировать один и тот же код для многих целевых платформ. На данный момент поддерживаемыми целевыми платформами являются JVM и JS, с добавлением Native в перспективе.
Структура мультиплатформенного проекта
Мультиплатформенный проект состоит из трех типов модулей:
- Общий модуль содержит код, который не свойствен какой-либо определённой платформе, а также объявления для реализации в платформо-зависимых API. Эти объявления позволяют общему коду быть зависимостью для реализаций для конкретных платформ.
- Платформенный модуль содержит реализации платформо-зависимых объявлений из общего модуля для конкретной платформы и другой платформенный код. Платформенный модуль всегда является реализацией одного общего модуля.
- Обычный модуль. Такие модули базируются на определённой платформе и могут либо быть зависимостью платформенных модулей, либо зависеть от них.
Общий модуль может зависеть только от других общих модулей и библиотек, включая общую
версию стандартной библиотеки Kotlin (kotlin-stdlib-common
). Общие модули содержат только код на Kotlin
и ни на каких иных языках.
Платформенный модуль можеть зависеть от любых модулей и библиотек для заданной платформы (включая библиотеки Java в случае с Kotlin/JVM и библиотеки JavaScript для Kotlin/JS). Платформенные модули для Kotlin/JVM также могут содержать код на Java и других языках для JVM.
Результат компиляции общего модуля - специальный файл метаданных, содержащий все объявления в этом модуле. Результат компиляции платформенного модуля - код для заданной платформы (байт-код JVM или исходный код JS) для исходного кода Kotlin как в платформенном, так и в реализуемом общем модуле.
Следовательно, каждую мультиплатформенную библиотеку необходимо распространять как набор артефактов - .jar с метаданными для общего кода и несколько .jar для каждого платформенного модуля.
Подготовка мультиплатформенного проекта
Kotlin 1.2 поддерживает сборку мультиплатформенных проектов только с Gradle; другие системы сборки не поддерживаются.
Для создания нового мультиплатформенного проекта в IDE откройте окно создания проекта и выберите опцию "Kotlin (Multiplatform)" во вкладке "Kotlin". IDE создаст проект с тремя модулями: общий и два платформенных для JVM и JS. Для добавления дополнительных модулей откройте окно создания модуля и выберите одну из опций "Kotlin (Multiplatform)" во вкладке "Gradle".
Если вам необходимо настроить проект вручную:
- Добавьте плагин Kotlin для Gradle в classpath скрипта сборки:
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- Примените плагин
kotlin-platform-common
к общему модулю - Добавьте зависимость
kotlin-stdlib-common
к общему модулю - Примените плагины
kotlin-platform-jvm
иkotlin-platform-js
для соответствующих платформенных модулей - Добавьте зависимости с областью
expectedBy
от платформенных модулей к общему
Пример файла build.gradle
для общего модуля с Kotlin 1.2-Beta:
buildscript {
ext.kotlin_version = '{{ site.data.releases.latest.version }}'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin-platform-common'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
}
Пример файла build.gradle
для платформенного модуля JVM. Обратите внимание на expectedBy
в блоке dependencies
:
buildscript {
ext.kotlin_version = '{{ site.data.releases.latest.version }}'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin-platform-jvm'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
expectedBy project(":")
testCompile "junit:junit:4.12"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}
Платформо-зависимые объявления
Одна из ключевых возможностей мультиплатформенного кода Kotlin - способ организации зависимости общего кода от платформенного. В других языках используется методика создания набора интерфейсов в общем коде и их реализация в платформенном. Однако, этот подход не идеален в тех случаях, когда у вас есть библиотека для целевой платформы, имеющая нужную вам функциональность, и вы хотите использовать API этой библиотеки напрямую, без дополнительных обёрток. Также, вы будете вынуждены представлять все общие объявления в виде интерфейсов, что не всегда применимо.
Kotlin же имеет механизм ожидаемых и актуальных объявлений. С этим механизмом общий модуль может иметь ожидаемые объявления, а платформенный модуль - актуальные объявления, соответствующие ожидаемым. Чтобы понять, как это работает, взглянем на пример. Отрывок кода общего модуля:
package org.jetbrains.foo
expect class Foo(bar: String) {
fun frob()
}
fun main(args: Array<String>) {
Foo("Hello").frob()
}
Код соответствующего платформенного JVM-модуля:
package org.jetbrains.foo
actual class Foo actual constructor(val bar: String) {
actual fun frob() {
println("Frobbing the $bar")
}
}
Пример описывает несколько важных моментов:
- Ожидаемые объявления в общих модулях и соответствующие им актуальные объявления всегда имеют абсолютно одинаковые имена.
- Ожидаемые объявления помечены ключевым словом
expect
, а актуальные -actual
. - Все актуальные объявления, соответствующие любому ожидаемому объявлению,
должны быть помечены ключевым словом
actual
. - Ожидаемые объявления никогда не реализуются в общем модуле.
Заметьте, что не только интерфейсы и их члены могут быть помечены как ожидаемые.
В этом примере ожидаемый класс имеет конструктор, и его объекты могут быть созданы прямо из общего кода.
Вы также можете применять модификатор expect
к объявлениям на верхнем уровне и аннотациям:
// Общий код
expect fun formatString(source: String, vararg args: Any): String
expect annotation class Test
// JVM
actual fun formatString(source: String, vararg args: Any) =
String.format(source, args)
actual typealias Test = org.junit.Test
Компилятор следит за тем, чтобы каждое ожидаемое объявление имело соответствующее актуальное объявление во всех платформенных модулях, реализующих соответствующий общий модуль, и сообщает об ошибке если какие-либо актуальные объявления отсутствуют. IDE имеет средства, которые могут помочь создать отсутствующие ожидаемые объявления.
Если у вас есть платформенная библиотека, которую вы хотите использовать в общем коде и одновременно иметь собственную реализацию для другой платформы, вы можете создать псевдоним для существующего класса как актуальное объявление:
expect class AtomicRef<V>(value: V) {
fun get(): V
fun set(value: V)
fun getAndSet(value: V): V
fun compareAndSet(expect: V, update: V): Boolean
}
actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>
Мультиплатформенные тесты
Вы можете иметь тесты в общем проекте, которые будут скомпилированы и запущены в каждом платформенном проекте.
Для этого в пакете kotlin.test
есть 4 аннотации дя пометки тестов в общем коде: @Test
, @Ignore
,
@BeforeTest
и @AfterTest
.
На платформе JVM эти аннотации соответствуют аннотациям JUnit 4, а в модуле для JS они уже доступны
с версии Kotlin 1.1.4 для поддержки модульного тестирования кода JS.
Для их использования добавьте зависимость kotlin-test-annotations-common
к общему модулю,
kotlin-test-junit
к платформенному модулю JVM и kotlin-test-js
к модулю для JS.