Rant about Kotlin gradle plugins

After 2 full days of trying to figure out how to make a project with multiple targets and have the java target use javafx, I think give up. I’m not the ranting type but I’ve decided to write about my journey and all the issues I’ve encountered which sometimes are many years old but are still untouched and goodluck finding a workaround.

A part of this has nothing to do with gradle kotlin but bear with me.

So, I want to create a multi-target Kotlin application using javafx for the Java target. I go to New project->Kotlin->Application. So far so good, right? Wrong. Already it doesn’t work, because it starts spitting a lot of errors on my screen. After some googling I find someone else who has the same issue: Gradle doesn’t work with JDK 17, I need JDK 15. Great, let’s go and install like the 3rd JDK today. I go to the java.net site, but it’s closed. Nice. So let’s google ‘JDK 15’ and click the first link. Ah, Oracle JDK archive, this is what I wanted. I click on ‘Download’ and Oracle politely asks me to create some account and provide full name, country, address, phone number, company, workplace info, credit card number, the 3 digits on the back and rights to my first-born child. Sounds good but let’s try an alternative. Let’s google ‘openjdk’. I go to their site and they redirect me to… java.net? But I thought it was closed? Whatever, I download jdk15, extract it and try to get on with my life. Create a new project (again) and it asks me to add a target. I’ve never used kotlin/gradle scripts before so here comes some intense research on how to use kotlin plugins. After some hours of trial and error, I think I understand how they work. So I create a new project (again), as the old one was full of said trial-and-error attempts over the build.gradle.kts files. And here starts the fun part.

I add a jvm target and create the ‘jvmMain’ and ‘jvmTest’ folders. I create a main function, add the application plugin and execute ‘gradle:run’. It works first try, I also create a native target and I configure the ‘entryPoint’, also works, Damn, I’m a hacker and everything’s so simple. So I now have some code in the common module used by the 2 target modules. Alright, let’s use javafx in my jvm target. Oh wait, stupid me thought Oracle would make it that simple. Spoiler: they didn’t. They thought it would be a good idea to remove javafx from java, although it still technically is part of openjdk. ? Whatever, I just add the javafx dependencies to my ‘jvmMain’ (I’m a hacker after all, I know how to do this) aaaand… it doesn’t work. Just like that, trolls me on the spot. Unresolved references everywhere. So some more intense googling and it seems like I have to use yet another plugin (yay!) - the javafx plugin, created specially to add javafx to java because they removed it from java. So I add the plugin and surprise surprise, it doesn’t work. I didn’t even expect it to work to be honest, I just wanted to start researching a different error, so this is what I do. And here starts the part where I’m mad at the kotlin plugins.

These kotlin plugins actually use ‘sourceSets’, where basically they create 10 different modules just because (jvmMain, commonMain etc.). And these new modules/sourceSets are not compatible with gradle plugins which try to add a dependency, or something. So I google how to use javafx and kotlin together and, apparently, ‘kotlin-multiplatform’ is compatible with pretty much nothing, as it uses completely different classes to represent internal objects such as sourceSets or projects, although they have the same name. Great, I can’t add javafx to my java sourceSets. So what’s the solution? None! Why would there be a solution, it’s only been a reported problem for at least 2 years. So let’s use a workaround instead: create a new module and use the ‘kotlin-jvm’ plugin, to which I can add dependencies like normal. Easier said than done, had to do some more ‘build.gradle.kts’ research. Anyways, it’s done - I have a new java module to where I moved all my code + the javafx plugin. No more Unresolved references! You’d think it’s over, but no.

