Skip to content

Commit df6f3ab

Browse files
[Uber] 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 <options> 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 uber-common/bazel@cb9f6f0 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. [BPM] [Kover] Add support for --excludeInheritedFrom flag Summary: [Kover] Add support for --excludeInheritedFrom flag Test Plan: Bazel CI build Reviewers: #ldap_mobile-platform-android Tags: #autoland Revert Plan: arc backout <revisionId> JIRA Issues: MOBDROID-3738 Differential Revision: https://code.uberinternal.com/D15720567 typo
1 parent 9895270 commit df6f3ab

File tree

5 files changed

+432
-11
lines changed

5 files changed

+432
-11
lines changed

kotlin/internal/jvm/compile.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ load(
5353
"@bazel_skylib//rules:common_settings.bzl",
5454
"BuildSettingInfo",
5555
)
56+
load("//kotlin/internal/jvm:kover.bzl",
57+
_is_kover_enabled = "is_kover_enabled"
58+
)
5659

5760
# UTILITY ##############################################################################################################
5861
def find_java_toolchain(ctx, target):
@@ -540,7 +543,7 @@ def _run_kt_builder_action(
540543
args.add_all("--source_jars", srcs.src_jars + generated_src_jars, omit_if_empty = True)
541544
args.add_all("--deps_artifacts", deps_artifacts, omit_if_empty = True)
542545
args.add_all("--kotlin_friend_paths", associates.jars, map_each = _associate_utils.flatten_jars)
543-
args.add("--instrument_coverage", ctx.coverage_instrumented())
546+
args.add("--instrument_coverage", ctx.coverage_instrumented() and not _is_kover_enabled(ctx))
544547
args.add("--track_class_usage", toolchains.kt.experimental_track_class_usage)
545548
args.add("--track_resource_usage", toolchains.kt.experimental_track_resource_usage)
546549
if ksp_opts:

kotlin/internal/jvm/impl.bzl

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ load(
3131
"//kotlin/internal/utils:utils.bzl",
3232
_utils = "utils",
3333
)
34+
load("//kotlin/internal/jvm:kover.bzl",
35+
_is_kover_enabled = "is_kover_enabled",
36+
_get_kover_agent_files = "get_kover_agent_file",
37+
_create_kover_agent_actions = "create_kover_agent_actions",
38+
_create_kover_metadata_action = "create_kover_metadata_action",
39+
_get_kover_jvm_flags = "get_kover_jvm_flags",
40+
)
3441
load("//third_party:jarjar.bzl", "jarjar_action")
3542

3643
# borrowed from skylib to avoid adding that to the release.
@@ -78,7 +85,7 @@ def _write_launcher_action(ctx, rjars, main_class, jvm_flags):
7885
if getattr(java_runtime, "version", 0) >= 17:
7986
jvm_flags = jvm_flags + " -Djava.security.manager=allow"
8087

81-
if ctx.configuration.coverage_enabled:
88+
if ctx.configuration.coverage_enabled and not _is_kover_enabled(ctx):
8289
jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner
8390
classpath = ctx.configuration.host_path_separator.join(
8491
["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list() + jacocorunner.files.to_list()],
@@ -271,12 +278,30 @@ _SPLIT_STRINGS = [
271278

272279
def kt_jvm_junit_test_impl(ctx):
273280
providers = _kt_jvm_produce_jar_actions(ctx, "kt_jvm_test")
274-
runtime_jars = depset(ctx.files._bazel_test_runner, transitive = [providers.java.transitive_runtime_jars])
275281

276282
coverage_runfiles = []
283+
coverage_inputs = []
284+
coverage_jvm_flags = []
285+
277286
if ctx.configuration.coverage_enabled:
278-
jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner
279-
coverage_runfiles = jacocorunner.files.to_list()
287+
if _is_kover_enabled(ctx):
288+
kover_agent_files = _get_kover_agent_files(ctx)
289+
kover_output_file, kover_args_file = _create_kover_agent_actions(ctx, ctx.attr.name)
290+
kover_output_metadata_file = _create_kover_metadata_action(
291+
ctx,
292+
ctx.attr.name,
293+
ctx.attr.deps + ctx.attr.associates,
294+
kover_output_file
295+
)
296+
flags = _get_kover_jvm_flags(kover_agent_files, kover_args_file)
297+
298+
# add Kover agent jvm_flag, inputs and outputs
299+
coverage_jvm_flags = [flags]
300+
coverage_inputs = [depset(kover_agent_files)]
301+
coverage_runfiles = [kover_args_file, kover_output_metadata_file]
302+
else:
303+
jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner
304+
coverage_runfiles = jacocorunner.files.to_list()
280305

281306
test_class = ctx.attr.test_class
282307

@@ -294,8 +319,13 @@ def kt_jvm_junit_test_impl(ctx):
294319
jvm_flags = []
295320
if hasattr(ctx.fragments.java, "default_jvm_opts"):
296321
jvm_flags = ctx.fragments.java.default_jvm_opts
322+
jvm_flags.extend(coverage_jvm_flags + ctx.attr.jvm_flags)
323+
324+
runtime_jars = depset(
325+
ctx.files._bazel_test_runner,
326+
transitive = [providers.java.transitive_runtime_jars] + coverage_inputs
327+
)
297328

298-
jvm_flags.extend(ctx.attr.jvm_flags)
299329
coverage_metadata = _write_launcher_action(
300330
ctx,
301331
runtime_jars,

kotlin/internal/jvm/kover.bzl

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Copyright 2018 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# This file contains logic to integrate with Kover for code coverage, using
16+
# Kover JVM agent and disabling JaCoCo instrumentation, which avoid having to
17+
# re-compile application code. It used from both JVM and Android kotlin tests.
18+
#
19+
#
20+
# How to use?
21+
#
22+
# Supply the version of Kover agent via toolchain (typically from jvm_rules_extrenal),
23+
# and enable Kover. Then run `bazel coverage //your/kotlin/test_target`. Output files
24+
# are created in working module directory (along test/library explicity outputs).
25+
#
26+
#
27+
# Notes :
28+
#
29+
# 1. Because Bazel test/coverage are 'terminal' and actions or aspects can't reuse the output
30+
# of these, the generation of the report is done outside bazel (typically
31+
# from Bazel wrapper). The logic here will generate both raw output (*.ic file) and
32+
# a metadata file ready to provide to Kover CLI, so that one can generate report simply by
33+
# running : `java -jar kover-cli.jar report @path_to_metadat_file <options>`
34+
#
35+
# We could possibly generate the report by hijacking test runner shell script template
36+
# and injecting this command to executed after tests are run. This is rather hacky
37+
# and is likely to require changes to Bazel project.
38+
#
39+
# 2. For mixed sourceset, disabling JaCoCo instrumenation is required. To do this properly,
40+
# one should add an extra parameter to java_common.compile() API, which require modifying both
41+
# rules_java and Bazel core. For now, we disabled JaCoCo instrumentation accross the board,
42+
# you will need to cherry-pick this PR https://github.com/uber-common/bazel/commit/cb9f6f042c64af96bbd77e21fe6fb75936c74f47
43+
#
44+
# 3. Code in `kt_android_local_test_impl.bzl` needs to be kept in sync with rules_android. There is ongoing
45+
# conversation with google to simply of to extend rules_android, and override pipeline's behavior without
46+
# duplicating their code, we should be able to simplify this soon.
47+
#
48+
49+
load(
50+
"//kotlin/internal:defs.bzl",
51+
_KtJvmInfo = "KtJvmInfo",
52+
_TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE",
53+
)
54+
load("@bazel_skylib//lib:paths.bzl",
55+
_paths = "paths",
56+
)
57+
58+
def is_kover_enabled(ctx):
59+
return ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_enabled
60+
61+
def get_kover_agent_file(ctx):
62+
""" Get the Kover agent runtime files, extracted from toolchain.
63+
64+
returns:
65+
the Kover agent runtime files
66+
"""
67+
68+
kover_agent = ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_agent
69+
if not kover_agent:
70+
fail("Kover agent wasn't specified in toolchain.")
71+
72+
kover_agent_info = kover_agent[DefaultInfo]
73+
return kover_agent_info.files.to_list()
74+
75+
def get_kover_jvm_flags(kover_agent_files, kover_args_file):
76+
""" Compute the jvm flag used to setup Kover agent.
77+
78+
returns:
79+
the flag string to be used by test runner jvm
80+
"""
81+
82+
return "-javaagent:%s=file:%s" % (kover_agent_files[0].short_path, kover_args_file.short_path)
83+
84+
def create_kover_agent_actions(ctx, name):
85+
""" Generate the actions needed to emit Kover code coverage metadata file. It creates
86+
the properly populated arguments input file needed by Kover agent.
87+
88+
returns:
89+
the kover metadata output file.
90+
the kover arguments file.
91+
"""
92+
93+
# declare code coverage raw data binary output file
94+
binary_output_name = "%s-kover_report.ic" % name
95+
kover_output_file = ctx.actions.declare_file(binary_output_name)
96+
97+
# Hack: there is curently no way to indicate this file will be created Kover agent
98+
ctx.actions.run_shell(
99+
outputs = [kover_output_file],
100+
command = "touch {}".format(kover_output_file.path),
101+
)
102+
103+
# declare args file - https://kotlin.github.io/kotlinx-kover/jvm-agent/#kover-jvm-arguments-file
104+
kover_args_file = ctx.actions.declare_file(
105+
"%s-kover.args.txt" % name,
106+
)
107+
ctx.actions.write(
108+
kover_args_file,
109+
"report.file=../../%s" % binary_output_name # Kotlin compiler runs in runfiles folder, make sure file is created is correct location
110+
)
111+
112+
return kover_output_file, kover_args_file
113+
114+
115+
def create_kover_metadata_action(
116+
ctx,
117+
name,
118+
deps,
119+
kover_output_file):
120+
""" Generate kover metadata file needed for invoking kover CLI to generate report.
121+
More info at: https://kotlin.github.io/kotlinx-kover/cli/
122+
123+
returns:
124+
the kover output metadata file.
125+
"""
126+
127+
metadata_output_name = "%s-kover_metadata.txt" % name
128+
kover_output_metadata_file = ctx.actions.declare_file(metadata_output_name)
129+
130+
srcs = []
131+
classfiles = []
132+
excludes = []
133+
134+
for dep in deps:
135+
if dep.label.package != ctx.label.package:
136+
continue
137+
138+
if InstrumentedFilesInfo in dep:
139+
for src in dep[InstrumentedFilesInfo].instrumented_files.to_list():
140+
if src.short_path.startswith(ctx.label.package + "/"):
141+
path = _paths.dirname(src.short_path)
142+
if path not in srcs:
143+
srcs.extend(["--src", path])
144+
145+
if JavaInfo in dep:
146+
for classfile in dep[JavaInfo].transitive_runtime_jars.to_list():
147+
if classfile.short_path.startswith(ctx.label.package + "/"):
148+
if classfile.path not in classfiles:
149+
classfiles.extend(["--classfiles", classfile.path])
150+
151+
for exclude in ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_exclude:
152+
excludes.extend(["--exclude", exclude])
153+
154+
for exclude_annotation in ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_exclude_annotation:
155+
excludes.extend(["--excludeAnnotation", exclude_annotation])
156+
157+
for exclude_inherited_from in ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_exclude_inherited_from:
158+
excludes.extend(["--excludeInheritedFrom", exclude_inherited_from])
159+
160+
ctx.actions.write(kover_output_metadata_file, "\n".join([
161+
"report",
162+
kover_output_file.path,
163+
"--title",
164+
"Code-Coverage Analysis: %s" % ctx.label,
165+
] + srcs + classfiles + excludes))
166+
167+
return kover_output_metadata_file

0 commit comments

Comments
 (0)