Spec-Zone.ru › Kotlin 1.8

Создание и публикация многоплатформенной библиотеки – учебник

В этом учебнике вы узнаете, как создать многоплатформенную библиотеку для JVM, JS и Native платформ, написать общие тесты для всех платформ и опубликовать библиотеку в локальном репозитории Maven.

Эта библиотека преобразует исходные данные – строки и массивы байтов – в формат Base64. Она может использоваться на Kotlin/JVM, Kotlin/JS и любой доступной Kotlin/Native платформе.

Вы будете использовать разные способы реализации преобразования в формат Base64 на разных платформах:

  • Для JVM – класс java.util.Base64.

  • Для JS – функция btoa().

  • Для Kotlin/Native – ваша собственная реализация.

Вы также протестируете свой код с помощью общих тестов и затем опубликуете библиотеку в локальном репозитории Maven.

Настройка среды

Вы можете выполнить этот учебник на любой операционной системе. Скачайте и установите последнюю версию IntelliJ IDEA с последним плагином Kotlin.

Создание проекта

  1. В IntelliJ IDEA выберите Файл | Новый | Проект.

  2. В левой панели выберите Kotlin Multiplatform.

  3. Введите имя проекта, а затем в разделе Multiplatform выберите Библиотека в качестве шаблона проекта.

    Select a project template

    По умолчанию ваш проект будет использовать Gradle с Kotlin DSL в качестве системы сборки.

  4. Укажите JDK, который необходим для разработки проектов Kotlin.

  5. Нажмите Далее, а затем Готово.

Дополнительная конфигурация проекта

Для более сложных проектов вам может потребоваться добавить больше модулей и целей:

  • Для добавления модулей выберите Проект и нажмите значок +. Выберите тип модуля.

  • Для добавления целевых платформ выберите библиотеку и нажмите значок +. Выберите целевую платформу.

  • Настройте параметры целевой платформы, такие как версия целевой JVM и фреймворк для тестов.

    Configure the project
  • Если необходимо, укажите зависимости между модулями:

    • Многоплатформенные и Android модули

    • Многоплатформенные и iOS модули

    • JVM модули

    Configure the project

Мастер создаст пример многоплатформенной библиотеки со следующей структурой:

Multiplatform library structure

Написание кроссплатформенного кода

Определите классы и интерфейсы, которые вы собираетесь реализовать в общем коде.

  1. В каталоге commonMain/kotlin, создайте пакет org.jetbrains.base64.

  2. Создайте файл Base64.kt в новом пакете.

  3. Определите интерфейс Base64Encoder, который преобразует байты в формат Base64:

    package org.jetbrains.base64
    
    interface Base64Encoder {
        fun encode(src: ByteArray): ByteArray
    }
    
  4. Определите объект Base64Factory, чтобы предоставить экземпляр интерфейса Base64Encoder общему коду:

    expect object Base64Factory {
        fun createEncoder(): Base64Encoder
    }
    

Объект-фабрика помечен ключевым словом expect в кроссплатформенном коде. Для каждой платформы вы должны предоставить реализацию actual объекта Base64Factory с платформенно-специфическим кодером. Подробнее о платформенно-специфических реализациях.

Предоставление платформенно-специфических реализаций

Теперь вы создадите actual реализации объекта Base64Factory для каждой платформы:

  • JVM

  • JS

  • Native

JVM

  1. В каталоге jvmMain/kotlin, создайте пакет org.jetbrains.base64.

  2. Создайте файл Base64.kt в новом пакете.

  3. Предоставьте простую реализацию объекта Base64Factory, которая делегирует классу java.util.Base64:

    Инспекции IDEA помогают создавать actual реализации для объявления expect.

    package org.jetbrains.base64
    import java.util.*
    
    actual object Base64Factory {
        actual fun createEncoder(): Base64Encoder = JvmBase64Encoder
    }
    
    object JvmBase64Encoder : Base64Encoder {
        override fun encode(src: ByteArray): ByteArray = Base64.getEncoder().encode(src)
    }
    

Довольно просто, не так ли? Вы предоставили платформенно-специфическую реализацию, используя прямое делегирование сторонней реализации.

