hydraulic-software / conveyor

Gradle plugin, user guide and discussion forums for Conveyor
https://conveyor.hydraulic.dev
Apache License 2.0
123 stars 9 forks source link

Issues with multimodule KMP #26

Closed manosbatsis closed 2 years ago

manosbatsis commented 2 years ago

I get the impression conveyor is too dependent on Gradle's Java Plugin. Forked compose-music-app for an example here.

In short, this will work fine:

plugins {
    kotlin("jvm")
    id("org.jetbrains.compose")
    id("dev.hydraulic.conveyor")
}

This will not:

plugins {
    id("kotlin-multiplatform")
    id("org.jetbrains.compose")
    id("dev.hydraulic.conveyor")
}

kotlin {
    jvm()
}

Seems to me that if you tweak a KMM desktop module to play along, conveyor will still choke on the overall KMM build if uses multiplatform VS jvm for e.g. an Android module.

Sample error:

manos@tower:~/git/compose-music-app-multimodule$ ./gradlew -q printConveyorConfig -S
// Generated by the Conveyor Gradle plugin.

// Gradle project data. The build directory is useful for importing built files.
gradle.build-dir = /home/manos/git/compose-music-app-multimodule/build
gradle.project-name = compose-music-sample
app.fsname = compose-music-sample
app.version = 0.9.10
app.rdns-name = dev.hydraulic.${app.fsname}

// Config from the JetPack Compose Desktop plugin.
app.jvm.gui.main-class = null

// Inputs from dependency configurations and the JAR task.
app.inputs += /home/manos/git/compose-music-app-multimodule/build/libs/compose-music-sample-0.9.10.jar

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':desktop:printConveyorConfig'.
> Configuration with name 'runtimeClasspath' not found.

* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':desktop:printConveyorConfig'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:142)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:140)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:128)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:69)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:327)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:314)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:307)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:293)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:417)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:339)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: org.gradle.api.artifacts.UnknownConfigurationException: Configuration with name 'runtimeClasspath' not found.
        at org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer.createNotFoundException(DefaultConfigurationContainer.java:108)
        at org.gradle.api.internal.DefaultNamedDomainObjectCollection.getByName(DefaultNamedDomainObjectCollection.java:333)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer.getByName(DefaultConfigurationContainer.java:98)
        at org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer.getByName(DefaultConfigurationContainer.java:50)
        at hydraulic.conveyor.gradle.ConveyorConfigTask$runtimeConfigCopy$2.invoke(ConveyorConfigTask.kt:53)
        at hydraulic.conveyor.gradle.ConveyorConfigTask$runtimeConfigCopy$2.invoke(ConveyorConfigTask.kt:53)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at hydraulic.conveyor.gradle.ConveyorConfigTask.getRuntimeConfigCopy(ConveyorConfigTask.kt:53)
        at hydraulic.conveyor.gradle.ConveyorConfigTask.importFromDependencyConfigurations(ConveyorConfigTask.kt:75)
        at hydraulic.conveyor.gradle.ConveyorConfigTask.generate(ConveyorConfigTask.kt:143)
        at hydraulic.conveyor.gradle.PrintConveyorConfigTask.print(PrintConveyorConfigTask.kt:16)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
manosbatsis commented 2 years ago

Turns out all the build needed was a withJava():

plugins {
    id("kotlin-multiplatform")
    id("org.jetbrains.compose")
    id("dev.hydraulic.conveyor")
}

kotlin {
    jvm {
        withJava()
    }
    sourceSets {
        named("jvmMain") {}
    }
}

Living this open in case it's handy for doc/example improvements, feel free to close otherwise

mikehearn commented 2 years ago

Hey Manos, glad you found the time to try it!

That's interesting about withJava() but I'm not convinced that's all it needs? Does the output make sense when you do that - have all the JARs for each platform? We don't yet support KMM because it's still in alpha and yeah the Gradle plugin is totally different. It's a high priority but it feels like it needs more work. If you just ask for "the conveyor config for this gradle build" then in KMM it's ambiguous. You might want to package the JVM version, the native version or even the web version with Electron. So how do you pick? The obvious answer is to have several different Gradle tasks like printConveyorConfigForJVM and so on, but that has to be designed.

BTW the Gradle plugin is open source and in this repository. If someone wants to tackle this before we get to it, just let us know so there's no duplication of effort. In theory it's easy enough. The code just needs to detect the KMM plugin, then add KMM specific config writing tasks.

manosbatsis commented 2 years ago

Apologies, forgot to update. Indeed withJava() simply tricked the code by offering the expected "runtimeClasspath" etc. but without any practical effect. I refactored my desktop module from kmm to jvm+compose Gradle plugins for now. This only buys me some time, will have to make kmm work eventually. A PR for basic kmm sourceSets shouldn't be that hard if you guys haven't worked on it by then.

mikehearn commented 2 years ago

We're currently planning on having proper KMP support by early October, so if you want to have a crack at it before then that'd brilliant of course, otherwise we'll have a go. KMP is maturing and hopefully it'll be treated as stable soon, but the new Gradle plugin seems more complicated than the JVM specific one.

mikehearn commented 2 years ago

@manosbatsis On investigation it seems the current plugin actually does work, as long as you use withJava(). The following build.gradle.kts seems to produce the correct outputs and working apps. It's using the (not yet announced) 1.1 version of the Conveyor plugin, but, only because a new version was needed to work against the Compose 1.2 Gradle plugin. No dependency logic changed. But you've got to watch out for the bug workarounds, otherwise you'll get either classpath conflicts or errors from Gradle about ambiguous depenencies.

We just released the v1.1 plugin. Maybe you can try this out and see if it works when your build looks like the one below?

import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet

plugins {
    java
    kotlin("multiplatform")
    id("org.jetbrains.compose")
    id("dev.hydraulic.conveyor") version "1.1"
}

group = "com.example"
version = "1.0"

java {
    toolchain {
        vendor.set(JvmVendorSpec.AMAZON)
        languageVersion.set(JavaLanguageVersion.of(11))
    }
}

kotlin {
    jvm {
        compilations.all {
            kotlinOptions.jvmTarget = "11"
        }
        withJava()
    }

    sourceSets {
        val jvmMain: KotlinSourceSet by getting {
            dependencies {
                implementation(project(":common"))
                implementation(compose.desktop.currentOs)
                implementation("org.apache.lucene:lucene-core:8.11.2")
            }
        }
        val jvmTest by getting
    }
}

dependencies {
    // Machine specific configs used by Conveyor for the JVM desktop version.
    "linuxAmd64"(compose.desktop.linux_x64)
    "macAmd64"(compose.desktop.macos_x64)
    "macAarch64"(compose.desktop.macos_arm64)
    "windowsAmd64"(compose.desktop.windows_x64)

    // Force override the Kotlin stdlib version used by Compose to 1.7 as otherwise we end up with a mix of 1.6 and 1.7 on our classpath.
    val v = "1.7.10"
    for (m in setOf("linuxAmd64", "macAmd64", "macAarch64", "windowsAmd64")) {
        m("org.jetbrains.kotlin:kotlin-stdlib:$v")
        m("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$v")
        m("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$v")
    }
}

// Workaround for https://github.com/JetBrains/compose-jb/issues/1404
configurations.all {
    attributes {
        attribute(Attribute.of("ui", String::class.java), "awt")
    }
}

compose.desktop {
    application {
        mainClass = "MainKt"
    }
}
mikehearn commented 2 years ago

@manosbatsis Conveyor 3 released with the fix for this. Please let us know if it works for you!