Привет, Хабр! На связи Виталий Зарубин, ведущий разработчик в Открытой мобильной платформе. В этой статье хочу рассказать о текущей поддержке Kotlin Multiplatform в операционной системе Аврора. Показать, как создать приложение ОС Аврора с модулем Kotlin Multiplatform, используя наши новые инструменты. Мы напишем демонстрационное приложение, отображающее взаимодействие KMP и ОС Аврора.

Для самостоятельного изучения доступен большой пример приложения с использованием базы данных, запросов в сеть — StudentBox. StudentBox позволяет записывать и редактировать список уроков на день для студента/ученика для Android и ОС Аврора:

Инструменты разработчика

Основные инструменты для разработки приложений под ОС Аврора — Qt/C++ QML. Qt/C++ дают высокую производительность, стабильность, прямой доступ к API ОС Аврора.

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

В релиз вышел фреймворк Flutter. В фреймворк была добавлена поддержка ОС Аврора. Идет работа над его улучшением, доработка существующих плагинов и написание новых. Сейчас доступно уже более 50 плагинов, и портирование приложения Flutter под ОС Аврора не составляет большого труда. А язык Dart снижает порог входа в разработку, относительно С++.

Гибридные приложения на базе WebView/CEF позволяют писать приложения на JavaScript фреймворках, таких как React, Angular, Vue и т.д.

Ведутся работы по поддержке PWA (прогрессивные веб-приложения). Планируется добавить поддержку в ОС Аврора 5.2, то есть, в недалеком будущем.

Подробнее ознакомиться со всеми инструментами можно на сайте developer.auroraos.ru.

Kotlin Multiplatform

Но мы не забыли о Kotlin Multiplatform, работы по поддержке ведутся давно, мы пробовали разные подходы, и вот мы рады представить инструменты поддержки KMP в ОС Аврора в Open-Source!

Kotlin Multiplatform (KMP) — технология, позволяющая переиспользовать код для разных платформ, написанный на Kotlin. Она позволяет вынести общую бизнес-логику в библиотеку, адаптированную для платформ Android, iOS, Web, Linux. Благодаря наличию Kotlin/Native делиться кодом можно везде, где есть возможность использовать С-библиотеки.

В ОС Аврора, как и в iOS, нет JVM. Kotlin поддерживает нативную компиляцию (Kotlin/Native), которая позволяет собрать статическую или динамическую библиотеку из Kotlin Multiplatform проекта. Низкоуровневые С-библиотеки, генерируемые KMP, нельзя назвать удобными для использования. В ОС Аврора эта проблема решена с помощью плагина QtBinding, который создаёт прослойку между низкоуровневым C и Qt. Поэтому писать приложения для ОС Аврора стало не сложнее, чем для iOS.

В Qt легко интегрируются С-библиотеки Kotlin/Native. Тем не менее такой подход сопряжён с некоторыми трудностями во время разработки:

  • генерируемый заголовочный файл содержит огромное количество строк;

  • экспортируемый интерфейс не поддерживает классы из сторонних библиотек;

  • ресурсы необходимо вручную освобождать с помощью вспомогательных экспортируемых функций;

  • в экспортируемом интерфейсе отсутствует объектно-ориентированный интерфейс классов;

  • не экспортируется интерфейс, поддерживающий работу с Coroutines.

QtBindings

Для упрощения работы с Kotlin/Native был разработан Gradle-плагин QtBindings, позволяющий адаптировать С-библиотеку для удобного использования в Qt. Плагин автоматически избавляет разработчика от лишней работы:

  • обновляет API экспортируемого Kotlin кода;

  • упрощает работу со стандартными коллекциями;

  • создаёт Qt-классы для экспортируемых Kotlin-классов;

  • автоматически освобождает ресурсы.

Задача QtBindings — автоматизировать и упростить работу с Kotlin/Native. Он позволяет:

  • генерировать обёртки для классов и функций;

  • использовать Coroutines через QFuture;

  • работать с Kotlin-списками (List, MutableList) через QList.

