Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Gradle sub-projects with Kotlin multiplatform

I'm using Kotlin multi-platform (JVM & JS), which in IDEA creates three projects: demo, demo-js and demo-jvm.

I would like to split the common code into more subprojects/submodules. Let's say I add commonmod; how do I make it compile?

The error right now, for gradle run -p demo-jvm, is:

demo/demo-js/src/main/kotlin/demo/commonmod/example.kt: (3, 12): Actual function 'getPlatform' has no corresponding expected declaration

but I think I'm doing this fundamentally wrong, as I don't know what should depend on what (although I tried quite some iterations). If I solve this error I get other ones, and then other ones again, until I'm back to this one.


As a minimal-but-still-large example, I have:

demo/settings.gradle:

rootProject.name = 'demo'

include 'demo-jvm', 'demo-js', 'commonmod'

demo/build.gradle:

buildscript { ... }

apply plugin: 'kotlin-platform-common'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
    compile project(':commonmod')
}

demo/demo-jvm/settings.gradle:

rootProject.name = 'demo'

demo/demo-jvm/build.gradle:

buildscript { ... }

apply plugin: 'kotlin-platform-jvm'
apply plugin: 'application'

repositories {
    mavenCentral()
}

mainClassName = "demo.MainKt"

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    expectedBy project(":")
    testCompile "junit:junit:4.12"
    testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}

demo/demo-js/settings.gradle:

rootProject.name = 'demo'

demo/demo-js/build.gradle:

buildscript { ... }

apply plugin: 'kotlin-platform-js'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
    expectedBy project(":")
    testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
}

demo/commonmod/settings.gradle:

rootProject.name = 'demo'

include 'demo-jvm', 'demo-js'

demo/commonmod/build.gradle:

buildscript { ... }

apply plugin: 'kotlin-platform-common'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
    compile project(':demo-js')
    compile project(':demo-jvm')
}
like image 754
Mark Avatar asked Jan 10 '18 19:01

Mark


1 Answers

This took a crazy amount of time, so I hope this is useful for someone!

There is a functional example on Github: kotlin_multiplatform_gradle_demo

Several sources helped, but a lot of it was trial and error, so if something is bad practise, please let me know!


For the minimal example, the structure is like this:

├── alpha
│   ├── alpha-js
│   │   └── build.gradle
│   ├── alpha-jvm
│   │   └── build.gradle
│   ├── build.gradle
│   └── src
│       └── main
│           ├── kotlin
│           │   └── demo
│           │       └── alpha
│           │           └── main.kt
├── beta
│   ├── beta-js
│   │   ├── build.gradle
│   │   └── src
│   │       └── main
│   │           └── kotlin
│   │               └── demo
│   │                   └── beta
│   │                       └── platform.kt
│   ├── beta-jvm
│   │   ├── build.gradle
│   │   └── src
│   │       └── main
│   │           └── kotlin
│   │               └── demo
│   │                   └── beta
│   │                       └── platform.kt
│   ├── build.gradle
│   └── src
│       └── main
│           └── kotlin
│               └── demo
│                   └── beta
│                       └── platform.kt
├── build.gradle
└── settings.gradle

The common modules (alpha and beta) need platform modules for each platform with at least a `build.gradle``.

The settings.gradle file imports all modules, including platform ones.

Dependencies, e.g. from alpha on beta, is declared in the common alpha module and all alpha platform modules.


Some patterns I learned:

  • Every 'normal' (common) module has one platform module for each platform.
  • For common module alpha, the javascript platform module must be called alpha-js (similar for -jvm).
  • If there is no platform specific code, this module can be just a gradle file in a directory.
  • The platform modules can be conveniently placed inside of the common module directory (so alpha:alpha-js).
  • The common module should not refer to the platform modules; the platform modules have a dependency expectedBy project(":the_common_module").
  • If module alpha depends on beta, then

    • alpha must have dependencies { compile project(":beta") }
    • alpha-js must have dependencies { compile project(":beta:beta-js") } (in addition to expectedBy)
    • alpha-jvm must have dependencies { compile project(":beta:beta-jvm") } (in addition to expectedBy) etc
  • Only the top module has settings.gradle, which includes ALL submodules (including platform ones).

  • Make sure to get the names right, as incorrect ones don't cause an error, they just fail silently. (It seems ridiculous but I guess there is a reason.)
  • Do NOT put all the output into a single, shared build directory - this results in several weird non-deterministic errors.

I used to have the full config files here, but it's better to just check the code on Github because it's really long.

like image 89
Mark Avatar answered Oct 05 '22 11:10

Mark