Im trying to publish an Android library (debug/release) with the new Kotlin MultiPlatform set up. Im successfully able to publish iOS frameworks, but not android libraries. This is the error I end up getting:
A problem occurred configuring project ':shared'.
Failed to notify project evaluation listener.
Kotlin target 'android' tried to set up publishing for Android build variants that are not library variants or do not exist:
* release
* debug
Check the 'publishLibraryVariants' property, it should point to existing Android library variants. Publishing of application and test variants is not supported.
However, I can clearly see the variants in my Android Studio. Im also able to run the Android/IOS app from Android Studio using the library dependecy as a project dependency
implementation(project(":shared"))
Only facing problems when trying to publish it.
This is my build.gradle.kts:
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
id("kotlin-android-extensions")
id("com.squareup.sqldelight")
id("dev.icerock.mobile.multiplatform-resources")
`maven-publish`
}
group = "com.sekhar.testkmp"
version = "0.1"
val coroutinesVersion = "1.3.9-native-mt"
val serializationVersion = "1.0.0-RC"
val ktorVersion = "1.4.0"
val sqlDelightVersion: String by project
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
kotlin {
android {
publishLibraryVariants("release", "debug")
}
ios {
binaries {
framework {
baseName = "shared"
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
implementation("com.squareup.sqldelight:runtime:$sqlDelightVersion")
implementation("dev.icerock.moko:resources:0.13.1")
implementation("dev.icerock.moko:parcelize:0.4.0")
implementation("dev.icerock.moko:graphics:0.4.0")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("com.google.android.material:material:1.2.1")
implementation("io.ktor:ktor-client-android:$ktorVersion")
implementation("com.squareup.sqldelight:android-driver:$sqlDelightVersion")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.12")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:$ktorVersion")
implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion")
}
}
val iosTest by getting
}
}
dependencies {
commonMainApi("dev.icerock.moko:resources:0.13.1")
}
multiplatformResources {
multiplatformResourcesPackage = "com.sekhar.testkmp" // required
}
android {
compileSdkVersion(29)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
getByName("debug") {
isMinifyEnabled = false
}
}
lintOptions {
isAbortOnError = false
}
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
val framework = kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
from({ framework.outputDirectory })
into(targetDir)
}
tasks.getByName("build").dependsOn(packForXcode)```
The most common solution to this is to apply the android plugin: id("com.android.library") before the kotlin multiplatform plugin: kotlin("multiplatform").
plugins {
id("com.android.library")
kotlin("multiplatform")
kotlin("plugin.serialization")
id("kotlin-android-extensions")
id("com.squareup.sqldelight")
id("dev.icerock.mobile.multiplatform-resources")
`maven-publish`
}
Related
I'm trying to create module with shared code for Android and iOS, but when I add any dependency to commonMain it's stopping to index that folder. If I remove dependencies, everything is okay. gradlew clean doesn't help. How could I resolve this issue?
build.gradle.kts:
object Versions {
const val coroutinesVersion = "1.5.0-native-mt"
const val ktorVersion = "1.6.1"
const val kotlinVersion = "1.5.21"
const val serializationVersion = "1.2.2"
}
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
kotlin("plugin.serialization")
}
version = "1.0"
kotlin {
android()
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutinesVersion}")
implementation("io.ktor:ktor-client-core:${Versions.ktorVersion}")
implementation("io.ktor:ktor-client-serialization:${Versions.ktorVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.serializationVersion}")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:${Versions.ktorVersion}")
}
}
val androidTest by getting
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
Screen of module:
Problem was with libraries versions. After update to a newer ones everything works fine:)
Newer versions:
object Versions {
const val coroutinesVersion = "1.6.1"
const val ktorVersion = "2.0.2"
const val serializationVersion = "1.3.3"
}
Hey I am publish library in kotlin multiplatform. I used this command ./gradlew clean assemble publish my three package is generated. I given the name var libraryArtifactId = "kmm-module" but I don't understand why other name is coming from.
1st package name is com.vivek.kmm-module
2nd package name is com.vivek.kotlinproject-android-debug
3rd package name is com.vivek.kotlinproject-android
I understand 1st package name, but I am not understanding where the 2nd and 3rd artificatId coming from?
build.gradle.kts
plugins {
kotlin("multiplatform") version "1.6.21"
id("com.android.library")
id("maven-publish")
}
val libraryVersion = "0.0.1"
var libraryGroup = "com.vivek"
var libraryArtifactId = "kmm-module"
repositories {
google()
mavenCentral()
}
kotlin {
android {
publishLibraryVariants("release", "debug")
}
sourceSets {
val commonMain by getting
val androidMain by getting
}
}
android {
compileSdk = 21
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 31
}
#Suppress("UnstableApiUsage") compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
publishing {
publications {
group = libraryGroup
publications.withType<MavenPublication> {
groupId = libraryGroup
artifactId = libraryArtifactId
version = libraryVersion
}
repositories {
maven {
url = uri("https://maven.pkg.github.com/vivek-modi/kotlinmultiplatfromproject")
credentials {
username = (System.getenv("GITHUB_USER") ?: project.properties["GITHUB_USER"]).toString()
password = (System.getenv("GITHUB_PERSONAL_ACCESS_TOKEN") ?: project.properties["GITHUB_PERSONAL_ACCESS_TOKEN"]).toString()
}
}
}
}
}
I am adding my github repository. I used github packages for publishing library. Thanks
you can remove this line to avoid the android artifact.
android {
publishLibraryVariants("release", "debug")
}
I am creating KMM library. I want to create a cocoapods framework, but when I tried to build my project, the framework is not generating. I tried clean project and build again. My cocoapods is not generating the at all in build folder. I didn't get why this is not working. I am adding my build file.
build.gradle.kts
plugins {
kotlin("multiplatform") version "1.6.21"
kotlin("native.cocoapods") version "1.6.21"
id("com.android.library")
id("org.jetbrains.dokka") version "1.6.10"
id("org.jetbrains.kotlin.plugin.serialization") version "1.6.21"
id("maven-publish")
}
val libraryVersion = "0.0.1"
var libraryGroup = "com.vivek"
var libraryArtifactId = "kmm-module"
var libraryUri = uri("https://maven.pkg.github.com/vivek/KotlinMultiplatformMobile")
repositories {
google()
mavenCentral()
}
kotlin {
android {
publishLibraryVariants("release", "debug")
}
cocoapods {
version = libraryVersion
summary = "Some description for a Kotlin/Native module"
homepage = ""
ios.deploymentTarget = "13.0"
name = "VivekKmmPod"
framework {
baseName = "VivekFramework"
isStatic = false
}
}
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val ktorVersion = "2.0.0"
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2")
implementation("io.insert-koin:koin-core:3.2.0")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("io.ktor:ktor-client-logging-jvm:$ktorVersion")
}
}
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
}
}
}
android {
compileSdk = 21
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 31
}
}
tasks {
create<Jar>("javadocJar") {
val dokkaHtml by getting(org.jetbrains.dokka.gradle.DokkaTask::class)
dependsOn(dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaHtml.outputDirectory)
}
}
publishing {
publications {
group = libraryGroup
publications.withType<MavenPublication> {
artifactId.toLowerCase()
groupId = libraryGroup
artifactId = libraryArtifactId
version = libraryVersion
artifact(tasks["javadocJar"])
}
repositories {
maven {
url = libraryUri
credentials {
username = (System.getenv("GITHUB_USER") ?: project.properties["GITHUB_USER"]).toString()
password = (System.getenv("GITHUB_PERSONAL_ACCESS_TOKEN") ?: project.properties["GITHUB_PERSONAL_ACCESS_TOKEN"]).toString()
}
}
}
}
}
As you can see no framework generated in build folder.
just sync the project ... the framework file will be created .. or try ./gradlew build
When I switch build variants I got compilation error, but the problem is only for non default build variants (debug & release). So if I define customBuild { } and then choose that one, it fails with this error log:
The consumer was configured to find a runtime of a component,
preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr'
with value 'customBuild', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm'.
However we cannot choose between the following variants of project :shared-module:
- iosArm64RuntimeOnly
- iosX64RuntimeOnly
All of them match the consumer attributes:
- Variant 'iosArm64RuntimeOnly' capability ExampleApp:shared-module:1.0:
- Unmatched attributes:
- Doesn't say anything about com.android.build.api.attributes.BuildTypeAttr (required 'customBuild')
- Doesn't say anything about its target Java environment (preferred optimized for Android)
- Doesn't say anything about its usage (required a runtime)
- Provides attribute 'org.jetbrains.kotlin.native.target' with value 'ios_arm64' but the consumer didn't ask for it
- Doesn't say anything about org.jetbrains.kotlin.platform.type (required 'androidJvm')
My build.gradle.kts for shared module
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
id("com.squareup.sqldelight")
kotlin("native.cocoapods")
}
repositories {
gradlePluginPortal()
google()
mavenCentral()
maven {
url = uri("https://plugins.gradle.org/m2/")
}
}
version = "1.0"
kotlin {
android {
publishAllLibraryVariants()
}
ios()
cocoapods {
framework {
baseName = "core"
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach {
it.binaries.framework {
baseName = "core"
}
}
}
targets.filterIsInstance<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().forEach {
it.binaries.filterIsInstance<org.jetbrains.kotlin.gradle.plugin.mpp.Framework>()
.forEach { lib ->
lib.isStatic = false
lib.linkerOpts.add("-lsqlite3")
}
}
sourceSets {
val commonMain by getting {
dependencies { ... }
}
val commonTest by getting {
dependencies { ... }
}
val androidMain by getting {
dependencies { ... }
}
val androidAndroidTestRelease by getting
val androidTest by getting {
dependsOn(androidAndroidTestRelease)
dependencies { ... }
}
val iosMain by getting {
dependencies { ... }
}
val iosTest by getting
}
}
configurations.all { }
android {
compileSdk = 30
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 30
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
sqldelight {
database("db") {
packageName = "com.example.app.core.sqldelight"
sourceFolders = listOf("sqldelight")
schemaOutputDirectory = file("src/commonMain/sqldelight/databases")
dialect = "sqlite:3.24"
}
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
val framework =
kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
...
}
tasks.getByName("build").dependsOn(packForXcode)
val generateIOSArm64Framework by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
val targetName = "iosArm64"
val framework =
kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
inputs.property("mode", mode)
...
}
val generateIOSXCFramework by tasks.creating(Sync::class) {
dependsOn("packForXcode", "generateIOSArm64Framework")
}
I created a new project in Android Studio using KMM wizard. I was following handson tutorial and I noticed that I don't have an option of creating a package inside of some directories. Specifically, inside of "shared" module only kotlin directory of androidMain folder is always marked as "sources root".
I manually marked kotlin directories in other folders (commonMain, iosMain) as "sources root". I also marked sqldelight directory inside of commonMain as "sources root".
But Android Studio keeps reverting that state back periodically. I don't know what is causing this issue. It also shows that kotlin directory of androidMain folder is set as "sources root" and also is not. Which is weird, directory can't be set and unset as "sources root" at the same time.
Is it a bug of Android Studio, KMM Plugin or some kind of setting in preferences?
Android Studio version: 4.1.1
KMM plugin version: 0.1.3-release-54-Studio4.1
EDIT:
build.gradle.kts for shared module:
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
id("kotlin-android-extensions")
id("com.squareup.sqldelight")
}
group = "com.example.kmmapplication"
version = "1.0-SNAPSHOT"
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
kotlin {
android()
ios {
binaries {
framework {
baseName = "shared"
}
}
}
// Block from https://github.com/cashapp/sqldelight/issues/2044#issuecomment-721299517.
val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?: false
if (onPhone) {
iosArm64("ios")
} else {
iosX64("ios")
}
val coroutinesVersion = "1.3.9-native-mt"
val serializationVersion = "1.0.1"
val ktorVersion = "1.4.2"
val sqlDelightVersion: String by project
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
implementation("com.squareup.sqldelight:runtime:$sqlDelightVersion")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:$ktorVersion")
implementation("com.squareup.sqldelight:android-driver:$sqlDelightVersion")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13.1")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:$ktorVersion")
implementation("com.squareup.sqldelight:native-driver:$sqlDelightVersion")
}
}
val iosTest by getting
}
}
android {
compileSdkVersion(29)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
sqldelight {
database("AppDatabase") {
packageName = "com.example.kmmapplication.shared.cache"
}
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
val framework =
kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
from({ framework.outputDirectory })
into(targetDir)
}
tasks.getByName("build").dependsOn(packForXcode)
settings.gradle.kts:
pluginManagement {
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") {
useModule("com.android.tools.build:gradle:4.0.1")
}
}
}
}
rootProject.name = "KMMApplication"
include(":androidApp")
include(":shared")
gradle-wrapper.properties:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
These ios target definitions conflict:
ios { //Target 1
binaries {
framework {
baseName = "shared"
}
}
}
// Block from https://github.com/cashapp/sqldelight/issues/2044#issuecomment-721299517.
val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?: false
if (onPhone) {
iosArm64("ios") //Target 2
} else {
iosX64("ios") //Target 3
}
You have ios, which is a combined target for arm and x64, then the individual targets iosArm64 and iosX64`. I don't know if that's what's causing the IDE issue, but it's certainly confusing.
The 2nd two don't define frameworks as they were taken from a context that would use cocoapods. If you look at the sqldelight issue comment, it came from me, and ultimately came from KaMPKit: https://github.com/touchlab/KaMPKit/blob/master/shared/build.gradle.kts#L28.
To get the IDE working, I'd suggest removing the first ios target. However, again, the sample from the sqldelight issue assumes you have cocoapods configured. You'll either need to add cocoapods or update both the target config and packForXcode.
The targets would look something like the following.
val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?: false
if (onPhone) {
iosArm64("ios") {
binaries {
framework {
baseName = "shared"
}
}
}
} else {
iosX64("ios") {
binaries {
framework {
baseName = "shared"
}
}
}
}
As an alternative, I'd suggest just using KaMPKit as a base for your project until you're more familiar with the KMM plugin samples and config options. They don't quite work out of the box yet.