Разработчику достаточно указать аннотацию @QtExport для классов или функций. QtBindings создаст все необходимы файлы и классы, которые можно будет подключить к приложению для ОС Аврора.

Планы по исследованиям KMP и CMP

Это начало развития инструментов, еще предстоит их упростить, оптимизировать. Но уже сейчас можно переиспользовать ваш код Kotlin в ОС Аврора, включаться в разработку инструментов — мы всегда будем рады предложениям по доработкам, замечаниям и мерж-реквестам.

Сейчас мы говорим о технологии KMP — использование общей бизнес логики в приложениях ОС Аврора. Compose Multiplatform будет позже, он есть в планах:

Проект с QtBindings

Создадим проект, «Hello, world», с использованием Kotlin Multiplatform для ОС Аврора. Покажем как создать проект с нуля, использовать методы KMP в ОС Аврора, синхронные и асинхронные (suspend). Для этого нам потребуются инструменты:

  • Аврора SDK — набор инструментов для сборки, разработки, отладки и тестирования прикладного ПО для ОС Аврора.

  • IntelliJ IDEA Community Edition — IDE для профессиональной разработки на Java и Kotlin.

Kotlin Multiplatform используется стандартный, доступный в IDEA. Более детально ознакомится с Kotlin Multiplatform можно на сайте документации: Get started with Kotlin Multiplatform. Для создания приложения используются цели Kotlin/Native linuxX64 и linuxArm64.

Демонстрационное приложение будет максимально простое для данной темы, главная его задача показать взаимодействие KMP и ОС Аврора. Мы выполним методы синхронные и асинхронные, затем выведем данные в терминал:

Проект Kotlin Multiplatform

Для создания библиотеки Kotlin Multiplatform можно воспользоваться сайтом Kotlin Multiplatform Wizard. Необходимый шаблон библиотеки называется Multiplatform Library:

По умолчанию в шаблоне Multiplatform Library добавлены все доступные цели сборки. Для реализации библиотеки под проект ОС Аврора необходимо оставить только цель linuxX64Main. Всё лишнее можно удалить.

После правок структура проекта должна выглядеть так:

.
├── build.gradle.kts
├── .gitignore
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library
│   ├── build.gradle.kts
│   └── src
│       ├── commonMain
│       │   └── kotlin
│       │       └── CustomFibi.kt
│       └── linuxX64Main
│           └── kotlin
│               └── fibiprops.linuxX64.kt
├── README.md
└── settings.gradle.kts

9 directories, 13 files

Название библиотеки будет часто фигурировать в проекте. Для удобства его можно поместить в library/gradle.properties:

#Library
libName=demo_kmp

На данный момент в шаблоне есть цель linuxX64, для поддержки архитектуры aarch64 необходимо добавить цель linuxArm64. В названиях целей можно обозначить задачу создания библиотеки Kotlin/Native для ОС Аврора.

plugins {
    alias(libs.plugins.kotlinMultiplatform)
}

group = "ru.auroraos.demo"
version = "0.0.1"

kotlin {
    linuxX64("auroraX64") {
        binaries {
            staticLib {
                baseName = findProperty("libName").toString()
            }
        }
    }
    linuxArm64("auroraArm64") {
        binaries {
            staticLib {
                baseName = findProperty("libName").toString()
            }
        }
    }
    sourceSets {
        val commonMain by getting {
            dependencies {
                //put your multiplatform dependencies here
            }
        }
    }
}

Необходимо создать директории auroraArm64Main и auroraX64Main из существующего шаблона linuxX64Main.

Обратите внимание на директории и названия файлов в них.

После правок целей структура проекта должна выглядеть так:

.
├── build.gradle.kts
├── .gitignore
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library
│   ├── build.gradle.kts
│   └── src
│       ├── auroraArm64Main
│       │   └── kotlin
│       │       └── fibiprops.auroraArm64.kt
│       ├── auroraX64Main
│       │   └── kotlin
│       │       └── fibiprops.auroraX64.kt
│       └── commonMain
│           └── kotlin
│               └── CustomFibi.kt
├── README.md
└── settings.gradle.kts

