355 lines
14 KiB
Python
355 lines
14 KiB
Python
# Copyright (C) 2022 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
load("@bazel_skylib//lib:paths.bzl", "paths")
|
|
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
|
|
load(
|
|
"@bazel_tools//tools/build_defs/cc:action_names.bzl",
|
|
"CPP_COMPILE_ACTION_NAME",
|
|
"C_COMPILE_ACTION_NAME",
|
|
)
|
|
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
|
|
load("@soong_injection//cc_toolchain:config_constants.bzl", "constants")
|
|
load("//build/bazel/product_config:product_variables_providing_rule.bzl", "ProductVariablesInfo")
|
|
load("//build/bazel/rules:common.bzl", "get_dep_targets")
|
|
load(":cc_library_common.bzl", "get_compilation_args")
|
|
|
|
ClangTidyInfo = provider(
|
|
"Info provided from clang-tidy actions",
|
|
fields = {
|
|
"tidy_files": "Outputs from the clang-tidy tool",
|
|
"transitive_tidy_files": "Outputs from the clang-tidy tool for all transitive dependencies." +
|
|
" Currently, these are needed so that mixed-build targets can also run clang-tidy for their dependencies.",
|
|
},
|
|
)
|
|
|
|
_TIDY_GLOBAL_NO_CHECKS = constants.TidyGlobalNoChecks.split(",")
|
|
_TIDY_GLOBAL_NO_ERROR_CHECKS = constants.TidyGlobalNoErrorChecks.split(",")
|
|
_TIDY_DEFAULT_GLOBAL_CHECKS = constants.TidyDefaultGlobalChecks.split(",")
|
|
_TIDY_EXTERNAL_VENDOR_CHECKS = constants.TidyExternalVendorChecks.split(",")
|
|
_TIDY_DEFAULT_GLOBAL_CHECKS_NO_ANALYZER = constants.TidyDefaultGlobalChecks.split(",") + ["-clang-analyzer-*"]
|
|
_TIDY_EXTRA_ARG_FLAGS = constants.TidyExtraArgFlags
|
|
|
|
def _check_bad_tidy_flags(tidy_flags):
|
|
"""should be kept up to date with
|
|
https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/check.go;l=128;drc=b45a2ea782074944f79fc388df20b06e01f265f7
|
|
"""
|
|
for flag in tidy_flags:
|
|
flag = flag.strip()
|
|
if not flag.startswith("-"):
|
|
fail("Flag `%s` must start with `-`" % flag)
|
|
if flag.startswith("-fix"):
|
|
fail("Flag `%s` is not allowed, since it could cause multiple writes to the same source file" % flag)
|
|
if flag.startswith("-checks="):
|
|
fail("Flag `%s` is not allowed, use `tidy_checks` property instead" % flag)
|
|
if "-warnings-as-errors=" in flag:
|
|
fail("Flag `%s` is not allowed, use `tidy_checks_as_errors` property instead" % flag)
|
|
if " " in flag:
|
|
fail("Bad flag: `%s` is not an allowed multi-word flag. Should it be split into multiple flags?" % flag)
|
|
|
|
def _check_bad_tidy_checks(tidy_checks):
|
|
"""should be kept up to date with
|
|
https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/check.go;l=145;drc=b45a2ea782074944f79fc388df20b06e01f265f7
|
|
"""
|
|
for check in tidy_checks:
|
|
if " " in check:
|
|
fail("Check `%s` invalid, cannot contain spaces" % check)
|
|
if "," in check:
|
|
fail("Check `%s` invalid, cannot contain commas. Split each entry into its own string instead" % check)
|
|
|
|
def _add_with_tidy_flags(ctx, tidy_flags):
|
|
with_tidy_flags = ctx.attr._with_tidy_flags[BuildSettingInfo].value
|
|
if with_tidy_flags:
|
|
return tidy_flags + with_tidy_flags
|
|
return tidy_flags
|
|
|
|
def _add_header_filter(ctx, tidy_flags):
|
|
"""If TidyFlags does not contain -header-filter, add default header filter.
|
|
"""
|
|
for flag in tidy_flags:
|
|
# Find the substring because the flag could also appear as --header-filter=...
|
|
# and with or without single or double quotes.
|
|
if "-header-filter=" in flag:
|
|
return tidy_flags
|
|
|
|
# Default header filter should include only the module directory,
|
|
# not the out/soong/.../ModuleDir/...
|
|
# Otherwise, there will be too many warnings from generated files in out/...
|
|
# If a module wants to see warnings in the generated source files,
|
|
# it should specify its own -header-filter flag.
|
|
default_dirs = ctx.attr._default_tidy_header_dirs[BuildSettingInfo].value
|
|
if default_dirs == "":
|
|
header_filter = "-header-filter=^" + ctx.label.package + "/"
|
|
else:
|
|
header_filter = "-header-filter=\"(^%s/|%s)\"" % (ctx.label.package, default_dirs)
|
|
return tidy_flags + [header_filter]
|
|
|
|
def _add_extra_arg_flags(tidy_flags):
|
|
return tidy_flags + ["-extra-arg-before=" + f for f in _TIDY_EXTRA_ARG_FLAGS]
|
|
|
|
def _add_quiet_if_not_global_tidy(ctx, tidy_flags):
|
|
if not ctx.attr._product_variables[ProductVariablesInfo].TidyChecks:
|
|
return tidy_flags + [
|
|
"-quiet",
|
|
"-extra-arg-before=-fno-caret-diagnostics",
|
|
]
|
|
return tidy_flags
|
|
|
|
def _clang_rewrite_tidy_checks(tidy_checks):
|
|
# List of tidy checks that should be disabled globally. When the compiler is
|
|
# updated, some checks enabled by this module may be disabled if they have
|
|
# become more strict, or if they are a new match for a wildcard group like
|
|
# `modernize-*`.
|
|
clang_tidy_disable_checks = [
|
|
"misc-no-recursion",
|
|
"readability-function-cognitive-complexity", # http://b/175055536
|
|
]
|
|
|
|
tidy_checks = tidy_checks + ["-" + c for c in clang_tidy_disable_checks]
|
|
|
|
# clang-tidy does not allow later arguments to override earlier arguments,
|
|
# so if we just disabled an argument that was explicitly enabled we must
|
|
# remove the enabling argument from the list.
|
|
return [t for t in tidy_checks if t not in clang_tidy_disable_checks]
|
|
|
|
def _add_checks_for_dir(directory):
|
|
"""should be kept up to date with
|
|
https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/config/tidy.go;l=170;drc=b45a2ea782074944f79fc388df20b06e01f265f7
|
|
"""
|
|
|
|
# This is a map of local path prefixes to the set of default clang-tidy checks
|
|
# to be used. This is like android.IsThirdPartyPath, but with more patterns.
|
|
# The last matched local_path_prefix should be the most specific to be used.
|
|
directory_checks = [
|
|
("external/", _TIDY_EXTERNAL_VENDOR_CHECKS),
|
|
("frameworks/compile/mclinker/", _TIDY_EXTERNAL_VENDOR_CHECKS),
|
|
("hardware/", _TIDY_EXTERNAL_VENDOR_CHECKS),
|
|
("hardware/google/", _TIDY_DEFAULT_GLOBAL_CHECKS),
|
|
("hardware/interfaces/", _TIDY_DEFAULT_GLOBAL_CHECKS),
|
|
("hardware/ril/", _TIDY_DEFAULT_GLOBAL_CHECKS),
|
|
("hardware/libhardware", _TIDY_DEFAULT_GLOBAL_CHECKS), # all 'hardware/libhardware*'
|
|
("vendor/", _TIDY_EXTERNAL_VENDOR_CHECKS),
|
|
("vendor/google", _TIDY_DEFAULT_GLOBAL_CHECKS), # all 'vendor/google*'
|
|
("vendor/google/external/", _TIDY_EXTERNAL_VENDOR_CHECKS),
|
|
("vendor/google_arc/libs/org.chromium.arc.mojom", _TIDY_EXTERNAL_VENDOR_CHECKS),
|
|
("vendor/google_devices/", _TIDY_EXTERNAL_VENDOR_CHECKS), # many have vendor code
|
|
]
|
|
|
|
for d, checks in reversed(directory_checks):
|
|
if directory.startswith(d):
|
|
return checks
|
|
|
|
return _TIDY_DEFAULT_GLOBAL_CHECKS
|
|
|
|
def _add_global_tidy_checks(ctx, local_checks, input_file):
|
|
tidy_checks = ctx.attr._product_variables[ProductVariablesInfo].TidyChecks
|
|
global_tidy_checks = []
|
|
if tidy_checks:
|
|
global_tidy_checks = tidy_checks
|
|
elif not input_file.is_source:
|
|
# don't run clang-tidy for generated files
|
|
global_tidy_checks = _TIDY_DEFAULT_GLOBAL_CHECKS_NO_ANALYZER
|
|
else:
|
|
global_tidy_checks = _add_checks_for_dir(ctx.label.package)
|
|
|
|
# If Tidy_checks contains "-*", ignore all checks before "-*".
|
|
for i, check in enumerate(local_checks):
|
|
if check == "-*":
|
|
global_tidy_checks = []
|
|
local_checks = local_checks[i:]
|
|
|
|
tidy_checks = global_tidy_checks + _clang_rewrite_tidy_checks(local_checks)
|
|
tidy_checks.extend(_TIDY_GLOBAL_NO_CHECKS)
|
|
|
|
#TODO(b/255747672) disable cert check on windows only
|
|
return tidy_checks
|
|
|
|
def _add_global_tidy_checks_as_errors(tidy_checks_as_errors):
|
|
return tidy_checks_as_errors + _TIDY_GLOBAL_NO_ERROR_CHECKS
|
|
|
|
def _create_clang_tidy_action(
|
|
ctx,
|
|
clang_tool,
|
|
input_file,
|
|
tidy_checks,
|
|
tidy_checks_as_errors,
|
|
tidy_flags,
|
|
clang_flags,
|
|
headers,
|
|
tidy_timeout):
|
|
tidy_flags = _add_with_tidy_flags(ctx, tidy_flags)
|
|
tidy_flags = _add_header_filter(ctx, tidy_flags)
|
|
tidy_flags = _add_extra_arg_flags(tidy_flags)
|
|
tidy_flags = _add_quiet_if_not_global_tidy(ctx, tidy_flags)
|
|
tidy_checks = _add_global_tidy_checks(ctx, tidy_checks, input_file)
|
|
tidy_checks_as_errors = _add_global_tidy_checks_as_errors(tidy_checks_as_errors)
|
|
|
|
_check_bad_tidy_checks(tidy_checks)
|
|
_check_bad_tidy_flags(tidy_flags)
|
|
|
|
args = ctx.actions.args()
|
|
args.add(input_file)
|
|
if tidy_checks:
|
|
args.add("-checks=" + ",".join(tidy_checks))
|
|
if tidy_checks_as_errors:
|
|
args.add("-warnings-as-errors=" + ",".join(tidy_checks_as_errors))
|
|
if tidy_flags:
|
|
args.add_all(tidy_flags)
|
|
args.add("--")
|
|
args.add_all(clang_flags)
|
|
|
|
tidy_file = ctx.actions.declare_file(paths.join(ctx.label.name, input_file.short_path + ".tidy"))
|
|
env = {
|
|
"CLANG_CMD": clang_tool,
|
|
"TIDY_FILE": tidy_file.path,
|
|
}
|
|
if tidy_timeout:
|
|
env["TIDY_TIMEOUT"] = tidy_timeout
|
|
|
|
ctx.actions.run(
|
|
inputs = [input_file] + headers,
|
|
outputs = [tidy_file],
|
|
arguments = [args],
|
|
env = env,
|
|
progress_message = "Running clang-tidy on {}".format(input_file.short_path),
|
|
tools = [
|
|
ctx.executable._clang_tidy,
|
|
ctx.executable._clang_tidy_real,
|
|
],
|
|
executable = ctx.executable._clang_tidy_sh,
|
|
execution_requirements = {
|
|
"no-sandbox": "1",
|
|
},
|
|
mnemonic = "ClangTidy",
|
|
)
|
|
|
|
return tidy_file
|
|
|
|
def generate_clang_tidy_actions(
|
|
ctx,
|
|
flags,
|
|
deps,
|
|
srcs,
|
|
hdrs,
|
|
language,
|
|
tidy_flags,
|
|
tidy_checks,
|
|
tidy_checks_as_errors,
|
|
tidy_timeout):
|
|
"""Generates actions for clang tidy
|
|
|
|
Args:
|
|
ctx (Context): rule context that is expected to contain
|
|
- ctx.executable._clang_tidy
|
|
- ctx.executable._clang_tidy_sh
|
|
- ctx.executable._clang_tidy_real
|
|
- ctx.label._with_tidy_flags
|
|
flags (list[str]): list of target-specific (non-toolchain) flags passed
|
|
to clang compile action
|
|
deps (list[Target]): list of Targets which provide headers to
|
|
compilation context
|
|
srcs (list[File]): list of srcs to which clang-tidy will be applied
|
|
hdrs (list[File]): list of headers used by srcs. This is used to provide
|
|
explicit inputs to the action
|
|
language (str): must be one of ["c++", "c"]. This is used to decide what
|
|
toolchain arguments are passed to the clang compile action
|
|
tidy_flags (list[str]): additional flags to pass to the clang-tidy tool
|
|
tidy_checks (list[str]): list of checks for clang-tidy to perform
|
|
tidy_checks_as_errors (list[str]): list of checks to pass as
|
|
"-warnings-as-errors" to clang-tidy
|
|
tidy_checks_as_errors (str): timeout to pass to clang-tidy tool
|
|
tidy_timeout (str): timeout in seconds after which to stop a clang-tidy
|
|
invocation
|
|
Returns:
|
|
tidy_file_outputs: (list[File]): list of .tidy files output by the
|
|
clang-tidy.sh tool
|
|
"""
|
|
toolchain = find_cpp_toolchain(ctx)
|
|
feature_config = cc_common.configure_features(
|
|
ctx = ctx,
|
|
cc_toolchain = toolchain,
|
|
language = "c++",
|
|
requested_features = ctx.features,
|
|
unsupported_features = ctx.disabled_features,
|
|
)
|
|
|
|
language = language
|
|
action_name = ""
|
|
if language == "c++":
|
|
action_name = CPP_COMPILE_ACTION_NAME
|
|
elif language == "c":
|
|
action_name = C_COMPILE_ACTION_NAME
|
|
else:
|
|
fail("invalid language:", language)
|
|
|
|
dep_info = cc_common.merge_cc_infos(direct_cc_infos = [d[CcInfo] for d in deps])
|
|
compilation_ctx = dep_info.compilation_context
|
|
args = get_compilation_args(
|
|
toolchain = toolchain,
|
|
feature_config = feature_config,
|
|
flags = flags,
|
|
compilation_ctx = compilation_ctx,
|
|
action_name = action_name,
|
|
)
|
|
|
|
clang_tool = cc_common.get_tool_for_action(
|
|
feature_configuration = feature_config,
|
|
action_name = action_name,
|
|
)
|
|
|
|
header_inputs = (
|
|
hdrs +
|
|
compilation_ctx.headers.to_list() +
|
|
compilation_ctx.direct_headers +
|
|
compilation_ctx.direct_private_headers +
|
|
compilation_ctx.direct_public_headers +
|
|
compilation_ctx.direct_textual_headers
|
|
)
|
|
|
|
tidy_file_outputs = []
|
|
for src in srcs:
|
|
tidy_file = _create_clang_tidy_action(
|
|
ctx = ctx,
|
|
input_file = src,
|
|
headers = header_inputs,
|
|
clang_tool = paths.basename(clang_tool),
|
|
tidy_checks = tidy_checks,
|
|
tidy_checks_as_errors = tidy_checks_as_errors,
|
|
tidy_flags = tidy_flags,
|
|
clang_flags = args,
|
|
tidy_timeout = tidy_timeout,
|
|
)
|
|
tidy_file_outputs.append(tidy_file)
|
|
|
|
return tidy_file_outputs
|
|
|
|
def collect_deps_clang_tidy_info(ctx):
|
|
transitive_clang_tidy_files = []
|
|
for attr_deps in get_dep_targets(ctx.attr, predicate = lambda target: ClangTidyInfo in target).values():
|
|
for dep in attr_deps:
|
|
transitive_clang_tidy_files.append(dep[ClangTidyInfo].transitive_tidy_files)
|
|
return ClangTidyInfo(
|
|
tidy_files = depset(),
|
|
transitive_tidy_files = depset(transitive = transitive_clang_tidy_files),
|
|
)
|
|
|
|
def _never_tidy_for_dir(directory):
|
|
# should stay up to date with https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/config/tidy.go;l=227;drc=f5864ba3633fdbadfb434483848887438fc11f59
|
|
return directory.startswith("external/grpc-grpc")
|
|
|
|
def clang_tidy_for_dir(allow_external_vendor, directory):
|
|
return not _never_tidy_for_dir(directory) and (
|
|
allow_external_vendor or _add_checks_for_dir(directory) != _TIDY_EXTERNAL_VENDOR_CHECKS
|
|
)
|