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