From ea7d8eb90feabe0f3cc032e0e15b9e5b5d0608fb Mon Sep 17 00:00:00 2001 From: vadymsavchenko Date: Fri, 5 Jun 2026 12:33:08 -0400 Subject: [PATCH] Move Kover agent setup to runtime --- kotlin/internal/jvm/impl.bzl | 37 ++++++++--- kotlin/internal/jvm/kover.bzl | 61 ++----------------- .../jvm/kt_android_local_test_impl.bzl | 18 +++--- src/test/starlark/internal/jvm/kover_test.bzl | 40 ++++++------ third_party/java_stub_template.txt | 11 ++++ 5 files changed, 70 insertions(+), 97 deletions(-) diff --git a/kotlin/internal/jvm/impl.bzl b/kotlin/internal/jvm/impl.bzl index a90ba3e2c..42f314125 100644 --- a/kotlin/internal/jvm/impl.bzl +++ b/kotlin/internal/jvm/impl.bzl @@ -26,11 +26,10 @@ load( ) load( "//kotlin/internal/jvm:kover.bzl", - _create_kover_agent_actions = "create_kover_agent_actions", _create_kover_metadata_action = "create_kover_metadata_action", _get_kover_agent_files = "get_kover_agent_file", - _get_kover_jvm_flags = "get_kover_jvm_flags", _is_kover_enabled = "is_kover_enabled", + _kover_jvm_flags_setup = "kover_jvm_flags_setup", ) load( "//kotlin/internal/utils:utils.bzl", @@ -124,6 +123,7 @@ def _write_launcher_action(ctx, rjars, main_class, jvm_flags): "%set_java_coverage_new_implementation%": """export JAVA_COVERAGE_NEW_IMPLEMENTATION=YES""", "%test_runtime_classpath_file%": "export TEST_RUNTIME_CLASSPATH_FILE=${JAVA_RUNFILES}", "%workspace_prefix%": ctx.workspace_name + "/", + "%kover_jvm_flags_setup%": "", }, is_executable = True, ) @@ -133,6 +133,30 @@ def _write_launcher_action(ctx, rjars, main_class, jvm_flags): ["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list()], ) + if ctx.configuration.coverage_enabled and _is_kover_enabled(ctx): + kover_agent_files = _get_kover_agent_files(ctx) + ctx.actions.expand_template( + template = template, + output = ctx.outputs.executable, + substitutions = { + "%classpath%": classpath, + "%java_start_class%": main_class, + "%javabin%": java_bin, + "%jvm_flags%": jvm_flags, + "%needs_runfiles%": "0" if _is_absolute(java_bin_path) else "1", + "%runfiles_manifest_only%": "", + "%set_jacoco_java_runfiles_root%": "", + "%set_jacoco_main_class%": "", + "%set_jacoco_metadata%": "", + "%set_java_coverage_new_implementation%": """export JAVA_COVERAGE_NEW_IMPLEMENTATION=NO""", + "%test_runtime_classpath_file%": "export TEST_RUNTIME_CLASSPATH_FILE=${JAVA_RUNFILES}", + "%workspace_prefix%": ctx.workspace_name + "/", + "%kover_jvm_flags_setup%": _kover_jvm_flags_setup(kover_agent_files, ctx.label.package, ctx.attr.name), + }, + is_executable = True, + ) + return [] + ctx.actions.expand_template( template = template, output = ctx.outputs.executable, @@ -149,6 +173,7 @@ def _write_launcher_action(ctx, rjars, main_class, jvm_flags): "%set_java_coverage_new_implementation%": """export JAVA_COVERAGE_NEW_IMPLEMENTATION=NO""", "%test_runtime_classpath_file%": "export TEST_RUNTIME_CLASSPATH_FILE=${JAVA_RUNFILES}", "%workspace_prefix%": ctx.workspace_name + "/", + "%kover_jvm_flags_setup%": "", }, is_executable = True, ) @@ -299,19 +324,13 @@ def kt_jvm_junit_test_impl(ctx): if ctx.configuration.coverage_enabled: 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] + coverage_runfiles = [kover_output_metadata_file] else: jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner coverage_runfiles = jacocorunner.files.to_list() diff --git a/kotlin/internal/jvm/kover.bzl b/kotlin/internal/jvm/kover.bzl index a4c7682c0..a817c4b94 100644 --- a/kotlin/internal/jvm/kover.bzl +++ b/kotlin/internal/jvm/kover.bzl @@ -75,61 +75,11 @@ def get_kover_agent_file(ctx): 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 flags used to setup Kover agent. +def kover_jvm_flags_setup(kover_agent_files, pkg, name): + """Return the %kover_jvm_flags_setup% substitution — a call to _setup_kover in the template.""" + return '_setup_kover "%s" "%s" "%s"' % (kover_agent_files[0].short_path, pkg, name) - Args: - kover_agent_files: List of Kover agent files. - kover_args_file: The Kover arguments file. - - Returns: - The flag string to be used by test runner JVM. - """ - jvm_args = [ - "-Xbootclasspath/a:%s" % (kover_agent_files[0].short_path), - "-javaagent:%s=file:%s" % (kover_agent_files[0].short_path, kover_args_file.short_path), - ] - return " ".join(jvm_args) - -def create_kover_agent_actions(ctx, name): - """Generate the actions needed to emit Kover code coverage metadata file. - - Creates the properly populated arguments input file needed by Kover agent. - - Args: - ctx: The rule context. - name: The name of the target. - - Returns: - A tuple of (kover_output_file, kover_args_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): +def create_kover_metadata_action(ctx, name, deps): """Generate kover metadata file needed for invoking kover CLI to generate report. More info at: https://kotlin.github.io/kotlinx-kover/cli/ @@ -138,7 +88,6 @@ def create_kover_metadata_action( ctx: The rule context. name: The name of the target. deps: The dependencies to collect coverage for. - kover_output_file: The Kover output file. Returns: The kover output metadata file. @@ -177,8 +126,6 @@ def create_kover_metadata_action( excludes.extend(["--excludeInheritedFrom", exclude_inherited_from]) ctx.actions.write(kover_output_metadata_file, "\n".join([ - "report", - kover_output_file.path, "--title", "Code-Coverage Analysis: %s" % ctx.label, ] + srcs + classfiles + excludes)) diff --git a/kotlin/internal/jvm/kt_android_local_test_impl.bzl b/kotlin/internal/jvm/kt_android_local_test_impl.bzl index 9600c5136..4b1aa9004 100644 --- a/kotlin/internal/jvm/kt_android_local_test_impl.bzl +++ b/kotlin/internal/jvm/kt_android_local_test_impl.bzl @@ -76,11 +76,10 @@ load( ) load( "//kotlin/internal/jvm:kover.bzl", - _create_kover_agent_actions = "create_kover_agent_actions", _create_kover_metadata_action = "create_kover_metadata_action", _get_kover_agent_files = "get_kover_agent_file", - _get_kover_jvm_flags = "get_kover_jvm_flags", _is_kover_enabled = "is_kover_enabled", + _kover_jvm_flags_setup = "kover_jvm_flags_setup", ) _JACOCOCO_CLASS = "com.google.testing.coverage.JacocoCoverageRunner" @@ -142,18 +141,13 @@ def _process_jvm(ctx, resources_ctx, **_unused_sub_ctxs): if ctx.configuration.coverage_enabled: 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])]) + transitive.extend([depset(kover_agent_files), depset([kover_output_metadata_file])]) java_start_class = ctx.attr.main_class coverage_start_class = None @@ -336,10 +330,16 @@ def _create_stub( subs["%set_jacoco_main_class%"] = "" subs["%set_jacoco_java_runfiles_root%"] = "" + if ctx.configuration.coverage_enabled and _is_kover_enabled(ctx): + kover_agent_files = _get_kover_agent_files(ctx) + subs["%kover_jvm_flags_setup%"] = _kover_jvm_flags_setup(kover_agent_files, ctx.label.package, ctx.attr.name) + else: + subs["%kover_jvm_flags_setup%"] = "" + subs.update(substitutes) ctx.actions.expand_template( - template = _utils.only(_get_android_toolchain(ctx).java_stub.files.to_list()), + template = ctx.attr.java_stub_template.files.to_list()[0], output = stub_file, substitutions = subs, is_executable = True, diff --git a/src/test/starlark/internal/jvm/kover_test.bzl b/src/test/starlark/internal/jvm/kover_test.bzl index 372c4214e..14dd305e3 100644 --- a/src/test/starlark/internal/jvm/kover_test.bzl +++ b/src/test/starlark/internal/jvm/kover_test.bzl @@ -15,45 +15,41 @@ """Tests for Kover code coverage integration.""" load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//kotlin/internal/jvm:kover.bzl", "get_kover_jvm_flags") +load("//kotlin/internal/jvm:kover.bzl", "kover_jvm_flags_setup") -def _get_kover_jvm_flags_test_impl(ctx): - """Test that get_kover_jvm_flags generates correct JVM agent flags including bootclasspath.""" +def _kover_jvm_flags_setup_test_impl(ctx): + """Test that kover_jvm_flags_setup emits the _setup_kover shell call with agent path, package and name.""" env = unittest.begin(ctx) - # Create mock file objects with short_path attribute + # Create a mock agent file object with a short_path attribute. mock_agent_file = struct(short_path = "external/kover/kover-jvm-agent.jar") - mock_args_file = struct(short_path = "bazel-out/k8-fastbuild/bin/test-kover.args.txt") - result = get_kover_jvm_flags([mock_agent_file], mock_args_file) + result = kover_jvm_flags_setup([mock_agent_file], "path/to/pkg", "my_test") - # Expected format includes both -Xbootclasspath/a and -javaagent flags - expected = "-Xbootclasspath/a:external/kover/kover-jvm-agent.jar -javaagent:external/kover/kover-jvm-agent.jar=file:bazel-out/k8-fastbuild/bin/test-kover.args.txt" + expected = '_setup_kover "external/kover/kover-jvm-agent.jar" "path/to/pkg" "my_test"' asserts.equals(env, expected, result) return unittest.end(env) -get_kover_jvm_flags_test = unittest.make(_get_kover_jvm_flags_test_impl) +_kover_jvm_flags_setup_test = unittest.make(_kover_jvm_flags_setup_test_impl) -def _kover_jvm_flags_format_test_impl(ctx): - """Test JVM flags format with different path patterns.""" +def _kover_jvm_flags_setup_format_test_impl(ctx): + """Test the kover_jvm_flags_setup call format with different path patterns.""" env = unittest.begin(ctx) - # Test with workspace-relative path mock_agent = struct(short_path = "maven/kover-agent-1.0.jar") - mock_args = struct(short_path = "pkg/test.args") - result = get_kover_jvm_flags([mock_agent], mock_args) + result = kover_jvm_flags_setup([mock_agent], "pkg", "test") - # Verify the format includes both bootclasspath and javaagent flags - asserts.true(env, "-Xbootclasspath/a:" in result) - asserts.true(env, "-javaagent:" in result) - asserts.true(env, "=file:" in result) - asserts.true(env, result.endswith("pkg/test.args")) + # Verify the call uses the first agent file and quotes all three arguments. + asserts.true(env, result.startswith("_setup_kover ")) + asserts.true(env, '"maven/kover-agent-1.0.jar"' in result) + asserts.true(env, '"pkg"' in result) + asserts.true(env, result.endswith('"test"')) return unittest.end(env) -kover_jvm_flags_format_test = unittest.make(_kover_jvm_flags_format_test_impl) +_kover_jvm_flags_setup_format_test = unittest.make(_kover_jvm_flags_setup_format_test_impl) def kover_test_suite(name): """Create the test suite for Kover integration tests. @@ -63,6 +59,6 @@ def kover_test_suite(name): """ unittest.suite( name, - get_kover_jvm_flags_test, - kover_jvm_flags_format_test, + _kover_jvm_flags_setup_test, + _kover_jvm_flags_setup_format_test, ) diff --git a/third_party/java_stub_template.txt b/third_party/java_stub_template.txt index d13b482ac..c12f65a63 100644 --- a/third_party/java_stub_template.txt +++ b/third_party/java_stub_template.txt @@ -292,6 +292,17 @@ if [[ -n "$TEST_TMPDIR" && -d "$TEST_TMPDIR" ]]; then JVM_FLAGS+=" -Djava.io.tmpdir=$TEST_TMPDIR" fi +function _setup_kover() { + local agent="$1" pkg="$2" name="$3" + local ic + ic=$(mktemp) + mkdir -p "${TEST_UNDECLARED_OUTPUTS_DIR}/${pkg}" + echo "report.file=${TEST_UNDECLARED_OUTPUTS_DIR}/${pkg}/${name}-kover_report.ic" > "$ic" + JVM_FLAGS="${JVM_FLAGS} -Xbootclasspath/a:${agent} -javaagent:${agent}=file:${ic}" +} + +%kover_jvm_flags_setup% + ARGS=( ${JVM_DEBUG_FLAGS} ${JVM_FLAGS}