Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building a Kotlin + Java 9 project with Gradle

I'm fairly new to Gradle (and Java 9, to be honest), and I'm trying to use Gradle to build a simple library project that is a mix of Java 9 and Kotlin. More in detail, there is an interface in Java and an implementation in Kotlin; I'd do everything in Kotlin, but the modules-info.java is java anyway, so I decided to do things this way.

I'm building on IntelliJ Idea, with the 1.2.0 kotlin plugin and gradle 4.3.1 defined externally.

Filesystem schema is:

+ src
  + main
    + java
      + some.package
        - Roundabout.java [an interface]
      - module-info.java
    + kotlin
      + some.package.impl
        - RoundaboutImpl.kt [implementing the interface]

module-info.java is:

module some.package {
  requires kotlin.stdlib;
  exports some.package;
}

and build.gradle is:

buildscript {
    ext.kotlin_version = '1.2.0'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

group 'some.package'
version '1.0-PRE_ALPHA'

apply plugin: 'java-library'
apply plugin: 'kotlin'

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

sourceCompatibility = 9

compileJava {
    dependsOn(':compileKotlin')
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
        ]
        classpath = files()
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: "$kotlin_version"
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

Notice that I had to specify a module path on the java compile task, or the compilation fails with:

error: module not found: kotlin.stdlib requires kotlin.stdlib;

Anyway, now this build fails with this error, and I can't figure out how to solve it:

error: package some.package.impl does not exist

import some.package.impl.RoundaboutImpl;

error: cannot find symbol

return new RoundaboutImpl<>(queueSize, parallelism, worker, threadPool);

I think that the Kotlin part of the compilation is going ok, then the java part fails because it doesn't "see" the kotlin side, so to speak.

I think I should tell it somehow to to load the already compiled kotlin classes in the classpath; but (first) how do I do this in gradle? and (second) is it even possible? I think you can't mix module path and class path in Java 9.

How can I solve this? I think it is a pretty common situation, as every java9-style module will be a mixed-language module (because of module-info.java), so I think I'm missing something really basic here.

Thanks in advance!

like image 626
Germano Rizzo Avatar asked Dec 05 '17 15:12

Germano Rizzo


4 Answers

Solved! It was sufficient to set the kotlin compilation dir to the same dir as Java:

compileKotlin.destinationDir = compileJava.destinationDir

It works now, both with the sources in the same tree or in different trees; but with a quirk: the jar task produces a jar with all the entries duplicated. I'll work on fix this, next.

Thanks to everyone!

like image 173
Germano Rizzo Avatar answered Oct 19 '22 10:10

Germano Rizzo


I am using the following gradle script where I put the module-info.java under src/module. It gets automatically included in the jar (without duplicates):

if (JavaVersion.current() >= JavaVersion.VERSION_1_9) {
    subprojects {
        def srcModule = "src/module"
        def moduleInfo = file("${project.projectDir}/$srcModule/module-info.java")
        if (moduleInfo.exists()) {

            sourceSets {
                module {
                    java {
                        srcDirs = [srcModule]
                        compileClasspath = main.compileClasspath
                        sourceCompatibility = '9'
                        targetCompatibility = '9'
                    }
                }
                main {
                    kotlin { srcDirs += [srcModule] }
                }
            }

            compileModuleJava.configure {
                dependsOn compileKotlin
                destinationDir = compileKotlin.destinationDir
                doFirst {
                    options.compilerArgs = ['--module-path', classpath.asPath,]
                    classpath = files()
                }
            }
            jar.dependsOn compileModuleJava
        }
    }
}

I won't update it any longer, have a look at https://github.com/robstoll/atrium/blob/master/build.gradle to see the current version in use.

like image 42
Robert Stoll Avatar answered Oct 19 '22 10:10

Robert Stoll


The accepted answer did not work for me (atleast not the way it was presented), but this is what worked:

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.3.50"
}

compileKotlin {
    doFirst {
        destinationDir = compileJava.destinationDir
    }
}

jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

Doing it the way the accepted answer suggests led to me getting this error:

Directory '/path/to/project/build/classes/kotlin/main' specified for property 'compileKotlinOutputClasses' does not exist.


Gradle version: 5.6

like image 28
smac89 Avatar answered Oct 19 '22 08:10

smac89


I ran into the same problem and the existing answers fixed only part of the issue for me, so I searched over all internet and ended up with a working solution. I don't know exactly why this works, but I decided to share my build.gradle.kts file here to help other people to find they way. This file is a combination of many pieces that I found on the internet.

I'm using Java 16, Kotlin 1.5.31 and Gradle 7.1.

The file tree is:

+ project
  - build.gradle.kts
  + src
    + main
      + java
        - module-info.java
        + my
          + package
            - SomeClasses.java
      + kotlin
        + my
          + package
            - MoreClasses.kt

module-info.java

module name.of.your.javamodule {
    requires kotlin.stdlib;
    requires kotlinx.coroutines.core.jvm;
    requires org.jetbrains.annotations;

    exports my.pacakge;
}

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    application
    kotlin("jvm") version "1.5.31"
    id("org.jetbrains.kotlin.plugin.serialization") version "1.5.31"
}
val kotlinVersion = "1.5.31"

group = "your.group.id"
version = "0.0.1-SNAPSHOT"
application {
    mainClass.set("full.name.of.your.MainClass")
    mainModule.set("name.of.your.javamodule") // Same defined in module-info.java
    executableDir = "run"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib-jdk8", kotlinVersion))
    implementation("com.michael-bull.kotlin-inline-logger:kotlin-inline-logger:1.0.3")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt")
    implementation("org.jetbrains:annotations:22.0.0")
    testImplementation(kotlin("test", kotlinVersion))
}

java {
    sourceCompatibility = JavaVersion.VERSION_16
    targetCompatibility = JavaVersion.VERSION_16
}

tasks {
    run.configure {
        dependsOn(jar)
        doFirst {
            jvmArgs = listOf(
                "--module-path", classpath.asPath
            )
            classpath = files()
        }
    }

    compileJava {
        dependsOn(compileKotlin)
        doFirst {
            options.compilerArgs = listOf(
                "--module-path", classpath.asPath
            )
        }
    }

    compileKotlin {
        destinationDirectory.set(compileJava.get().destinationDirectory)
    }

    jar {
        duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    }
}

tasks.withType<KotlinCompile>().configureEach {
    kotlinOptions {
        jvmTarget = "16"
    }
}

like image 1
José Roberto Araújo Júnior Avatar answered Oct 19 '22 08:10

José Roberto Araújo Júnior