blob: cb4aa52029db056c33f88c0d6a03ec7569722d1e [file] [log] [blame]
# Copyright 2023 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
import("//build_overrides/pigweed.gni")
import("//build_overrides/pigweed_environment.gni")
import("$dir_pw_build/python_action.gni")
import("$dir_pw_toolchain/host_clang/toolchains.gni")
# Expands to code coverage targets that can be used as dependencies to generate
# coverage reports at build time.
#
# Arguments:
# - enable_if (optional): Conditionally activates coverage report generation
# when set to a boolean expression that evaluates to true.
# - failure_mode (optional/unstable): Specify the failure mode for llvm-profdata
# (used to merge inidividual profraw files from pw_test runs). Available
# options are "any" (default) or "all". This should be considered an
# unstable/deprecated argument that should only be used as a last resort to
# get a build working again. Using failure_mode = "all" usually indicates that
# there are underlying problems in the build or test infrastructure that
# should be independently resolved. Please reach out to the Pigweed team for
# assistance.
# - Coverage Settings
# - filter_paths (optional): List of file paths (using GN path helpers like
# `//` is supported). These will be translated into absolute paths before
# being used. These filter source files so that the coverage report *ONLY*
# includes files that match one of these paths. These cannot be regular
# expressions, but can be concrete file or folder paths. Folder paths will
# allow all files in that directory or any recursive child directory.
# - ignore_filename_patterns (optional): List of file path regular expressions
# to ignore when generating the coverage report.
# - pw_test Depedencies (required): These control which test binaries are used
# to collect usage data for the coverage report. The following can basically
# be used interchangeably with no actual difference in the template expansion.
# Only one of these is required to be provided.
# - tests: A list of pw_test targets.
# - group_deps: A list of pw_test_group targets.
#
# Expands To:
# pw_coverage_report follows the overall Pigweed pattern where targets exist
# for all build configurations, but are only configured to do meaningful work
# under the correct build configuration. In this vein, pw_coverage_report
# ensures that a coverage-enabled toolchain is being used and the provided
# enable_if evaluates to true (if provided).
#
# - If a coverage-enabled toolchain is being used and the provided enable_if
# evaluates to true (if provided):
# - <target_name>.text: Generates a text representation of the coverage
# report. This is the output of
# `llvm-cov show --format text`.
# - <target_name>.html: Generates an HTML representation of the coverage
# report. This is the output of
# `llvm-cov show --format html`.
# - <target_name>.lcov: Generates an LCOV representation of the coverage
# report. This is the output of
# `llvm-cov export --format lcov`.
# - <target_name>.json: Generates a JSON representation of the coverage
# report. This is the output of
# `llvm-cov export --format text`.
#
# - <target_name>: A group that takes dependencies on <target_name>.text,
# <target_name>.html, <target_name>.lcov, and
# <target_name>.json. This can be used to force generation of
# all coverage artifacts without manually depending on each
# target.
#
# - The other targets this expands to should be considered private and not
# used as dependencies.
# - If a coverage-enabled toolchain is not being used or the provided enable_if
# evaluates to false (if provided).
# - All of the above target names, but they are empty groups.
template("pw_coverage_report") {
assert(defined(invoker.tests) || defined(invoker.group_deps),
"One of `tests` or `group_deps` must be provided.")
assert(!defined(invoker.failure_mode) ||
(invoker.failure_mode == "any" || invoker.failure_mode == "all"),
"failure_mode only supports \"any\" or \"all\".")
_report_name = target_name
_format_types = [
"text",
"html",
"lcov",
"json",
]
_should_enable = !defined(invoker.enable_if) || invoker.enable_if
# These two Pigweed build arguments are required to be in these states to
# ensure binaries are instrumented for coverage and profraw files are
# exported.
if (_should_enable && pw_toolchain_COVERAGE_ENABLED) {
_test_metadata = "$target_out_dir/$_report_name.test_metadata.json"
_profdata_file = "$target_out_dir/merged.profdata"
_arguments = {
filter_paths = []
if (defined(invoker.filter_paths)) {
filter_paths += invoker.filter_paths
}
ignore_filename_patterns = []
if (defined(invoker.ignore_filename_patterns)) {
ignore_filename_patterns += invoker.ignore_filename_patterns
}
# Merge any provided `tests` or `group_deps` to `deps` and `run_deps`.
#
# `deps` are used to generate the .test_metadata.json file.
# `run_deps` are used to block on the test execution to generate a profraw
# file.
deps = []
run_deps = []
test_or_group_deps = []
if (defined(invoker.tests)) {
test_or_group_deps += invoker.tests
}
if (defined(invoker.group_deps)) {
test_or_group_deps += invoker.group_deps
}
foreach(dep, test_or_group_deps) {
deps += [ dep ]
dep_target = get_label_info(dep, "label_no_toolchain")
dep_toolchain = get_label_info(dep, "toolchain")
run_deps += [ "$dep_target.run($dep_toolchain)" ]
}
}
# Generate a list of all test binaries and their associated profraw files
# after executing we can use to generate the coverage report.
generated_file("_$_report_name.test_metadata") {
outputs = [ _test_metadata ]
data_keys = [
"unit_tests",
"profraws",
]
output_conversion = "json"
deps = _arguments.deps
}
# Merge the generated profraws from instrumented binaries into a single
# profdata.
pw_python_action("_$_report_name.merge_profraws") {
_depfile_path = "$target_out_dir/$_report_name.merged_profraws.d"
module = "pw_build.merge_profraws"
args = [
"--llvm-profdata-path",
pw_toolchain_clang_tools.llvm_profdata,
"--test-metadata-path",
rebase_path(_test_metadata, root_build_dir),
"--profdata-path",
rebase_path(_profdata_file, root_build_dir),
"--depfile-path",
rebase_path(_depfile_path, root_build_dir),
]
# TODO: b/256651964 - We really want `--failure-mode any` always to guarantee
# we don't silently ignore any profraw report. However, there are downstream
# projects that currently break when using `--failure-mode any`.
#
# See the task for examples of what is currently going wrong.
#
# Invalid profraw files will be ignored so coverage reports might have a
# slight variance between runs depending on if something failed or not.
if (defined(invoker.failure_mode)) {
args += [
"--failure-mode",
invoker.failure_mode,
]
}
inputs = [ _test_metadata ]
sources = []
depfile = _depfile_path
outputs = [ _profdata_file ]
python_deps = [ "$dir_pw_build/py" ]
deps = _arguments.run_deps
public_deps = [ ":_$_report_name.test_metadata" ]
}
foreach(format, _format_types) {
pw_python_action("$_report_name.$format") {
_depfile_path = "$target_out_dir/$_report_name.$format.d"
_output_dir = "$target_out_dir/$_report_name/$format/"
module = "pw_build.generate_report"
args = [
"--llvm-cov-path",
pw_toolchain_clang_tools.llvm_cov,
"--format",
format,
"--test-metadata-path",
rebase_path(_test_metadata, root_build_dir),
"--profdata-path",
rebase_path(_profdata_file, root_build_dir),
"--root-dir",
rebase_path("//", root_build_dir),
"--build-dir",
".",
"--output-dir",
rebase_path(_output_dir, root_build_dir),
"--depfile-path",
rebase_path(_depfile_path, root_build_dir),
]
foreach(filter_path, _arguments.filter_paths) {
args += [
# We rebase to absolute paths here to resolve any "//" used in the
# filter_paths.
"--filter-path",
rebase_path(filter_path),
]
}
foreach(ignore_filename_pattern, _arguments.ignore_filename_patterns) {
args += [
"--ignore-filename-pattern",
ignore_filename_pattern,
]
}
inputs = [
_test_metadata,
_profdata_file,
]
sources = []
depfile = _depfile_path
outputs = []
if (format == "text") {
outputs += [ "$_output_dir/index.txt" ]
} else if (format == "html") {
outputs += [ "$_output_dir/index.html" ]
} else if (format == "lcov") {
outputs += [ "$_output_dir/report.lcov" ]
} else if (format == "json") {
outputs += [ "$_output_dir/report.json" ]
}
python_deps = [ "$dir_pw_build/py" ]
deps = [ ":_$_report_name.merge_profraws" ]
}
}
} else {
not_needed(invoker, "*")
foreach(format, _format_types) {
group("$_report_name.$format") {
}
}
}
group("$_report_name") {
deps = []
foreach(format, _format_types) {
deps += [ ":$_report_name.$format" ]
}
}
}