11 directories, 14 files

Чтобы убедиться, что все правки внесены верно, можно собрать проект:

./gradlew linkReleaseStaticAuroraX64 linkReleaseStaticAuroraX64

В конце лога сборки должно быть выведено сообщение: BUILD SUCCESSFUL.

Шаблон под разработку библиотеки с бизнес-логикой приложения для ОС Аврора готов. Позже будет добавлено все необходимое для того, чтобы выполнять функции из библиотеки в приложении. Но сначала необходимо создать приложение.

Проект ОС Аврора

Аврора IDE, входящая в состав Аврора SDK, позволяет создать приложение из доступных шаблонов. Для этого необходимо открыть Аврора IDE, нажать New в разделе Welcome → Projects.

После конфигурации проекта структура проекта должна выглядеть так:

.
├── icons
│   ├── 108x108
│   │   └── ru.auroraos.demo_kmp.png
│   ├── 128x128
│   │   └── ru.auroraos.demo_kmp.png
│   ├── 172x172
│   │   └── ru.auroraos.demo_kmp.png
│   └── 86x86
│       └── ru.auroraos.demo_kmp.png
├── qml
│   ├── cover
│   │   └── DefaultCoverPage.qml
│   ├── demo_kmp.qml
│   ├── icons
│   │   └── demo_kmp.svg
│   └── pages
│       ├── AboutPage.qml
│       └── MainPage.qml
├── rpm
│   └── ru.auroraos.demo_kmp.spec
├── ru.auroraos.demo_kmp.desktop
├── ru.auroraos.demo_kmp.pro
├── src
│   └── main.cpp
└── translations
    ├── ru.auroraos.demo_kmp-ru.ts
    └── ru.auroraos.demo_kmp.ts

13 directories, 15 files

Собрать проект можно, нажав ? (слева внизу).

Публикация QtBindings

QtBindings на данный момент, не доступен в публичных Maven репозиториях. Для использования QtBindings необходимо использовать Maven Local Repositories.

Для сборки и публикации плагина QtBindings в Maven Local нужно клонировать открытый проект из GitLab:

git clone https://gitlab.com/omprussia/kmp/qt-bindings.git

Затем перейти в директорию с проектом и выполнить команду:

./gradlew publishToMavenLocal

Эта команда соберет проект и опубликует его в Maven Local, после чего плагин станет доступен на персональном компьютере разработчика.

В будущем планируется выложить плагин в публичный Maven репозиторий. На данный момент это самый простой способ использовать плагин.

Объединение проектов

Подготовка библиотеки Multiplatform

Kotlin Multiplatform для целей Linux позволяет собрать динамическую или статическую библиотеку, которую можно использовать в Qt. Статическая библиотека более удобна в использовании, поэтому в примере используется именно она.

К проекту библиотеки Kotlin/Native необходимо подключить QtBindings. Он был добавлен в Maven Local, который необходимо включить в проекте. В файл settings.gradle.kts допишем методы mavenLocal():

pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
        mavenCentral()
        mavenLocal()
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        mavenLocal()
    }
}

В файл gradle/libs.versions.toml добавим плагин QtBindings и KSP:

[versions]
kotlin = "2.1.10"
qtbindings = "0.1.0"
devtoolsKsp = "2.1.10-1.0.31"

[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
qtBindings = { id = "ru.auroraos.kmp.qtbindings", version.ref = "qtbindings" }
devtoolsKsp = { id = "com.google.devtools.ksp", version.ref = "devtoolsKsp" }

Добавим плагины в build.gradle.kts:

plugins {
    alias(libs.plugins.kotlinMultiplatform) apply false
    alias(libs.plugins.qtBindings) apply false
    alias(libs.plugins.devtoolsKsp) apply false
}

Добавим плагины в library/build.gradle.kts:

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.qtBindings)
    alias(libs.plugins.devtoolsKsp)
}

