Skip to content

Commit 0614f67

Browse files
committed
[bazel] Add aspects to run clang-tidy
This commit adds "fix" and "check" aspects that run clang-tidy. Example usage: ./bazelisk.sh build --config=clang_tidy_fix \ --config=riscv32 //sw/device/lib/base:memory ./bazelisk.sh build --config=clang_tidy_check \ --config=riscv32 //sw/device/lib/base:memory This also adds a new target, //quality:clang_tidy_check, which runs clang-tidy against a very incomplete list of hand-picked targets. Signed-off-by: Dan McArdle <[email protected]>
1 parent 4951219 commit 0614f67

File tree

4 files changed

+369
-1
lines changed

4 files changed

+369
-1
lines changed

.bazelrc

+14
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ test:local_test_jobs_per_cpus --local_test_jobs=HOST_CPUS*0.22
7272
# We have verilator tests that take more than an hour to complete
7373
test --test_timeout=60,300,1500,7200
7474

75+
# Configuration to run clang-tidy alongside build actions. This is a strict
76+
# analysis, which will fail the build when any of its checks fail. Enable with
77+
# `--config=clang_tidy_check`.
78+
build:clang_tidy_check --aspects rules/quality.bzl%clang_tidy_check_aspect
79+
build:clang_tidy_check --output_groups=clang_tidy
80+
81+
# Configuration to run clang-tidy alongside build actions. In this mode, fixes
82+
# will be applied automatically when possible. This is not a strict analysis;
83+
# clang-tidy errors will not fail the build. Enable with
84+
# `--config=clang_tidy_check`.
85+
build:clang_tidy_fix --aspects rules/quality.bzl%clang_tidy_fix_aspect
86+
build:clang_tidy_fix --output_groups=clang_tidy
87+
build:clang_tidy_fix --spawn_strategy=local
88+
7589
# AddressSanitizer (ASan) catches runtime out-of-bounds accesses to globals, the
7690
# stack, and (less importantly for OpenTitan) the heap. ASan instruments
7791
# programs at compile time and also requires a runtime library.

quality/BUILD.bazel

+41-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
load("@ot_hack_bazelbuild_buildtools//buildifier:def.bzl", "buildifier", "buildifier_test")
66
load("@lowrisc_lint//rules:rules.bzl", "licence_test")
7-
load("//rules:quality.bzl", "clang_format_check", "clang_format_test", "html_coverage_report")
7+
load("//rules:quality.bzl", "clang_format_check", "clang_format_test", "clang_tidy_check", "clang_tidy_check_rv", "html_coverage_report")
88
load("@rules_rust//rust:defs.bzl", "rustfmt_test")
99

1010
package(default_visibility = ["//visibility:public"])
@@ -103,6 +103,46 @@ clang_format_check(
103103
mode = "fix",
104104
)
105105

106+
# TODO(dmcardle) Add more targets that should be automatically checked with
107+
# clang-tidy. Find a way to automate this.
108+
clang_tidy_check_rv(
109+
name = "clang_tidy_check_rv",
110+
testonly = True,
111+
deps = [
112+
"//sw/device/lib/base:memory",
113+
"//sw/device/lib/base:memory_perftest",
114+
"//sw/device/silicon_creator/rom:boot_policy",
115+
"//sw/device/silicon_creator/rom:bootstrap",
116+
"//sw/device/silicon_creator/rom:rom_common",
117+
"//sw/device/silicon_creator/rom:rom_epmp",
118+
"//sw/device/silicon_creator/rom:rom_start",
119+
"//sw/device/silicon_creator/rom:sigverify_keys",
120+
"//sw/device/silicon_creator/rom:sigverify_keys_rsa",
121+
"//sw/device/silicon_creator/rom:sigverify_keys_spx",
122+
"//sw/device/silicon_creator/rom/keys/fake",
123+
"//sw/device/silicon_creator/rom/keys/fake/spx:fake",
124+
],
125+
)
126+
127+
clang_tidy_check(
128+
name = "clang_tidy_check_host",
129+
testonly = True,
130+
deps = [
131+
"//sw/device/silicon_creator/rom:boot_policy_unittest",
132+
"//sw/device/silicon_creator/rom:bootstrap_unittest",
133+
#"//sw/device/lib/base:memory_unittest",
134+
],
135+
)
136+
137+
filegroup(
138+
name = "clang_tidy_check",
139+
testonly = True,
140+
srcs = [
141+
":clang_tidy_check_host",
142+
":clang_tidy_check_rv",
143+
],
144+
)
145+
106146
buildifier_exclude = [
107147
"./WORKSPACE", # Prevent Buildifier from inserting unnecessary newlines.
108148
"./**/vendor/**",

rules/quality.bzl

+215
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"""Quality check rules for OpenTitan.
66
"""
77

8+
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "C_COMPILE_ACTION_NAME")
89
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")
912

