compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

Adding any compose dependency breaks resolving the Gradle configuration

Open jakobkmar opened this issue 3 years ago • 20 comments

When I am adding the compose dependency to a (resolvable) Gradle configuration (includeImplementation is my custom configuration) as follows:

includeImplementation(compose.desktop.currentOs) // same with common

and then trying to resolve the dependencies of this configuration

includeImplementation.resolvedConfiguration.firstLevelModuleDependencies

I am getting the following error

Caused by: org.gradle.internal.component.AmbiguousConfigurationSelectionException: Cannot choose between the following variants of org.jetbrains.compose.foundation:foundation:1.0.0-beta5:
  - debugRuntimeElements-published
  - desktopRuntimeElements-published
  - releaseRuntimeElements-published

(probably relates to this)

My custom configuration above is one example, but there are other scenarios where you have to use custom configurations from third-party Gradle plugins, and therefore using this together with Compose does not work.

jakobkmar avatar Nov 15 '21 19:11 jakobkmar

Do you use Kotlin multiplatform gradle plugin? Since Compose is published like a multiplatform solution, this plugin is required for dependency resolution

akurasov avatar Nov 18 '21 13:11 akurasov

I am not using the multiplatform plugin, because I am having trouble to use the custom configuration from the third party plugin in the scope of KotlinDependencyHandler, as this scope only has some predefined standard configurations available for use.

jakobkmar avatar Nov 18 '21 14:11 jakobkmar

plugins {
    kotlin("jvm") version "1.5.31"
    id("fabric-loom") version "0.10-SNAPSHOT"
    kotlin("plugin.serialization") version "1.5.31"
    id("org.jetbrains.compose") version "1.0.0-beta5"
}

repositories {
    mavenCentral()
    google()
    maven("https://maven.fabricmc.net/")
    mavenLocal()
    maven("https://oss.sonatype.org/content/repositories/snapshots")
    maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}

val includeImplementation by configurations.creating {
    configurations.implementation.configure { extendsFrom(this@creating) }
}

dependencies {
    minecraft("com.mojang:minecraft:$minecraftVersion")
    mappings("net.fabricmc:yarn:$yarnMappingsVersion")
    modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion")
    modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion")
    modImplementation("net.fabricmc:fabric-language-kotlin:$fabricLanguageKotlinVersion")

    includeImplementation(compose.desktop.currentOs)

    includeImplementation("org.litote.kmongo:kmongo-coroutine-core:4.3.0")
    includeImplementation("org.litote.kmongo:kmongo-serialization-mapping:4.3.0")
    includeImplementation("org.slf4j:slf4j-simple:1.7.32")
    includeImplementation("org.apache.commons:commons-text:1.9")

    // example usage which triggers the error
    includeImplementation.resolvedConfiguration.firstLevelModuleDependencies
}

Or is there any way I could convert this project using Compose to a project using the multiplatform plugin?

jakobkmar avatar Nov 18 '21 15:11 jakobkmar

Or is there any way I could convert this project using Compose to a project using the multiplatform plugin?

Yes, it is quite easy.

Replace kotlin("jvm") version "1.5.31" with kotlin("multiplatform") version "1.5.31"

add the code below and move dependencies there. It should work.

kotlin {
    jvm {
        withJava()
    }
    sourceSets {
        named("jvmMain") {
            dependencies {
>>put dependencies here
            }
        }
    }
}

akurasov avatar Nov 19 '21 12:11 akurasov

Yes I already tried that, but the custom configurations such as modImplementation etc are not available there.

jakobkmar avatar Nov 19 '21 12:11 jakobkmar

What are these custom functions for?

akurasov avatar Nov 19 '21 12:11 akurasov

As an option, you could split code in two modules. Use Compose+mpp plugin in one module and custom imports in another.

akurasov avatar Nov 19 '21 12:11 akurasov

What are these custom functions for?

They are Gradle configurations, minecraft specified the Minecraft dependency, mappings specifies the mappings for Minecraft and modImplementation adds Minecraft mods.

jakobkmar avatar Nov 19 '21 12:11 jakobkmar

As an option, you could split code in two modules. Use Compose+mpp plugin in one module and custom imports in another.

I could do that, but I would still have to execute includeImplementation.resolvedConfiguration.firstLevelModuleDependencies in the other module dependening on the compose module, which would lead to the same error.

jakobkmar avatar Nov 19 '21 12:11 jakobkmar

I could do that, but I would still have to execute includeImplementation.resolvedConfiguration.firstLevelModuleDependencies in the other module dependening on the compose module, which would lead to the same error

Could you share you build scripts? I struggle to understand that is the issue

akurasov avatar Dec 15 '21 10:12 akurasov

The simplest example would be just to try and shade the compose dependency using the Gradle shadow plugin in a JVM context. Not that I'd recommend this, but this will trigger the problem I mean, as it tried to resolve the compose dependency as well.

