22

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!

Naman
  • 27,789
  • 26
  • 218
  • 353
Germano Rizzo
  • 481
  • 1
  • 3
  • 8
  • to be pedantic, `module-info.java` is not _really_ Java, it's a special DSL, BUT the Kotlin devs have said they [see no value in reinventing it](https://discuss.kotlinlang.org/t/kotlin-support-for-java-9-module-system/2499) so it will remain as-is. – Salem Dec 05 '17 at 16:22
  • Yes, thanks for the clarification! – Germano Rizzo Dec 05 '17 at 16:30
  • 2
    What if you keep both the interface and impl in either Java or Kotlin. Does it compile then? Also, why would you compileKotlin using JVM 1.8? – Naman Dec 05 '17 at 17:27
  • Thanks for commenting. An all-java solution compiles, while an all-kotlin solution would always be a mixed project, because as said modules-info.java is managed by javac, so the result is the same. Also, I don't think I'm using JVM 1.8 for compileKotlin, just telling the compiler to use 1.8 target (it's the newest available); am I wrong? – Germano Rizzo Dec 06 '17 at 13:15
  • 1
    Starting with Kotlin version [1.3.30](https://blog.jetbrains.com/kotlin/2019/04/kotlin-1-3-30-released/) you can now specify jvmTarget 9-12. – StephanS Apr 17 '19 at 10:59
  • @StephanS thanks, but it says "Note that so far newer versions don’t add any bytecode optimizations or features beyond the ones that exist in lower versions, but that is going to change in the future." I don't think it helps much with the case at hand, as it still won't compile a modules-info.java. – Germano Rizzo Apr 18 '19 at 15:13
  • Discussed here: https://github.com/gradle/gradle/issues/17271 – tkruse Feb 13 '22 at 05:14

5 Answers5

16

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!

Germano Rizzo
  • 481
  • 1
  • 3
  • 8
2

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.

Robert Stoll
  • 371
  • 3
  • 11
2

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

smac89
  • 39,374
  • 15
  • 132
  • 179
1

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"
    }
}

Polyana Fontes
  • 3,156
  • 1
  • 27
  • 41
0

On gradle 7.4 with kotlin DSL, I need to:

  • move the module-info.java to src/main/java
  • create any java file inside each package to export under src/main/java, at least empty package-info.java

In build.gradle.kts:

val compileKotlin: KotlinCompile by tasks
val compileJava: JavaCompile by tasks
compileKotlin.destinationDirectory.set(compileJava.destinationDirectory)

Also discussed here: https://github.com/gradle/gradle/issues/17271

tkruse
  • 10,222
  • 7
  • 53
  • 80