• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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