@@ -66,7 +66,7 @@ clippy_flags = rule(
6666 build_setting = config .string_list (flag = True ),
6767)
6868
69- def _get_clippy_ready_crate_info (target , aspect_ctx = None ):
69+ def get_clippy_ready_crate_info (target , aspect_ctx = None ):
7070 """Check that a target is suitable for clippy and extract the `CrateInfo` provider from it.
7171
7272 Args:
@@ -101,17 +101,25 @@ def _get_clippy_ready_crate_info(target, aspect_ctx = None):
101101 else :
102102 return None
103103
104- def _clippy_aspect_impl (target , ctx ):
105- # Exit early if a target already has a clippy output group. This
106- # can be useful for rules which always want to inhibit clippy.
107- if OutputGroupInfo in target :
108- if hasattr (target [OutputGroupInfo ], "clippy_checks" ):
109- return []
104+ def rust_clippy_action (ctx , clippy_executable , process_wrapper , crate_info , config , output = None , success_marker = None , cap_at_warnings = False , extra_clippy_flags = [], error_format = None , clippy_diagnostics_file = None ):
105+ """Run clippy with the specified parameters.
110106
111- crate_info = _get_clippy_ready_crate_info (target , ctx )
112- if not crate_info :
113- return [ClippyInfo (output = depset ([]))]
107+ Args:
108+ ctx (ctx): The aspect's context object. This function should not read ctx.attr, but it might read ctx.rule.attr
109+ clippy_executable (File): The clippy executable to run
110+ process_wrapper (File): An executable process wrapper that can run clippy, usually @rules_rust//utils/process_wrapper
111+ crate_info (CrateInfo): The source crate information
112+ config (File): The clippy configuration file. Reference: https://doc.rust-lang.org/clippy/configuration.html#configuring-clippy
113+ output (File): The output file for clippy stdout/stderr. If None, no output will be captured
114+ success_marker (File): A file that will be written if clippy succeeds
115+ cap_at_warnings (bool): If set, it will cap all reports as warnings, allowing the build to continue even with clippy failures
116+ extra_clippy_flags (List[str]): A list of extra options to pass to clippy. If not set, every warnings will be turned into errors
117+ error_format (str): Which error format to use. Must be acceptable by rustc: https://doc.rust-lang.org/beta/rustc/command-line-arguments.html#--error-format-control-how-errors-are-produced
118+ clippy_diagnostics_file (File): File to output diagnostics to. If None, no diagnostics will be written
114119
120+ Returns:
121+ None
122+ """
115123 toolchain = find_toolchain (ctx )
116124 cc_toolchain , feature_configuration = find_cc_toolchain (ctx )
117125
@@ -147,25 +155,17 @@ def _clippy_aspect_impl(target, ctx):
147155 lint_files ,
148156 )
149157
150- use_clippy_error_format = ctx .attr ._incompatible_change_clippy_error_format [IncompatibleFlagInfo ].enabled
151- error_format = get_error_format (
152- ctx .attr ,
153- "_clippy_error_format" if use_clippy_error_format else "_error_format" ,
154- )
155-
156- clippy_diagnostics = None
157- if ctx .attr ._clippy_output_diagnostics [ClippyOutputDiagnosticsInfo ].output_diagnostics :
158- clippy_diagnostics = ctx .actions .declare_file (ctx .label .name + ".clippy.diagnostics" , sibling = crate_info .output )
158+ if clippy_diagnostics_file :
159159 crate_info_dict = structs .to_dict (crate_info )
160- crate_info_dict ["rustc_output" ] = clippy_diagnostics
160+ crate_info_dict ["rustc_output" ] = clippy_diagnostics_file
161161 crate_info = rust_common .create_crate_info (** crate_info_dict )
162162
163163 args , env = construct_arguments (
164164 ctx = ctx ,
165165 attr = ctx .rule .attr ,
166166 file = ctx .file ,
167167 toolchain = toolchain ,
168- tool_path = toolchain . clippy_driver .path ,
168+ tool_path = clippy_executable .path ,
169169 cc_toolchain = cc_toolchain ,
170170 feature_configuration = feature_configuration ,
171171 crate_info = crate_info ,
@@ -179,7 +179,7 @@ def _clippy_aspect_impl(target, ctx):
179179 build_flags_files = build_flags_files ,
180180 emit = ["dep-info" , "metadata" ],
181181 skip_expanding_rustc_env = True ,
182- use_json_output = bool (clippy_diagnostics ),
182+ use_json_output = bool (clippy_diagnostics_file ),
183183 error_format = error_format ,
184184 )
185185
@@ -188,60 +188,109 @@ def _clippy_aspect_impl(target, ctx):
188188
189189 # Then append the clippy flags specified from the command line, so they override what is
190190 # specified on the library.
191- clippy_flags = clippy_flags + ctx . attr . _clippy_flags [ ClippyFlagsInfo ]. clippy_flags
191+ clippy_flags += extra_clippy_flags
192192
193- if hasattr (ctx .attr , "_clippy_flag" ):
194- clippy_flags = clippy_flags + ctx .attr ._clippy_flag [ClippyFlagsInfo ].clippy_flags
193+ outputs = []
195194
196- # For remote execution purposes, the clippy_out file must be a sibling of crate_info.output
197- # or rustc may fail to create intermediate output files because the directory does not exist.
198- if ctx .attr ._capture_output [CaptureClippyOutputInfo ].capture_output :
199- clippy_out = ctx .actions .declare_file (ctx .label .name + ".clippy.out" , sibling = crate_info .output )
200- args .process_wrapper_flags .add ("--stderr-file" , clippy_out )
195+ if output != None :
196+ args .process_wrapper_flags .add ("--stderr-file" , output )
197+ outputs .append (output )
201198
202- if clippy_flags or lint_files :
203- args .rustc_flags .add_all (clippy_flags )
199+ if success_marker != None :
200+ args .process_wrapper_flags .add ("--touch-file" , success_marker )
201+ outputs .append (success_marker )
204202
205- # If we are capturing the output, we want the build system to be able to keep going
206- # and consume the output. Some clippy lints are denials, so we cap everything at warn.
207- args .rustc_flags .add ("--cap-lints=warn" )
203+ if clippy_flags or lint_files :
204+ args .rustc_flags .add_all (clippy_flags )
208205 else :
209- # A marker file indicating clippy has executed successfully.
210- # This file is necessary because "ctx.actions.run" mandates an output.
211- clippy_out = ctx .actions .declare_file (ctx .label .name + ".clippy.ok" , sibling = crate_info .output )
212- args .process_wrapper_flags .add ("--touch-file" , clippy_out )
206+ # The user didn't provide any clippy flags explicitly so we apply conservative defaults.
213207
214- if clippy_flags or lint_files :
215- args . rustc_flags . add_all ( clippy_flags )
216- else :
217- # The user didn't provide any clippy flags explicitly so we apply conservative defaults.
208+ # Turn any warnings from clippy or rustc into an error, as otherwise
209+ # Bazel will consider the execution result of the aspect to be "success",
210+ # and Clippy won't be re-triggered unless the source file is modified.
211+ args . rustc_flags . add ( "-Dwarnings" )
218212
219- # Turn any warnings from clippy or rustc into an error, as otherwise
220- # Bazel will consider the execution result of the aspect to be "success",
221- # and Clippy won't be re-triggered unless the source file is modified .
222- args .rustc_flags .add ("-Dwarnings " )
213+ if cap_at_warnings :
214+ # If we are capturing the output, we want the build system to be able to keep going
215+ # and consume the output. Some clippy lints are denials, so we cap everything at warn .
216+ args .rustc_flags .add ("--cap-lints=warn " )
223217
224218 # Upstream clippy requires one of these two filenames or it silently uses
225219 # the default config. Enforce the naming so users are not confused.
226220 valid_config_file_names = [".clippy.toml" , "clippy.toml" ]
227- if ctx . file . _config .basename not in valid_config_file_names :
221+ if config .basename not in valid_config_file_names :
228222 fail ("The clippy config file must be named one of: {}" .format (valid_config_file_names ))
229- env ["CLIPPY_CONF_DIR" ] = "${{pwd}}/{}" .format (ctx . file . _config .dirname )
230- compile_inputs = depset ([ctx . file . _config ], transitive = [compile_inputs ])
223+ env ["CLIPPY_CONF_DIR" ] = "${{pwd}}/{}" .format (config .dirname )
224+ compile_inputs = depset ([config ], transitive = [compile_inputs ])
231225
232226 ctx .actions .run (
233- executable = ctx . executable . _process_wrapper ,
227+ executable = process_wrapper ,
234228 inputs = compile_inputs ,
235- outputs = [ clippy_out ] + [x for x in [clippy_diagnostics ] if x ],
229+ outputs = outputs + [x for x in [clippy_diagnostics_file ] if x ],
236230 env = env ,
237- tools = [toolchain . clippy_driver ],
231+ tools = [clippy_executable ],
238232 arguments = args .all ,
239233 mnemonic = "Clippy" ,
240234 progress_message = "Clippy %{label}" ,
241235 toolchain = "@rules_rust//rust:toolchain_type" ,
242236 )
243237
244- output_group_info = {"clippy_checks" : depset ([clippy_out ])}
238+ def _clippy_aspect_impl (target , ctx ):
239+ # Exit early if a target already has a clippy output group. This
240+ # can be useful for rules which always want to inhibit clippy.
241+ if OutputGroupInfo in target :
242+ if hasattr (target [OutputGroupInfo ], "clippy_checks" ):
243+ return []
244+
245+ crate_info = get_clippy_ready_crate_info (target , ctx )
246+ if not crate_info :
247+ return [ClippyInfo (output = depset ([]))]
248+
249+ toolchain = find_toolchain (ctx )
250+
251+ # For remote execution purposes, the clippy_out file must be a sibling of crate_info.output
252+ # or rustc may fail to create intermediate output files because the directory does not exist.
253+ if ctx .attr ._capture_output [CaptureClippyOutputInfo ].capture_output :
254+ clippy_out = ctx .actions .declare_file (ctx .label .name + ".clippy.out" , sibling = crate_info .output )
255+ clippy_success_marker = None
256+ else :
257+ # A marker file indicating clippy has executed successfully.
258+ # This file is necessary because "ctx.actions.run" mandates an output.
259+ clippy_success_marker = ctx .actions .declare_file (ctx .label .name + ".clippy.ok" , sibling = crate_info .output )
260+ clippy_out = None
261+
262+ use_clippy_error_format = ctx .attr ._incompatible_change_clippy_error_format [IncompatibleFlagInfo ].enabled
263+ error_format = get_error_format (
264+ ctx .attr ,
265+ "_clippy_error_format" if use_clippy_error_format else "_error_format" ,
266+ )
267+
268+ clippy_flags = ctx .attr ._clippy_flags [ClippyFlagsInfo ].clippy_flags
269+
270+ if hasattr (ctx .attr , "_clippy_flag" ):
271+ clippy_flags = clippy_flags + ctx .attr ._clippy_flag [ClippyFlagsInfo ].clippy_flags
272+
273+ clippy_diagnostics = None
274+ if ctx .attr ._clippy_output_diagnostics [ClippyOutputDiagnosticsInfo ].output_diagnostics :
275+ clippy_diagnostics = ctx .actions .declare_file (ctx .label .name + ".clippy.diagnostics" , sibling = crate_info .output )
276+
277+ # Run clippy using the extracted function
278+ rust_clippy_action (
279+ ctx = ctx ,
280+ clippy_executable = toolchain .clippy_driver ,
281+ process_wrapper = ctx .executable ._process_wrapper ,
282+ crate_info = crate_info ,
283+ config = ctx .file ._config ,
284+ output = clippy_out ,
285+ cap_at_warnings = clippy_out != None , # If we're capturing output, we want the build to continue.
286+ success_marker = clippy_success_marker ,
287+ extra_clippy_flags = clippy_flags ,
288+ error_format = error_format ,
289+ clippy_diagnostics_file = clippy_diagnostics ,
290+ )
291+
292+ clippy_checks = [file for file in [clippy_out , clippy_success_marker ] if file != None ]
293+ output_group_info = {"clippy_checks" : depset (clippy_checks )}
245294 if clippy_diagnostics :
246295 output_group_info ["clippy_output" ] = depset ([clippy_diagnostics ])
247296
0 commit comments