Плагину QtBindings необходимо указать название библиотеки. Название должно соответствовать названию библиотеки, указанному в целях auroraArm64Main и auroraX64Main.

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.qtBindings)
    alias(libs.plugins.devtoolsKsp)
}

qtBindings {
    libName = findProperty("libName").toString()
}

Аннотация @QtExport

В демонстрационной функции шаблона библиотеки используется метод с sequence:

fun generateFibi() = sequence {
    var a = firstElement
    yield(a)
    var b = secondElement
    yield(b)
    while (true) {
        val c = a + b
        yield(c)
        a = b
        b = c
    }
}

Эта функциональность на данный момент не поддерживается, список поддержки будет расширяться, и будет обновляться документация по QtBindings. Более подробно с информацией о поддержке можно ознакомиться в документации.

Можно добавить промежуточную функцию generateFibiWrapper и включить её в экспорт:

import ru.auroraos.kmp.qtbindings.QtExport

@QtExport
fun generateFibiWrapper(take: Int): List<Int> {
    return generateFibi().take(take).toList()
}

Всё необходимое для генерации привязок имеется, запустим сборку библиотеки Kotlin/Native:

./gradlew linkReleaseStaticAuroraX64 linkReleaseStaticAuroraArm64

После добавления QtBindings и сборки будут созданы С++ файлы, которые можно добавить к проекту для ОС Аврора:

  • library/build/generated/ksp/auroraArm64/auroraArm64Main/resources/*

  • library/build/generated/ksp/auroraX64/auroraX64Main/resources/*

.
├── io
│   └── github
│       └── kotlin
│           └── fibonacci
│               ├── CustomFibi.cpp
│               └── CustomFibi.hpp
└── ru
    └── aurora
        └── kmp
            └── qtbindings
                ├── CallbackContext.cpp
                ├── CallbackContext.hpp
                ├── CoroutineException.cpp
                ├── CoroutineException.hpp
                ├── CoroutineLauncher.cpp
                ├── CoroutineLauncher.hpp
                ├── CoroutineOperation.hpp
                └── cruntime.h

9 directories, 10 files

Подготовка приложения ОС Аврора

После сборки проекта библиотеки Kotlin/Native, QtBindings создаст файлы C++ .hpp и .cpp. Их нужно добавить напрямую в проект, но удобнее отделить их от основного проекта. Для этого в проекте можно сделать субдиректорию.

В корне проекта ОС Аврора нужно:

  1. Создать директорию ru.auroraos.demo_kmp для основного приложения.

  2. Создать директорию kmp.bindings для сгенерированных Qt-привязок.

  3. Создать директорию kmp.libs для C-библиотек Kotlin Multiplatform.

Далее необходимо перенести основной проект ОС Аврора в директорию ru.auroraos.demo_kmp. Структура проекта примет следующий вид:

.
├── kmp.bindings
├── kmp.libs
├── rpm
│   └── ru.auroraos.demo_kmp.spec
└── ru.auroraos.demo_kmp
    ├── icons
    │   ├── 108x108
    │   │   └── ru.auroraos.demo_kmp.png
    │   ├── 128x128
    │   │   └── ru.auroraos.demo_kmp.png
    │   ├── 172x172
    │   │   └── ru.auroraos.demo_kmp.png
    │   └── 86x86
    │       └── ru.auroraos.demo_kmp.png
    ├── qml
    │   ├── cover
    │   │   └── DefaultCoverPage.qml
    │   ├── demo_kmp.qml
    │   ├── icons
    │   │   └── demo_kmp.svg
    │   └── pages
    │       ├── AboutPage.qml
    │       └── MainPage.qml
    ├── ru.auroraos.demo_kmp.desktop
    ├── ru.auroraos.demo_kmp.pro
    ├── src
    │   └── main.cpp
    └── translations
        ├── ru.auroraos.demo_kmp-ru.ts
        └── ru.auroraos.demo_kmp.ts

16 directories, 16 files

В корень проекта ОС Аврора необходимо добавить файл demo_kmp.pro со следующим содержимым:

TEMPLATE = subdirs

OTHER_FILES += $$files(rpm/*)

SUBDIRS += \
    kmp.bindings \
    ru.auroraos.demo_kmp \

ru.auroraos.demo_kmp.depends = kmp.bindings

CONFIG += ordered

Затем в Аврора IDE можно открыть файл demo_kmp.pro и указать необходимые цели сборки:

Объединение проектов

После любых модификаций библиотеки Kotlin/Native следует её сборка, что, вероятно, изменит файлы, генерируемые QtBindings. Для того, чтобы не переносить изменения самостоятельно, можно автоматизировать этот процесс через Gradle-задачи.

Чтобы получить относительные пути копирования необходимых файлов, можно совместить проект библиотеки Multiplatform и приложения для ОС Аврора:

.
├── auroraApp
│   ├── demo_kmp.pro
│   ├── kmp.bindings
│   ├── kmp.libs
│   ├── rpm
│   │   └── ru.auroraos.demo_kmp.spec
│   └── ru.auroraos.demo_kmp
│       ├── icons
│       │   ├── 108x108
│       │   │   └── ru.auroraos.demo_kmp.png
│       │   ├── 128x128
│       │   │   └── ru.auroraos.demo_kmp.png
│       │   ├── 172x172
│       │   │   └── ru.auroraos.demo_kmp.png
│       │   └── 86x86
│       │       └── ru.auroraos.demo_kmp.png
│       ├── qml
│       │   ├── cover
│       │   │   └── DefaultCoverPage.qml
│       │   ├── demo_kmp.qml
│       │   ├── icons
│       │   │   └── demo_kmp.svg
│       │   └── pages
│       │       ├── AboutPage.qml
│       │       └── MainPage.qml
│       ├── ru.auroraos.demo_kmp.desktop
│       ├── ru.auroraos.demo_kmp.pro
│       ├── src
│       │   └── main.cpp
│       └── translations
│           ├── ru.auroraos.demo_kmp-ru.ts
│           └── ru.auroraos.demo_kmp.ts
└── library
    ├── build.gradle.kts
    ├── gradle
    │   ├── libs.versions.toml
    │   └── wrapper
    │       ├── gradle-wrapper.jar
    │       └── gradle-wrapper.properties
    ├── gradle.properties
    ├── gradlew
    ├── gradlew.bat
    ├── library
    │   ├── build.gradle.kts
    │   └── src
    │       ├── auroraArm64Main
    │       │   └── kotlin
    │       │       └── fibiprops.auroraArm64.kt
    │       ├── auroraX64Main
    │       │   └── kotlin
    │       │       └── fibiprops.auroraX64.kt
    │       └── commonMain
    │           └── kotlin
    │               └── CustomFibi.kt
    ├── README.md
    └── settings.gradle.kts

28 directories, 28 files

Добавление задачи Gradle

Для автоматизации процесса обновления приложения ОС Аврора, после сборки проекта библиотеки Kotlin/Native зарегистрируем задачу. Эта задача выполнит следующее:

  1. Соберёт статические C-библиотеки для архитектур x86_64 и aarch64.

  2. Скопирует собранные библиотеки в проект ОС Аврора.

  3. Скопирует привязки QtBindings в проект ОС Аврора.

  4. Создаст pro-файл для компиляции Qt-привязок в приложении ОС Аврора.

  5. Выведет информацию по подключению библиотек к приложению ОС Аврора.

interface Injected {
    @get:Inject val fs: FileSystemOperations
    @get:Inject val layout: ProjectLayout
    @get:Inject val objects: ObjectFactory
}

tasks.register("buildAuroraLibs") {
    // Injected
    val libName = findProperty("libName").toString()
    val injected = project.objects.newInstance<Injected>()
    // Task depends
    dependsOn("linkReleaseStaticAuroraX64", "linkReleaseStaticAuroraArm64")
    // Copy kmp lib x64
    doLast {
        injected.fs.delete {
            delete("${injected.layout.projectDirectory}/../../auroraApp/kmp.libs/x64")
        }
        injected.fs.copy {
            from(injected.layout.buildDirectory.dir("bin/auroraX64/releaseStatic")) { include("**/*.a", "**/*.h") }
            into("${injected.layout.projectDirectory}/../../auroraApp/kmp.libs/x64")
        }
    }
    // Copy kmp lib arm64
    doLast {
        injected.fs.delete {
            delete("${injected.layout.projectDirectory}/../../auroraApp/kmp.libs/arm64")
        }
        injected.fs.copy {
            from(injected.layout.buildDirectory.dir("bin/auroraArm64/releaseStatic")) { include("**/*.a", "**/*.h") }
            into("${injected.layout.projectDirectory}/../../auroraApp/kmp.libs/arm64")
        }
    }
    // Copy kmp bindings
    // The target is not important where we will get the bindings from
    doLast {
        injected.fs.copy {
            from(injected.layout.buildDirectory.dir("generated/ksp/auroraX64/auroraX64Main/resources"))
            into("${injected.layout.projectDirectory}/../../auroraApp/kmp.bindings")
        }
    }
    // Gen pro bindings
    doLast {
        val search = injected.layout.buildDirectory.dir("generated/ksp/auroraX64/auroraX64Main/resources")
        val headers = mutableListOf<String>()
        val sources = mutableListOf<String>()
        val includes = mutableListOf<String>()
        injected.objects.fileTree().from(search).forEach {
            when {
                it.path.endsWith(".h") || it.path.endsWith(".hpp") -> {
                    headers.add(it.path.replace("${search.get().asFile}/", ""))
                }

                it.path.endsWith(".cpp") -> {
                    sources.add(it.path.replace("${search.get().asFile}/", ""))
                }
            }
        }
        headers.forEach {
            includes.add(it.replaceAfterLast("/", ""))
        }
        val file = File("${injected.layout.projectDirectory}/../../auroraApp/kmp.bindings/kmp.bindings.pro")
        file.writeText("""
            |TEMPLATE = lib
            |TARGET = kmpbindings
            |CONFIG += staticlib warn_off
            |
            |HEADERS += \
            |${headers.joinToString("\n") { "    $it \\" }}
            |
            |SOURCES += \
            |${sources.joinToString("\n") { "    $it \\" }}
            |
            |contains(QMAKE_HOST.arch, armv7l): {
            |    error("Unsupported architecture armv7l")
            |}
            |contains(QMAKE_HOST.arch, x86_64): {
            |    LIB_DEMO_KMP=kmp.libs/x64
            |}
            |contains(QMAKE_HOST.arch, aarch64): {
            |    LIB_DEMO_KMP=kmp.libs/arm64
            |}
            |
            |INCLUDEPATH += $${'$'}PWD/../${'$'}${'$'}{LIB_DEMO_KMP}
            |${includes.distinct().joinToString("\n") { "INCLUDEPATH += $${'$'}PWD/$it" }}
        """.trimMargin())
        file.createNewFile()
        // Print what needs to be added to the pro file of the application
        println("""
            |Check your application pro file:
            |################################
            |# kmp.targets
            |contains(QMAKE_HOST.arch, armv7l): {
            |    error("Unsupported architecture armv7l")
            |}
            |contains(QMAKE_HOST.arch, x86_64): {
            |    LIB_DEMO_KMP=kmp.libs/x64
            |}
            |contains(QMAKE_HOST.arch, aarch64): {
            |    LIB_DEMO_KMP=kmp.libs/arm64
            |}
            |
            |# kmp.bindings
            |${includes.distinct().joinToString("\n") { "INCLUDEPATH += $${'$'}PWD/../kmp.bindings/$it" }}
            |LIBS += -L${'$'}${'$'}OUT_PWD/../kmp.bindings -lkmpbindings
            |
            |# kmp.libs
            |INCLUDEPATH += ${'$'}${'$'}PWD/../${'$'}${'$'}{LIB_DEMO_KMP}
            |LIBS += -L${'$'}${'$'}PWD/../${'$'}${'$'}{LIB_DEMO_KMP} -l${libName}
            |################################
        """.trimMargin())
    }
}

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

Вывод задачи Gradle:

# kmp.targets
contains(QMAKE_HOST.arch, armv7l): {
    error("Unsupported architecture armv7l")
}
contains(QMAKE_HOST.arch, x86_64): {
    LIB_DEMO_KMP=kmp.libs/x64
}
contains(QMAKE_HOST.arch, aarch64): {
    LIB_DEMO_KMP=kmp.libs/arm64
}

# kmp.bindings
INCLUDEPATH += $$PWD/../kmp.bindings/ru/aurora/kmp/qtbindings/
INCLUDEPATH += $$PWD/../kmp.bindings/io/github/kotlin/fibonacci/
LIBS += -L$$OUT_PWD/../kmp.bindings -lkmpbindings

# kmp.libs
INCLUDEPATH += $$PWD/../$${LIB_DEMO_KMP}
LIBS += -L$$PWD/../$${LIB_DEMO_KMP} -ldemo_kmp

Вывод из Gradle следует добавить в конец файла auroraApp/ru.auroraos.demo_kmp/ru.auroraos.demo_kmp.pro проекта ОС Аврора.

Теперь приложение ОС Аврора можно собрать:

Выполнение метода

На данный момент общий проект содержит:

  1. Библиотеку Kotlin/Native.

  2. Приложение Qt/QML для ОС Аврора.

  3. Задачу buildAuroraLibs, автоматизирующую процесс сборки и синхронизации.

  4. Статическую библиотеку с файлами генерации QtBindings, интегрированную с приложением.

  5. Метод в библиотеке Kotlin/Native с аннотацией @QtExport, позволяющий выполнить его в приложении.

Метод generateFibiWrapper с аннотацией @QtExport помещён в пространство имён соответственно пакету:

namespace io {
namespace github {
namespace kotlin {
namespace fibonacci {

QList<int> generateFibiWrapper();

} /* namespace fibonacci */
} /* namespace kotlin */
} /* namespace github */
} /* namespace io */

