Multi-platform project with a JVM common target

#1

I’m working on a JVM project that can be compiled against a couple different Java APIs. I’d like to use Kotlin’s multi-platform features for this, since they look super useful for what I’m doing.

I would like to have a common module that targets the JVM, and some JVM targets with various JDKs that use common to share code.

The build script I’m playing around with below. It can’t execute because common depends on itself, since the jvm preset has a dependency on common. I’m also not sure if there are any issues with replacing/removing the common module (e.g. there’s something going on with a metadata target, which I don’t know much about).

Is there a good way to approach this kind of multi-platform project?

settings.gradle.kts:

rootProject.name = "jdk-multiplatform-test"

enableFeaturePreview("GRADLE_METADATA")

build.gradle.kts:

plugins {
    kotlin("multiplatform") version "1.3.21"
}

repositories {
    mavenCentral()
}

kotlin {
    // Is this a problem, since common is a default target?
    jvm("common") // JDK 6+

    jvm("javaApi0") // JDK 6+
    jvm("javaApi1") // JDK 8+

    sourceSets {
        val commonMain by getting {
            // Defined by the jvm preset, causing a circular dependency problem:
            //dependsOn(commonMain)

            dependencies {
                // May not be necessary if the jvm preset defines this.
                implementation(kotlin("stdlib"))
            }
        }

        val javaApi0Main by getting {
            dependsOn(commonMain) // May not be necessary.

            dependencies {
                // May not be necessary, since commonMain already has this.
                implementation(kotlin("stdlib")) 
            }
        }

        val javaApi1Main by getting {
            dependsOn(commonMain) // May not be necessary.

            dependencies {
                implementation(kotlin("stdlib-jdk8"))
            }
        }
    }
}
1 Like
#2

Multiplatform is designed to have shared kotlin code target multiple different platforms, not sure you are going to be able to achieve what you are looking for with multi platform but then you don’t really need to use it at all if you are only targeting the JVM.

Why not just use the java-library gradle plugin and have multiple libs that depend on each other?

1 Like
#3

I’m mainly after the expect/actual mechanism, and it looks like that can be used without the kotlin("multiplatform") plugin (which I didn’t realize). Now I’m wondering if there’s a good way to setup a JVM Kotlin project with multiple source sets, similar to how multi-platform projects are organized. That is, one common with expect declarations, and a few source sets with the actual implementations.

Specifically, I’m working on a plugin for Minecraft servers that could be for against a couple different server platforms/APIs (e.g. Bukkit and Sponge). They share a lot of the same constructs, like players, blocks, and items, but have different APIs for them. Expect and actual would make sharing code between the two implementations a lot better compared to what I’m doing right now, which is creating adapters and factories for all the classes I have and providing them through dependency injection. This works, but it has a lot of complexity and overhead that could be avoided.

#4

I did not realize expect/actual could be used without multiplatform, although I wonder how it would work.

The adapters would still be needed in any case so you can create a consistent API surface, its the factories/DI you could do away with as you can just instantiate the adapters classes directly (as the correct actual implementation has been selected at compile time)

You can achieve the same thing without expect/actual by creating separate libs for the server platforms/APIs but with identical public API surfaces (your adapters with the exact same class names), compile your shared code against one of the libs (or a dummy/stub one) with the compileOnly gradle statement.

Then depend the correct one at the client level where you depend on the shared lib.

#5

What you intent to do can be done. More common in Android vs JVM targets. The support in intellij for this is however not quite there. Gradle will compile correctly as common code will actually only be compiled when compiling the target. Unfortunately it is not possible to mark a source set as being JVM so that it can JVM methods (gradle doesn’t care, Intellij does).

3 Likes
#6

I’m facing exactly the same issue at the moment with multiplatform project. I have common JVM source set that is used by JVM and Android targets source sets. It compiles well with Gradle, but IDEA even does not see “java” package so I can’t import “java.util.*”. Is there any solution or workaround?

2 Likes
#7

There is no proper solution yet, please follow https://youtrack.jetbrains.com/issue/KT-28194 for updates.

1 Like