Kotlin, Groovy and Java Compilation


(Matt Johnson) #1

I am currently working on a Gradle project that has both Java and Groovy sources. I am interested in some of the language features of Kotlin and am evaluating adding it in our code base. I have a sample project created that will compile and run all tests in IntelliJ however I can’t figure out how to make everything play nice with Gradle.

I’ve tried configuring sourceSets to enable groovy joint compilation using:
sourceSets {
main {
java { srcDirs = [] }
groovy { srcDirs = [“src/main/java”] }
}
}

I’ve also played around with trying to call compile for each language. I believe what I need is something similar to how groovy compiles stubs but I can’t figure it out…

My example project is here: https://github.com/mattjohnson/why-kotlin

Any insights are appreciated.


(Stefan Oehme) #2

The only way to get joint compilation across JVM languages would be if every language supported an explicit stubbing mechanism. This is not the case, so joint compilation of Groovy and Kotlin is not possible

  • you can jointly compile Groovy and Java
  • you can jointly compile Kotlin and Java
  • you can serially compile any combination (but that means dependencies can only go in one direction, e.g. Java < Groovy < Kotlin)

If you want an honest opinion: Don’t create a tower of babel. Stick to one alternative general-purpose language per project.


(Erik Pragt) #3

Hi Stefan,

I’m having the same challenge here. It’s only for a hobby project, for a real project I’d not take this approach, but right now I’m actually interested in doing a similar thing, and Java < Groovy < Kotlin is exactly what I want.

You mention you can do this by serially compiling your code, but can you explain how I could accomplish such a thing?

Thanks!


(Matt Johnson) #4

I don’t know how joint compilation works with Kotlin but hopefully I can explain the general process based on how groovy achieves the same goal. When doing joint compilation of groovy and Java sources that have dependencies both ways you would use the groovy compiler to first compile stubs of the groovy sources, then compile the Java sources and finally finish compiling the the full groovy sources. Introducing Kotlin into the mix would need to take the same approach. I imagine you would need to do the following:

  • Compile Groovy Stubs
  • Compile Kotlin Stubs
  • Compile Java
  • Compile Groovy
  • Compile Kotlin

As I stated originally, IntelliJ does this properly. From what I’ve seen in the Gradle documentation is that control over joint compilation isn’t possible, or at least not without more intimate knowledge of the plumbing where the documentation does delve into.

And I do kind of agree with Stefan regarding limiting the number of languages in a single project. On some days writing Java I wish I could exclusively use two alternative languages and skip out on Java completely though :slight_smile:

Reference for groovy joint compilation:
http://groovy-lang.org/groovyc.html


(Erik Pragt) #5

H Matt, thanks for the reply! I’m not looking for cross compilation or anything like that, I just want to ‘force’ Gradle into compiling the Groovy code first, and then the Kotlin code, just as like having a jar dependency, but I’d prefer to not to go into the trouble of creating an extra module per language, but I don’t know how to change the build order. Right now, it compiles like this:

(master)*$ ./gradlew -m clean build
:clean SKIPPED
:compileKotlin SKIPPED
:compileJava SKIPPED
:compileGroovy SKIPPED
:processResources SKIPPED

I’d just to compileGroovy first.


(Matt Johnson) #6

Maybe not ideal but gradle should respect the order of tasks you supply so you could be explicit and say:

gradle clean compileGroovy build

That should do what you want.


(Erik Pragt) #7

I might be missing something here, but if I do:

gradle -m clean compileGroovy build

Gradle still wants to compile my Kotlin first:

:clean SKIPPED
:compileKotlin SKIPPED
:compileJava SKIPPED
:compileGroovy SKIPPED
:processResources SKIPPED
:classes SKIPPED
:findMainClass SKIPPED
:jar SKIPPED
:bootRepackage SKIPPED
:assemble SKIPPED
:compileTestKotlin SKIPPED
:compileTestJava SKIPPED
:compileTestGroovy SKIPPED
:processTestResources SKIPPED
:testClasses SKIPPED
:test SKIPPED
:check SKIPPED
:build SKIPPED

(Matt Johnson) #8

That was unexpected!

Using this plugin you can see the dependencies between tasks: https://plugins.gradle.org/plugin/com.dorongold.task-tree

When I ran it on my sample project I got the following for build:

:build
+--- :assemble
|    \--- :jar
|         \--- :classes
|              +--- :compileGroovy
|              |    \--- :compileJava
|              |         \--- :compileKotlin
|              +--- :compileJava
|              |    \--- :compileKotlin
|              \--- :processResources

As you can see compileGroovy depends on compileJava and compileJava depends on compileKotlin.

What you can try is this:

./gradlew -m compileGroovy -x compileJava && ./gradlew -m compileJava -x compileGroovy

This will result in:

:compileGroovy SKIPPED

BUILD SUCCESSFUL

Total time: 0.63 secs
:compileKotlin SKIPPED
:compileJava SKIPPED

BUILD SUCCESSFUL

Total time: 0.639 secs

It’s so crazy that it just might work :wink:


(Stefan Oehme) #9

The problem here is simple: The Kotlin plugin authors decided that Kotlin should be compiled before Java. The Groovy plugin on the other hand makes the opposite assumption.

You can try overriding the Kotlin plugin’s behavior by doing:

compileJava.dependsOn = compileJava.taskDependencies.values - compileKotlin
compileKotlin.dependsOn(compileGroovy)

(Danny Thomas) #10

Looks like what you need is:

compileGroovy.dependsOn = compileGroovy.taskDependencies.values - 'compileJava'
compileKotlin.dependsOn compileGroovy
compileKotlin.classpath += files(compileGroovy.destinationDir)
classes.dependsOn compileKotlin
  • taskDependencies.values contains a String for the task name
  • You need an explicit dependency for the classes task, so it still runs before tests etc, because compileKotlin doesn’t output to the standard classes dir, it uses a post hook to copy from the kotlin-classes directory
  • Likewise, for Kotlin to compile against Groovy, you need to make the files output by the compileGroovy task visible, without using the sourceset output, as that’ll cause a circular dependency for classes

It means that interop can only go one way, Kotlin -> Groovy, so you need to use reflection at the seams, or move your key wiring classes to Kotlin.

Incidentally, this is a handy extension for Groovy Closure interop:

fun <T> T.groovyClosure(function: () -> Unit) = object : Closure<Unit>(this) {
    @Suppress("unused")
    fun doCall() {
        function()
    }
}

(Braam Wijsmuller) #11

Maybe interesting to know is that the clearest fail safe way to combine, in my view, is to have independent modules in a multi module project. Just put interfaces in java, have the modules implement them. Wire things up using dependency injection and let implementions be whatever jvm language.
So groovy and kotlin just use java interfaces and implement java interfaces. Both modules will do joint compilation wit java, no problems.