В шаблоне приложения ОС Аврора можно найти основную функцию С++ — main. В ней можно проверить результат работы QtBindings и библиотеки Kotlin/Native:

#include <auroraapp.h>
#include <QtQuick>
#include <QDebug>

#include "CustomFibi.hpp"

int main(int argc, char *argv[])
{
    // Run from library Kotlin/Native
    qDebug() << io::github::kotlin::fibonacci::generateFibiWrapper(5);

    // Qt/Qml application
    QScopedPointer<QGuiApplication> application(Aurora::Application::application(argc, argv));
    application->setOrganizationName(QStringLiteral("ru.auroraos"));
    application->setApplicationName(QStringLiteral("demo_kmp"));

    QScopedPointer<QQuickView> view(Aurora::Application::createView());
    view->setSource(Aurora::Application::pathTo(QStringLiteral("qml/demo_kmp.qml")));
    view->show();

    return application->exec();
}

После запуска приложения в логах можно наблюдать выполнение функции generateFibiWrapper и привязок QtBindings из библиотеки Kotlin/Native:

Использование Coroutines

QtBindings позволяет работать с Kotlin Coroutines через Qt:

  • ожидать завершение suspend-функции;

  • отменять его при необходимости;

  • получать исключения.

В библиотеке Kotlin/Native есть синхронная функция generateFibiWrapper, которая была вызвана ранее. Для демонстрации работы с асинхронными функциями добавим suspend-функцию:

