Do Kotlin 1.2.10 and Java 9 have opposite rules re

2019-02-17 07:12发布

问题:

I have a Gradle project using the Kotlin Gradle plugin. I want to build a Java 9 module, so my directory structure looks like this:

src/main/java/
    - module-info.java
src/main/kotlin/
    - Foo.kt
    - Bar.kt
build.gradle
...

My build.gradle declares the following dependencies:

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.10"
    compile "org.jetbrains.kotlin:kotlin-reflect:1.2.10"
    compile "org.junit.jupiter:junit-jupiter-api:5.0.2"
}

and I use all of these dependencies in my Kotlin source (Foo.kt, Bar.kt, ...).

Everything works hunky-dory if I write my module-info.java like so:

module my.module {
    requires kotlin.stdlib;
    exports my.module.pkg;
}

and if I supply all my compile-time dependencies to javac during the compileJava task using this technique.

However if I turn on -Xlint:all for the Java compiler during the compileJava task (to compile module-info.java), I get the following warnings:

/path/to/my.module/src/main/java/module-info.java:26: warning: requires directive for an automatic module
    requires kotlin.stdlib;
                   ^

So here we have the Java compiler, javac complaining that kotlin.stdlib is an automatic module so I shouldn't have a requires clause for it.

But if I delete the requires clause to make javac happy, it makes kotlinc even angrier than javac was (I get an error not a warning):

e: /path/to/my.module/src/main/java/module-info.java: The Kotlin standard library is not found in the module graph. Please ensure you have the 'requires kotlin.stdlib' clause in your module definition

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':my.module:compileKotlin'.

Now I can fix that, too, by editing my compileKotlin task thus:

compileKotlin {
    doFirst {
        kotlinOptions.freeCompilerArgs = ['-Xallow-kotlin-package']
    }
}

But this only leads to MORE errors during the compileKotlin task, all looking like this one:

e: /path/to/my.module/src/main/kotlin/Foo.kt: (27, 30): Symbol is declared in module 'org.junit.jupiter.api' which current module does not depend on

And then if I try to force compileKotlin to take a module path rather than a classpath by adding "-Xmodule-path=${classpath.asPath}" to freeCompilerArgs and setting classpath to be empty, the Kotlin compiler can't find anything at all, and I end up with zillions of unresolved reference errors!

Why is the Kotlin compiler telling me I have to have requires kotlin.stdlib; when the Java compiler says the opposite? How can I get Kotlin and Java to work together to produce a Java 9 module?

回答1:

If you're authoring Java 9 module in Kotlin, you have to declare requires kotlin.stdlib in your module-info.java in order to satisfy runtime dependencies of the compiled Kotlin code in addition to the explicit dependencies on the standard library API.

javac warns you about requiring an automatic module when lint is enabled, because automatic modules have some potential drawbacks compared to normal modules. Until the standard library is compiled as a normal module, you have to deal with this warning.

-Xallow-kotlin-package compiler flag allows you to omit require kotlin.stdlib, because it is intended to be used only when the standard library itself is compiled. Obviously, if you specify this flag and omit that requirement, you won't be able to use any API from the standard library, so this is not really an option for you.



回答2:

I find it easiest to simply suppress the warnings for use of an automatic module. (In my case I have to use some automatic modules whether I want to or not, so these warnings are just distracting noise.) I have the following in my build.gradle.kts:

val compilerArgs = listOf(
    "-Xlint:all",                           // Enable all warnings except...
    "-Xlint:-requires-automatic",           // Suppress "requires directive for an automatic module" warnings from module-info.java
    "-Xlint:-requires-transitive-automatic" // Suppress "requires transitive directive for an automatic module" warnings from module-info.java
)

// This task will compile all Java code in the target module except for test code.
tasks.compileJava {
    doFirst {
        options.compilerArgs.addAll(compilerArgs)
    }
}

// This task will compile all Java test code in the target module.
tasks.compileTestJava {
    doFirst {
        options.compilerArgs.addAll(compilerArgs)
    }
}