JS

Реализация JS будет очень похожа на JVM.

  1. В каталоге jsMain/kotlin, создайте пакет org.jetbrains.base64.

  2. Создайте файл Base64.kt в новом пакете.

  3. Предоставьте простую реализацию объекта Base64Factory, которая делегирует функции btoa().

    package org.jetbrains.base64
    
    import kotlinx.browser.window
    
    actual object Base64Factory {
        actual fun createEncoder(): Base64Encoder = JsBase64Encoder
    }
    
    object JsBase64Encoder : Base64Encoder {
        override fun encode(src: ByteArray): ByteArray {
            val string = src.decodeToString()
            val encodedString = window.btoa(string)
            return encodedString.encodeToByteArray()
        }
    }
    

Native

К сожалению, для всех целей Kotlin/Native нет доступной сторонней реализации, поэтому вам нужно написать ее самостоятельно.

  1. В каталоге nativeMain/kotlin, создайте пакет org.jetbrains.base64.

  2. Создайте файл Base64.kt в новом пакете.

  3. Предоставьте собственную реализацию объекта Base64Factory:

    package org.jetbrains.base64
    
    private val BASE64_ALPHABET: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    private val BASE64_MASK: Byte = 0x3f
    private val BASE64_PAD: Char = '='
    private val BASE64_INVERSE_ALPHABET = IntArray(256) {
        BASE64_ALPHABET.indexOf(it.toChar())
    }
    
    private fun Int.toBase64(): Char = BASE64_ALPHABET[this]
    
    actual object Base64Factory {
        actual fun createEncoder(): Base64Encoder = NativeBase64Encoder
    }
    
    object NativeBase64Encoder : Base64Encoder {
        override fun encode(src: ByteArray): ByteArray {
            fun ByteArray.getOrZero(index: Int): Int = if (index >= size) 0 else get(index).toInt()
            // 4n / 3 is expected Base64 payload
            val result = ArrayList<Byte>(4 * src.size / 3)
            var index = 0
            while (index < src.size) {
                val symbolsLeft = src.size - index
                val padSize = if (symbolsLeft >= 3) 0 else (3 - symbolsLeft) * 8 / 6
                val chunk = (src.getOrZero(index) shl 16) or (src.getOrZero(index + 1) shl 8) or src.getOrZero(index + 2)
                index += 3
    
                for (i in 3 downTo padSize) {
                    val char = (chunk shr (6 * i)) and BASE64_MASK.toInt()
                    result.add(char.toBase64().code.toByte())
                }
                // Fill the pad with '='
                repeat(padSize) { result.add(BASE64_PAD.code.toByte()) }
            }
    
            return result.toByteArray()
        }
    }
    

Тестирование вашей библиотеки

Теперь, когда у вас есть actual реализации объекта Base64Factory для всех платформ, пришло время протестировать вашу многоплатформенную библиотеку.

Чтобы сэкономить время на тестировании, вы можете написать общие тесты, которые будут выполняться на всех платформах вместо тестирования каждой платформы по отдельности.

Предварительные условия

Перед написанием тестов добавьте метод encodeToString с реализацией по умолчанию в интерфейс Base64Encoder, который определен в commonMain/kotlin/org/jetbrains/base64/Base64.kt. Эта реализация преобразует массивы байтов в строки, что значительно упрощает тестирование.

interface Base64Encoder {
    fun encode(src: ByteArray): ByteArray

    fun encodeToString(src: ByteArray): String {
        val encoded = encode(src)
        return buildString(encoded.size) {
            encoded.forEach { append(it.toInt().toChar()) }
        }
    }
}

Вы также можете предоставить более эффективную реализацию этого метода для конкретной платформы, например, для JVM в jvmMain/kotlin/org/jetbrains/base64/Base64.kt:

object JvmBase64Encoder : Base64Encoder {
    override fun encode(src: ByteArray): ByteArray = Base64.getEncoder().encode(src)
    override fun encodeToString(src: ByteArray): String = Base64.getEncoder().encodeToString(src)
}

Одним из преимуществ многоплатформенной библиотеки является наличие реализации по умолчанию с опциональными платформно-специфическими переопределениями.

