Как добавить два или более собственных модуля kotlin в проект iOS

TL;DR;

Как добавить два или более собственных модуля kotlin в проект iOS без duplicate symbols ошибки?

Подробный вопрос

Предположим, что в следующем примере имеется многомодульный проект KMP, в котором существует собственное приложение для Android и собственное приложение для iOS, а также два общих модуля для хранения общего кода kotlin.

.
├── android
│   └── app
├── common
│   ├── moduleA
│   └── moduleB
├── ios
│   └── app

Модуль A содержит класс данных HelloWorld и не имеет зависимостей от модулей:

package hello.world.modulea

data class HelloWorld(
    val message: String
)

Модуль B содержит функцию расширения для класса HelloWorld, поэтому он зависит от модуля A:

package hello.world.moduleb

import hello.world.modulea.HelloWorld

fun HelloWorld.egassem() = message.reversed()

Конфигурация модулей build.gradle:

  • Модуль А
apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"

…

kotlin {
    targets {
        jvm("android")

        def iosClosure = {
            binaries {
                framework("moduleA")
            }
        }
        if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
    }

    cocoapods {…}

    sourceSets {
        commonMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
        }
        androidMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
        }
        iosMain.dependencies {
        }
    }
}
  • Модуль B
apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"
…

kotlin {
    targets {
        jvm("android")

        def iosClosure = {
            binaries {
                framework("moduleB")
            }
        }
        if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
    }

    cocoapods {…}

    sourceSets {
        commonMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
            implementation project(":common:moduleA")
        }
        androidMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
        }
        iosMain.dependencies {
        }
    }
}

Это выглядит довольно просто и работает даже на Android, если я настрою зависимости gradle сборки Android следующим образом:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72"
    implementation project(":common:moduleA")
    implementation project(":common:moduleB")
}

Однако это не кажется правильным способом организации нескольких модулей в iOS, потому что при запуске ./gradlew podspec я получаю BUILD SUCCESSFUL, как и ожидалось, со следующими модулями:

pod 'moduleA', :path => '…/HelloWorld/common/moduleA'
pod 'moduleB', :path => '…/HelloWorld/common/moduleB'

Даже запустив pod install, я получаю успешный результат Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed., что выглядит правильно, когда Xcode показывает модуль A и модуль B в разделе Pods.

Однако, если я попытаюсь создать проект iOS, я получаю следующую ошибку:

Ld …/Hello_World-…/Build/Products/Debug-iphonesimulator/Hello\ World.app/Hello\ World normal x86_64 (in target 'Hello World' from project 'Hello World')
    cd …/HelloWorld/ios/app
…
duplicate symbol '_ktypew:kotlin.Any' in:
    …/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
    …/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
… a lot of duplicate symbol more …
duplicate symbol '_kfun:[email protected]<#STAR>.()' in:
    …/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
    …/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
ld: 9928 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

У меня не так много знаний в области iOS, поэтому, на мой неподготовленный взгляд, похоже, что каждый модуль добавляет свою собственную версию вещей вместо того, чтобы использовать какую-то стратегию разрешения, чтобы поделиться ею.

Если я использую только модуль A, код работает и выполняется, как ожидалось, поэтому я знаю, что сам код правильный, проблема в том, как управлять более чем одним модулем, поэтому возникает вопрос, как добавить оба (модуль A и модуль B ) на iOS и заставить все работать?

P.S

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


person ademar111190    schedule 27.04.2020    source источник


Ответы (2)


Несколько фреймворков Kotlin могут быть непростыми, но они должны работать с версией 1.3.70, которая, как я вижу, у вас есть.

Проблема, похоже, в том, что обе структуры статичны, что в настоящее время является проблемой в 1.3.70, поэтому она не работает. (Это должно быть обновлено до 1.40). Похоже, что по умолчанию плагин cocoapods устанавливает статические фреймворки, что не работает. Я не знаю, как изменить cocoapods, чтобы установить его как динамический, но я тестировал сборку без cocoapods и с использованием переменной isStatic в задаче gradle и получил проект iOS для компиляции. Что-то вроде:

binaries {
    framework("moduleA"){
        isStatic = false
    }
}

На данный момент вы можете обойти проблему с помощью этого метода, используя приведенный выше код и создав задачу для создания фреймворков (вот пример)

Стоит также отметить, что на стороне iOS классы HelloWorld будут отображаться как два отдельных класса, несмотря на то, что оба исходят из moduleA. Это еще одна странная ситуация с несколькими фреймворками Kotlin, но я думаю, что расширение все равно будет работать в этом случае, поскольку вы возвращаете строку.

На самом деле я только что написал в блоге сообщение о нескольких фреймворках Kotlin, которое может помочь с некоторыми другими вопросами, если вы хотите взглянуть. https://touchlab.co/multiple-kotlin-frameworks-in-application/ < / а>

РЕДАКТИРОВАТЬ: похоже, что cocoapodsext также имеет переменную isStatic, поэтому установите для нее значение isStatic = false.

tl: dr В настоящее время у вас не может быть более одного статического фреймворка Kotlin в одном проекте iOS. Установите их не статическими с помощью isStatic = false.

person Kevin    schedule 28.04.2020
comment
Большое спасибо, ваш ответ и дополнительный материал - это то, что я искал. Проблемы, которые вы поднимаете, актуальны, имея в виду, что я следую этой стратегии: Сохраняйте модуляризацию (цель вопроса) Для Android я сохранил обычные модули gradle Для собственных модулей я бы добавил толстый модуль без кода и конфигурация Gradle для использования кода всех других модулей вместе и генерирует единую структуру, решающую проблему префикса класса, а также дублированные символы. - person ademar111190; 02.05.2020
comment
Надеюсь, в будущих версиях kotlin будет улучшена многомодульная поддержка нативного кода, так что я смогу избавиться от толстого модуля и использовать уже организованные модули. а пока давайте поработаем с этим: D - person ademar111190; 02.05.2020
comment
@ ademar111190, что именно не так в вашем случае с подходом на основе жирного модуля? - person Svyatoslav Scherbina; 23.12.2020

Однако, если я попытаюсь создать проект iOS, я получаю следующую ошибку:

Эта конкретная ошибка - известная проблема. Несколько статических фреймворков отладки несовместимы с кешами компилятора.

Итак, чтобы обойти проблему, вы можете отключить кеши компилятора, поместив следующую строку в свой gradle.properties:

kotlin.native.cacheKind=none

или сделайте фреймворки динамическими, добавив следующий фрагмент в свой скрипт сборки Gradle:

kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework> {
            isStatic = false
        }
    }
}

См. https://youtrack.jetbrains.com/issue/KT-42254 для получения дополнительной информации. Детали.

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

У меня не так много знаний в области iOS, поэтому на мой неподготовленный взгляд кажется, что каждый модуль добавляет свою собственную версию вещей вместо того, чтобы использовать какую-то стратегию разрешения, чтобы поделиться ею.

Именно так он и должен работать в настоящий момент. Но версии вещей в каждой из фреймворков помещаются в отдельные независимые пространства имен, поэтому ошибок связывания быть не должно, а та, с которой вы столкнулись, является ошибкой.

person Svyatoslav Scherbina    schedule 23.12.2020