No error in editor but attempting to run it throws me “Error: JavaFX runtime components are missing” …but I thought the plugin deals with that? What does that plugin even tho? Easy fix this time tho: adding the ‘application’ plugin and running it with ‘gradle:run’ works! Also, before I couldn’t add the ‘application’ plugin because, you’d never guess it, it’s not compatible with ‘kotlin-multiplatform’. Who cares tho, I finally have the module running. However, more Unresolved references now: this new project can’t acces my common code. Easy fix, right? Just add implementation(rootProject) …it does nothing. Great, let’s see a sample project and see how they do it… oh, it’s implementation(project(":")), silly me …it still does nothing. You’d think a 9 year old issue would be fixed. I wouldn’t, because it isn’t. Noone explains why or how it should (or not) work, the only reply is “it’s uncommon to do this”. Fantastic support right there. Anyway, time for yet another workaround! Create a new ‘core project’, move all the ‘kotlin-multiplatform’ there and leave the top project empty, only to contain the other 2. So I do this, and I try to build. At this point the screen is full of errors and more red (redder?) than my eyes, which is a good sign. Gradle is spitting org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension_Decorated cannot be cast toorg.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension at me and I’m just crying. Alright, let’s run it from console with --info, maybe I get more information. Ah, finally a documented error: I’m sent to [a nice webpage](https://org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension_Decorated cannot be cast to class org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension) which helps me fix the error. My gradle manages to sync! Amazing. Is it… over?

Nowhere near. I try to add implementation(project(":core")) and… no gradle error this time! But it won’t build, and the Unresolved references are back. But… my ‘jvm’ project is depending on the ‘core’ project, and gradle isn’t complaining so… what’s wrong? I can’t seem to google the right terms, so I look at a sample project where a ‘jvm’ modules depends on a ‘core’ module, see how they do it. And… their ‘jvm’ module uses ‘kotlin-multiplatform’ with a jvm target instead of ‘kotlin-jvm’ as a workaround. Great, I just change it too, right? Except… I initially started with just a ‘kotlin-multiplatform’ project and swapped to ‘kotlin-jvm’ as another workaround… but now I need to switch back… or else I can’t use my common code… but if I switch back, I can’t use javafx, as it’s not compatible with ‘kotlin-multiplatform’, only with ‘kotlin-jvm’…

God just end me.

.

Basically, why is everything incompatible with everything in these kotlin gradle plugins?
BTW, this is a sample project which does not compile with the above setup:
Sample.zip (84.0 KB)

2 Likes

I didn’t use JavaFX since it was separated, so I can’t speak about this specific problem. It definitely sounds bad that we need to use a plugin for something that seems like a library. Maybe this is because JavaFX probably isn’t just a Java lib and may be partially dependent on the platform? I’m not sure.

Anyway, most of your other problems really seem like you are not very familiar with Gradle and Kotlin Multiplatform, so you use them mostly by trial and error. Such approach always end up with… well, a lot of trial and error.

1 Like

Here, a sample project consisting of some multiplatform code and JavaFX submodule that uses it. I believe it even downloads JDK 11 for you, so you don’t have to go this terrible experience of installing anything on Windows (this is my guess. Installing Java on linux is like saying: “Hey, install me Java 11, please!” and it just does this automatically). But I didn’t test it on windows, so maybe I’m wrong and it won’t go so smoothly.

I was even quicker with this than you answering me! :stuck_out_tongue:

Update:

Ahh, if you’re not aware: you run it by going into the project directory and typing: ./gradlew gui:run or gradle.bat gui:run.

sample-multiplatform-javafx.7z (56.2 KB)

Indeed, I’m also doing this to learn the gradle build system and scripts; however, my problem is that it’s not really documented how to add project dependencies anywhere; also the incompatibility between, for example, source sets (like KotlinJvmSourceSet and KotlinMultiplatformSourceSet, I think they’re called): neither inherits the other and you cannot have a dependency on a source set because of this, and they’re not explained anywhere. Also, I’m mad with some issue being acknowledged and untouched for years (mostly with gradle, again). Good example is how they said literally 7 years ago that “they are adding multi-catch”, but they haven’t added it yet: Does Kotlin have multi-catch?

Thanks a lot for your reply! Funny enough, I managed to make it work randomly by testing things: adding the jvm target to the multiplatform project does the trick (aka adding jvm()) in the core project’s ‘build.gradle.kts’; although not sure why the plugin won’t share the common sourceset otherwise.

Looking at the project you gave me, it’s pretty much identical to what I have (although mine took 2 days of trial end error to exist), would’ve been nice if I had it from the beginning, but hey, at least I learned how to write gradle build scripts I guess.

I’ve also worked on linux and indeed after having to just type sudo apt install whatever, going back to windows where I have to download manually, go through the installer and eventually add some bin folder to %PATH% does suck.

Unfortunately, this is due to how multiplatform projects work right now. Multiplatform modules don’t share their common code with other modules, but instead they are compiled in-place to all supported targets and then this compiled code is shared. As a result, Kotlin/JVM module can only depend on Kotlin multiplatform module if the latter defines JVM as its target.

I believe this is strictly technical limitation and it will be improved, so we will be able to have “pure-common” modules. Kotlin team just have to finalize the new compiler/IR and then eventually such modules will be possible. So probably in next 9 years or so :wink: I hope it will happen much earlier, maybe in the next year :crossed_fingers:

2 Likes

There is an official JavaFX plugin for gradle and you can see some examples of its usage here (JVM) and here (multiplatform). Sadly, it is rather limited and not made to play with multiplatform well, so we are using our own solution right now.

Did you know that you could download a JDK right from the new project wizard? Or it didn’t work for you for some reason?

I did see that, however I did not really like the fact that only 3rd parties were available for version 15 (I prefer using OpenJDK):

image

That’s the plugin I use, however I could not make it work with multiplatform; that is why I decided to create a Kotlin/JVM module for it.

You need to add ‘withJava’ to jvm target configuration to make work.

This would’ve been a real life saver! I definitely didn’t think of it, since the documentation claims it’s only used to add Java source files to a project (which I don’t have); nothing to do with libraries:

The problem is that plugins like javafx are explicitly using java source sets, they ignore kotlin source sets. Adding withJava exposes kotlin source sets to java-oriented plugins. You can see it in the multiplatform example I referenced above.

It is not always a good solution, because sometimes IDEA got confused by it.But it still solves the problem.

2 Likes

Ah, I understand now. Thanks a lot for clearing this up!

Most of the listed options are OpenJDK builds. The thing about OpenJDK is that, by itself, OpenJDK is just source code. If you want a pre-built binary distribution, you have to choose one of the many vendors out there, that offer OpenJDK binaries. The ones you find on jdk.java.net are the OpenJDK builds by Oracle. I would not recommend those, as Oracle does not provide security updates for more than 6 months for each release, even LTS releases. If you have no particular reason to choose otherwise, I recommend Temurin (formerly known as AdoptOpenJDK), which is backed by the Eclipse Foundation.

3 Likes