Написание общих тестов

Теперь у вас есть API, основанный на строках, который можно покрыть базовыми тестами.

  1. В каталоге commonTest/kotlin создайте пакет org.jetbrains.base64.

  2. Создайте файл Base64Test.kt в новом пакете.

  3. Добавьте тесты в этот файл:

    package org.jetbrains.base64
    
    import kotlin.test.Test
    import kotlin.test.assertEquals
    
    class Base64Test {
        @Test
        fun testEncodeToString() {
            checkEncodeToString("Kotlin is awesome", "S290bGluIGlzIGF3ZXNvbWU=")
        }
    
        @Test
        fun testPaddedStrings() {
            checkEncodeToString("", "")
            checkEncodeToString("1", "MQ==")
            checkEncodeToString("22", "MjI=")
            checkEncodeToString("333", "MzMz")
            checkEncodeToString("4444", "NDQ0NA==")
        }
    
        private fun checkEncodeToString(input: String, expectedOutput: String) {
            assertEquals(expectedOutput, Base64Factory.createEncoder().encodeToString(input.asciiToByteArray()))
        }
    
        private fun String.asciiToByteArray() = ByteArray(length) {
            get(it).code.toByte()
        }
    }
    
  4. В Терминале выполните задачу Gradle check:

    ./gradlew check
    

    Вы также можете запустить задачу Gradle check двойным щелчком по ней в списке задач Gradle.

Тесты будут выполняться на всех платформах (JVM, JS и Native).

Добавление платформно-специфических тестов

Вы также можете добавить тесты, которые будут выполняться только для определенной платформы. Например, вы можете добавить тесты UTF-16 на JVM:

  1. В каталоге jvmTest/kotlin создайте пакет org.jetbrains.base64.

  2. Создайте файл Base64Test.kt в новом пакете.

  3. Добавьте тесты в этот файл:

    package org.jetbrains.base64
    
    import kotlin.test.Test
    import kotlin.test.assertEquals
    
    class Base64JvmTest {
        @Test
        fun testNonAsciiString() {
            val utf8String = "Gödel"
            val actual = Base64Factory.createEncoder().encodeToString(utf8String.toByteArray())
            assertEquals("R8O2ZGVs", actual)
        }
    }
    

Этот тест будет автоматически выполняться на платформе JVM помимо общих тестов.

Опубликовать вашу библиотеку в локальный репозиторий Maven

Ваша многоплатформенная библиотека готова к публикации, чтобы вы могли использовать её в других проектах.

Для публикации вашей библиотеки используйте плагин Gradle maven-publish.

  1. В файле build.gradle.kts примените плагин maven-publish и укажите группу и версию вашей библиотеки:

    plugins {
        kotlin("multiplatform") version "1.8.0"
        id("maven-publish")
    }
    
    group = "org.jetbrains.base64"
    version = "1.0.0"
    
  2. В Терминале выполните задачу Gradle publishToMavenLocal для публикации вашей библиотеки в локальный репозиторий Maven:

    ./gradlew publishToMavenLocal
    

    Вы также можете запустить задачу Gradle publishToMavenLocal двойным щелчком по ней в списке задач Gradle.

Ваша библиотека будет опубликована в локальном репозитории Maven.

Опубликуйте свою библиотеку в внешний репозиторий Maven Central

Вы можете опубликовать свою многоплатформенную библиотеку в Maven Central, удаленный репозиторий, где хранятся и управляются артефакты Maven. Таким образом, другие разработчики смогут найти её и добавить в качестве зависимости в свои проекты.

Зарегистрировать учётную запись Sonatype и сгенерировать ключи GPG

Если это ваша первая библиотека или вы раньше использовали устаревший Bintray, вам сначала нужно зарегистрировать учётную запись Sonatype.

Вы можете воспользоваться статьёй GetStream для создания и настройки своей учётной записи. В разделе Регистрация учётной записи Sonatype описано, как:

  1. Зарегистрировать учётную запись Sonatype Jira.

  2. Создать новую задачу. Вы можете использовать нашу задачу в качестве примера.

  3. Подтвердить владение доменом, соответствующим идентификатору группы, который вы хотите использовать для публикации артефактов.

