1# -*- bazel-starlark -*- 2# Copyright 2023 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Siso config version of clang_code_coverage_wrapper.py""" 6# LINT.IfChange 7 8load("@builtin//struct.star", "module") 9 10# Logics are copied from build/toolchain/clang_code_coverage_wrapper.py 11# in ordre to strip coverage flags without process invocation. 12# This is neceesary for Siso to send clang command to RBE without the wrapper and instrument file. 13 14# Flags used to enable coverage instrumentation. 15# Flags should be listed in the same order that they are added in 16# build/config/coverage/BUILD.gn 17_COVERAGE_FLAGS = [ 18 "-fprofile-instr-generate", 19 "-fcoverage-mapping", 20 "-mllvm", 21 "-runtime-counter-relocation=true", 22 # Following experimental flags remove unused header functions from the 23 # coverage mapping data embedded in the test binaries, and the reduction 24 # of binary size enables building Chrome's large unit test targets on 25 # MacOS. Please refer to crbug.com/796290 for more details. 26 "-mllvm", 27 "-limited-coverage-experimental=true", 28] 29 30# Files that should not be built with coverage flags by default. 31_DEFAULT_COVERAGE_EXCLUSION_LIST = [] 32 33# Map of exclusion lists indexed by target OS. 34# If no target OS is defined, or one is defined that doesn't have a specific 35# entry, use _DEFAULT_COVERAGE_EXCLUSION_LIST. 36_COVERAGE_EXCLUSION_LIST_MAP = { 37 "android": [ 38 # This file caused webview native library failed on arm64. 39 "../../device/gamepad/dualshock4_controller.cc", 40 ], 41 "fuchsia": [ 42 # TODO(crbug.com/1174725): These files caused clang to crash while 43 # compiling them. 44 "../../base/allocator/partition_allocator/src/partition_alloc/pcscan.cc", 45 "../../third_party/skia/src/core/SkOpts.cpp", 46 "../../third_party/skia/src/opts/SkOpts_hsw.cpp", 47 "../../third_party/skia/third_party/skcms/skcms.cc", 48 ], 49 "linux": [ 50 # These files caused a static initializer to be generated, which 51 # shouldn't. 52 # TODO(crbug.com/990948): Remove when the bug is fixed. 53 "../../chrome/browser/media/router/providers/cast/cast_internal_message_util.cc", #pylint: disable=line-too-long 54 "../../components/media_router/common/providers/cast/channel/cast_channel_enum.cc", #pylint: disable=line-too-long 55 "../../components/media_router/common/providers/cast/channel/cast_message_util.cc", #pylint: disable=line-too-long 56 "../../components/media_router/common/providers/cast/cast_media_source.cc", #pylint: disable=line-too-long 57 "../../ui/events/keycodes/dom/keycode_converter.cc", 58 ], 59 "chromeos": [ 60 # These files caused clang to crash while compiling them. They are 61 # excluded pending an investigation into the underlying compiler bug. 62 "../../third_party/webrtc/p2p/base/p2p_transport_channel.cc", 63 "../../third_party/icu/source/common/uts46.cpp", 64 "../../third_party/icu/source/common/ucnvmbcs.cpp", 65 "../../base/android/android_image_reader_compat.cc", 66 ], 67} 68 69# Map of force lists indexed by target OS. 70_COVERAGE_FORCE_LIST_MAP = { 71 # clang_profiling.cc refers to the symbol `__llvm_profile_dump` from the 72 # profiling runtime. In a partial coverage build, it is possible for a 73 # binary to include clang_profiling.cc but have no instrumented files, thus 74 # causing an unresolved symbol error because the profiling runtime will not 75 # be linked in. Therefore we force coverage for this file to ensure that 76 # any target that includes it will also get the profiling runtime. 77 "win": [r"..\..\base\test\clang_profiling.cc"], 78 # TODO(crbug.com/1141727) We're seeing runtime LLVM errors in mac-rel when 79 # no files are changed, so we suspect that this is similar to the other 80 # problem with clang_profiling.cc on Windows. The TODO here is to force 81 # coverage for this specific file on ALL platforms, if it turns out to fix 82 # this issue on Mac as well. It's the only file that directly calls 83 # `__llvm_profile_dump` so it warrants some special treatment. 84 "mac": ["../../base/test/clang_profiling.cc"], 85} 86 87def _remove_flags_from_command(command): 88 # We need to remove the coverage flags for this file, but we only want to 89 # remove them if we see the exact sequence defined in _COVERAGE_FLAGS. 90 # That ensures that we only remove the flags added by GN when 91 # "use_clang_coverage" is true. Otherwise, we would remove flags set by 92 # other parts of the build system. 93 start_flag = _COVERAGE_FLAGS[0] 94 num_flags = len(_COVERAGE_FLAGS) 95 start_idx = 0 96 97 def _start_flag_idx(cmd, start_idx): 98 for i in range(start_idx, len(cmd)): 99 if cmd[i] == start_flag: 100 return i 101 102 # Workaround to emulate while loop in Starlark. 103 for _ in range(0, len(command)): 104 idx = _start_flag_idx(command, start_idx) 105 if not idx: 106 # Coverage flags are not included anymore. 107 return command 108 if command[idx:idx + num_flags] == _COVERAGE_FLAGS: 109 # Starlark doesn't have `del`. 110 command = command[:idx] + command[idx + num_flags:] 111 112 # There can be multiple sets of _COVERAGE_FLAGS. All of these need to be 113 # removed. 114 start_idx = idx 115 else: 116 start_idx = idx + 1 117 return command 118 119def __run(ctx, args): 120 """Runs the main logic of clang_code_coverage_wrapper. 121 122 This is slightly different from the main function of clang_code_coverage_wrapper.py 123 because starlark can't use Python's standard libraries. 124 """ 125 # We need to remove the coverage flags for this file, but we only want to 126 # remove them if we see the exact sequence defined in _COVERAGE_FLAGS. 127 # That ensures that we only remove the flags added by GN when 128 # "use_clang_coverage" is true. Otherwise, we would remove flags set by 129 # other parts of the build system. 130 131 if len(args) == 0: 132 return args 133 if not args[0].endswith("python3") and not args[0].endswith("python3.exe"): 134 return args 135 136 has_coveage_wrapper = False 137 instrument_file = None 138 compile_command_pos = None 139 target_os = None 140 source_flag = "-c" 141 source_flag_index = None 142 for i, arg in enumerate(args): 143 if i == 0: 144 continue 145 if arg == "../../build/toolchain/clang_code_coverage_wrapper.py": 146 has_coveage_wrapper = True 147 continue 148 if arg.startswith("--files-to-instrument="): 149 instrument_file = arg.removeprefix("--files-to-instrument=") 150 continue 151 if arg.startswith("--target-os="): 152 target_os = arg.removeprefix("--target-os=") 153 if target_os == "win": 154 source_flag = "/c" 155 continue 156 if not compile_command_pos and not args[i].startswith("-") and "clang" in args[i]: 157 compile_command_pos = i 158 continue 159 if args[i] == source_flag: 160 # The command is assumed to use Clang as the compiler, and the path to the 161 # source file is behind the -c argument, and the path to the source path is 162 # relative to the root build directory. For example: 163 # clang++ -fvisibility=hidden -c ../../base/files/file_path.cc -o \ 164 # obj/base/base/file_path.o 165 # On Windows, clang-cl.exe uses /c instead of -c. 166 source_flag_index = i 167 continue 168 169 if not has_coveage_wrapper or not compile_command_pos: 170 print("this is not clang coverage command. %s" % str(args)) 171 return args 172 173 compile_command = args[compile_command_pos:] 174 175 if not source_flag_index: 176 fail("%s argument is not found in the compile command. %s" % (source_flag, str(args))) 177 178 if source_flag_index + 1 >= len(args): 179 fail("Source file to be compiled is missing from the command.") 180 181 # On Windows, filesystem paths should use '\', but GN creates build commands 182 # that use '/'. 183 # The original logic in clang_code_coverage_wrapper.py uses 184 # os.path.normpath() to ensure to ensure that the path uses the correct 185 # separator for the current platform. i.e. '\' on Windows and '/' otherwise 186 # Siso's ctx.fs.canonpath() ensures '/' on all platforms, instead. 187 # TODO: Consdier coverting the paths in instrument file and hardcoded lists 188 # only once at initialization if it improves performance. 189 190 compile_source_file = ctx.fs.canonpath(args[source_flag_index + 1]) 191 192 extension = compile_source_file.rsplit(".", 1)[1] 193 if not extension in ["c", "cc", "cpp", "cxx", "m", "mm", "S"]: 194 fail("Invalid source file %s found. extension=%s" % (compile_source_file, extension)) 195 196 exclusion_list = _COVERAGE_EXCLUSION_LIST_MAP.get( 197 target_os, 198 _DEFAULT_COVERAGE_EXCLUSION_LIST, 199 ) 200 exclusion_list = [ctx.fs.canonpath(f) for f in exclusion_list] 201 force_list = _COVERAGE_FORCE_LIST_MAP.get(target_os, []) 202 force_list = [ctx.fs.canonpath(f) for f in force_list] 203 204 files_to_instrument = [] 205 if instrument_file: 206 files_to_instrument = str(ctx.fs.read(ctx.fs.canonpath(instrument_file))).splitlines() 207 208 # strip() is for removing '\r' on Windows. 209 files_to_instrument = [ctx.fs.canonpath(f).strip() for f in files_to_instrument] 210 211 should_remove_flags = False 212 if compile_source_file not in force_list: 213 if compile_source_file in exclusion_list: 214 should_remove_flags = True 215 elif instrument_file and compile_source_file not in files_to_instrument: 216 should_remove_flags = True 217 218 if should_remove_flags: 219 return _remove_flags_from_command(compile_command) 220 print("Keeping code coverage flags for %s" % compile_source_file) 221 return compile_command 222 223clang_code_coverage_wrapper = module( 224 "clang_code_coverage_wrapper", 225 run = __run, 226) 227 228# LINT.ThenChange(/build/toolchain/clang_code_coverage_wrapper.py) 229