rules_kotlin icon indicating copy to clipboard operation
rules_kotlin copied to clipboard

kotlin-reflect on compilation classpath by default but not runtime, can lead to ClassNotFoundException

Open th0masb opened this issue 3 years ago • 3 comments

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.

th0masb avatar Jan 22 '22 11:01 th0masb

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())
}

th0masb avatar Jan 25 '22 11:01 th0masb

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.

th0masb avatar Jan 25 '22 14:01 th0masb

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.

cgruber avatar Feb 04 '22 20:02 cgruber