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