diff --git a/cc/common/cc_helper.bzl b/cc/common/cc_helper.bzl index 5b795900f..96d833e62 100644 --- a/cc/common/cc_helper.bzl +++ b/cc/common/cc_helper.bzl @@ -305,16 +305,6 @@ def _is_compilation_outputs_empty(compilation_outputs): return (len(compilation_outputs.pic_objects) == 0 and len(compilation_outputs.objects) == 0) -def _is_code_coverage_enabled(ctx): - if ctx.coverage_instrumented(): - return True - if hasattr(ctx.attr, "deps"): - for dep in ctx.attr.deps: - if CcInfo in dep: - if ctx.coverage_instrumented(dep): - return True - return False - def _build_precompiled_files(ctx): objects = [] pic_objects = [] @@ -1140,7 +1130,6 @@ cc_helper = struct( get_cpp_module_interfaces = _get_cpp_module_interfaces, get_private_hdrs = _get_private_hdrs, get_public_hdrs = _get_public_hdrs, - is_code_coverage_enabled = _is_code_coverage_enabled, is_compilation_outputs_empty = _is_compilation_outputs_empty, get_linking_contexts_from_deps = _get_linking_contexts_from_deps, dll_hash_suffix = _dll_hash_suffix, diff --git a/cc/common/cc_helper_internal.bzl b/cc/common/cc_helper_internal.bzl index 6d0cb95f0..5b1a8d3b2 100644 --- a/cc/common/cc_helper_internal.bzl +++ b/cc/common/cc_helper_internal.bzl @@ -142,7 +142,10 @@ def get_linkstamp_stamps( ) stamps = { "GPLATFORM": cc_toolchain.toolchain_id, - "BUILD_COVERAGE_ENABLED": "1" if feature_configuration.is_enabled("coverage") else "0", + # Requested by cc_common.configure_features if coverage is generally enabled. This is + # checked instead of the feature being enabled so that the define is also correct for + # toolchains that don't define the feature. + "BUILD_COVERAGE_ENABLED": "1" if feature_configuration.is_requested("coverage_enabled") else "0", # G3_TARGET_NAME is a C string literal that normally contain the label of the target # being linked. However, they are set differently when using shared native deps. In # that case, a single .so file is shared by multiple targets, and its contents cannot diff --git a/cc/private/cc_common.bzl b/cc/private/cc_common.bzl index a63bfea20..37458494f 100644 --- a/cc/private/cc_common.bzl +++ b/cc/private/cc_common.bzl @@ -486,7 +486,6 @@ def _compile( additional_module_maps = _UNBOUND, propagate_module_map_to_compile_action = _UNBOUND, do_not_generate_module_map = _UNBOUND, - code_coverage_enabled = _UNBOUND, hdrs_checking_mode = _UNBOUND, variables_extension = {}, language = None, @@ -499,7 +498,6 @@ def _compile( additional_exported_hdrs != _UNBOUND or \ propagate_module_map_to_compile_action != _UNBOUND or \ do_not_generate_module_map != _UNBOUND or \ - code_coverage_enabled != _UNBOUND or \ purpose != _UNBOUND or \ hdrs_checking_mode != _UNBOUND or \ implementation_compilation_contexts != _UNBOUND or \ @@ -516,8 +514,6 @@ def _compile( additional_exported_hdrs = [] if do_not_generate_module_map == _UNBOUND: do_not_generate_module_map = False - if code_coverage_enabled == _UNBOUND: - code_coverage_enabled = False if purpose == _UNBOUND: purpose = None if hdrs_checking_mode == _UNBOUND: @@ -567,7 +563,6 @@ def _compile( module_map = module_map, additional_module_maps = additional_module_maps, do_not_generate_module_map = do_not_generate_module_map, - code_coverage_enabled = code_coverage_enabled, hdrs_checking_mode = hdrs_checking_mode, variables_extension = variables_extension, language = language, diff --git a/cc/private/compile/compile.bzl b/cc/private/compile/compile.bzl index 5ea1727e2..39340e4cc 100644 --- a/cc/private/compile/compile.bzl +++ b/cc/private/compile/compile.bzl @@ -129,7 +129,6 @@ def compile( module_map = None, additional_module_maps = [], do_not_generate_module_map = False, - code_coverage_enabled = False, # TODO(b/396122076): seems unused; double-check and remove hdrs_checking_mode = None, # buildifier: disable=unused-variable variables_extension = None, @@ -199,7 +198,6 @@ def compile( module_map: undocumented additional_module_maps: undocumented do_not_generate_module_map: undocumented - code_coverage_enabled: undocumented hdrs_checking_mode: undocumented variables_extension: undocumented language: undocumented @@ -377,7 +375,6 @@ def compile( feature_configuration = feature_configuration, generate_no_pic_action = generate_no_pic_action, generate_pic_action = generate_pic_action, - is_code_coverage_enabled = code_coverage_enabled, label = label, private_headers = private_hdrs_artifacts, public_headers = public_hdrs_artifacts, @@ -657,7 +654,6 @@ def _create_cc_compile_actions_with_cpp20_module_helper( cxxopts, fdo_context, feature_configuration, - is_code_coverage_enabled, label, private_headers, # buildifier: disable=unused-variable public_headers, # buildifier: disable=unused-variable @@ -848,7 +844,6 @@ def _create_cc_compile_actions_with_cpp20_module_helper( fdo_build_variables = fdo_build_variables, output_category = artifact_category.CLIF_OUTPUT_PROTO if cpp_source.type == CPP_SOURCE_TYPE_CLIF_INPUT_PROTO else artifact_category.OBJECT_FILE, add_object = True, - enable_coverage = is_code_coverage_enabled, generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -995,7 +990,6 @@ def _create_cc_compile_actions_with_cpp20_module_helper( fdo_build_variables = fdo_build_variables, output_category = artifact_category.CLIF_OUTPUT_PROTO if cpp_source.type == CPP_SOURCE_TYPE_CLIF_INPUT_PROTO else artifact_category.OBJECT_FILE, add_object = True, - enable_coverage = is_code_coverage_enabled, generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1030,7 +1024,6 @@ def _create_cc_compile_actions_with_cpp20_module( feature_configuration, generate_no_pic_action, generate_pic_action, - is_code_coverage_enabled, label, private_headers, public_headers, @@ -1068,7 +1061,6 @@ def _create_cc_compile_actions_with_cpp20_module( cxxopts = cxxopts, fdo_context = fdo_context, feature_configuration = feature_configuration, - is_code_coverage_enabled = is_code_coverage_enabled, label = label, private_headers = private_headers, public_headers = public_headers, @@ -1103,7 +1095,6 @@ def _create_cc_compile_actions( feature_configuration, generate_no_pic_action, generate_pic_action, - is_code_coverage_enabled, label, private_headers, public_headers, @@ -1147,7 +1138,6 @@ def _create_cc_compile_actions( feature_configuration = feature_configuration, generate_no_pic_action = generate_no_pic_action, generate_pic_action = generate_pic_action, - is_code_coverage_enabled = is_code_coverage_enabled, label = label, private_headers = private_headers, public_headers = public_headers, @@ -1231,7 +1221,6 @@ def _create_cc_compile_actions( fdo_context = fdo_context, auxiliary_fdo_inputs = auxiliary_fdo_inputs, feature_configuration = feature_configuration, - is_code_coverage_enabled = is_code_coverage_enabled, label = label, common_toolchain_variables = common_compile_build_variables, fdo_build_variables = fdo_build_variables, @@ -1278,7 +1267,6 @@ def _create_cc_compile_actions( fdo_build_variables = fdo_build_variables, output_category = (artifact_category.CLIF_OUTPUT_PROTO if source_type == CPP_SOURCE_TYPE_CLIF_INPUT_PROTO else artifact_category.OBJECT_FILE), add_object = True, - enable_coverage = is_code_coverage_enabled, generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1426,7 +1414,6 @@ def _create_pic_nopic_compile_source_actions( fdo_build_variables, output_category, add_object, - enable_coverage, generate_dwo, bitcode_output, fdo_context, @@ -1461,7 +1448,6 @@ def _create_pic_nopic_compile_source_actions( output_category = output_category, module_name = module_name, add_object = add_object, - enable_coverage = enable_coverage, generate_dwo = generate_dwo, bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1498,7 +1484,6 @@ def _create_pic_nopic_compile_source_actions( output_category = output_category, module_name = module_name, add_object = add_object, - enable_coverage = enable_coverage, generate_dwo = generate_dwo, bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1535,7 +1520,6 @@ def _create_compile_source_action( fdo_build_variables, output_category, add_object, - enable_coverage, generate_dwo, bitcode_output, fdo_context, @@ -1598,7 +1582,7 @@ def _create_compile_source_action( cc_toolchain = cc_toolchain, cpp_configuration = cpp_configuration, configuration = configuration, - enable_coverage = enable_coverage, + feature_configuration = feature_configuration, ) # Assembly files do not support -ftime-trace; skip trace output @@ -1646,7 +1630,7 @@ def _create_compile_source_action( compile_variables = get_specific_compile_build_variables( source_file = source_artifact, output_file = object_file, - code_coverage_enabled = enable_coverage, + code_coverage_enabled = feature_configuration.is_requested("coverage_instrumented"), gcno_file = gcno_file, dwo_file = dwo_file, using_fission = generate_dwo, @@ -1868,7 +1852,6 @@ def _create_temps_action( preprocess_compile_variables = get_specific_compile_build_variables( source_file = source_artifact, output_file = preprocess_object_file, - code_coverage_enabled = False, gcno_file = None, dwo_file = None, using_fission = False, @@ -1884,7 +1867,6 @@ def _create_temps_action( assembly_compile_variables = get_specific_compile_build_variables( source_file = source_artifact, output_file = assembly_object_file, - code_coverage_enabled = False, gcno_file = None, dwo_file = None, using_fission = False, @@ -1954,7 +1936,6 @@ def _create_module_codegen_action( fdo_context, auxiliary_fdo_inputs, feature_configuration, - is_code_coverage_enabled, label, common_toolchain_variables, fdo_build_variables, @@ -1966,18 +1947,15 @@ def _create_module_codegen_action( use_pic = ".pic" in module.basename output_name = paths.basename(module.basename) - gcno_file = None - if is_code_coverage_enabled and not cpp_configuration.use_llvm_coverage_map_format(): - gcno_file = _get_compile_output_file( - ctx = action_construction_context, - label = label, - configuration = configuration, - output_name = _cc_internal.get_artifact_name_for_category( - cc_toolchain = cc_toolchain, - category = artifact_category.COVERAGE_DATA_FILE, - output_name = output_name, - ), - ) + gcno_file = _maybe_declare_gcno_file( + ctx = action_construction_context, + label = label, + output_name = output_name, + cc_toolchain = cc_toolchain, + cpp_configuration = cpp_configuration, + configuration = configuration, + feature_configuration = feature_configuration, + ) bitcode_output = (feature_configuration.is_enabled("thin_lto") and paths.split_extension(module.basename)[-1] in LTO_SOURCE_EXTENSIONS) @@ -2046,7 +2024,7 @@ def _create_module_codegen_action( specific_compile_build_variables = get_specific_compile_build_variables( source_file = module, output_file = object_file, - code_coverage_enabled = is_code_coverage_enabled, + code_coverage_enabled = feature_configuration.is_requested("coverage_instrumented"), gcno_file = gcno_file, dwo_file = dwo_file, using_fission = generate_dwo, @@ -2153,7 +2131,6 @@ def _create_module_action( output_category = artifact_category.CPP_MODULE, module_name = module_name, add_object = False, - enable_coverage = False, generate_dwo = False, bitcode_output = False, additional_compilation_inputs = additional_compilation_inputs, @@ -2307,9 +2284,10 @@ def _maybe_declare_gcno_file( cc_toolchain, cpp_configuration, configuration, - enable_coverage): + feature_configuration): gcno_file = None - if enable_coverage and not cpp_configuration.use_llvm_coverage_map_format(): + if (feature_configuration.is_requested("coverage_instrumented") and + not cpp_configuration.use_llvm_coverage_map_format()): gcno_file = _get_compile_output_file( ctx = ctx, label = label, diff --git a/cc/private/compile/compile_build_variables.bzl b/cc/private/compile/compile_build_variables.bzl index 7ca69594d..e4e637af9 100644 --- a/cc/private/compile/compile_build_variables.bzl +++ b/cc/private/compile/compile_build_variables.bzl @@ -341,12 +341,11 @@ def get_specific_compile_build_variables( if diagnostics_file: result[_VARS.SERIALIZED_DIAGNOSTICS_FILE] = diagnostics_file - if gcno_file: - result[_VARS.GCOV_GCNO_FILE] = gcno_file - elif code_coverage_enabled: - # TODO: Blaze currently uses `gcov_gcno_file` to detect if code coverage - # is enabled. It should use a different signal. - result[_VARS.GCOV_GCNO_FILE] = "" + if code_coverage_enabled: + # TODO: Blaze currently uses the presence of `gcov_gcno_file` to detect if the current + # target is instrumented for coverage, so it has to be set even if no .gcno file is + # generated. It should use the `coverage_instrumented` feature instead. + result[_VARS.GCOV_GCNO_FILE] = gcno_file if gcno_file else "" if dwo_file: result[_VARS.PER_OBJECT_DEBUG_INFO_FILE] = dwo_file if using_fission: diff --git a/cc/private/rules_impl/cc_binary_impl.bzl b/cc/private/rules_impl/cc_binary_impl.bzl index c7bce928c..1cb753937 100644 --- a/cc/private/rules_impl/cc_binary_impl.bzl +++ b/cc/private/rules_impl/cc_binary_impl.bzl @@ -532,7 +532,6 @@ def cc_binary_impl(ctx, additional_linkopts, force_linkstatic = False): srcs = cc_helper.get_srcs(ctx), module_interfaces = cc_helper.get_cpp_module_interfaces(ctx), compilation_contexts = compilation_context_deps, - code_coverage_enabled = cc_helper.is_code_coverage_enabled(ctx = ctx), additional_inputs = ctx.files.additional_compiler_inputs, ) precompiled_file_objects = cc_common.create_compilation_outputs( diff --git a/cc/private/rules_impl/cc_library_impl.bzl b/cc/private/rules_impl/cc_library_impl.bzl index 3c3234ba5..2dff79899 100755 --- a/cc/private/rules_impl/cc_library_impl.bzl +++ b/cc/private/rules_impl/cc_library_impl.bzl @@ -71,7 +71,6 @@ def _cc_library_impl(ctx): module_interfaces = cc_helper.get_cpp_module_interfaces(ctx), private_hdrs = cc_helper.get_private_hdrs(ctx), public_hdrs = cc_helper.get_public_hdrs(ctx), - code_coverage_enabled = cc_helper.is_code_coverage_enabled(ctx), compilation_contexts = compilation_contexts, implementation_compilation_contexts = implementation_compilation_contexts, textual_hdrs = ctx.files.textual_hdrs, diff --git a/cc/private/rules_impl/objc_compilation_support.bzl b/cc/private/rules_impl/objc_compilation_support.bzl index 82f95d8a6..e2e1e0d36 100644 --- a/cc/private/rules_impl/objc_compilation_support.bzl +++ b/cc/private/rules_impl/objc_compilation_support.bzl @@ -204,7 +204,6 @@ def _compile( module_map = module_map, variables_extension = extension, language = "objc", - code_coverage_enabled = cc_helper.is_code_coverage_enabled(ctx = common_variables.ctx), hdrs_checking_mode = "strict", do_not_generate_module_map = not generate_module_map or (module_map.file.is_source if type(module_map.file) == "File" else module_map.file().is_source), purpose = purpose, diff --git a/cc/private/toolchain_config/BUILD b/cc/private/toolchain_config/BUILD index f763ad44a..7676b78fc 100644 --- a/cc/private/toolchain_config/BUILD +++ b/cc/private/toolchain_config/BUILD @@ -22,6 +22,7 @@ bzl_library( "//cc:action_names_bzl", "//cc:cc_toolchain_config_lib_bzl", "//cc/common:semantics_bzl", + "//cc/private:cc_info_bzl", "//cc/private:cc_internal_bzl", "@bazel_skylib//lib:paths", ], diff --git a/cc/private/toolchain_config/configure_features.bzl b/cc/private/toolchain_config/configure_features.bzl index 3be731451..b8dac3c49 100644 --- a/cc/private/toolchain_config/configure_features.bzl +++ b/cc/private/toolchain_config/configure_features.bzl @@ -16,6 +16,10 @@ load("//cc:action_names.bzl", "ACTION_NAMES") load("//cc/common:semantics.bzl", cc_semantics = "semantics") +# Loaded from //cc/private instead of //cc/common to avoid a load cycle through the compatibility +# proxy, which loads //cc/private:cc_common.bzl, which loads this file. +load("//cc/private:cc_info.bzl", "CcInfo") + ALL_COMPILE_ACTIONS = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, @@ -60,13 +64,56 @@ OBJC_ACTIONS = [ ACTION_NAMES.objc_executable, ] -def _get_coverage_features(cpp_configuration): - coverage_features = [] - coverage_features.append("coverage") +# Requested whenever coverage is generally enabled. Since it doesn't tell the toolchain whether the +# current target is actually instrumented, prefer the two features below. +_LEGACY_COVERAGE_FEATURE = "coverage" + +# Requested whenever coverage is generally enabled, even if the current target isn't instrumented. +_COVERAGE_ENABLED_FEATURE = "coverage_enabled" + +# Requested only if the current target is instrumented for coverage. +_COVERAGE_INSTRUMENTED_FEATURE = "coverage_instrumented" + +_LLVM_COVERAGE_MAP_FORMAT_FEATURE = "llvm_coverage_map_format" +_GCC_COVERAGE_MAP_FORMAT_FEATURE = "gcc_coverage_map_format" + +def _should_instrument_for_coverage(ctx): + if ctx.coverage_instrumented(): + return True + + # The target itself isn't matched by the instrumentation filter, but it may still include + # headers provided by an instrumented dependency, which have to be instrumented as well. + # For example, think about a cc_test that tests functionality defined in a header file that is + # supplied by a cc_library. + # + # Only direct dependencies are checked, for two reasons: + # a) It is a good practice to declare libraries which you directly rely on. Including headers + # from a library hidden deep inside the transitive closure makes build dependencies less + # readable and can lead to unexpected breakage. + # b) Traversing the transitive closure for each target would be too expensive. + for attr_name in ["deps", "implementation_deps"]: + attr_value = getattr(ctx.attr, attr_name, None) + + # configure_features is public API and thus can be called by rules that have arbitrary + # attributes with these names. + if type(attr_value) != "list": + continue + for dep in attr_value: + if type(dep) == "Target" and CcInfo in dep and ctx.coverage_instrumented(dep): + return True + return False + +def _get_coverage_features(ctx, cpp_configuration): + coverage_features = [ + _LEGACY_COVERAGE_FEATURE, + _COVERAGE_ENABLED_FEATURE, + ] if cpp_configuration.use_llvm_coverage_map_format(): - coverage_features.append("llvm_coverage_map_format") + coverage_features.append(_LLVM_COVERAGE_MAP_FORMAT_FEATURE) else: - coverage_features.append("gcc_coverage_map_format") + coverage_features.append(_GCC_COVERAGE_MAP_FORMAT_FEATURE) + if _should_instrument_for_coverage(ctx): + coverage_features.append(_COVERAGE_INSTRUMENTED_FEATURE) return coverage_features def configure_features( @@ -154,7 +201,7 @@ def configure_features( all_features.append("nonhost") if ctx.configuration.coverage_enabled: - all_features.extend(_get_coverage_features(cpp_configuration)) + all_features.extend(_get_coverage_features(ctx, cpp_configuration)) if "fdo_instrument" not in all_unsupported_features_set: if cpp_configuration.fdo_instrument() != None: diff --git a/cc/private/toolchain_config/legacy_features.bzl b/cc/private/toolchain_config/legacy_features.bzl index 7a3b82946..aa5b88a8a 100644 --- a/cc/private/toolchain_config/legacy_features.bzl +++ b/cc/private/toolchain_config/legacy_features.bzl @@ -971,6 +971,11 @@ def get_legacy_features(ctx, platform, existing_feature_names, linker_tool_path) )], )) + if "coverage_enabled" not in existing_feature_names: + result.append(feature(name = "coverage_enabled")) + if "coverage_instrumented" not in existing_feature_names: + result.append(feature(name = "coverage_instrumented")) + if "coverage" not in existing_feature_names: result.extend([ feature(name = "coverage"), diff --git a/docs/features.md b/docs/features.md index bf430cc9f..b12569d22 100644 --- a/docs/features.md +++ b/docs/features.md @@ -155,6 +155,28 @@ linker flags to produce the instrumented binaries. These features are all off by default. Toolchains should make the format features dependent on `coverage` being enabled. +### `coverage_enabled` / `coverage_instrumented` + +`coverage_enabled` is requested whenever `coverage` is, that is, whenever +coverage collection is generally enabled for the build. +`coverage_instrumented` is requested in addition to it, but only for those +targets that are actually instrumented for coverage, either because they +match +[`--instrumentation_filter`](https://bazel.build/reference/command-line-reference#flag--instrumentation_filter) +themselves or because one of their direct `deps` or `implementation_deps` +does and may thus provide instrumented headers. + +Toolchains should use these features instead of `coverage` to restrict +instrumentation flags to the compile actions that actually need them, which +is considerably cheaper than instrumenting every target in the build. Link +actions still need the coverage runtime whenever `coverage_enabled` is +requested, as an uninstrumented binary can link in instrumented libraries. + +These features are off by default and requested as described above. Older +versions of Bazel and `rules_cc` only request `coverage`, so toolchains +that support those as well should keep their `coverage` flag sets and guard +them with `with_feature_set(not_features = ["coverage_enabled"])`. + ### `cpp_modules` A marker feature for enabling C++20 modules. This also depends on diff --git a/tests/cc/common/cc_library_configured_target_tests.bzl b/tests/cc/common/cc_library_configured_target_tests.bzl index 4b583bf4e..7ff852ee9 100644 --- a/tests/cc/common/cc_library_configured_target_tests.bzl +++ b/tests/cc/common/cc_library_configured_target_tests.bzl @@ -126,6 +126,98 @@ def _test_absolute_includes_windows_fail_impl(env, target): ), ) +# Only targets whose label ends in "/instrumented" are matched by the instrumentation filter below. +_COVERAGE_CONFIG_SETTINGS = { + "//command_line_option:collect_code_coverage": "true", + "//command_line_option:instrumentation_filter": "/instrumented$", +} + +def _declare_instrumented_lib(name): + util.empty_file(name + "/instrumented.cc") + util.helper_target( + cc_library, + name = name + "/instrumented", + srcs = [name + "/instrumented.cc"], + hdrs = ["header.h"], + ) + return name + "/instrumented" + +# A target that is matched by the instrumentation filter is instrumented, which shows up as the +# .gcno files it declares as coverage metadata. +def _test_coverage_matching_target_is_instrumented(name, **kwargs): + cc_analysis_test( + name = name, + impl = _test_coverage_target_is_instrumented_impl, + target = _declare_instrumented_lib(name), + config_settings = _COVERAGE_CONFIG_SETTINGS, + **kwargs + ) + +# A target that isn't matched by the instrumentation filter itself still has to be instrumented if +# it may include headers from an instrumented dependency. +def _test_coverage_dep_makes_target_instrumented(name, **kwargs): + util.empty_file(name + "/dep_user.cc") + util.helper_target( + cc_library, + name = name + "/dep_user", + srcs = [name + "/dep_user.cc"], + deps = [_declare_instrumented_lib(name)], + ) + cc_analysis_test( + name = name, + impl = _test_coverage_target_is_instrumented_impl, + target = name + "/dep_user", + config_settings = _COVERAGE_CONFIG_SETTINGS, + **kwargs + ) + +def _test_coverage_implementation_dep_makes_target_instrumented(name, **kwargs): + util.empty_file(name + "/impl_dep_user.cc") + util.helper_target( + cc_library, + name = name + "/impl_dep_user", + srcs = [name + "/impl_dep_user.cc"], + implementation_deps = [_declare_instrumented_lib(name)], + ) + cc_analysis_test( + name = name, + impl = _test_coverage_target_is_instrumented_impl, + target = name + "/impl_dep_user", + config_settings = _COVERAGE_CONFIG_SETTINGS, + **kwargs + ) + +# metadata_files is transitive, so it isn't sufficient to assert that it is non-empty: the .gcno +# file of the target's own source has to be in there. +def _test_coverage_target_is_instrumented_impl(env, target): + own_gcno = target.label.name.split("/")[-1] + ".gcno" + env.expect.that_target(target).provider( + InstrumentedFilesInfo, + ).metadata_files().contains_predicate(matching.file_basename_equals(own_gcno)) + +# A target that is neither matched by the instrumentation filter nor depends on an instrumented +# target isn't instrumented, even though coverage is enabled for the build. +def _test_coverage_unmatched_target_is_not_instrumented(name, **kwargs): + util.empty_file(name + "/unrelated.cc") + util.helper_target( + cc_library, + name = name + "/unrelated", + srcs = [name + "/unrelated.cc"], + hdrs = ["header.h"], + ) + cc_analysis_test( + name = name, + impl = _test_coverage_unmatched_target_is_not_instrumented_impl, + target = name + "/unrelated", + config_settings = _COVERAGE_CONFIG_SETTINGS, + **kwargs + ) + +def _test_coverage_unmatched_target_is_not_instrumented_impl(env, target): + env.expect.that_target(target).provider( + InstrumentedFilesInfo, + ).metadata_files().is_empty() + def cc_library_configured_target_tests(name): test_suite( name = name, @@ -133,5 +225,9 @@ def cc_library_configured_target_tests(name): _test_cc_library_data_in_runfiles, _test_absolute_includes_fail, _test_absolute_includes_windows_fail, + _test_coverage_matching_target_is_instrumented, + _test_coverage_dep_makes_target_instrumented, + _test_coverage_implementation_dep_makes_target_instrumented, + _test_coverage_unmatched_target_is_not_instrumented, ] if bazel_features.cc.cc_common_is_in_rules_cc else [], )