rules_kotlin
rules_kotlin copied to clipboard
kotlin-reflect on compilation classpath by default but not runtime, can lead to ClassNotFoundException
Consider this minimal example:
WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_bazel_rules_kotlin",
sha256 = "6cbd4e5768bdfae1598662e40272729ec9ece8b7bded8f0d2c81c8ff96dc139d",
urls = ["https://github.com/bazelbuild/rules_kotlin/releases/download/v1.5.0-beta-4/rules_kotlin_release.tgz"],
)
load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories")
kotlin_repositories()
load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_register_toolchains")
kt_register_toolchains()
BUILD.bazel
load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary")
kt_jvm_binary(
name = "app",
srcs = [
":Main.kt",
],
main_class = "MainKt",
)
Main.kt
import kotlin.reflect.jvm.javaMethod
fun main() {
println(::main.javaMethod)
}
In this situation bazel build //:app completes successfully since by default the default kotlinc compiler options adds kotlin-reflect to the classpath. However bazel run //:app fails with a ClassNotFoundException as it looks like kotlin-reflect is not part of the runtime classpath by default. Should the default kotlinc options exclude kotlin-reflect? Indeed when I use a custom toolchain with kt_kotlinc_options.include_stdlibs = "stdlibs" then I get the expected behaviour of compilation failing.
Looking here https://github.com/bazelbuild/rules_kotlin/blob/26c811765f8ed42d4538a1963dffb9dc2d12f3b8/kotlin/internal/toolchains.bzl#L163 it does seem kotlin-reflect is excluded from runtime classpath by default.
This behaviour is actually the same with other stdlibs, for example with the same WORKSPACE and BUILD.bazel as above you can use the following Main.kt to achieve the same - bazel build is successful but bazel run throws a ClassNotFoundException. Here it is because stdlib-jdk8 is on the compile classpath but not runtime classpath.
import java.util.stream.Stream
import kotlin.streams.asSequence
fun main() {
println(Stream.empty<String>().asSequence().toList())
}
So it looks like
https://github.com/bazelbuild/rules_kotlin/blob/503434f0a03a9cc578b926d880484ff804799d3b/kotlin/internal/toolchains.bzl#L56-L63
is causing the issue, why are we adding these libraries only to the compile classpath by default? They aren't added by kotlinc by default. Surely we want them added to both compile + runtime classpath or to neither.
As you pointed out, this is a generalized issue with handling deps for compilation. We have a few options we're considering.
For now, we will treat reflect differently from stdlib (and do as gradle does) which is not auto-supply the compilation dep for reflect - if you need it, add it to your deps directly. For stdlibs, we probably have a different strategy.