Code generation in Kotlin MPP with gradle

Another topic on my favorite subject Gradle Must Die

I need to do a quite trivial thing: a purely code generated module. I.e.:

  • there is a tool (a class with main method), written in Kotlin and deployed to maven repo as someToolLib
  • there is also a couple of other kotlin libraries
  • before any kotlin MPP compilation, a tool must be executed, with a libraryB on its classpath, to code-generate some kotlin common sources, which then have to be processed by kotlin MPP with additional dependencies.

I have spent almost a day, trying to understand what needs to be configured inside gradle to achieve this :frowning: (The similar things always worked for me like a charm with Maven)

Does anybody know, what needs to be fixed in buildscript example below to make it work?

import org.jetbrains.kotlin.gradle.dsl.KotlinCompile

val generatedKotlinSourcesDir = "$buildDir/generated/sources/kotlin"

kotlin {
    sourceSets {
        commonMain {

            dependencies {
        jvmMain {
            dependencies {

tasks.register("generateSourceCodeBeforeKotlinCompilation", JavaExec::class) {
    mainClass = "xxx.yyy.MainClass"
    val runtimeClasspath = configurations["jvmRuntimeClasspath"] + kotlin.targets["jvm"].compilations["main"].output.allOutputs //+ kotlin.targets["jvm"].compilations["main"].runtimeDependencyFiles!!
    classpath = runtimeClasspath //sourceSets["commonMain"].runtimeClasspath
    args = listOf(generatedKotlinSourcesDir, "someArgument")

    println("Code generation classpath:\n  ${runtimeClasspath.joinToString("\n  ")}")

tasks.withType( {

Because right now it fails with:

Circular dependency between the following tasks:

\--- :xyz:generateSourceCodeBeforeKotlinCompilation
     +--- :xyz:compileKotlinJvm (*)
     \--- :xyz:jvmMainClasses
          \--- :xyz:compileKotlinJvm (*)

Wait, do you mean to run a class from project X, which responsibility is to generate the source code of project X? Sounds like a chicken-egg problem to me. For me it makes much more sense to separate the generator code to another module, no matter the build tool.

Ahh, sorry, xxx.yyy.MainClass is inside one of included libs, correct?

Yes, xxx.yyy.MainClass is inside one of included libs, correct?


If I make build.dependsOn(“codeGenerationTask”) then everything works, except Kotlin compilation happens BEFORE code generation and geneared code is not compiled :frowning:

This is far from anything I ever did in Gradle, but I suspect the problem is here:

val runtimeClasspath = configurations["jvmRuntimeClasspath"] + kotlin.targets["jvm"].compilations["main"].output.allOutputs //+ kotlin.targets["jvm"].compilations["main"].runtimeDependencyFiles!!

You basically say that for running the source generation you need the classpath of the current module, which includes its own sources. This is a chicken-egg. We should probably put only the dependencies into the classpath. However, I don’t know how to do it and I admit I’m making educated guesses here.

I am pretty sure that what you described is the reason, BUT:

Why configuration part, that just declares dependencies of the module, produces circular dependency between execution tasks, which must be absolutely independent???

This is one those weird things about gradle :frowning: It is very badly designed. I hope this damned tool will sink in oblivion as it deserves.

This pattern (and even more comnplicated, when tool is part of multi-module project), works perfectly fine with maven, because maven is designed properly.

I don’t know :slight_smile: For me it makes perfect sense that if I put e.g. module A in a classpath of module B, then A has to be built before B, because otherwise that classpath dependency wouldn’t make any sense. But I honestly don’t know if this is what’s happening here and if this is how Gradle manages task dependencies.

I would try jvmCompileClasspath instead of jvmRuntimeClasspath, because I assume when compiling we need dependencies and when running: dependencies + self code. But I would also probably start with a simple Java module and only when succeeded, move to MPP, because that common-jvm separation also adds complexity and makes debugging harder.

1 Like

I tried jvmCompileClasspath

It worked somehow, thank you. Though I had to change the type of dependency on the toollib to ‘compileOnly’, from ‘runtimeOnly’ and it stopped pulling transitive dependencies of the tool library, so I had manually include them :frowning:

Thank you for idea, @broot

1 Like