rules_kotlin
rules_kotlin copied to clipboard
Failed on inline functions when coverage is enabled
On the master branch commit 89a082c69ce3bc7b0a996aea12cb2d118ab0bbc7 of rules_kotlin.
It either complains of accessing private member $jacocoInit from outside of the class, at runtime; or failed with the message of invalid bytecode at compile time. While the source code could pass tests and run flawlessly.
I think the reason is that the inline functions will be embedded into the caller, and jacoco instrumentation breaks it.
For example,
An inline function Foo.func will be embedded to the caller Bar.func in another jar, and jacoco will generate an instrumented version of Foo.func(let's call it Foo.func_inst, although its name is still Foo.func), in which a private member Foo.$jacocoInit is called. If Bar.func embeds this Foo.func_inst, it also embeds the invocation to Foo.$jacocoInit, which is causing compile-time error or runtime error.
I think the solution would be generating two jars side by side: one is original and one is instrumented. At compile time, always use the original one, but at runtime, instrumented one could be used to collect coverage. In this way, the instrumentation will be always after the code generation.
I am seeing this issue as well. Here is a simple repro based off of a rules_kotlin example.
https://github.com/bazelbuild/rules_kotlin/pull/600/files
The snippet is from a project we are migrating from gradle to bazel. Instrumentation worked in gradle. Note that in gradle, we did precisely what James Lan is describing.
There are two approaches to solve these inline function related issues:
-
Similar to gradle, we need to use uninstrumented classes during compilation stage but use instrumented classes during dexing stage. Gradle achieves this by having instrumented classes under a separated folder. This approach requires much more code changes because we have to generate different output follow and change the way dexer works in bazel repository.
-
We noticed that this issue can be workaround by enabling
experimental_use_abi_jarsflag in kotlin rule. However, some targets which is incompatible with abi jar solution will still cause issues after applyingkt_abi_plugin_incompatible. What we can do is to use uncovered jar as abi jar for those targets which is incompatible withexperimental_use_abi_jarsflag here
if (outputs.abijar.isNotEmpty() || (outputs.abijar.isEmpty() && coverageIsOn) {
if (coverageIsOn)
context.execute("create uncovered jar to serve as abi jar", ::createOutputJar)
} else {
context.execute("create abi jar", ::createAbiJar)
}
I think we should revisit this issue. Internally we have this workaround in compile.bzl under _run_kt_java_builder_actions
# Build Kotlin
if has_kt_sources:
...
# This is used to workaround https://github.com/bazelbuild/rules_kotlin/issues/509
if ctx.coverage_instrumented():
uninstrumented_jar = ctx.actions.declare_file(ctx.label.name + "-kt-uninstrumented.jar")
outputs["uninstrumented_jar"] = uninstrumented_jar
if "kt_abi_plugin_incompatible" in ctx.attr.tags:
if ctx.coverage_instrumented():
kt_compile_jar = uninstrumented_jar
else:
kt_compile_jar = kt_runtime_jar
else:
kt_compile_jar = ctx.actions.declare_file(ctx.label.name + "-kt.abi.jar")
outputs["abi_jar"] = kt_compile_jar