@QtExport
suspend fun generateFibiWrapperAsync(take: Int): List<Int> {
    delay(5000L)
    return generateFibi().take(take).toList()
}

После добавления функции в библиотеку Kotlin/Native её необходимо собрать и обновить файлы, генерируемые QtBindings в проекте ОС Аврора. Сделать это можно через добавленную ранее задачу Gradle buildAuroraLibs:

./gradlew buildAuroraLibs

QtBindings из suspend-функции создаст QFuture, который можно использовать с QFutureWatcher, что позволит использовать весь основной функционал Coroutines.

Вызвать функцию из Qt можно так:

auto fibiWrapperAsync = new QFutureWatcher<QList<int>>();
auto feature = io::github::kotlin::fibonacci::generateFibiWrapperAsync(5);
fibiWrapperAsync->setFuture(feature);
QObject::connect(fibiWrapperAsync, &QFutureWatcher<QList<int>>::finished, [=]() {
    qDebug() << fibiWrapperAsync->result();
    fibiWrapperAsync->deleteLater();
});

QtBindings позволяет завершать запросы и получать исключения из suspend-функций. Добавим метод, на котором можно это проверить:

@QtExport
suspend fun testCatchAsync(isError: Boolean): Boolean {
    delay(5000L)
    if (isError) {
        throw RuntimeException("My custom error")
    }
    return true
}

