Export Kotlin code to jar is recognized as Java code on the consumer project - kotlin

I'm working on a multi-module repository. One module exports Kotlin library, and the other one consumes the artifact and uses it.
I've noticed that once I'm trying to use named parameters inside the consumer it tells me that I cannot do this in a non-kotlin code (e.g. from Java). When I've tried to decompile the code of the library I've seen that the code I see there is indeed Java code (however this might be related to the fact that IntelliJ decompile everything into Java?)
The entire code can be found in this following link where demo-app is the consumer and rabbit-hole is the producer
Just for complicity, I'll put the build gradle file of both here as well:
Library:
plugins {
id "java"
id "org.jetbrains.kotlin.jvm"
id "org.jetbrains.kotlin.plugin.allopen"
id "jacoco"
id "maven-publish"
id "org.springframework.boot"
id "io.spring.dependency-management"
id "com.diffplug.spotless"
}
jar {
enabled = true
}
bootJar {
enabled = false
}
dependencies {
implementation group: "org.jetbrains.kotlin", name: "kotlin-stdlib-jdk8"
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-amqp'
// Tests
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation group: 'io.mockk', name: 'mockk', version: '1.12.1'
}
build {
finalizedBy spotlessApply
}
test {
useJUnitPlatform()
finalizedBy spotlessApply
finalizedBy jacocoTestReport
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'com.yonatankarp'
artifactId = project.name
version = project.version
from components.java
versionMapping {
usage('java-runtime') {
fromResolutionResult()
}
}
}
}
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/yonatankarp/rabbit-hole")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")
}
}
}
}
allOpen {
annotation("org.springframework.context.annotation.Configuration")
}
Consumer:
plugins {
id "java"
id "org.jetbrains.kotlin.jvm"
id "org.jetbrains.kotlin.plugin.allopen"
id "jacoco"
id "org.springframework.boot"
id "io.spring.dependency-management"
id "com.diffplug.spotless"
}
group = 'com.yonatankarp'
version "${version}"
sourceCompatibility = '11'
jar {
enabled = false
}
bootJar {
enabled = true
}
repositories {
mavenLocal()
mavenCentral()
maven {
name = "Rabbit Hole"
url = uri("https://maven.pkg.github.com/yonatankarp/rabbit-hole")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") ?: System.getenv('GITHUB_TOKEN')
}
}
}
dependencies {
implementation group: "org.jetbrains.kotlin", name: "kotlin-stdlib-jdk8"
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation "com.yonatankarp:rabbit-hole:+"
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
build {
finalizedBy spotlessApply
}
test {
useJUnitPlatform()
finalizedBy spotlessApply
finalizedBy jacocoTestReport
}
allOpen {
annotation("org.springframework.context.annotation.Configuration")
}
EDIT
For clarity: Both the library and the consumer of it are written in Kotlin

Related

JLink: Cannot find module-info.java in project

I was trying to create runtime for my project, but suddenly I got an error and I couldn't find any info about it. The project is setted up as kotlin+gradle and nothing more.
I use badass-jlink-plugin and log4j2. The error:
Execution failed for task ':prepareModulesDir'.
> Error while evaluating property 'moduleName' of task ':prepareModulesDir'
> Failed to query the value of extension 'jlink' property 'moduleName'.
> Cannot find module-info.java in [ProjectFolder\src\main\java]
My build.gradle (not full):
buildscript {
...
}
plugins {
id 'application'
id 'org.jetbrains.kotlin.jvm' version '1.8.0'
id 'org.beryx.jlink' version '2.26.0'
// id 'org.jetbrains.kotlin.multiplatform' version '1.8.0'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.0'
}
apply plugin: 'kotlin'
apply plugin: 'kotlinx-serialization'
group = 'com.adisalagic.codenames'
version = '1.0-SNAPSHOT'
mainClassName = 'com.adisalagic.codenames.Main'
repositories {
mavenCentral()
}
dependencies {
// implementation "org.jetbrains.kotlin:kotlin-serialization:1.8.0"
testImplementation 'org.jetbrains.kotlin:kotlin-test'
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0-RC")
implementation 'org.apache.logging.log4j:log4j-api:2.19.0'
implementation 'org.apache.logging.log4j:log4j-core:2.19.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
test {
useJUnitPlatform()
}
jlink {
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
forceMerge 'log4j-api'
}
jar {
manifest {
attributes "Main-Class": "$mainClassName"
}
from {
configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
tasks.withType(Jar).configureEach {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes["Main-Class"] = "$mainClassName"
}
}
kotlin {
// jvmToolchain(8)
}
Should I create module-info myself?
I've discovered that it only happened when I use forceMerge. But everywhere it is suggested to be used.

ShareStateFlow in Intelji plugin

I have a ToolWindowFactory class like this
class MyToolWindowFactory : ToolWindowFactory {
private val sharedFlow = MutableSharedFlow<String>(replay = 1)
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
CoroutineScope(Dispatchers.IO).launch {
sharedFlow.emit("VALUE")
}
CoroutineScope(Dispatchers.IO).launch {
sharedFlow.collectLatest {
println("$it")
}
}
}
}
I use SharedFlow for variables, but I get this error after run.
Exception in thread "DefaultDispatcher-worker-3" java.lang.NoClassDefFoundError: Could not initialize class kotlinx.coroutines.CoroutineExceptionHandlerImplKt
at kotlinx.coroutines.CoroutineExceptionHandlerKt.handleCoroutineException(CoroutineExceptionHandler.kt:33)
at kotlinx.coroutines.DispatchedTask.handleFatalException$kotlinx_coroutines_core(DispatchedTask.kt:95)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:64)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Does ShareStateFlow work in Intelji Plugin?
build.gradle
plugins {
id 'org.jetbrains.intellij' version '1.5.2'
id 'org.jetbrains.kotlin.jvm' version '1.6.10'
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.9"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
// See https://github.com/JetBrains/gradle-intellij-plugin/
intellij {
version = '2021.3.3'
}
patchPluginXml {
changeNotes = """
Add change notes here.<br>
<em>most HTML tags may be used</em>"""
}
test {
useJUnitPlatform()
}

How to package a kotlin project with JavaFX in it?

my team got a kotlin project with JavaFX as frontEnd. The problem we have is using jlink to produce a package.
Here is my build.gradle for the project.
Could anyone help me take a look at how to change this?
ps: I have already tried with
command-line javac
jlink from the intellij
create a new Main method and use the shadow plugin for jlink.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
}
}
plugins {
id "java"
id "application"
id "org.jetbrains.kotlin.jvm" version "1.6.10"
id "org.openjfx.javafxplugin" version "0.0.12"
id "org.beryx.jlink" version "2.25.0"
// Json
id "org.jetbrains.kotlin.plugin.serialization" version "1.6.10"
}
group "com.yyil"
version "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
ext {
junitVersion = "5.8.1"
}
sourceCompatibility = "16"
targetCompatibility = "16"
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
application {
mainClass = 'com.yyil.noteapp.NewMain'
}
[compileKotlin, compileTestKotlin].forEach {
it.kotlinOptions {
jvmTarget = "16"
}
}
javafx {
version = "17.0.2"
modules = ["javafx.controls", "javafx.fxml", "javafx.web", "javafx.swing", "javafx.graphics", "javafx.base"]
}
dependencies {
implementation("org.controlsfx:controlsfx:11.1.0")
implementation("net.synedra:validatorfx:0.1.13") {
exclude(group: "org.openjfx")
}
implementation("org.kordamp.ikonli:ikonli-javafx:12.2.0")
implementation("eu.hansolo:tilesfx:11.48") {
exclude(group: "org.openjfx")
}
// JSON
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
// testing JavaFX
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
testImplementation("org.testfx:testfx-core:4.0.16-alpha")
testImplementation("org.testfx:testfx-junit5:4.0.16-alpha")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
// database (sqlite)
implementation "org.xerial:sqlite-jdbc:3.30.1"
// svg support
implementation("org.apache.xmlgraphics:batik-transcoder:1.14")
}
test {
useJUnitPlatform()
}
When I try to build it, I got errors like:

build.gradle.kts following githubs guide: Configuring Gradle for use with GitHub Packages. But it fails finding the "from" function

I have a sample kotlin project that i am trying to get send to github as a package. Below is the contents of my buid.gradle.kts file. I am following this guide: https://docs.github.com/en/packages/guides/configuring-gradle-for-use-with-github-packages#example-using-kotlin-dsl-for-a-single-package-in-the-same-repository
But i can find any help as to why this error happens with the "from" function"
buid.gradle.kts content:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
repositories {
mavenCentral()
}
plugins {
kotlin("jvm") version "1.4.31"
`maven-publish`
}
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/OWNER/REPOSITORY")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
}
}
}
publications {
register("gpr") {
from(components["java"]) //This part fails with a Unresolved reference. None of the following
//candidates is applicable because of receiver type mismatch:
}
}
}
group = "me.jeppe"
version = "1.0-SNAPSHOT"
dependencies {
testImplementation(kotlin("test-junit"))
}
tasks.test {
useJUnit()
}
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "1.8"
}
After searching wide and far it appears that i need to write this:
register("gpr", MavenPublication::class)
instead of:
register("gpr")

Kotlin-multiplatform: ShadowJar gradle plugin creates empty jar

I tried to use ShadowJar gradle pluging to pack my ktor app into fat jar. But as result of shadowJar task i get every time almost empty jar. It contains only manifest (main class is properly set).
Gradle configuration (groovy):
import org.gradle.jvm.tasks.Jar
buildscript {
ext.kotlin_version = '1.3.72'
ext.ktor_version = '1.3.2'
ext.serialization_version = '0.20.0'
ext.sl4j_version = '1.6.1'
repositories { jcenter() }
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
apply plugin: 'kotlinx-serialization'
apply plugin: 'application'
apply plugin: 'java'
mainClassName = 'com.example.JvmMainKt'
apply plugin: 'com.github.johnrengelman.shadow'
repositories {
mavenCentral()
jcenter()
}
group 'com.example'
version '0.0.1'
apply plugin: 'maven-publish'
kotlin {
jvm {
}
js {
browser {
}
nodejs {
}
}
// For ARM, should be changed to iosArm32 or iosArm64
// For Linux, should be changed to e.g. linuxX64
// For MacOS, should be changed to e.g. macosX64
// For Windows, should be changed to e.g. mingwX64
mingwX64("mingw") {
binaries {
executable {
// Change to specify fully qualified name of your application's entry point:
entryPoint = 'main'
// Specify command-line arguments, if necessary:
runTask?.args('')
}
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
implementation "io.ktor:ktor-client-core:$ktor_version"
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation kotlin('stdlib-jdk8')
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
implementation "io.ktor:ktor-serialization:$ktor_version"
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "org.slf4j:slf4j-simple:$sl4j_version"
}
}
jvmTest {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
jsMain {
dependencies {
implementation kotlin('stdlib-js')
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
mingwMain {
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
}
}
mingwTest {
}
}
}
shadowJar {
mainClassName = 'com.example.JvmMainKt'
mergeServiceFiles()
}
marcu's answer is correct but he doesn't explain why that snipped solved his issue, and he's code snippet cannot be directly converted to build.gradle.kts because a cast is needed to see runtimeDependencyFiles from Kotlin. In groovy, his snippet works because groovy supports duck typing while Kotlin doesnt.
I needed this solution in Gradle Kotlin, so I'm sharing it this way.
The com.github.johnrengelman.shadow gradle plugin is designed to work with regular the java gradle plugin, which builds a single jar by default, so it automatically generates a single fat-jar based on that jar classpath.
The Kotlin-Multiplatform gradle plugin works differently, it creates jar files for each jvm target based on a lot of custom settings that is set up using a methodology that is unique to Kotlin-Multiplatform, that's why the default shadowJar task does not work out of the box on Kotlin-Multiplatform.
To solve this problem, we have to create a new ShadowJar task for each jvm target that we need a fat-jar manually, we can do it after we declare the targets (as marcu's example is showing), or during the creation of them. I'm going to show both ways, but I recommend doing during the creation to avoid having to cast objects.
Another thing is that I created function to apply the needed configuration because I have a 8 JVM targets, so this way I don't need to copy-paste the snipped 8 times, I only call the function instead.
During the creation of the target
The explanations are commented on the code:
// Add this imports on top of your build.gradle.kts file
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
// Since you need a main class, I added this default constant
// which you can change as you need:
val defaultMainClassName: String? = null
// Here I declare the function that will register the new ShadowJar task for the target
// If you need another mainClassName, you can pass it as parameter
fun KotlinJvmTarget.registerShadowJar(mainClassName: String? = defaultMainClassName) {
// We get the name from the target here to avoid conflicting
// with the name of the compilation unit
val targetName = name
// Access the main compilation
// We only want to create ShadowJar
// for the main compilation of the target, not the test
compilations.named("main") {
// Access the tasks
tasks {
// Here we register our custom ShadowJar task,
// it's being prefixed by the target name
val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
// Allows our task to be grouped alongside with other build task
// this is only for organization
group = "build"
// This is important, it adds all output of the build to the fat-jar
from(output)
// This tells ShadowJar to merge all jars in runtime environment
// into the fat-jar for this target
configurations = listOf(runtimeDependencyFiles)
// Here we configure the name of the resulting fat-jar file
// appendix makes sure we append the target name before the version
archiveAppendix.set(targetName)
// classifier is appended after the version,
// it's a common practice to set this classifier to fat-jars
archiveClassifier.set("all")
// Apply the main class name attribute
if (mainClassName != null) {
manifest {
attributes("Main-Class" to mainClassName)
}
}
// This instruction tells the ShadowJar plugin to combine
// ServiceLoader files together, this is needed because
// a lot of Kotlin libs uses service files and
// they would break without this instruction
mergeServiceFiles()
}
// Finally, we get the normal jar task for this target
// and tells kotlin to execute our recently created ShadowJar task
// after the normal jar task completes
getByName("${targetName}Jar") {
finalizedBy(shadowJar)
}
}
}
}
kotlin {
// Here we create a JVM target
jvm("jvm8") {
// We can configure anything we want
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
// If we need a ShadowJar for it,
// all we have to do now is call
// our custom function
// Here's an example of what I'm saying:
// https://stackoverflow.com/a/57951962/804976
registerShadowJar()
}
// Another one just to illustrate the example
jvm("jvm16") {
compilations.all {
kotlinOptions.jvmTarget = "16"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
registerShadowJar()
}
}
With comments removed
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
val defaultMainClassName: String? = null
fun KotlinJvmTarget.registerShadowJar(mainClassName: String? = defaultMainClassName) {
val targetName = name
compilations.named("main") {
tasks {
val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
group = "build"
from(output)
configurations = listOf(runtimeDependencyFiles)
archiveAppendix.set(targetName)
archiveClassifier.set("all")
if (mainClassName != null) {
manifest {
attributes("Main-Class" to mainClassName)
}
}
mergeServiceFiles()
}
getByName("${targetName}Jar") {
finalizedBy(shadowJar)
}
}
}
}
kotlin {
jvm("jvm8") {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
registerShadowJar()
}
jvm("jvm16") {
compilations.all {
kotlinOptions.jvmTarget = "16"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
registerShadowJar()
}
}
After the creation of the target
Now, I'm going to comment only the changes:
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
kotlin {
jvm("jvm8") {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
// Not registering on creation this time,
// we are going to register the task later
}
jvm("jvm16") {
compilations.all {
kotlinOptions.jvmTarget = "16"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}
}
val defaultMainClassName: String? = null
// Instead of having KotlinJvmTarget as receiver,
// we are going to cast the target to it.
// We are also getting the target name from
// the function parameter
fun registerShadowJar(targetName: String, mainClassName: String? = defaultMainClassName) {
// Get the target casting to KotlinJvmTarget in the process
kotlin.targets.named<KotlinJvmTarget>(targetName) {
// Access the main compilation
compilations.named("main") {
tasks {
val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
group = "build"
from(output)
configurations = listOf(runtimeDependencyFiles)
archiveAppendix.set(targetName)
archiveClassifier.set("all")
if (mainClassName != null) {
manifest {
attributes("Main-Class" to mainClassName)
}
}
mergeServiceFiles()
}
getByName("${targetName}Jar") {
finalizedBy(shadowJar)
}
}
}
}
}
// Now we call the method for each JVM target that we need to create a ShadowJar
registerShadowJar("jvm8")
registerShadowJar("jvm16")
With comments removed
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
kotlin {
jvm("jvm8") {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}
jvm("jvm16") {
compilations.all {
kotlinOptions.jvmTarget = "16"
}
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}
}
val defaultMainClassName: String? = null
fun registerShadowJar(targetName: String, mainClassName: String? = defaultMainClassName) {
kotlin.targets.named<KotlinJvmTarget>(targetName) {
compilations.named("main") {
tasks {
val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
group = "build"
from(output)
configurations = listOf(runtimeDependencyFiles)
archiveAppendix.set(targetName)
archiveClassifier.set("all")
if (mainClassName != null) {
manifest {
attributes("Main-Class" to mainClassName)
}
}
mergeServiceFiles()
}
getByName("${targetName}Jar") {
finalizedBy(shadowJar)
}
}
}
}
}
registerShadowJar("jvm8")
registerShadowJar("jvm16")
I've faced the same issue in the past, the syntax that was working (for groovy gradle) for me was:
shadowJar {
mergeServiceFiles()
manifest {
attributes 'Main-Class': 'com.example.JvmMainKt'
}
}
I finally found solution to my problem.
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
//...
task shadowJar2(type: ShadowJar) {
def target = kotlin.targets.jvm
from target.compilations.main.output
def runtimeClasspath = target.compilations.main.runtimeDependencyFiles
manifest {
attributes 'Main-Class': mainClassName
}
configurations = [runtimeClasspath]
}