Skip to content
File

Blob: build/tools/clang_tidy/clang_tidy.bzl

7.5 KB
1"""Clang tidy aspect.
2 
3The aspect, when enabled runs clang_tidy on every compiled c++ file.
4"""
5 
6load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
7load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
8load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
9load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
10 
11def _clang_tidy_aspect_impl(target, ctx):
12 # not a c++ target
13 if not CcInfo in target:
14 return []
15 
16 cc_toolchain = find_cc_toolchain(ctx)
17 feature_configuration = cc_common.configure_features(
18 ctx = ctx,
19 cc_toolchain = cc_toolchain,
20 )
21 compile_variables = cc_common.create_compile_variables(
22 feature_configuration = feature_configuration,
23 cc_toolchain = cc_toolchain,
24 user_compile_flags = ctx.fragments.cpp.cxxopts + ctx.fragments.cpp.copts,
25 )
26 toolchain_flags = cc_common.get_memory_inefficient_command_line(
27 feature_configuration = feature_configuration,
28 action_name = ACTION_NAMES.cpp_compile,
29 variables = compile_variables,
30 )
31 
32 compilation_context = target[CcInfo].compilation_context
33 
34 rule_copts = getattr(ctx.rule.attr, "copts", [])
35 
36 # we use $location in our copts, expand it
37 rule_copts = [ctx.expand_location(opt) for opt in rule_copts]
38 
39 srcs = []
40 if hasattr(ctx.rule.attr, "srcs"):
41 for src in ctx.rule.attr.srcs:
42 srcs += [
43 src
44 for src in src.files.to_list()
45 if src.is_source and src.short_path.endswith((".c++", ".c", ".h"))
46 ]
47 if hasattr(ctx.rule.attr, "hdrs"):
48 for src in ctx.rule.attr.hdrs:
49 srcs += [
50 src
51 for src in src.files.to_list()
52 if src.is_source and src.short_path.endswith((".c++", ".c", ".h"))
53 ]
54 
55 defines = compilation_context.defines.to_list()
56 local_defines = compilation_context.local_defines.to_list()
57 includes = compilation_context.includes.to_list()
58 quote_includes = compilation_context.quote_includes.to_list()
59 system_includes = compilation_context.system_includes.to_list()
60 external_includes = compilation_context.external_includes.to_list()
61 headers = compilation_context.headers
62 
63 # disable clang tidy if no-clang-tidy tag is defined.
64 # todo: figure out a better way to control clang tidy on a per-target basis.
65 if "no-clang-tidy" in ctx.rule.attr.tags:
66 return []
67 
68 # bazel doesn't expose implementation deps through compilation context
69 # https://github.com/bazelbuild/bazel/issues/19663
70 if hasattr(ctx.rule.attr, "implementation_deps"):
71 deps = [dep[CcInfo].compilation_context for dep in ctx.rule.attr.implementation_deps if CcInfo in dep]
72 defines = depset(
73 defines,
74 transitive = [dep.defines for dep in deps],
75 )
76 includes = depset(
77 includes,
78 transitive = [dep.includes for dep in deps],
79 )
80 system_includes = depset(
81 system_includes,
82 transitive = [dep.system_includes for dep in deps],
83 )
84 quote_includes = depset(
85 quote_includes,
86 transitive = [dep.quote_includes for dep in deps],
87 )
88 external_includes = depset(
89 external_includes,
90 transitive = [dep.external_includes for dep in deps],
91 )
92 headers = depset(
93 headers.to_list(),
94 transitive = [dep.headers for dep in deps],
95 )
96 
97 tools = [
98 ctx.attr._clang_tidy_executable.files,
99 ctx.attr._clang_tidy_wrapper.files,
100 ctx.attr._clang_tidy_config.files,
101 ]
102 
103 outs = []
104 for src in srcs:
105 # run actions need to produce something, declare a dummy file
106 # multiple labels can use the same path, so disambiguate.
107 out = ctx.actions.declare_file(src.path + "." + ctx.label.name + ".clang_tidy")
108 outs.append(out)
109 
110 args = ctx.actions.args()
111 
112 # these are consumed by clang_tidy_wrapper,sh
113 args.add(ctx.attr._clang_tidy_executable.files_to_run.executable)
114 args.add(out)
115 
116 # clang-tidy arguments
117 # do not print statistics
118 args.add("--quiet")
119 args.add("--config-file=" + ctx.attr._clang_tidy_config.files.to_list()[0].short_path)
120 
121 if ctx.attr.clang_tidy_args:
122 args.add_all(ctx.attr.clang_tidy_args.split(" "))
123 
124 args.add(src.path)
125 
126 # compiler arguments
127 args.add("--")
128 
129 args.add("-xc++")
130 
131 args.add_all(ctx.attr._clang_tidy_compiler_flags)
132 args.add_all(rule_copts)
133 args.add_all(defines, before_each = "-D")
134 args.add_all(local_defines, before_each = "-D")
135 args.add_all(includes, before_each = "-I")
136 args.add_all(quote_includes, before_each = "-iquote")
137 args.add_all(system_includes, before_each = "-isystem")
138 args.add_all(external_includes, before_each = "-isystem")
139 
140 args.add_all(toolchain_flags)
141 
142 # Silence warnings about unused functions or #pragma once being present in header files. For
143 # source files, we already cover these warnings in regular compilation
144 args.add("-Wno-pragma-once-outside-header")
145 args.add("-Wno-unused")
146 
147 # TODO(cleanup): These paths provide required includes, but if the toolchain was working
148 # properly we wouldn't need them in the first place...
149 # Linux includes
150 args.add("-isystem/usr/lib/llvm-19/include/c++/v1")
151 args.add("-isystem/usr/lib/llvm-19/lib/clang/19/include")
152 args.add("-isystem/usr/include")
153 args.add("-isystem/usr/include/x86_64-linux-gnu")
154 
155 # macOS includes
156 args.add("-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1")
157 
158 # Xcode 26.4 bumps the Apple Clang version from 17 to 21, support both for now
159 args.add("-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/21/include")
160 args.add("-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/17/include")
161 args.add("-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include")
162 
163 inputs = depset(
164 direct = [src],
165 transitive = [headers],
166 )
167 
168 ctx.actions.run(
169 outputs = [out],
170 arguments = [args],
171 executable = ctx.attr._clang_tidy_wrapper.files_to_run.executable,
172 progress_message = "Run clang-tidy on {}".format(src.short_path),
173 tools = tools,
174 mnemonic = "ClangTidy",
175 inputs = inputs,
176 )
177 
178 return [
179 OutputGroupInfo(clang_tidy_checks = depset(direct = outs)),
180 ]
181 
182clang_tidy_aspect = aspect(
183 implementation = _clang_tidy_aspect_impl,
184 fragments = ["cpp"],
185 attrs = {
186 "_clang_tidy_wrapper": attr.label(
187 default = Label("@//build/tools/clang_tidy:clang_tidy_wrapper.sh"),
188 allow_single_file = True,
189 ),
190 "_clang_tidy_executable": attr.label(
191 default = Label("//tools:clang-tidy"),
192 allow_single_file = True,
193 ),
194 "_clang_tidy_config": attr.label(
195 default = Label("//:clang_tidy_config"),
196 allow_single_file = True,
197 ),
198 "_clang_tidy_compiler_flags": attr.string_list(
199 default = [],
200 ),
201 "clang_tidy_args": attr.string(default = ""),
202 },
203 toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
204)