# 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): # - .text: Generates a text representation of the coverage # report. This is the output of # `llvm-cov show --format text`. # - .html: Generates an HTML representation of the coverage # report. This is the output of # `llvm-cov show --format html`. # - .lcov: Generates an LCOV representation of the coverage # report. This is the output of # `llvm-cov export --format lcov`. # - .json: Generates a JSON representation of the coverage # report. This is the output of # `llvm-cov export --format text`. # # - : A group that takes dependencies on .text, # .html, .lcov, and # .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" ] } } }