|
| 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=%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 | + |
| 108 | + # The format specified in official documentation is failing, we follow instead the format |
| 109 | + # described in the provided error output (see below). |
| 110 | + # |
| 111 | + # Failed to parse agent arguments: java.lang.IllegalArgumentException: At least 5 arguments expected but 1 found. |
| 112 | + # Expected arguments are: |
| 113 | + # 0) data file to save coverage result |
| 114 | + # 1) a flag to enable tracking per test coverage |
| 115 | + # 2) a flag to calculate coverage for unloaded classes |
| 116 | + # 3) a flag to use data file as initial coverage, also use it if several parallel processes are to write into one file |
| 117 | + # 4) a flag to run line coverage or branch coverage otherwise |
| 118 | + |
| 119 | + ctx.actions.write(kover_args_file, "\n".join([ |
| 120 | + "../../%s" % binary_output_name, # Kotlin compiler runs in runfiles folder, make sure file is created is correct location |
| 121 | + "true", |
| 122 | + "false", |
| 123 | + "true", |
| 124 | + "true" |
| 125 | + ])) |
| 126 | + |
| 127 | + return kover_output_file, kover_args_file |
| 128 | + |
| 129 | + |
| 130 | +def create_kover_metadata_action( |
| 131 | + ctx, |
| 132 | + name, |
| 133 | + deps, |
| 134 | + kover_output_file): |
| 135 | + """ Generate kover metadata file needed for invoking kover CLI to generate report. |
| 136 | + More info at: https://kotlin.github.io/kotlinx-kover/cli/ |
| 137 | +
|
| 138 | + returns: |
| 139 | + the kover output metadata file. |
| 140 | + """ |
| 141 | + |
| 142 | + metadata_output_name = "%s-kover_metadata.txt" % name |
| 143 | + kover_output_metadata_file = ctx.actions.declare_file(metadata_output_name) |
| 144 | + |
| 145 | + srcs = [] |
| 146 | + classfiles = [] |
| 147 | + excludes = [] |
| 148 | + |
| 149 | + for dep in deps: |
| 150 | + if dep.label.package != ctx.label.package: |
| 151 | + continue |
| 152 | + |
| 153 | + if InstrumentedFilesInfo in dep: |
| 154 | + for src in dep[InstrumentedFilesInfo].instrumented_files.to_list(): |
| 155 | + if src.short_path.startswith(ctx.label.package + "/"): |
| 156 | + path = _paths.dirname(src.short_path) |
| 157 | + if path not in srcs: |
| 158 | + srcs.extend(["--src", path]) |
| 159 | + |
| 160 | + if JavaInfo in dep: |
| 161 | + for classfile in dep[JavaInfo].transitive_runtime_jars.to_list(): |
| 162 | + if classfile.short_path.startswith(ctx.label.package + "/"): |
| 163 | + if classfile.path not in classfiles: |
| 164 | + classfiles.extend(["--classfiles", classfile.path]) |
| 165 | + |
| 166 | + for exclude in ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_exclude: |
| 167 | + excludes.extend(["--exclude", exclude]) |
| 168 | + |
| 169 | + for exclude_annotation in ctx.toolchains[_TOOLCHAIN_TYPE].experimental_kover_exclude_annotation: |
| 170 | + excludes.extend(["--excludeAnnotation", exclude_annotation]) |
| 171 | + |
| 172 | + ctx.actions.write(kover_output_metadata_file, "\n".join([ |
| 173 | + "report", |
| 174 | + kover_output_file.path, |
| 175 | + "--title", |
| 176 | + "Code-Coverage Analysis: %s" % ctx.label, |
| 177 | + ] + srcs + classfiles + excludes)) |
| 178 | + |
| 179 | + return kover_output_metadata_file |
0 commit comments