|
5 | 5 | """Quality check rules for OpenTitan.
|
6 | 6 | """
|
7 | 7 |
|
| 8 | +load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "C_COMPILE_ACTION_NAME") |
8 | 9 | load("@bazel_skylib//lib:shell.bzl", "shell")
|
| 10 | +load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain") |
| 11 | +load("//rules:rv.bzl", "rv_rule") |
9 | 12 |
|
10 | 13 | def _ensure_tag(tags, *tag):
|
11 | 14 | for t in tag:
|
@@ -95,6 +98,208 @@ def clang_format_test(**kwargs):
|
95 | 98 | kwargs["tags"] = _ensure_tag(tags, "no-sandbox", "no-cache", "external")
|
96 | 99 | _clang_format_test(**kwargs)
|
97 | 100 |
|
| 101 | +# To see which checks clang-tidy knows about, run this command: |
| 102 | +# |
| 103 | +# ./bazelisk.sh run @lowrisc_rv32imcb_files//:bin/clang-tidy -- --checks='*' --list-checks |
| 104 | +_CLANG_TIDY_CHECKS = [ |
| 105 | + "clang-analyzer-core.*", |
| 106 | +] |
| 107 | + |
| 108 | +def _clang_tidy_aspect_impl(target, ctx): |
| 109 | + """Aspect implementation that runs clang-tidy on C/C++.""" |
| 110 | + |
| 111 | + if ctx.rule.kind not in ["cc_library", "cc_binary", "cc_test"]: |
| 112 | + return [OutputGroupInfo(clang_tidy = [])] |
| 113 | + |
| 114 | + if CcInfo in target: |
| 115 | + cc_info = target[CcInfo] |
| 116 | + elif hasattr(ctx.rule.attr, "deps"): |
| 117 | + # Some rules, like cc_binary, do NOT produce a CcInfo provider. Therefore, |
| 118 | + # we need to build one from its dependencies. |
| 119 | + cc_info = cc_common.merge_cc_infos( |
| 120 | + direct_cc_infos = [dep[CcInfo] for dep in ctx.rule.attr.deps if CcInfo in dep], |
| 121 | + ) |
| 122 | + else: |
| 123 | + return [OutputGroupInfo(clang_tidy = [])] |
| 124 | + cc_compile_ctx = cc_info.compilation_context |
| 125 | + |
| 126 | + cc_toolchain = find_cc_toolchain(ctx).cc |
| 127 | + feature_configuration = cc_common.configure_features( |
| 128 | + ctx = ctx, |
| 129 | + cc_toolchain = cc_toolchain, |
| 130 | + requested_features = ctx.features, |
| 131 | + unsupported_features = ctx.disabled_features, |
| 132 | + ) |
| 133 | + |
| 134 | + if not hasattr(ctx.rule.attr, "srcs"): |
| 135 | + return [OutputGroupInfo(clang_tidy = [])] |
| 136 | + all_srcs = [] |
| 137 | + for src in ctx.rule.attr.srcs: |
| 138 | + all_srcs += [src for src in src.files.to_list() if src.is_source] |
| 139 | + |
| 140 | + outputs = [] |
| 141 | + for src in all_srcs: |
| 142 | + if src.path.startswith("external/"): |
| 143 | + continue |
| 144 | + if not src.extension in ["c", "cc", "h"]: |
| 145 | + continue |
| 146 | + |
| 147 | + generated_file = ctx.actions.declare_file("{}.{}.clang-tidy.out".format(src.basename, target.label.name)) |
| 148 | + outputs.append(generated_file) |
| 149 | + |
| 150 | + opts = ctx.fragments.cpp.copts |
| 151 | + if hasattr(ctx.rule.attr, "copts"): |
| 152 | + opts += ctx.rule.attr.copts |
| 153 | + |
| 154 | + # TODO(dmcardle) What if an .h file should be compiled for C++? Perhaps |
| 155 | + # this should match the behavior in rules/cc_side_outputs.bzl. |
| 156 | + if src.extension in ["c", "h"]: |
| 157 | + opts += ctx.fragments.cpp.conlyopts |
| 158 | + else: |
| 159 | + opts += ctx.fragments.cpp.cxxopts |
| 160 | + if hasattr(ctx.rule.attr, "cxxopts"): |
| 161 | + opts += ctx.rule.attr.cxxopts |
| 162 | + |
| 163 | + c_compile_variables = cc_common.create_compile_variables( |
| 164 | + feature_configuration = feature_configuration, |
| 165 | + cc_toolchain = cc_toolchain, |
| 166 | + source_file = src.path, |
| 167 | + user_compile_flags = opts, |
| 168 | + include_directories = depset( |
| 169 | + direct = [src.dirname for src in cc_compile_ctx.headers.to_list()], |
| 170 | + transitive = [cc_compile_ctx.includes], |
| 171 | + ), |
| 172 | + quote_include_directories = cc_compile_ctx.quote_includes, |
| 173 | + system_include_directories = cc_compile_ctx.system_includes, |
| 174 | + framework_include_directories = cc_compile_ctx.framework_includes, |
| 175 | + preprocessor_defines = depset( |
| 176 | + direct = ctx.rule.attr.local_defines, |
| 177 | + transitive = [cc_compile_ctx.defines], |
| 178 | + ), |
| 179 | + ) |
| 180 | + |
| 181 | + command_line = cc_common.get_memory_inefficient_command_line( |
| 182 | + feature_configuration = feature_configuration, |
| 183 | + action_name = ACTION_NAMES.c_compile, |
| 184 | + variables = c_compile_variables, |
| 185 | + ) |
| 186 | + |
| 187 | + args = ctx.actions.args() |
| 188 | + |
| 189 | + # Add args that are consumed by the wrapper script. |
| 190 | + if ctx.attr._enable_fix: |
| 191 | + args.add("--ignore-clang-tidy-error") |
| 192 | + args.add(".clang-tidy.lock") |
| 193 | + args.add(generated_file) |
| 194 | + args.add_all(ctx.attr._clang_tidy.files) |
| 195 | + |
| 196 | + # Add args for clang-tidy. |
| 197 | + if len(_CLANG_TIDY_CHECKS) > 0: |
| 198 | + checks_pattern = ",".join(_CLANG_TIDY_CHECKS) |
| 199 | + args.add("--checks=" + checks_pattern) |
| 200 | + |
| 201 | + # Treat warnings from every enabled check as errors. |
| 202 | + args.add("--warnings-as-errors=" + checks_pattern) |
| 203 | + if ctx.attr._enable_fix: |
| 204 | + args.add("--fix") |
| 205 | + args.add("--fix-errors") |
| 206 | + |
| 207 | + # Use the nearest .clang_format file to format code adjacent to fixes. |
| 208 | + args.add("--format-style=file") |
| 209 | + |
| 210 | + # Specify a regex header filter. Without this, clang-tidy will not |
| 211 | + # report or fix errors in header files. |
| 212 | + args.add("--header-filter=.*\\.h$") |
| 213 | + args.add("--use-color") |
| 214 | + |
| 215 | + for arg in command_line: |
| 216 | + # Skip the src file argument. We give that to clang-tidy separately. |
| 217 | + if arg == src.path: |
| 218 | + continue |
| 219 | + elif arg == "-fno-canonical-system-headers": |
| 220 | + continue |
| 221 | + args.add("--extra-arg=" + arg) |
| 222 | + |
| 223 | + # Tell clang-tidy which source file to analyze. |
| 224 | + args.add(src) |
| 225 | + |
| 226 | + ctx.actions.run( |
| 227 | + executable = ctx.attr._clang_tidy_wrapper.files_to_run, |
| 228 | + arguments = [args], |
| 229 | + inputs = depset( |
| 230 | + direct = [src], |
| 231 | + transitive = [ |
| 232 | + cc_toolchain.all_files, |
| 233 | + cc_compile_ctx.headers, |
| 234 | + ], |
| 235 | + ), |
| 236 | + tools = [ctx.attr._clang_tidy.files_to_run], |
| 237 | + outputs = [generated_file], |
| 238 | + progress_message = "Running clang tidy on {}".format(src.path), |
| 239 | + ) |
| 240 | + |
| 241 | + return [ |
| 242 | + OutputGroupInfo( |
| 243 | + clang_tidy = depset(direct = outputs), |
| 244 | + ), |
| 245 | + ] |
| 246 | + |
| 247 | +def _make_clang_tidy_aspect(enable_fix): |
| 248 | + return aspect( |
| 249 | + implementation = _clang_tidy_aspect_impl, |
| 250 | + attr_aspects = ["deps"], |
| 251 | + attrs = { |
| 252 | + "_clang_tidy_wrapper": attr.label( |
| 253 | + default = "//rules/scripts:clang_tidy.py", |
| 254 | + allow_single_file = True, |
| 255 | + cfg = "host", |
| 256 | + executable = True, |
| 257 | + ), |
| 258 | + "_clang_tidy": attr.label( |
| 259 | + default = "@lowrisc_rv32imcb_files//:bin/clang-tidy", |
| 260 | + allow_single_file = True, |
| 261 | + cfg = "host", |
| 262 | + executable = True, |
| 263 | + doc = "The clang-tidy executable", |
| 264 | + ), |
| 265 | + "_enable_fix": attr.bool(default = enable_fix), |
| 266 | + }, |
| 267 | + incompatible_use_toolchain_transition = True, |
| 268 | + fragments = ["cpp"], |
| 269 | + host_fragments = ["cpp"], |
| 270 | + toolchains = ["@rules_cc//cc:toolchain_type"], |
| 271 | + ) |
| 272 | + |
| 273 | +clang_tidy_fix_aspect = _make_clang_tidy_aspect(True) |
| 274 | +clang_tidy_check_aspect = _make_clang_tidy_aspect(False) |
| 275 | + |
| 276 | +def _clang_tidy_check_impl(ctx): |
| 277 | + return [ |
| 278 | + DefaultInfo( |
| 279 | + files = depset( |
| 280 | + transitive = [dep[OutputGroupInfo].clang_tidy for dep in ctx.attr.deps], |
| 281 | + ), |
| 282 | + ), |
| 283 | + ] |
| 284 | + |
| 285 | +clang_tidy_check_rv = rv_rule( |
| 286 | + implementation = _clang_tidy_check_impl, |
| 287 | + attrs = { |
| 288 | + "deps": attr.label_list( |
| 289 | + aspects = [clang_tidy_check_aspect], |
| 290 | + ), |
| 291 | + }, |
| 292 | +) |
| 293 | + |
| 294 | +clang_tidy_check = rule( |
| 295 | + implementation = _clang_tidy_check_impl, |
| 296 | + attrs = { |
| 297 | + "deps": attr.label_list( |
| 298 | + aspects = [clang_tidy_check_aspect], |
| 299 | + ), |
| 300 | + }, |
| 301 | +) |
| 302 | + |
98 | 303 | def _html_coverage_report_impl(ctx):
|
99 | 304 | out_file = ctx.actions.declare_file(ctx.label.name + ".bash")
|
100 | 305 | substitutions = {}
|
|
0 commit comments