• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/product_config:product_variables_providing_rule.bzl", "ProductVariablesInfo")
25load("//build/bazel/rules:common.bzl", "get_dep_targets")
26load(":cc_library_common.bzl", "get_compilation_args")
27
28ClangTidyInfo = provider(
29    "Info provided from clang-tidy actions",
30    fields = {
31        "tidy_files": "Outputs from the clang-tidy tool",
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    if not ctx.attr._product_variables[ProductVariablesInfo].TidyChecks:
103        return tidy_flags + [
104            "-quiet",
105            "-extra-arg-before=-fno-caret-diagnostics",
106        ]
107    return tidy_flags
108
109def _clang_rewrite_tidy_checks(tidy_checks):
110    # List of tidy checks that should be disabled globally. When the compiler is
111    # updated, some checks enabled by this module may be disabled if they have
112    # become more strict, or if they are a new match for a wildcard group like
113    # `modernize-*`.
114    clang_tidy_disable_checks = [
115        "misc-no-recursion",
116        "readability-function-cognitive-complexity",  # http://b/175055536
117    ]
118
119    tidy_checks = tidy_checks + ["-" + c for c in clang_tidy_disable_checks]
120
121    # clang-tidy does not allow later arguments to override earlier arguments,
122    # so if we just disabled an argument that was explicitly enabled we must
123    # remove the enabling argument from the list.
124    return [t for t in tidy_checks if t not in clang_tidy_disable_checks]
125
126def _add_checks_for_dir(directory):
127    """should be kept up to date with
128    https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/config/tidy.go;l=170;drc=b45a2ea782074944f79fc388df20b06e01f265f7
129    """
130
131    # This is a map of local path prefixes to the set of default clang-tidy checks
132    # to be used.  This is like android.IsThirdPartyPath, but with more patterns.
133    # The last matched local_path_prefix should be the most specific to be used.
134    directory_checks = [
135        ("external/", _TIDY_EXTERNAL_VENDOR_CHECKS),
136        ("frameworks/compile/mclinker/", _TIDY_EXTERNAL_VENDOR_CHECKS),
137        ("hardware/", _TIDY_EXTERNAL_VENDOR_CHECKS),
138        ("hardware/google/", _TIDY_DEFAULT_GLOBAL_CHECKS),
139        ("hardware/interfaces/", _TIDY_DEFAULT_GLOBAL_CHECKS),
140        ("hardware/ril/", _TIDY_DEFAULT_GLOBAL_CHECKS),
141        ("hardware/libhardware", _TIDY_DEFAULT_GLOBAL_CHECKS),  # all 'hardware/libhardware*'
142        ("vendor/", _TIDY_EXTERNAL_VENDOR_CHECKS),
143        ("vendor/google", _TIDY_DEFAULT_GLOBAL_CHECKS),  # all 'vendor/google*'
144        ("vendor/google/external/", _TIDY_EXTERNAL_VENDOR_CHECKS),
145        ("vendor/google_arc/libs/org.chromium.arc.mojom", _TIDY_EXTERNAL_VENDOR_CHECKS),
146        ("vendor/google_devices/", _TIDY_EXTERNAL_VENDOR_CHECKS),  # many have vendor code
147    ]
148
149    for d, checks in reversed(directory_checks):
150        if directory.startswith(d):
151            return checks
152
153    return _TIDY_DEFAULT_GLOBAL_CHECKS
154
155def _add_global_tidy_checks(ctx, local_checks, input_file):
156    tidy_checks = ctx.attr._product_variables[ProductVariablesInfo].TidyChecks
157    global_tidy_checks = []
158    if tidy_checks:
159        global_tidy_checks = tidy_checks
160    elif not input_file.is_source:
161        # don't run clang-tidy for generated files
162        global_tidy_checks = _TIDY_DEFAULT_GLOBAL_CHECKS_NO_ANALYZER
163    else:
164        global_tidy_checks = _add_checks_for_dir(ctx.label.package)
165
166    # If Tidy_checks contains "-*", ignore all checks before "-*".
167    for i, check in enumerate(local_checks):
168        if check == "-*":
169            global_tidy_checks = []
170            local_checks = local_checks[i:]
171
172    tidy_checks = global_tidy_checks + _clang_rewrite_tidy_checks(local_checks)
173    tidy_checks.extend(_TIDY_GLOBAL_NO_CHECKS)
174
175    #TODO(b/255747672) disable cert check on windows only
176    return tidy_checks
177
178def _add_global_tidy_checks_as_errors(tidy_checks_as_errors):
179    return tidy_checks_as_errors + _TIDY_GLOBAL_NO_ERROR_CHECKS
180
181def _create_clang_tidy_action(
182        ctx,
183        clang_tool,
184        input_file,
185        tidy_checks,
186        tidy_checks_as_errors,
187        tidy_flags,
188        clang_flags,
189        headers,
190        tidy_timeout):
191    tidy_flags = _add_with_tidy_flags(ctx, tidy_flags)
192    tidy_flags = _add_header_filter(ctx, tidy_flags)
193    tidy_flags = _add_extra_arg_flags(tidy_flags)
194    tidy_flags = _add_quiet_if_not_global_tidy(ctx, tidy_flags)
195    tidy_checks = _add_global_tidy_checks(ctx, tidy_checks, input_file)
196    tidy_checks_as_errors = _add_global_tidy_checks_as_errors(tidy_checks_as_errors)
197
198    _check_bad_tidy_checks(tidy_checks)
199    _check_bad_tidy_flags(tidy_flags)
200
201    args = ctx.actions.args()
202    args.add(input_file)
203    if tidy_checks:
204        args.add("-checks=" + ",".join(tidy_checks))
205    if tidy_checks_as_errors:
206        args.add("-warnings-as-errors=" + ",".join(tidy_checks_as_errors))
207    if tidy_flags:
208        args.add_all(tidy_flags)
209    args.add("--")
210    args.add_all(clang_flags)
211
212    tidy_file = ctx.actions.declare_file(paths.join(ctx.label.name, input_file.short_path + ".tidy"))
213    env = {
214        "CLANG_CMD": clang_tool,
215        "TIDY_FILE": tidy_file.path,
216    }
217    if tidy_timeout:
218        env["TIDY_TIMEOUT"] = tidy_timeout
219
220    ctx.actions.run(
221        inputs = [input_file] + headers,
222        outputs = [tidy_file],
223        arguments = [args],
224        env = env,
225        progress_message = "Running clang-tidy on {}".format(input_file.short_path),
226        tools = [
227            ctx.executable._clang_tidy,
228            ctx.executable._clang_tidy_real,
229        ],
230        executable = ctx.executable._clang_tidy_sh,
231        execution_requirements = {
232            "no-sandbox": "1",
233        },
234        mnemonic = "ClangTidy",
235    )
236
237    return tidy_file
238
239def generate_clang_tidy_actions(
240        ctx,
241        flags,
242        deps,
243        srcs,
244        hdrs,
245        language,
246        tidy_flags,
247        tidy_checks,
248        tidy_checks_as_errors,
249        tidy_timeout):
250    """Generates actions for clang tidy
251
252    Args:
253        ctx (Context): rule context that is expected to contain
254            - ctx.executable._clang_tidy
255            - ctx.executable._clang_tidy_sh
256            - ctx.executable._clang_tidy_real
257            - ctx.label._with_tidy_flags
258        flags (list[str]): list of target-specific (non-toolchain) flags passed
259            to clang compile action
260        deps (list[Target]): list of Targets which provide headers to
261            compilation context
262        srcs (list[File]): list of srcs to which clang-tidy will be applied
263        hdrs (list[File]): list of headers used by srcs. This is used to provide
264            explicit inputs to the action
265        language (str): must be one of ["c++", "c"]. This is used to decide what
266            toolchain arguments are passed to the clang compile action
267        tidy_flags (list[str]): additional flags to pass to the clang-tidy tool
268        tidy_checks (list[str]): list of checks for clang-tidy to perform
269        tidy_checks_as_errors (list[str]): list of checks to pass as
270            "-warnings-as-errors" to clang-tidy
271        tidy_checks_as_errors (str): timeout to pass to clang-tidy tool
272        tidy_timeout (str): timeout in seconds after which to stop a clang-tidy
273            invocation
274    Returns:
275        tidy_file_outputs: (list[File]): list of .tidy files output by the
276            clang-tidy.sh tool
277    """
278    toolchain = find_cpp_toolchain(ctx)
279    feature_config = cc_common.configure_features(
280        ctx = ctx,
281        cc_toolchain = toolchain,
282        language = "c++",
283        requested_features = ctx.features,
284        unsupported_features = ctx.disabled_features,
285    )
286
287    language = language
288    action_name = ""
289    if language == "c++":
290        action_name = CPP_COMPILE_ACTION_NAME
291    elif language == "c":
292        action_name = C_COMPILE_ACTION_NAME
293    else:
294        fail("invalid language:", language)
295
296    dep_info = cc_common.merge_cc_infos(direct_cc_infos = [d[CcInfo] for d in deps])
297    compilation_ctx = dep_info.compilation_context
298    args = get_compilation_args(
299        toolchain = toolchain,
300        feature_config = feature_config,
301        flags = flags,
302        compilation_ctx = compilation_ctx,
303        action_name = action_name,
304    )
305
306    clang_tool = cc_common.get_tool_for_action(
307        feature_configuration = feature_config,
308        action_name = action_name,
309    )
310
311    header_inputs = (
312        hdrs +
313        compilation_ctx.headers.to_list() +
314        compilation_ctx.direct_headers +
315        compilation_ctx.direct_private_headers +
316        compilation_ctx.direct_public_headers +
317        compilation_ctx.direct_textual_headers
318    )
319
320    tidy_file_outputs = []
321    for src in srcs:
322        tidy_file = _create_clang_tidy_action(
323            ctx = ctx,
324            input_file = src,
325            headers = header_inputs,
326            clang_tool = paths.basename(clang_tool),
327            tidy_checks = tidy_checks,
328            tidy_checks_as_errors = tidy_checks_as_errors,
329            tidy_flags = tidy_flags,
330            clang_flags = args,
331            tidy_timeout = tidy_timeout,
332        )
333        tidy_file_outputs.append(tidy_file)
334
335    return tidy_file_outputs
336
337def collect_deps_clang_tidy_info(ctx):
338    transitive_clang_tidy_files = []
339    for attr_deps in get_dep_targets(ctx.attr, predicate = lambda target: ClangTidyInfo in target).values():
340        for dep in attr_deps:
341            transitive_clang_tidy_files.append(dep[ClangTidyInfo].transitive_tidy_files)
342    return ClangTidyInfo(
343        tidy_files = depset(),
344        transitive_tidy_files = depset(transitive = transitive_clang_tidy_files),
345    )
346
347def _never_tidy_for_dir(directory):
348    # should stay up to date with https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/config/tidy.go;l=227;drc=f5864ba3633fdbadfb434483848887438fc11f59
349    return directory.startswith("external/grpc-grpc")
350
351def clang_tidy_for_dir(allow_external_vendor, directory):
352    return not _never_tidy_for_dir(directory) and (
353        allow_external_vendor or _add_checks_for_dir(directory) != _TIDY_EXTERNAL_VENDOR_CHECKS
354    )
355