Затем, поскольку артефакты, опубликованные в Maven Central, должны быть подписаны, выполните действия из раздела Генерация пары ключей GPG, чтобы:

  1. Сгенерировать пару ключей GPG для подписи артефактов.

  2. Опубликовать ваш открытый ключ.

  3. Экспортировать ваш закрытый ключ.

После подготовки репозитория Maven и ключей подписи для вашей библиотеки, вы можете перейти к настройке сборки для загрузки артефактов библиотеки в репозиторий подготовки, а затем опубликовать их.

Настройка публикации

Теперь вам нужно указать Gradle, как опубликовать библиотеку. Большая часть работы уже выполнена плагинами Gradle для maven-publish и Kotlin, все необходимые публикации создаются автоматически. Вы уже знаете результат, когда библиотека опубликована в локальный репозиторий Maven. Чтобы опубликовать её в Maven Central, вам необходимо выполнить дополнительные шаги:

  1. Настройте URL-адрес публичного репозитория Maven и учетные данные.

  2. Укажите описание и javadocs для всех компонентов библиотеки.

  3. Подпишите публикации.

Вы можете выполнить все эти задачи с помощью скриптов Gradle. Давайте вынесем всю логику, связанную с публикацией, из модуля библиотеки build.script, чтобы в будущем легко её повторно использовать для других модулей.

Самый удобный и гибкий способ сделать это — использовать предварительно скомпилированные скриптовые плагины Gradle прекомпилированные скриптовые плагины. Вся логика сборки будет предоставляться в виде предварительно скомпилированного скриптового плагина и может применяться по идентификатору плагина ко всем модулям нашей библиотеки.

Для этого перенесите логику публикации в отдельный проект Gradle:

  1. Добавьте новый проект Gradle в корневой проект вашей библиотеки. Для этого создайте новую папку с именем convention-plugins с файлом src/build.gradle.kts в ней.

  2. Обновите этот файл build.gradle.kts следующим кодом:

    plugins {
        "kotlin-dsl" // Is needed to turn our build logic written in Kotlin into the Gradle Plugin
    }
    
    repositories {
        gradlePluginPortal() // To use 'maven-publish' and 'signing' plugins in our own plugin
    }
    
  3. В директории convention-plugins/src создайте файл main/kotlin/convention.publication.gradle.kts для хранения всей логики публикации.

  4. Добавьте всю необходимую логику в новый файл:

    import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.`maven-publish` import org.gradle.kotlin.dsl.signing import java.util.* plugins { 'maven-publish' signing } // Заглушка для секретов, чтобы проект синхронизировался и собирался без значений публикации ext["signing.keyId"] = null ext["signing.password"] = null ext["signing.secretKeyRingFile"] = null ext["ossrhUsername"] = null ext["ossrhPassword"] = null // Получение секретов из файла local.properties или из переменных окружения, которые могут использоваться в CI val secretPropsFile = project.rootProject.file("local.properties") if (secretPropsFile.exists()) { secretPropsFile.reader().use { Properties().apply { load(it) } }.onEach { (name, value) -> ext[name.toString()] = value } } else { ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") ext["signing.password"] = System.getenv("SIGNING_PASSWORD") ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD") } val javadocJar by tasks.registering(Jar::class) { archiveClassifier.set("javadoc") } fun getExtraString(name: String) = ext[name]?.toString() publishing { // Настройка репозитория maven central repositories { maven { name = "sonatype" setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") credentials { username = getExtraString("ossrhUsername") password = getExtraString("ossrhPassword") } } } // Настройка всех публикаций publications.withType<MavenPublication> { // Заглушка для артефакта javadoc.jar artifact(javadocJar.get()) // Предоставление информации об артефактах, необходимой Maven Central pom { name.set("MPP Sample library") description.set("Sample Kotlin Multiplatform library (jvm + ios + js) test") url.set("https://github.com/<your-github-repo>/mpp-sample-lib") licenses { license { name.set("MIT") url.set("https://opensource.org/licenses/MIT") } } developers { developer { id.set("<your-github-profile>") name.set("<your-name>") email.set("<your-email>") } } scm { url.set("https://github.com/<your-github-repo>/mpp-sample-lib") } } } } // Подпись артефактов. Значения свойств Signing.* будут использоваться signing { sign(publishing.publications) }

    Применения только maven-publish достаточно для публикации в локальном репозитории Maven, но не в Maven Central. В предоставленном скрипте вы получаете учетные данные из local.properties или переменных окружения, выполняете всю необходимую настройку в разделе publishing, и подписываете свои публикации с помощью плагина подписи.

  5. Вернитесь к своему проекту библиотеки. Чтобы попросить Gradle предварительно собрать ваши плагины, обновите корневой файл settings.gradle.kts следующим образом:

    rootProject.name = "multiplatform-lib" // your project name
    includeBuild("convention-plugins")
    
  6. Теперь вы можете применить эту логику в модуле build.script библиотеки. В разделе plugins замените maven-publish на conventional.publication.

    plugins {
        kotlin("multiplatform") version "1.8.0"
        id("convention.publication")
    }
    
  7. Создайте файл local.properties со всеми необходимыми учетными данными и убедитесь, что добавили его в .gitignore.

    # The GPG key pair ID (last 8 digits of its fingerprint)
    signing.keyId=...
    # The passphrase of the key pair
    signing.password=...
    # Private key you exported earlier
    signing.secretKeyRingFile=...
    # Your credentials for the Jira account
    ossrhUsername=...
    ossrhPassword=...
    
  8. Запустите ./gradlew clean и синхронизируйте проект.