jakobkmar avatar Dec 19 '21 04:12 jakobkmar

To trigger the problem:

plugins {
    kotlin("jvm") version "1.6.10"
    id("org.jetbrains.compose") version "1.0.1-rc2"
    id("com.github.johnrengelman.shadow") version "7.1.1"
}

repositories {
    mavenCentral()
    google()
    maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}

val embed: Configuration by configurations.creating

dependencies {
    embed(implementation(compose.desktop.currentOs)!!)
}

tasks {
    shadowJar {
        configurations = listOf(embed)
    }
}

Now you might suggest that I can just use the multiplatform plugin instead, but that is not an option as it does provide all the functionality that I need. edit: it does not provide

jakobkmar avatar Dec 19 '21 04:12 jakobkmar

I guess you meant don not provide. What exactly is missing?

akurasov avatar Dec 20 '21 09:12 akurasov

I guess you meant don not provide.

correct

What exactly is missing?

The custom Gradle configurations (minecraft, modImplementation) and compatiblity with other Gradle plugins as shown in my previous comment.

jakobkmar avatar Dec 20 '21 17:12 jakobkmar

With release 1.0.1 the issue was resolved for the Compose dependencies themselves, but with 1.1.0 it was reintroduced, this time caused by the skiko dependency:

   > Cannot choose between the following variants of org.jetbrains.skiko:skiko:0.7.12:
       - androidRuntimeElements-published
       - awtRuntimeElements-published
     All of them match the consumer attributes:
       - Variant 'androidRuntimeElements-published' capability org.jetbrains.skiko:skiko:0.7.12:
           - Unmatched attributes:
               - Provides org.gradle.category 'library' but the consumer didn't ask for it
               - Provides org.gradle.libraryelements 'jar' but the consumer didn't ask for it
               - Provides org.gradle.status 'release' but the consumer didn't ask for it
               - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
               - Provides org.jetbrains.kotlin.platform.type 'jvm' but the consumer didn't ask for it
               - Provides ui 'android' but the consumer didn't ask for it
       - Variant 'awtRuntimeElements-published' capability org.jetbrains.skiko:skiko:0.7.12:
           - Unmatched attributes:
               - Provides org.gradle.category 'library' but the consumer didn't ask for it
               - Provides org.gradle.libraryelements 'jar' but the consumer didn't ask for it
               - Provides org.gradle.status 'release' but the consumer didn't ask for it
               - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
               - Provides org.jetbrains.kotlin.platform.type 'jvm' but the consumer didn't ask for it

I know that technically I should use the multiplatform plugin (but it is not possible right now), but I am curious why release 1.0.1 fixed all the issues - and now they are back because of the skiko publication.

jakobkmar avatar Feb 27 '22 19:02 jakobkmar

Have the same issues building fatjar with compose dependencies

trychen avatar Mar 25 '22 16:03 trychen

I tried to use a separate project with the Kotlin Multiplatform Gradle plugin, but the same issue comes up there as well.

Asking more generally: How can I get a list of all transitive dependencies of compose.desktop.common, using either the JVM or the Multiplatform Plugin. With all the methods I tried, I always get the "Cannot choose between the following variants" error.

jakobkmar avatar Jun 05 '22 21:06 jakobkmar

Related issue: https://github.com/JetBrains/skiko/issues/547


Generally speaking, the issue here is not if one uses the Kotlin Multiplatform plugin or not, but that Gradle does not have enough information about which artifact it should use.

To fix this, the Compose Gradle plugin should ask for the correct UI attribute. For this to work correctly, I think skiko has to provide the correct attributes with each artifact first. Also, skiko should add a namespace to the ui attribute - I also added that to the skiko issue.

Temporary fix

I found out that you can apparently "fix" this by using some weird Gradle behaviour, and ask for an ui attribute that does not even exist. Let's use awt, just because it makes sense in this case.

To apply this fix, do this, either using configurations.all or just for your configuration which you use to shade or include dependencies:

configurations.all {
    attributes {
        attribute(Attribute.of("ui", String::class.java), "awt")
    }
}

jakobkmar avatar Jun 05 '22 22:06 jakobkmar

Some time ago I was running into Cannot choose between the following variants and came up with this snippet:

tasks.register("printRuntimeDependencies") {
    println("Project runtime dependencies:")
    allprojects {
        println()
        println("-------- ${project.name} --------")
        project.configurations.matching { it.name == "desktopRuntimeClasspath" }
            .matching { !it.allDependencies.isEmpty() }
            .forEach {
                it.allDependencies.forEach { dep ->
                    if (dep.group != null) {
                        println("${dep.group}:${dep.name}:${dep.version}")
                    }
                }
            }
    }
}

kirill-grouchnikov avatar Jun 06 '22 18:06 kirill-grouchnikov

@jakobkmar Thank you for your workaround!

mikehearn avatar Jul 07 '22 17:07 mikehearn