From 9d848872e5244a4c89cd2ae64bda99ede4acbf3e Mon Sep 17 00:00:00 2001 From: Olivier Notteghem Date: Wed, 16 Oct 2024 13:53:03 -0700 Subject: [PATCH] Kover integration [kotlin part] This diff contains logic to integrate with Kover for code coverage, using Kover JVM agent and disabling JaCoCo instrumentation, which avoid having to re-compile application code. It used from both JVM and Android kotlin tests. How to use? Supply the version of Kover agent via toolchain (typically from jvm_rules_extrenal), and enable Kover (separate diff). Then run bazel coverage //your/kotlin/test_target. Output files are created in working module directory (along test/library explicity outputs). Please note : Notes : Because Bazel test/coverage are 'terminal' and actions or aspects can't reuse the output of these, the generation of the report is done outside bazel (typically from Bazel wrapper). The logic here will generate both raw output (*.ic file) and a metadata file ready to provide to Kover CLI, so that one can generate report simply by running : java -jar kover-cli.jar report @path_to_metadat_file We could possibly generate the report by hijacking test runner shell script template and injecting this command to executed after tests are run. This is rather hacky and is likely to require changes to Bazel project. For mixed sourceset, disabling JaCoCo instrumenation is required. To do this properly, one should add an extra parameter to java_common.compile() API, which require modifying both rules_java and Bazel core. For now, we disabled JaCoCo instrumentation accross the board, you will need to cherry-pick this PR https://github.com/uber-common/bazel/commit/cb9f6f042c64af96bbd77e21fe6fb75936c74f47 Code in kt_android_local_test_impl.bzl needs to be kept in sync with rules_android. There is ongoing conversation with google to simply of to extend rules_android, and override pipeline's behavior without duplicating their code, we should be able to simplify this soon. --- kotlin/internal/jvm/compile.bzl | 5 +- kotlin/internal/jvm/impl.bzl | 40 +++- kotlin/internal/jvm/kover.bzl | 164 +++++++++++++++ .../jvm/kt_android_local_test_impl.bzl | 199 +++++++++++++++++- kotlin/internal/toolchains.bzl | 26 +++ 5 files changed, 423 insertions(+), 11 deletions(-) create mode 100644 kotlin/internal/jvm/kover.bzl diff --git a/kotlin/internal/jvm/compile.bzl b/kotlin/internal/jvm/compile.bzl index b5f28b91c..d314c9d7a 100644 --- a/kotlin/internal/jvm/compile.bzl +++ b/kotlin/internal/jvm/compile.bzl @@ -55,6 +55,9 @@ load( "@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo", ) +load("//kotlin/internal/jvm:kover.bzl", + _is_kover_enabled = "is_kover_enabled" +) # UTILITY ############################################################################################################## @@ -525,7 +528,7 @@ def _run_kt_builder_action( args.add_all("--source_jars", srcs.src_jars + generated_src_jars, omit_if_empty = True) args.add_all("--deps_artifacts", deps_artifacts, omit_if_empty = True) args.add_all("--kotlin_friend_paths", associates.jars, map_each = _associate_utils.flatten_jars) - args.add("--instrument_coverage", ctx.coverage_instrumented()) + args.add("--instrument_coverage", ctx.coverage_instrumented() and not _is_kover_enabled(ctx)) args.add("--track_class_usage", toolchains.kt.experimental_track_class_usage) args.add("--track_resource_usage", toolchains.kt.experimental_track_resource_usage) if ksp_opts: diff --git a/kotlin/internal/jvm/impl.bzl b/kotlin/internal/jvm/impl.bzl index 2ddc98f28..b1fee2934 100644 --- a/kotlin/internal/jvm/impl.bzl +++ b/kotlin/internal/jvm/impl.bzl @@ -31,6 +31,13 @@ load( "//kotlin/internal/utils:utils.bzl", _utils = "utils", ) +load("//kotlin/internal/jvm:kover.bzl", + _is_kover_enabled = "is_kover_enabled", + _get_kover_agent_files = "get_kover_agent_file", + _create_kover_agent_actions = "create_kover_agent_actions", + _create_kover_metadata_action = "create_kover_metadata_action", + _get_kover_jvm_flags = "get_kover_jvm_flags", +) load("//third_party:jarjar.bzl", "jarjar_action") # borrowed from skylib to avoid adding that to the release. @@ -80,7 +87,7 @@ def _write_launcher_action(ctx, rjars, main_class, jvm_flags): if java_runtime.version >= 17: jvm_flags = jvm_flags + " -Djava.security.manager=allow" - if ctx.configuration.coverage_enabled: + if ctx.configuration.coverage_enabled and not _is_kover_enabled(ctx): jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner classpath = ctx.configuration.host_path_separator.join( ["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list() + jacocorunner.files.to_list()], @@ -276,12 +283,30 @@ _SPLIT_STRINGS = [ def kt_jvm_junit_test_impl(ctx): providers = _kt_jvm_produce_jar_actions(ctx, "kt_jvm_test") - runtime_jars = depset(ctx.files._bazel_test_runner, transitive = [providers.java.transitive_runtime_jars]) coverage_runfiles = [] + coverage_inputs = [] + coverage_jvm_flags = [] + if ctx.configuration.coverage_enabled: - jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner - coverage_runfiles = jacocorunner.files.to_list() + if _is_kover_enabled(ctx): + kover_agent_files = _get_kover_agent_files(ctx) + kover_output_file, kover_args_file = _create_kover_agent_actions(ctx, ctx.attr.name) + kover_output_metadata_file = _create_kover_metadata_action( + ctx, + ctx.attr.name, + ctx.attr.deps + ctx.attr.associates, + kover_output_file + ) + flags = _get_kover_jvm_flags(kover_agent_files, kover_args_file) + + # add Kover agent jvm_flag, inputs and outputs + coverage_jvm_flags = [flags] + coverage_inputs = [depset(kover_agent_files)] + coverage_runfiles = [kover_args_file, kover_output_metadata_file] + else: + jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner + coverage_runfiles = jacocorunner.files.to_list() test_class = ctx.attr.test_class @@ -299,8 +324,13 @@ def kt_jvm_junit_test_impl(ctx): jvm_flags = [] if hasattr(ctx.fragments.java, "default_jvm_opts"): jvm_flags = ctx.fragments.java.default_jvm_opts + jvm_flags.extend(coverage_jvm_flags + ctx.attr.jvm_flags) + + runtime_jars = depset( + ctx.files._bazel_test_runner, + transitive = [providers.java.transitive_runtime_jars] + coverage_inputs + ) - jvm_flags.extend(ctx.attr.jvm_flags) coverage_metadata = _write_launcher_action( ctx, runtime_jars, diff --git a/kotlin/internal/jvm/kover.bzl b/kotlin/internal/jvm/kover.bzl new file mode 100644 index 000000000..4c32cfea2 --- /dev/null +++ b/kotlin/internal/jvm/kover.bzl @@ -0,0 +1,164 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file contains logic to integrate with Kover for code coverage, using +# Kover JVM agent and disabling JaCoCo instrumentation, which avoid having to +# re-compile application code. It used from both JVM and Android kotlin tests. +# +# +# How to use? +# +# Supply the version of Kover agent via toolchain (typically from jvm_rules_extrenal), +# and enable Kover. Then run `bazel coverage //your/kotlin/test_target`. Output files +# are created in working module directory (along test/library explicity outputs). +# +# +# Notes : +# +# 1. Because Bazel test/coverage are 'terminal' and actions or aspects can't reuse the output +# of these, the generation of the report is done outside bazel (typically +# from Bazel wrapper). The logic here will generate both raw output (*.ic file) and +# a metadata file ready to provide to Kover CLI, so that one can generate report simply by +# running : `java -jar kover-cli.jar report @path_to_metadat_file ` +# +# We could possibly generate the report by hijacking test runner shell script template +# and injecting this command to executed after tests are run. This is rather hacky +# and is likely to require changes to Bazel project. +# +# 2. For mixed sourceset, disabling JaCoCo instrumenation is required. To do this properly, +# one should add an extra parameter to java_common.compile() API, which require modifying both +# rules_java and Bazel core. For now, we disabled JaCoCo instrumentation accross the board, +# you will need to cherry-pick this PR https://github.com/uber-common/bazel/commit/cb9f6f042c64af96bbd77e21fe6fb75936c74f47 +# +# 3. Code in `kt_android_local_test_impl.bzl` needs to be kept in sync with rules_android. There is ongoing +# conversation with google to simply of to extend rules_android, and override pipeline's behavior without +# duplicating their code, we should be able to simplify this soon. +# + +load( + "//kotlin/internal:defs.bzl", + _KtJvmInfo = "KtJvmInfo", + _TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE", +) +load("@bazel_skylib//lib:paths.bzl", + _paths = "paths", +) + +def is_kover_enabled(ctx): + return ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_enabled + +def get_kover_agent_file(ctx): + """ Get the Kover agent runtime files, extracted from toolchain. + + returns: + the Kover agent runtime files + """ + + kover_agent = ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_agent + if not kover_agent: + fail("Kover agent wasn't specified in toolchain.") + + kover_agent_info = kover_agent[DefaultInfo] + return kover_agent_info.files.to_list() + +def get_kover_jvm_flags(kover_agent_files, kover_args_file): + """ Compute the jvm flag used to setup Kover agent. + + returns: + the flag string to be used by test runner jvm + """ + + return "-javaagent:%s=file:%s" % (kover_agent_files[0].short_path, kover_args_file.short_path) + +def create_kover_agent_actions(ctx, name): + """ Generate the actions needed to emit Kover code coverage metadata file. It creates + the properly populated arguments input file needed by Kover agent. + + returns: + the kover metadata output file. + the kover arguments file. + """ + + # declare code coverage raw data binary output file + binary_output_name = "%s-kover_report.ic" % name + kover_output_file = ctx.actions.declare_file(binary_output_name) + + # Hack: there is curently no way to indicate this file will be created Kover agent + ctx.actions.run_shell( + outputs = [kover_output_file], + command = "touch {}".format(kover_output_file.path), + ) + + # declare args file - https://kotlin.github.io/kotlinx-kover/jvm-agent/#kover-jvm-arguments-file + kover_args_file = ctx.actions.declare_file( + "%s-kover.args.txt" % name, + ) + ctx.actions.write( + kover_args_file, + "report.file=../../%s" % binary_output_name # Kotlin compiler runs in runfiles folder, make sure file is created is correct location + ) + + return kover_output_file, kover_args_file + + +def create_kover_metadata_action( + ctx, + name, + deps, + kover_output_file): + """ Generate kover metadata file needed for invoking kover CLI to generate report. + More info at: https://kotlin.github.io/kotlinx-kover/cli/ + + returns: + the kover output metadata file. + """ + + metadata_output_name = "%s-kover_metadata.txt" % name + kover_output_metadata_file = ctx.actions.declare_file(metadata_output_name) + + srcs = [] + classfiles = [] + excludes = [] + + for dep in deps: + if dep.label.package != ctx.label.package: + continue + + if InstrumentedFilesInfo in dep: + for src in dep[InstrumentedFilesInfo].instrumented_files.to_list(): + if src.short_path.startswith(ctx.label.package + "/"): + path = _paths.dirname(src.short_path) + if path not in srcs: + srcs.extend(["--src", path]) + + if JavaInfo in dep: + for classfile in dep[JavaInfo].transitive_runtime_jars.to_list(): + if classfile.short_path.startswith(ctx.label.package + "/"): + if classfile.path not in classfiles: + classfiles.extend(["--classfiles", classfile.path]) + + for exclude in ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_exclude: + excludes.extend(["--exclude", exclude]) + + for exclude_annotation in ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_exclude_annotation: + excludes.extend(["--excludeAnnotation", exclude_annotation]) + + ctx.actions.write(kover_output_metadata_file, "\n".join([ + "report", + kover_output_file.path, + "--title", + "Code-Coverage Analysis: %s" % ctx.label, + ] + srcs + classfiles + excludes)) + + return kover_output_metadata_file diff --git a/kotlin/internal/jvm/kt_android_local_test_impl.bzl b/kotlin/internal/jvm/kt_android_local_test_impl.bzl index 1136fa349..15595d547 100644 --- a/kotlin/internal/jvm/kt_android_local_test_impl.bzl +++ b/kotlin/internal/jvm/kt_android_local_test_impl.bzl @@ -19,6 +19,9 @@ load( "@rules_android//rules:attrs.bzl", _attrs = "attrs", ) +load("@rules_android//rules:common.bzl", + _common = "common", +) load( "@rules_android//rules:java.bzl", _java = "java", @@ -46,12 +49,16 @@ load( load( "@rules_android//rules/android_local_test:impl.bzl", _BASE_PROCESSORS = "PROCESSORS", + _DEFAULT_VERIFY_FLAGS = "DEFAULT_VERIFY_FLAGS", + _DEFAULT_JIT_FLAGS = "DEFAULT_JIT_FLAGS", + _DEFAULT_GC_FLAGS = "DEFAULT_GC_FLAGS", _filter_jdeps = "filter_jdeps", _finalize = "finalize", ) load( "//kotlin/internal:defs.bzl", _JAVA_RUNTIME_TOOLCHAIN_TYPE = "JAVA_RUNTIME_TOOLCHAIN_TYPE", + _KtJvmInfo = "KtJvmInfo", _TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE", ) load( @@ -60,6 +67,14 @@ load( _export_only_providers = "export_only_providers", _kt_jvm_produce_output_jar_actions = "kt_jvm_produce_output_jar_actions", ) +load("//kotlin/internal/jvm:kover.bzl", + _is_kover_enabled = "is_kover_enabled", + _get_kover_agent_files = "get_kover_agent_file", + _create_kover_agent_actions = "create_kover_agent_actions", + _create_kover_metadata_action = "create_kover_metadata_action", + _get_kover_jvm_flags = "get_kover_jvm_flags", +) +load("@bazel_skylib//lib:paths.bzl", "paths") JACOCOCO_CLASS = "com.google.testing.coverage.JacocoCoverageRunner" @@ -114,10 +129,31 @@ def _process_jvm(ctx, resources_ctx, **unused_sub_ctxs): getattr(ctx.attr, "deps", []) ) + jvm_flags = [] + transitive = [] + if ctx.configuration.coverage_enabled: - deps.append(ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner) - java_start_class = JACOCOCO_CLASS - coverage_start_class = ctx.attr.main_class + if _is_kover_enabled(ctx): + kover_agent_files = _get_kover_agent_files(ctx) + kover_output_file, kover_args_file = _create_kover_agent_actions(ctx, ctx.attr.name) + kover_output_metadata_file = _create_kover_metadata_action( + ctx, + ctx.attr.name, + ctx.attr.deps + ctx.attr.associates, + kover_output_file + ) + + flags = _get_kover_jvm_flags(kover_agent_files, kover_args_file) + jvm_flags.append(flags) + + transitive.extend([depset(kover_agent_files), depset([kover_args_file]), depset([kover_output_metadata_file])]) + + java_start_class = ctx.attr.main_class + coverage_start_class = None + else: + deps.append(ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner) + java_start_class = JACOCOCO_CLASS + coverage_start_class = ctx.attr.main_class else: java_start_class = ctx.attr.main_class coverage_start_class = None @@ -161,7 +197,6 @@ def _process_jvm(ctx, resources_ctx, **unused_sub_ctxs): runfiles.append(filtered_jdeps) # Append the security manager override - jvm_flags = [] java_runtime = ctx.toolchains[_JAVA_RUNTIME_TOOLCHAIN_TYPE].java_runtime if java_runtime.version >= 17: jvm_flags.append("-Djava.security.manager=allow") @@ -177,13 +212,167 @@ def _process_jvm(ctx, resources_ctx, **unused_sub_ctxs): android_properties_file = ctx.file.robolectric_properties_file.short_path, additional_jvm_flags = jvm_flags, ), - runfiles = ctx.runfiles(files = runfiles), + runfiles = ctx.runfiles( + files = runfiles, + transitive_files = depset(transitive = transitive) + ), + ) + +# TODO: follow up with Google to have rules_android provide better extensibility points +def _process_stub(ctx, deploy_jar_ctx, jvm_ctx, stub_preprocess_ctx, **_unused_sub_ctxs): + runfiles = [] + + merged_instr = None + if ctx.configuration.coverage_enabled: + if not _is_kover_enabled(ctx): + merged_instr = ctx.actions.declare_file(ctx.label.name + "_merged_instr.jar") + _java.singlejar( + ctx, + [f for f in deploy_jar_ctx.classpath.to_list() if f.short_path.endswith(".jar")], + merged_instr, + mnemonic = "JavaDeployJar", + include_build_data = True, + java_toolchain = _common.get_java_toolchain(ctx), + ) + runfiles.append(merged_instr) + + stub = ctx.actions.declare_file(ctx.label.name) + classpath_file = ctx.actions.declare_file(ctx.label.name + "_classpath") + runfiles.append(classpath_file) + test_class = _get_test_class(ctx) + if not test_class: + fail("test_class could not be derived for " + str(ctx.label) + + ". Explicitly set test_class or move this source file to " + + "a java source root.") + + _create_stub( + ctx, + stub_preprocess_ctx.substitutes, + stub, + classpath_file, + deploy_jar_ctx.classpath, + _get_jvm_flags(ctx, test_class, jvm_ctx.android_properties_file, jvm_ctx.additional_jvm_flags), + jvm_ctx.java_start_class, + jvm_ctx.coverage_start_class, + merged_instr, + ) + return _ProviderInfo( + name = "stub_ctx", + value = struct( + stub = stub, + ), + runfiles = ctx.runfiles( + files = runfiles, + transitive_files = depset( + transitive = stub_preprocess_ctx.runfiles, + ), + ), ) +def _get_test_class(ctx): + # Use the specified test_class if set + if ctx.attr.test_class != "": + return ctx.attr.test_class + + # Use a heuristic based on the rule name and the "srcs" list + # to determine the primary Java class. + expected = "/" + ctx.label.name + ".java" + for f in ctx.attr.srcs: + path = f.label.package + "/" + f.label.name + if path.endswith(expected): + return _java.resolve_package(path[:-5]) + + # Last resort: Use the name and package name of the target. + return _java.resolve_package(ctx.label.package + "/" + ctx.label.name) + +def _create_stub( + ctx, + substitutes, + stub_file, + classpath_file, + runfiles, + jvm_flags, + java_start_class, + coverage_start_class, + merged_instr): + subs = { + "%needs_runfiles%": "1", + "%runfiles_manifest_only%": "", + # To avoid cracking open the depset, classpath is read from a separate + # file created in its own action. Needed as expand_template does not + # support ctx.actions.args(). + "%classpath%": "$(eval echo $(<%s))" % (classpath_file.short_path), + "%java_start_class%": java_start_class, + "%jvm_flags%": " ".join(jvm_flags), + "%workspace_prefix%": ctx.workspace_name + "/", + } + + if coverage_start_class: + prefix = ctx.attr._runfiles_root_prefix[_BuildSettingInfo].value + subs["%set_jacoco_metadata%"] = ( + "export JACOCO_METADATA_JAR=${JAVA_RUNFILES}/" + prefix + + merged_instr.short_path + ) + subs["%set_jacoco_main_class%"] = ( + "export JACOCO_MAIN_CLASS=" + coverage_start_class + ) + subs["%set_jacoco_java_runfiles_root%"] = ( + "export JACOCO_JAVA_RUNFILES_ROOT=${JAVA_RUNFILES}/" + prefix + ) + else: + subs["%set_jacoco_metadata%"] = "" + subs["%set_jacoco_main_class%"] = "" + subs["%set_jacoco_java_runfiles_root%"] = "" + + subs.update(substitutes) + + ctx.actions.expand_template( + template = _utils.only(_get_android_toolchain(ctx).java_stub.files.to_list()), + output = stub_file, + substitutions = subs, + is_executable = True, + ) + + args = ctx.actions.args() + args.add_joined( + runfiles, + join_with = ":", + map_each = _get_classpath, + ) + args.set_param_file_format("multiline") + ctx.actions.write( + output = classpath_file, + content = args, + ) + return stub_file + +def _get_classpath(s): + return "${J3}" + s.short_path + +def _get_jvm_flags(ctx, main_class, robolectric_properties_path, additional_jvm_flags): + return [ + "-ea", + "-Dbazel.test_suite=" + main_class, + "-Drobolectric.offline=true", + "-Drobolectric-deps.properties=" + robolectric_properties_path, + "-Duse_framework_manifest_parser=true", + "-Drobolectric.logging=stdout", + "-Drobolectric.logging.enabled=true", + "-Dorg.robolectric.packagesToNotAcquire=com.google.testing.junit.runner.util", + ] + _DEFAULT_JIT_FLAGS + _DEFAULT_GC_FLAGS + _DEFAULT_VERIFY_FLAGS + additional_jvm_flags + [ + ctx.expand_make_variables( + "jvm_flags", + ctx.expand_location(flag, ctx.attr.data), + {}, + ) + for flag in ctx.attr.jvm_flags + ] + PROCESSORS = _processing_pipeline.replace( _BASE_PROCESSORS, ResourceProcessor = _process_resources, JvmProcessor = _process_jvm, + StubProcessor = _process_stub, ) _PROCESSING_PIPELINE = _processing_pipeline.make_processing_pipeline( diff --git a/kotlin/internal/toolchains.bzl b/kotlin/internal/toolchains.bzl index f28030682..ce1423447 100644 --- a/kotlin/internal/toolchains.bzl +++ b/kotlin/internal/toolchains.bzl @@ -95,6 +95,10 @@ def _kotlin_toolchain_impl(ctx): empty_jar = ctx.file._empty_jar, empty_jdeps = ctx.file._empty_jdeps, jacocorunner = ctx.attr.jacocorunner, + experimental_kover_enabled = ctx.attr.experimental_kover_enabled, + experimental_kover_agent = ctx.attr.experimental_kover_agent, + experimental_kover_exclude = ctx.attr.experimental_kover_exclude, + experimental_kover_exclude_annotation = ctx.attr.experimental_kover_exclude_annotation, experimental_prune_transitive_deps = ctx.attr._experimental_prune_transitive_deps[BuildSettingInfo].value, ) @@ -293,6 +297,20 @@ _kt_toolchain = rule( "jacocorunner": attr.label( default = Label("@bazel_tools//tools/jdk:JacocoCoverage"), ), + "experimental_kover_enabled": attr.bool( + doc = """Use kover for code coverage.""", + default = False, + ), + "experimental_kover_agent": attr.label( + doc = """Kover agent Jar target used for code coverage, only used if experimental_kover_enabled is true.""", + providers = [JavaInfo], + ), + "experimental_kover_exclude": attr.string_list( + doc = """List of exclusions to use when generating kover reports.""", + ), + "experimental_kover_exclude_annotation": attr.string_list( + doc = """List of annotation exclusions to use when generating kover reports.""", + ), "_experimental_prune_transitive_deps": attr.label( doc = """If enabled, compilation is performed against only direct dependencies. Transitive deps required for compilation must be explicitly added. Using @@ -337,6 +355,10 @@ def define_kt_toolchain( experimental_track_resource_usage = None, experimental_compile_with_transitive_deps = True, experimental_multiplex_workers = None, + experimental_kover_enabled = False, + experimental_kover_agent = None, + experimental_kover_exclude = [], + experimental_kover_exclude_annotation = [], javac_options = Label("//kotlin/internal:default_javac_options"), kotlinc_options = Label("//kotlin/internal:default_kotlinc_options"), jacocorunner = None): @@ -361,6 +383,10 @@ def define_kt_toolchain( experimental_track_class_usage = experimental_track_class_usage, experimental_track_resource_usage = experimental_track_resource_usage, experimental_compile_with_transitive_deps = experimental_compile_with_transitive_deps, + experimental_kover_enabled = experimental_kover_enabled, + experimental_kover_agent = experimental_kover_agent, + experimental_kover_exclude = experimental_kover_exclude, + experimental_kover_exclude_annotation = experimental_kover_exclude_annotation, javac_options = javac_options, kotlinc_options = kotlinc_options, visibility = ["//visibility:public"],