xref: /aosp_15_r20/external/cronet/build/config/siso/clang_code_coverage_wrapper.star (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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