blob: d1464e9dc4ef700f6a2e1bb8a3b96ea38079120a [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001# Copyright 2020 The Bazel Authors. All rights reserved.
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
15"""A module defining clippy rules"""
16
17load("//rust/private:common.bzl", "rust_common")
18load("//rust/private:providers.bzl", "CaptureClippyOutputInfo", "ClippyInfo")
19load(
20 "//rust/private:rustc.bzl",
21 "collect_deps",
22 "collect_inputs",
23 "construct_arguments",
24)
25load(
26 "//rust/private:utils.bzl",
27 "determine_output_hash",
28 "find_cc_toolchain",
29 "find_toolchain",
30)
31
32def _get_clippy_ready_crate_info(target, aspect_ctx):
33 """Check that a target is suitable for clippy and extract the `CrateInfo` provider from it.
34
35 Args:
36 target (Target): The target the aspect is running on.
37 aspect_ctx (ctx, optional): The aspect's context object.
38
39 Returns:
40 CrateInfo, optional: A `CrateInfo` provider if clippy should be run or `None`.
41 """
42
43 # Ignore external targets
44 if target.label.workspace_root.startswith("external"):
45 return None
46
47 # Targets annotated with `noclippy` will not be formatted
48 if aspect_ctx and "noclippy" in aspect_ctx.rule.attr.tags:
49 return None
50
51 # Obviously ignore any targets that don't contain `CrateInfo`
52 if rust_common.crate_info not in target:
53 return None
54
55 return target[rust_common.crate_info]
56
57def _clippy_aspect_impl(target, ctx):
58 crate_info = _get_clippy_ready_crate_info(target, ctx)
59 if not crate_info:
60 return [ClippyInfo(output = depset([]))]
61
62 toolchain = find_toolchain(ctx)
63 cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
64
65 dep_info, build_info, linkstamps = collect_deps(
66 deps = crate_info.deps,
67 proc_macro_deps = crate_info.proc_macro_deps,
68 aliases = crate_info.aliases,
69 # Clippy doesn't need to invoke transitive linking, therefore doesn't need linkstamps.
70 are_linkstamps_supported = False,
71 )
72
73 compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
74 ctx,
75 ctx.rule.file,
76 ctx.rule.files,
77 linkstamps,
78 toolchain,
79 cc_toolchain,
80 feature_configuration,
81 crate_info,
82 dep_info,
83 build_info,
84 )
85
86 args, env = construct_arguments(
87 ctx = ctx,
88 attr = ctx.rule.attr,
89 file = ctx.file,
90 toolchain = toolchain,
91 tool_path = toolchain.clippy_driver.path,
92 cc_toolchain = cc_toolchain,
93 feature_configuration = feature_configuration,
94 crate_info = crate_info,
95 dep_info = dep_info,
96 linkstamp_outs = linkstamp_outs,
97 ambiguous_libs = ambiguous_libs,
98 output_hash = determine_output_hash(crate_info.root, ctx.label),
99 rust_flags = [],
100 out_dir = out_dir,
101 build_env_files = build_env_files,
102 build_flags_files = build_flags_files,
103 emit = ["dep-info", "metadata"],
104 )
105
106 if crate_info.is_test:
107 args.rustc_flags.add("--test")
108
109 # For remote execution purposes, the clippy_out file must be a sibling of crate_info.output
110 # or rustc may fail to create intermediate output files because the directory does not exist.
111 if ctx.attr._capture_output[CaptureClippyOutputInfo].capture_output:
112 clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.out", sibling = crate_info.output)
113 args.process_wrapper_flags.add("--stderr-file", clippy_out.path)
114
115 # If we are capturing the output, we want the build system to be able to keep going
116 # and consume the output. Some clippy lints are denials, so we treat them as warnings.
117 args.rustc_flags.add("-Wclippy::all")
118 else:
119 # A marker file indicating clippy has executed successfully.
120 # This file is necessary because "ctx.actions.run" mandates an output.
121 clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.ok", sibling = crate_info.output)
122 args.process_wrapper_flags.add("--touch-file", clippy_out.path)
123
124 # Turn any warnings from clippy or rustc into an error, as otherwise
125 # Bazel will consider the execution result of the aspect to be "success",
126 # and Clippy won't be re-triggered unless the source file is modified.
127 if "__bindgen" in ctx.rule.attr.tags:
128 # bindgen-generated content is likely to trigger warnings, so
129 # only fail on clippy warnings
130 args.rustc_flags.add("-Dclippy::style")
131 args.rustc_flags.add("-Dclippy::correctness")
132 args.rustc_flags.add("-Dclippy::complexity")
133 args.rustc_flags.add("-Dclippy::perf")
134 else:
135 # fail on any warning
136 args.rustc_flags.add("-Dwarnings")
137
138 # Upstream clippy requires one of these two filenames or it silently uses
139 # the default config. Enforce the naming so users are not confused.
140 valid_config_file_names = [".clippy.toml", "clippy.toml"]
141 if ctx.file._config.basename not in valid_config_file_names:
142 fail("The clippy config file must be named one of: {}".format(valid_config_file_names))
143 env["CLIPPY_CONF_DIR"] = "${{pwd}}/{}".format(ctx.file._config.dirname)
144 compile_inputs = depset([ctx.file._config], transitive = [compile_inputs])
145
146 ctx.actions.run(
147 executable = ctx.executable._process_wrapper,
148 inputs = compile_inputs,
149 outputs = [clippy_out],
150 env = env,
151 tools = [toolchain.clippy_driver],
152 arguments = args.all,
153 mnemonic = "Clippy",
154 )
155
156 return [
157 OutputGroupInfo(clippy_checks = depset([clippy_out])),
158 ClippyInfo(output = depset([clippy_out])),
159 ]
160
161# Example: Run the clippy checker on all targets in the codebase.
162# bazel build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect \
163# --output_groups=clippy_checks \
164# //...
165rust_clippy_aspect = aspect(
166 fragments = ["cpp"],
167 host_fragments = ["cpp"],
168 attrs = {
169 "_capture_output": attr.label(
170 doc = "Value of the `capture_clippy_output` build setting",
171 default = Label("//:capture_clippy_output"),
172 ),
173 "_cc_toolchain": attr.label(
174 doc = (
175 "Required attribute to access the cc_toolchain. See [Accessing the C++ toolchain]" +
176 "(https://docs.bazel.build/versions/master/integrating-with-rules-cc.html#accessing-the-c-toolchain)"
177 ),
178 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
179 ),
180 "_config": attr.label(
181 doc = "The `clippy.toml` file used for configuration",
182 allow_single_file = True,
183 default = Label("//:clippy.toml"),
184 ),
185 "_error_format": attr.label(
186 doc = "The desired `--error-format` flags for clippy",
187 default = "//:error_format",
188 ),
189 "_extra_rustc_flags": attr.label(default = "//:extra_rustc_flags"),
190 "_process_wrapper": attr.label(
191 doc = "A process wrapper for running clippy on all platforms",
192 default = Label("//util/process_wrapper"),
193 executable = True,
194 cfg = "exec",
195 ),
196 },
197 provides = [ClippyInfo],
198 toolchains = [
199 str(Label("//rust:toolchain")),
200 "@bazel_tools//tools/cpp:toolchain_type",
201 ],
202 incompatible_use_toolchain_transition = True,
203 implementation = _clippy_aspect_impl,
204 doc = """\
205Executes the clippy checker on specified targets.
206
207This aspect applies to existing rust_library, rust_test, and rust_binary rules.
208
209As an example, if the following is defined in `examples/hello_lib/BUILD.bazel`:
210
211```python
212load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
213
214rust_library(
215 name = "hello_lib",
216 srcs = ["src/lib.rs"],
217)
218
219rust_test(
220 name = "greeting_test",
221 srcs = ["tests/greeting.rs"],
222 deps = [":hello_lib"],
223)
224```
225
226Then the targets can be analyzed with clippy using the following command:
227
228```output
229$ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect \
230 --output_groups=clippy_checks //hello_lib:all
231```
232""",
233)
234
235def _rust_clippy_rule_impl(ctx):
236 clippy_ready_targets = [dep for dep in ctx.attr.deps if "clippy_checks" in dir(dep[OutputGroupInfo])]
237 files = depset([], transitive = [dep[OutputGroupInfo].clippy_checks for dep in clippy_ready_targets])
238 return [DefaultInfo(files = files)]
239
240rust_clippy = rule(
241 implementation = _rust_clippy_rule_impl,
242 attrs = {
243 "deps": attr.label_list(
244 doc = "Rust targets to run clippy on.",
245 providers = [rust_common.crate_info],
246 aspects = [rust_clippy_aspect],
247 ),
248 },
249 doc = """\
250Executes the clippy checker on a specific target.
251
252Similar to `rust_clippy_aspect`, but allows specifying a list of dependencies \
253within the build system.
254
255For example, given the following example targets:
256
257```python
258load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
259
260rust_library(
261 name = "hello_lib",
262 srcs = ["src/lib.rs"],
263)
264
265rust_test(
266 name = "greeting_test",
267 srcs = ["tests/greeting.rs"],
268 deps = [":hello_lib"],
269)
270```
271
272Rust clippy can be set as a build target with the following:
273
274```python
275load("@rules_rust//rust:defs.bzl", "rust_clippy")
276
277rust_clippy(
278 name = "hello_library_clippy",
279 testonly = True,
280 deps = [
281 ":hello_lib",
282 ":greeting_test",
283 ],
284)
285```
286""",
287)
288
289def _capture_clippy_output_impl(ctx):
290 """Implementation of the `capture_clippy_output` rule
291
292 Args:
293 ctx (ctx): The rule's context object
294
295 Returns:
296 list: A list containing the CaptureClippyOutputInfo provider
297 """
298 return [CaptureClippyOutputInfo(capture_output = ctx.build_setting_value)]
299
300capture_clippy_output = rule(
301 doc = "Control whether to print clippy output or store it to a file, using the configured error_format.",
302 implementation = _capture_clippy_output_impl,
303 build_setting = config.bool(flag = True),
304)