Новые задачи Gradle, связанные с репозиторием Sonatype, должны появиться в группе публикации, это означает, что всё готово для публикации вашей библиотеки.

Опубликуйте свою библиотеку в Maven Central

Чтобы загрузить свою библиотеку в репозиторий Sonatype, выполните следующую команду:

./gradlew publishAllPublicationsToSonatypeRepository

Репозиторий подготовки будет создан, и все артефакты всех публикаций будут загружены в этот репозиторий. Осталось проверить, что все необходимые артефакты были загружены, и нажать кнопку «Опубликовать».

Эти шаги описаны в разделе Ваша первая публикация. Вкратце, вам нужно:

  1. Перейдите на https://s01.oss.sonatype.org и войдите в систему, используя ваши учетные данные в Sonatype Jira.

  2. Найдите свой репозиторий в разделе Репозитории подготовки.

  3. Закройте его.

  4. Опубликуйте библиотеку.

  5. Для активации синхронизации с Maven Central вернитесь к созданной задаче Jira и оставьте комментарий, что вы опубликовали свой первый компонент. Этот шаг необходим только при первой публикации.

Вскоре ваша библиотека станет доступна по адресу https://repo1.maven.org/maven2, и другие разработчики смогут добавить её в качестве зависимости. Через пару часов другие разработчики смогут найти её с помощью Поиска репозитория Maven Central.

Добавление зависимости на опубликованную библиотеку

Вы можете добавить свою библиотеку в другие проекты многоплатформенной разработки в качестве зависимости.

В файле build.gradle.kts добавьте mavenLocal() или MavenCentral() (если библиотека была опубликована в внешний репозиторий) и добавьте зависимость от вашей библиотеки:

repositories {
    mavenCentral()
    mavenLocal()
}

kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.base64:multiplatform-lib:1.0.0")
            }
        }
    }
}

Зависимость implementation состоит из:

  • Идентификатор группы и версия — указанные ранее в файле build.gradle.kts

  • Идентификатор артефакта — по умолчанию, это имя вашего проекта, указанное в файле settings.gradle.kts

Для получения более подробной информации см. документацию Gradle по плагину maven-publish

Что дальше?

  • Узнайте больше о публикации многоплатформенных библиотек.

  • Узнайте больше о Kotlin Multiplatform.

  • Создание первого кроссплатформенного мобильного приложения — учебник.

  • Разработка полнофункционального веб-приложения с Kotlin Multiplatform — учебник.

Последнее изменение: 10 января 2023
Разработка полнофункционального веб-приложения с Kotlin Multiplatform Публикация многоплатформенных библиотек

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/multiplatform-library.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API