Вот так можно вызвать функцию testCatchAsync с отменой её ожидания:

auto testCatchAsyncCancel = new QFutureWatcher<bool>();
auto featureCancel = io::github::kotlin::fibonacci::testCatchAsync(false);
testCatchAsyncCancel->setFuture(featureCancel);
QObject::connect(testCatchAsyncCancel, &QFutureWatcher<bool>::canceled, [=]() {
    qDebug() << "testCatchAsyncCancel - canceled";
});
// Отмена
testCatchAsyncCancel->cancel();

Для перехвата ошибки в функции testCatchAsync укажем параметр isError как true и воспользуемся try/catch:

auto testCatchAsyncError = new QFutureWatcher<bool>();
auto featureError = io::github::kotlin::fibonacci::testCatchAsync(true);
testCatchAsyncError->setFuture(featureError);
QObject::connect(testCatchAsyncError, &QFutureWatcher<bool>::canceled, [=]() {
    try {
        auto value =  testCatchAsyncError->result();
        qDebug() << value;
    } catch (Aurora::Kmp::QtBindings::CoroutineException& ex) {
        qDebug() << ex.message();
    }
});

Заключение

QtBindings позволяет взаимодействовать с библиотекой Kotlin Multiplatform нативными средствами Qt. Он существенно упрощает разработку приложений: добавление привязок происходит не сложнее, чем на других платформах, и разработчику больше не нужно погружаться в низкоуровневую логику С-библиотек.

