This is doable with user defined transitions, there are some performance considerations here that are worth reading over. It's also relatively complicated.
# transition.bzl
def _platform_transition_impl(settings, attr):
_ignore = settings
return {"//command_line_option:collect_code_coverage": True}
coverage_transition = transition(
implementation = _coverage_transition_impl,
inputs = [],
outputs = ["//command_line_option:collect_code_coverage"],
)
def _coverage_impl(ctx):
executable = ctx.actions.declare_file(ctx.attr.name)
ctx.actions.symlink(output = executable, target_file = ctx.file.target)
return [DefaultInfo(executable = executable)]
build_with_collect_coverage = rule(
implementation = _coverage_impl,
attrs = {
"target": attr.label(mandatory = True, allow_single_file = True, cfg = coverage_transition),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
)
Then add the transition rule to BUILD.bazel e.g.
load(":transition.bzl", "build_with_collect_coverage")
cc_binary(
name = "target"
#...
)
build_with_collect_coverage(
name = "target_with_coverage_collection",
target = ":target",
)
Note: While this is a generic solution that is incredibly powerful and supports any language you might simply be able to get away with using the features attribute e.g.
cc_binary(
name = "target",
#...
features = ["coverage"],
)