xref: /aosp_15_r20/build/bazel/rules/cc/clang_tidy.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1# Copyright (C) 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://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,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15load("@bazel_skylib//lib:paths.bzl", "paths")
16load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
17load(
18    "@bazel_tools//tools/build_defs/cc:action_names.bzl",
19    "CPP_COMPILE_ACTION_NAME",
20    "C_COMPILE_ACTION_NAME",
21)
22load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
23load("@soong_injection//cc_toolchain:config_constants.bzl", "constants")
24load("//build/bazel/rules:common.bzl", "get_dep_targets")
25load(":cc_library_common.bzl", "get_compilation_args")
26
27ClangTidyInfo = provider(
28    "Info provided from clang-tidy actions",
29    fields = {
30        "tidy_files": "Outputs from the clang-tidy tool",
31        # TODO(b/282188514) stop propagating transitive tidy files after Bazel migration
32        "transitive_tidy_files": "Outputs from the clang-tidy tool for all transitive dependencies." +
33                                 " Currently, these are needed so that mixed-build targets can also run clang-tidy for their dependencies.",
34    },
35)
36
37_TIDY_GLOBAL_NO_CHECKS = constants.TidyGlobalNoChecks.split(",")
38_TIDY_GLOBAL_NO_ERROR_CHECKS = constants.TidyGlobalNoErrorChecks.split(",")
39_TIDY_DEFAULT_GLOBAL_CHECKS = constants.TidyDefaultGlobalChecks.split(",")
40_TIDY_EXTERNAL_VENDOR_CHECKS = constants.TidyExternalVendorChecks.split(",")
41_TIDY_DEFAULT_GLOBAL_CHECKS_NO_ANALYZER = constants.TidyDefaultGlobalChecks.split(",") + ["-clang-analyzer-*"]
42_TIDY_EXTRA_ARG_FLAGS = constants.TidyExtraArgFlags
43
44def _check_bad_tidy_flags(tidy_flags):
45    """should be kept up to date with
46    https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/check.go;l=128;drc=b45a2ea782074944f79fc388df20b06e01f265f7
47    """
48    for flag in tidy_flags:
49        flag = flag.strip()
50        if not flag.startswith("-"):
51            fail("Flag `%s` must start with `-`" % flag)
52        if flag.startswith("-fix"):
53            fail("Flag `%s` is not allowed, since it could cause multiple writes to the same source file" % flag)
54        if flag.startswith("-checks="):
55            fail("Flag `%s` is not allowed, use `tidy_checks` property instead" % flag)
56        if "-warnings-as-errors=" in flag:
57            fail("Flag `%s` is not allowed, use `tidy_checks_as_errors` property instead" % flag)
58        if " " in flag:
59            fail("Bad flag: `%s` is not an allowed multi-word flag. Should it be split into multiple flags?" % flag)
60
61def _check_bad_tidy_checks(tidy_checks):
62    """should be kept up to date with
63    https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/check.go;l=145;drc=b45a2ea782074944f79fc388df20b06e01f265f7
64    """
65    for check in tidy_checks:
66        if " " in check:
67            fail("Check `%s` invalid, cannot contain spaces" % check)
68        if "," in check:
69            fail("Check `%s` invalid, cannot contain commas. Split each entry into its own string instead" % check)
70
71def _add_with_tidy_flags(ctx, tidy_flags):
72    with_tidy_flags = ctx.attr._with_tidy_flags[BuildSettingInfo].value
73    if with_tidy_flags:
74        return tidy_flags + with_tidy_flags
75    return tidy_flags
76
77def _add_header_filter(ctx, tidy_flags):
78    """If TidyFlags does not contain -header-filter, add default header filter.
79    """
80    for flag in tidy_flags:
81        # Find the substring because the flag could also appear as --header-filter=...
82        # and with or without single or double quotes.
83        if "-header-filter=" in flag:
84            return tidy_flags
85
86    # Default header filter should include only the module directory,
87    # not the out/soong/.../ModuleDir/...
88    # Otherwise, there will be too many warnings from generated files in out/...
89    # If a module wants to see warnings in the generated source files,
90    # it should specify its own -header-filter flag.
91    default_dirs = ctx.attr._default_tidy_header_dirs[BuildSettingInfo].value
92    if default_dirs == "":
93        header_filter = "-header-filter=^" + ctx.label.package + "/"
94    else:
95        header_filter = "-header-filter=\"(^%s/|%s)\"" % (ctx.label.package, default_dirs)
96    return tidy_flags + [header_filter]
97
98def _add_extra_arg_flags(tidy_flags):
99    return tidy_flags + ["-extra-arg-before=" + f for f in _TIDY_EXTRA_ARG_FLAGS]
100
101def _add_quiet_if_not_global_tidy(ctx, tidy_flags):
102    tidy_checks = ctx.attr._tidy_checks[BuildSettingInfo].value
103    if not tidy_checks:
104        return tidy_flags + [
105            "-quiet",
106            "-extra-arg-before=-fno-caret-diagnostics",
107        ]
108    return tidy_flags
109
110def _clang_rewrite_tidy_checks(tidy_checks):
111    # List of tidy checks that should be disabled globally. When the compiler is
112    # updated, some checks enabled by this module may be disabled if they have
113    # become more strict, or if they are a new match for a wildcard group like
114    # `modernize-*`.
115    clang_tidy_disable_checks = [
116        "misc-no-recursion",
117        "readability-function-cognitive-complexity",  # http://b/175055536
118    ]
119
120    tidy_checks = tidy_checks + ["-" + c for c in clang_tidy_disable_checks]
121
122    # clang-tidy does not allow later arguments to override earlier arguments,
123    # so if we just disabled an argument that was explicitly enabled we must
124    # remove the enabling argument from the list.
125    return [t for t in tidy_checks if t not in clang_tidy_disable_checks]
126
127def _add_checks_for_dir(directory):
128    """should be kept up to date with
129    https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/config/tidy.go;l=170;drc=b45a2ea782074944f79fc388df20b06e01f265f7
130    """
131
132    # This is a map of local path prefixes to the set of default clang-tidy checks
133    # to be used.  This is like android.IsThirdPartyPath, but with more patterns.
134    # The last matched local_path_prefix should be the most specific to be used.
135    directory_checks = [
136        ("external/", _TIDY_EXTERNAL_VENDOR_CHECKS),
137        ("frameworks/compile/mclinker/", _TIDY_EXTERNAL_VENDOR_CHECKS),
138        ("hardware/", _TIDY_EXTERNAL_VENDOR_CHECKS),
139        ("hardware/google/", _TIDY_DEFAULT_GLOBAL_CHECKS),
140        ("hardware/interfaces/", _TIDY_DEFAULT_GLOBAL_CHECKS),
141        ("hardware/ril/", _TIDY_DEFAULT_GLOBAL_CHECKS),
142        ("hardware/libhardware", _TIDY_DEFAULT_GLOBAL_CHECKS),  # all 'hardware/libhardware*'
143        ("vendor/", _TIDY_EXTERNAL_VENDOR_CHECKS),
144        ("vendor/google", _TIDY_DEFAULT_GLOBAL_CHECKS),  # all 'vendor/google*'
145        ("vendor/google/external/", _TIDY_EXTERNAL_VENDOR_CHECKS),
146        ("vendor/google_arc/libs/org.chromium.arc.mojom", _TIDY_EXTERNAL_VENDOR_CHECKS),
147        ("vendor/google_devices/", _TIDY_EXTERNAL_VENDOR_CHECKS),  # many have vendor code
148    ]
149
150    for d, checks in reversed(directory_checks):
151        if directory.startswith(d):
152            return checks
153
154    return _TIDY_DEFAULT_GLOBAL_CHECKS
155
156def _add_global_tidy_checks(ctx, local_checks, input_file):
157    tidy_checks = ctx.attr._tidy_checks[BuildSettingInfo].value
158    global_tidy_checks = []
159    if tidy_checks:
160        global_tidy_checks = tidy_checks
161    elif not input_file.is_source:
162        # don't run clang-tidy for generated files
163        global_tidy_checks = _TIDY_DEFAULT_GLOBAL_CHECKS_NO_ANALYZER
164    else:
165        global_tidy_checks = _add_checks_for_dir(ctx.label.package)
166
167    # If Tidy_checks contains "-*", ignore all checks before "-*".
168    for i, check in enumerate(local_checks):
169        if check == "-*":
170            global_tidy_checks = []
171            local_checks = local_checks[i:]
172
173    tidy_checks = global_tidy_checks + _clang_rewrite_tidy_checks(local_checks)
174    tidy_checks.extend(_TIDY_GLOBAL_NO_CHECKS)
175
176    #TODO(b/255747672) disable cert check on windows only
177    return tidy_checks
178
179def _add_global_tidy_checks_as_errors(tidy_checks_as_errors):
180    return tidy_checks_as_errors + _TIDY_GLOBAL_NO_ERROR_CHECKS
181
182def _create_clang_tidy_action(
183        ctx,
184        clang_tool,
185        input_file,
186        tidy_checks,
187        tidy_checks_as_errors,
188        tidy_flags,
189        clang_flags,
190        headers,
191        tidy_timeout):
192    tidy_flags = _add_with_tidy_flags(ctx, tidy_flags)
193    tidy_flags = _add_header_filter(ctx, tidy_flags)
194    tidy_flags = _add_extra_arg_flags(tidy_flags)
195    tidy_flags = _add_quiet_if_not_global_tidy(ctx, tidy_flags)
196    tidy_checks = _add_global_tidy_checks(ctx, tidy_checks, input_file)
197    tidy_checks_as_errors = _add_global_tidy_checks_as_errors(tidy_checks_as_errors)
198
199    _check_bad_tidy_checks(tidy_checks)
200    _check_bad_tidy_flags(tidy_flags)
201
202    args = ctx.actions.args()
203    args.add(input_file)
204    if tidy_checks:
205        args.add("-checks=" + ",".join(tidy_checks))
206    if tidy_checks_as_errors:
207        args.add("-warnings-as-errors=" + ",".join(tidy_checks_as_errors))
208    if tidy_flags:
209        args.add_all(tidy_flags)
210    args.add("--")
211    args.add_all(clang_flags)
212
213    tidy_file = ctx.actions.declare_file(paths.join(ctx.label.name, input_file.short_path + ".tidy"))
214    env = {
215        "CLANG_CMD": clang_tool,
216        "TIDY_FILE": tidy_file.path,
217    }
218    if tidy_timeout:
219        env["TIDY_TIMEOUT"] = tidy_timeout
220
221    ctx.actions.run(
222        inputs = [input_file] + headers,
223        outputs = [tidy_file],
224        arguments = [args],
225        env = env,
226        progress_message = "Running clang-tidy on {}".format(input_file.short_path),
227        tools = [
228            ctx.executable._clang_tidy,
229            ctx.executable._clang_tidy_real,
230        ],
231        executable = ctx.executable._clang_tidy_sh,
232        execution_requirements = {
233            "no-sandbox": "1",
234        },
235        mnemonic = "ClangTidy",
236    )
237
238    return tidy_file
239
240def generate_clang_tidy_actions(
241        ctx,
242        flags,
243        deps,
244        srcs,
245        hdrs,
246        language,
247        tidy_flags,
248        tidy_checks,
249        tidy_checks_as_errors,
250        tidy_timeout):
251    """Generates actions for clang tidy
252
253    Args:
254        ctx (Context): rule context that is expected to contain
255            - ctx.executable._clang_tidy
256            - ctx.executable._clang_tidy_sh
257            - ctx.executable._clang_tidy_real
258            - ctx.label._with_tidy_flags
259        flags (list[str]): list of target-specific (non-toolchain) flags passed
260            to clang compile action
261        deps (list[Target]): list of Targets which provide headers to
262            compilation context
263        srcs (list[File]): list of srcs to which clang-tidy will be applied
264        hdrs (list[File]): list of headers used by srcs. This is used to provide
265            explicit inputs to the action
266        language (str): must be one of ["c++", "c"]. This is used to decide what
267            toolchain arguments are passed to the clang compile action
268        tidy_flags (list[str]): additional flags to pass to the clang-tidy tool
269        tidy_checks (list[str]): list of checks for clang-tidy to perform
270        tidy_checks_as_errors (list[str]): list of checks to pass as
271            "-warnings-as-errors" to clang-tidy
272        tidy_checks_as_errors (str): timeout to pass to clang-tidy tool
273        tidy_timeout (str): timeout in seconds after which to stop a clang-tidy
274            invocation
275    Returns:
276        tidy_file_outputs: (list[File]): list of .tidy files output by the
277            clang-tidy.sh tool
278    """
279    toolchain = find_cpp_toolchain(ctx)
280    feature_config = cc_common.configure_features(
281        ctx = ctx,
282        cc_toolchain = toolchain,
283        language = "c++",
284        requested_features = ctx.features,
285        unsupported_features = ctx.disabled_features,
286    )
287
288    language = language
289    action_name = ""
290    if language == "c++":
291        action_name = CPP_COMPILE_ACTION_NAME
292    elif language == "c":
293        action_name = C_COMPILE_ACTION_NAME
294    else:
295        fail("invalid language:", language)
296
297    dep_info = cc_common.merge_cc_infos(direct_cc_infos = [d[CcInfo] for d in deps])
298    compilation_ctx = dep_info.compilation_context
299    args = get_compilation_args(
300        toolchain = toolchain,
301        feature_config = feature_config,
302        flags = flags,
303        compilation_ctx = compilation_ctx,
304        action_name = action_name,
305    )
306
307    clang_tool = cc_common.get_tool_for_action(
308        feature_configuration = feature_config,
309        action_name = action_name,
310    )
311
312    header_inputs = (
313        hdrs +
314        compilation_ctx.headers.to_list() +
315        compilation_ctx.direct_headers +
316        compilation_ctx.direct_private_headers +
317        compilation_ctx.direct_public_headers +
318        compilation_ctx.direct_textual_headers
319    )
320
321    tidy_file_outputs = []
322    for src in srcs:
323        tidy_file = _create_clang_tidy_action(
324            ctx = ctx,
325            input_file = src,
326            headers = header_inputs,
327            clang_tool = paths.basename(clang_tool),
328            tidy_checks = tidy_checks,
329            tidy_checks_as_errors = tidy_checks_as_errors,
330            tidy_flags = tidy_flags,
331            clang_flags = args,
332            tidy_timeout = tidy_timeout,
333        )
334        tidy_file_outputs.append(tidy_file)
335
336    return tidy_file_outputs
337
338def collect_deps_clang_tidy_info(ctx):
339    transitive_clang_tidy_files = []
340    for attr_deps in get_dep_targets(ctx.attr, predicate = lambda target: ClangTidyInfo in target).values():
341        for dep in attr_deps:
342            transitive_clang_tidy_files.append(dep[ClangTidyInfo].transitive_tidy_files)
343    return ClangTidyInfo(
344        tidy_files = depset(),
345        transitive_tidy_files = depset(transitive = transitive_clang_tidy_files),
346    )
347
348def _never_tidy_for_dir(directory):
349    # should stay up to date with https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/config/tidy.go;l=227;drc=f5864ba3633fdbadfb434483848887438fc11f59
350    return directory.startswith("external/grpc-grpc")
351
352def clang_tidy_for_dir(allow_external_vendor, directory):
353    return not _never_tidy_for_dir(directory) and (
354        allow_external_vendor or _add_checks_for_dir(directory) != _TIDY_EXTERNAL_VENDOR_CHECKS
355    )
356