Развитие поддержки Kotlin Multiplatform в ОС Аврора только начинается, но уже можно писать приложения с использованием целей linuxX64 и linuxArm64 на ОС Аврора!

Ссылки

Kotlin Multiplatform

Другие инструменты

Комментарии (7)


  1. AGalilov
    05.06.2025 06:40

    Интересная статья.

    Где можно скачать эмулятор с ОС Аврора или виртуальную машину для ознакомления и изучения? Сами устройства я в продаже не видел пока.


    1. KeyGenQt
      05.06.2025 06:40

      Эмулятор входит в состав Аврора SDK https://developer.auroraos.ru/downloads/sdk_mb2/5.1.3.85/linux


    1. omprussia Автор
      05.06.2025 06:40

      В продаже есть F+ R570E с ОС Аврора: https://vsesmart.ru/catalog/smartfon-f-pro-r570e-4-64gb-avrora/


  1. Byaka8kaka
    05.06.2025 06:40

    За мной приедут, если отрицательный отзыв оставлю?)


    1. chuvilin
      05.06.2025 06:40

      Что в этой статье не так, чтобы оставлять отрицательный отзыв?


  1. fortidet
    05.06.2025 06:40

    Разработка на эмуляторах — это, конечно, интересно, но в маркете Авроры есть минимум одно банковское приложение, разработанное на эмуляторе и не протестированное на устройстве, после чего его использование весьма сомнительно. Так что пока не будет девайсов — не будет разрабов, не будет разрабов — не будет распространения. Пока что всё идёт к тому, чтобы через несколько лет с грустью вспоминали о том, какие возможности потеряны


    1. chuvilin
      05.06.2025 06:40

      Какое именно приложение, и откуда информация, что оно не протестировано на устройствах?