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