1# Copyright 2023 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://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, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14 15import("//build_overrides/pigweed.gni") 16import("//build_overrides/pigweed_environment.gni") 17 18import("$dir_pw_build/python_action.gni") 19import("$dir_pw_toolchain/host_clang/toolchains.gni") 20 21# Expands to code coverage targets that can be used as dependencies to generate 22# coverage reports at build time. 23# 24# Arguments: 25# - enable_if (optional): Conditionally activates coverage report generation 26# when set to a boolean expression that evaluates to true. 27# - failure_mode (optional/unstable): Specify the failure mode for llvm-profdata 28# (used to merge inidividual profraw files from pw_test runs). Available 29# options are "any" (default) or "all". This should be considered an 30# unstable/deprecated argument that should only be used as a last resort to 31# get a build working again. Using failure_mode = "all" usually indicates that 32# there are underlying problems in the build or test infrastructure that 33# should be independently resolved. Please reach out to the Pigweed team for 34# assistance. 35# - Coverage Settings 36# - filter_paths (optional): List of file paths (using GN path helpers like 37# `//` is supported). These will be translated into absolute paths before 38# being used. These filter source files so that the coverage report *ONLY* 39# includes files that match one of these paths. These cannot be regular 40# expressions, but can be concrete file or folder paths. Folder paths will 41# allow all files in that directory or any recursive child directory. 42# - ignore_filename_patterns (optional): List of file path regular expressions 43# to ignore when generating the coverage report. 44# - pw_test Depedencies (required): These control which test binaries are used 45# to collect usage data for the coverage report. The following can basically 46# be used interchangeably with no actual difference in the template expansion. 47# Only one of these is required to be provided. 48# - tests: A list of pw_test targets. 49# - group_deps: A list of pw_test_group targets. 50# 51# Expands To: 52# pw_coverage_report follows the overall Pigweed pattern where targets exist 53# for all build configurations, but are only configured to do meaningful work 54# under the correct build configuration. In this vein, pw_coverage_report 55# ensures that a coverage-enabled toolchain is being used and the provided 56# enable_if evaluates to true (if provided). 57# 58# - If a coverage-enabled toolchain is being used and the provided enable_if 59# evaluates to true (if provided): 60# - <target_name>.text: Generates a text representation of the coverage 61# report. This is the output of 62# `llvm-cov show --format text`. 63# - <target_name>.html: Generates an HTML representation of the coverage 64# report. This is the output of 65# `llvm-cov show --format html`. 66# - <target_name>.lcov: Generates an LCOV representation of the coverage 67# report. This is the output of 68# `llvm-cov export --format lcov`. 69# - <target_name>.json: Generates a JSON representation of the coverage 70# report. This is the output of 71# `llvm-cov export --format text`. 72# 73# - <target_name>: A group that takes dependencies on <target_name>.text, 74# <target_name>.html, <target_name>.lcov, and 75# <target_name>.json. This can be used to force generation of 76# all coverage artifacts without manually depending on each 77# target. 78# 79# - The other targets this expands to should be considered private and not 80# used as dependencies. 81# - If a coverage-enabled toolchain is not being used or the provided enable_if 82# evaluates to false (if provided). 83# - All of the above target names, but they are empty groups. 84template("pw_coverage_report") { 85 assert(defined(invoker.tests) || defined(invoker.group_deps), 86 "One of `tests` or `group_deps` must be provided.") 87 assert(!defined(invoker.failure_mode) || 88 (invoker.failure_mode == "any" || invoker.failure_mode == "all"), 89 "failure_mode only supports \"any\" or \"all\".") 90 91 _report_name = target_name 92 _format_types = [ 93 "text", 94 "html", 95 "lcov", 96 "json", 97 ] 98 _should_enable = !defined(invoker.enable_if) || invoker.enable_if 99 100 # These two Pigweed build arguments are required to be in these states to 101 # ensure binaries are instrumented for coverage and profraw files are 102 # exported. 103 if (_should_enable && pw_toolchain_COVERAGE_ENABLED) { 104 _test_metadata = "$target_out_dir/$_report_name.test_metadata.json" 105 _profdata_file = "$target_out_dir/merged.profdata" 106 _arguments = { 107 filter_paths = [] 108 if (defined(invoker.filter_paths)) { 109 filter_paths += invoker.filter_paths 110 } 111 112 ignore_filename_patterns = [] 113 if (defined(invoker.ignore_filename_patterns)) { 114 ignore_filename_patterns += invoker.ignore_filename_patterns 115 } 116 117 # Merge any provided `tests` or `group_deps` to `deps` and `run_deps`. 118 # 119 # `deps` are used to generate the .test_metadata.json file. 120 # `run_deps` are used to block on the test execution to generate a profraw 121 # file. 122 deps = [] 123 run_deps = [] 124 test_or_group_deps = [] 125 if (defined(invoker.tests)) { 126 test_or_group_deps += invoker.tests 127 } 128 if (defined(invoker.group_deps)) { 129 test_or_group_deps += invoker.group_deps 130 } 131 foreach(dep, test_or_group_deps) { 132 deps += [ dep ] 133 134 dep_target = get_label_info(dep, "label_no_toolchain") 135 dep_toolchain = get_label_info(dep, "toolchain") 136 run_deps += [ "$dep_target.run($dep_toolchain)" ] 137 } 138 } 139 140 # Generate a list of all test binaries and their associated profraw files 141 # after executing we can use to generate the coverage report. 142 generated_file("_$_report_name.test_metadata") { 143 outputs = [ _test_metadata ] 144 data_keys = [ 145 "unit_tests", 146 "profraws", 147 ] 148 output_conversion = "json" 149 deps = _arguments.deps 150 } 151 152 # Merge the generated profraws from instrumented binaries into a single 153 # profdata. 154 pw_python_action("_$_report_name.merge_profraws") { 155 _depfile_path = "$target_out_dir/$_report_name.merged_profraws.d" 156 157 module = "pw_build.merge_profraws" 158 args = [ 159 "--llvm-profdata-path", 160 pw_toolchain_clang_tools.llvm_profdata, 161 "--test-metadata-path", 162 rebase_path(_test_metadata, root_build_dir), 163 "--profdata-path", 164 rebase_path(_profdata_file, root_build_dir), 165 "--depfile-path", 166 rebase_path(_depfile_path, root_build_dir), 167 ] 168 169 # TODO: b/256651964 - We really want `--failure-mode any` always to guarantee 170 # we don't silently ignore any profraw report. However, there are downstream 171 # projects that currently break when using `--failure-mode any`. 172 # 173 # See the task for examples of what is currently going wrong. 174 # 175 # Invalid profraw files will be ignored so coverage reports might have a 176 # slight variance between runs depending on if something failed or not. 177 if (defined(invoker.failure_mode)) { 178 args += [ 179 "--failure-mode", 180 invoker.failure_mode, 181 ] 182 } 183 184 inputs = [ _test_metadata ] 185 sources = [] 186 depfile = _depfile_path 187 188 outputs = [ _profdata_file ] 189 190 python_deps = [ "$dir_pw_build/py" ] 191 deps = _arguments.run_deps 192 public_deps = [ ":_$_report_name.test_metadata" ] 193 } 194 195 foreach(format, _format_types) { 196 pw_python_action("$_report_name.$format") { 197 _depfile_path = "$target_out_dir/$_report_name.$format.d" 198 _output_dir = "$target_out_dir/$_report_name/$format/" 199 200 module = "pw_build.generate_report" 201 args = [ 202 "--llvm-cov-path", 203 pw_toolchain_clang_tools.llvm_cov, 204 "--format", 205 format, 206 "--test-metadata-path", 207 rebase_path(_test_metadata, root_build_dir), 208 "--profdata-path", 209 rebase_path(_profdata_file, root_build_dir), 210 "--root-dir", 211 rebase_path("//", root_build_dir), 212 "--build-dir", 213 ".", 214 "--output-dir", 215 rebase_path(_output_dir, root_build_dir), 216 "--depfile-path", 217 rebase_path(_depfile_path, root_build_dir), 218 ] 219 foreach(filter_path, _arguments.filter_paths) { 220 args += [ 221 # We rebase to absolute paths here to resolve any "//" used in the 222 # filter_paths. 223 "--filter-path", 224 rebase_path(filter_path), 225 ] 226 } 227 foreach(ignore_filename_pattern, _arguments.ignore_filename_patterns) { 228 args += [ 229 "--ignore-filename-pattern", 230 ignore_filename_pattern, 231 ] 232 } 233 234 inputs = [ 235 _test_metadata, 236 _profdata_file, 237 ] 238 sources = [] 239 depfile = _depfile_path 240 241 outputs = [] 242 if (format == "text") { 243 outputs += [ "$_output_dir/index.txt" ] 244 } else if (format == "html") { 245 outputs += [ "$_output_dir/index.html" ] 246 } else if (format == "lcov") { 247 outputs += [ "$_output_dir/report.lcov" ] 248 } else if (format == "json") { 249 outputs += [ "$_output_dir/report.json" ] 250 } 251 252 python_deps = [ "$dir_pw_build/py" ] 253 deps = [ ":_$_report_name.merge_profraws" ] 254 } 255 } 256 } else { 257 not_needed(invoker, "*") 258 foreach(format, _format_types) { 259 group("$_report_name.$format") { 260 } 261 } 262 } 263 264 group("$_report_name") { 265 deps = [] 266 foreach(format, _format_types) { 267 deps += [ ":$_report_name.$format" ] 268 } 269 } 270} 271