1013
def _ensure_tag(tags, *tag):
1114
for t in tag:
@@ -95,6 +98,218 @@ def clang_format_test(**kwargs):
9598
kwargs["tags"] = _ensure_tag(tags, "no-sandbox", "no-cache", "external")
9699
_clang_format_test(**kwargs)
97100

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+
193+
# For debugging the args passed to clang-tidy:
194+
#args.add("--print-args")
195+
196+
# Perform our best effort to analyze and fix errors, but do not fail the
197+
# build when an error occurrs.
198+
199+
args.add(".clang-tidy.lock")
200+
args.add(generated_file)
201+
args.add_all(ctx.attr._clang_tidy.files)
202+
203+
# Add args for clang-tidy.
204+
if len(_CLANG_TIDY_CHECKS) > 0:
205+
checks_pattern = ",".join(_CLANG_TIDY_CHECKS)
206+
args.add("--checks=" + checks_pattern)
207+
208+
# Treat warnings from every enabled check as errors.
209+
args.add("--warnings-as-errors=" + checks_pattern)
210+
if ctx.attr._enable_fix:
211+
args.add("--fix")
212+
args.add("--fix-errors")
213+
214+
# Specify a regex header filter. Without this, clang-tidy will not
215+
# report or fix errors in header files.
216+
args.add("--header-filter=.*\\.h$")
217+
args.add("--use-color")
218+
219+
# Use the nearest .clang_format file.
220+
args.add("--format-style=file")
221+
222+
for arg in command_line:
223+
# Skip the src file argument. We give that to clang-tidy separately.
224+
if arg == src.path:
225+
continue
226+
elif arg == "":
227+
continue
228+
elif arg == "-fno-canonical-system-headers":
229+
continue
230+
231+
args.add("--extra-arg=" + arg)
232+
233+
# Tell clang-tidy which source file to analyze.
234+
args.add(src)
235+
236+
ctx.actions.run(
237+
executable = ctx.attr._clang_tidy_wrapper.files_to_run,
238+
arguments = [args],
239+
inputs = depset(
240+
direct = [src],
241+
transitive = [
242+
cc_toolchain.all_files,
243+
cc_compile_ctx.headers,
244+
],
245+
),
246+
tools = [ctx.attr._clang_tidy.files_to_run],
247+
outputs = [generated_file],
248+
progress_message = "Running clang tidy on {}".format(src.path),
249+
)
250+
251+
return [
252+
OutputGroupInfo(
253+
clang_tidy = depset(direct = outputs),
254+
),
255+
]
256+
257+
def _make_clang_tidy_aspect(enable_fix):
258+
return aspect(
259+
implementation = _clang_tidy_aspect_impl,
260+
attr_aspects = ["deps"],
261+
attrs = {
262+
"_clang_tidy_wrapper": attr.label(
263+
default = "//rules/scripts:clang_tidy.py",
264+
allow_single_file = True,
265+
cfg = "host",
266+
executable = True,
267+
),
268+
"_clang_tidy": attr.label(
269+
default = "@lowrisc_rv32imcb_files//:bin/clang-tidy",
270+
allow_single_file = True,
271+
cfg = "host",
272+
executable = True,
273+
doc = "The clang-tidy executable",
274+
),
275+
"_enable_fix": attr.bool(default = enable_fix),
276+
},
277+
incompatible_use_toolchain_transition = True,
278+
fragments = ["cpp"],
279+
host_fragments = ["cpp"],
280+
toolchains = ["@rules_cc//cc:toolchain_type"],
281+
)
282+
283+
clang_tidy_fix_aspect = _make_clang_tidy_aspect(True)
284+
clang_tidy_check_aspect = _make_clang_tidy_aspect(False)
285+
286+
def _clang_tidy_check_impl(ctx):
287+
return [
288+
DefaultInfo(
289+
files = depset(
290+
transitive = [dep[OutputGroupInfo].clang_tidy for dep in ctx.attr.deps],
291+
),
292+
),
293+
]
294+
295+
clang_tidy_check_rv = rv_rule(
296+
implementation = _clang_tidy_check_impl,
297+
attrs = {
298+
"deps": attr.label_list(
299+
aspects = [clang_tidy_check_aspect],
300+
),
301+
},
302+
)
303+
304+
clang_tidy_check = rule(
305+
implementation = _clang_tidy_check_impl,
306+
attrs = {
307+
"deps": attr.label_list(
308+
aspects = [clang_tidy_check_aspect],
309+
),
310+
},
311+
)
312+
98313
def _html_coverage_report_impl(ctx):
99314
out_file = ctx.actions.declare_file(ctx.label.name + ".bash")
100315
substitutions = {}

0 commit comments

Comments
 (0)