bootstrap/core/build_steps/
clippy.rs

1//! Implementation of running clippy on the compiler, standard library and various tools.
2
3use super::check;
4use super::compile::{run_cargo, rustc_cargo, std_cargo};
5use super::tool::{SourceType, prepare_tool_cargo};
6use crate::builder::{Builder, ShouldRun};
7use crate::core::build_steps::compile::std_crates_for_run_make;
8use crate::core::builder;
9use crate::core::builder::{Alias, Kind, RunConfig, Step, crate_description};
10use crate::utils::build_stamp::{self, BuildStamp};
11use crate::{Mode, Subcommand, TargetSelection};
12
13/// Disable the most spammy clippy lints
14const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[
15    "many_single_char_names", // there are a lot in stdarch
16    "collapsible_if",
17    "type_complexity",
18    "missing_safety_doc", // almost 3K warnings
19    "too_many_arguments",
20    "needless_lifetimes", // people want to keep the lifetimes
21    "wrong_self_convention",
22    "approx_constant", // libcore is what defines those
23];
24
25fn lint_args(builder: &Builder<'_>, config: &LintConfig, ignored_rules: &[&str]) -> Vec<String> {
26    fn strings<'a>(arr: &'a [&str]) -> impl Iterator<Item = String> + 'a {
27        arr.iter().copied().map(String::from)
28    }
29
30    let Subcommand::Clippy { fix, allow_dirty, allow_staged, .. } = &builder.config.cmd else {
31        unreachable!("clippy::lint_args can only be called from `clippy` subcommands.");
32    };
33
34    let mut args = vec![];
35    if *fix {
36        #[rustfmt::skip]
37            args.extend(strings(&[
38                "--fix", "-Zunstable-options",
39                // FIXME: currently, `--fix` gives an error while checking tests for libtest,
40                // possibly because libtest is not yet built in the sysroot.
41                // As a workaround, avoid checking tests and benches when passed --fix.
42                "--lib", "--bins", "--examples",
43            ]));
44
45        if *allow_dirty {
46            args.push("--allow-dirty".to_owned());
47        }
48
49        if *allow_staged {
50            args.push("--allow-staged".to_owned());
51        }
52    }
53
54    args.extend(strings(&["--"]));
55
56    if config.deny.is_empty() && config.forbid.is_empty() {
57        args.extend(strings(&["--cap-lints", "warn"]));
58    }
59
60    let all_args = std::env::args().collect::<Vec<_>>();
61    args.extend(get_clippy_rules_in_order(&all_args, config));
62
63    args.extend(ignored_rules.iter().map(|lint| format!("-Aclippy::{lint}")));
64    args.extend(builder.config.free_args.clone());
65    args
66}
67
68/// We need to keep the order of the given clippy lint rules before passing them.
69/// Since clap doesn't offer any useful interface for this purpose out of the box,
70/// we have to handle it manually.
71pub fn get_clippy_rules_in_order(all_args: &[String], config: &LintConfig) -> Vec<String> {
72    let mut result = vec![];
73
74    for (prefix, item) in
75        [("-A", &config.allow), ("-D", &config.deny), ("-W", &config.warn), ("-F", &config.forbid)]
76    {
77        item.iter().for_each(|v| {
78            let rule = format!("{prefix}{v}");
79            // Arguments added by bootstrap in LintConfig won't show up in the all_args list, so
80            // put them at the end of the command line.
81            let position = all_args.iter().position(|t| t == &rule || t == v).unwrap_or(usize::MAX);
82            result.push((position, rule));
83        });
84    }
85
86    result.sort_by_key(|&(position, _)| position);
87    result.into_iter().map(|v| v.1).collect()
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash)]
91pub struct LintConfig {
92    pub allow: Vec<String>,
93    pub warn: Vec<String>,
94    pub deny: Vec<String>,
95    pub forbid: Vec<String>,
96}
97
98impl LintConfig {
99    fn new(builder: &Builder<'_>) -> Self {
100        match builder.config.cmd.clone() {
101            Subcommand::Clippy { allow, deny, warn, forbid, .. } => {
102                Self { allow, warn, deny, forbid }
103            }
104            _ => unreachable!("LintConfig can only be called from `clippy` subcommands."),
105        }
106    }
107
108    fn merge(&self, other: &Self) -> Self {
109        let merged = |self_attr: &[String], other_attr: &[String]| -> Vec<String> {
110            self_attr.iter().cloned().chain(other_attr.iter().cloned()).collect()
111        };
112        // This is written this way to ensure we get a compiler error if we add a new field.
113        Self {
114            allow: merged(&self.allow, &other.allow),
115            warn: merged(&self.warn, &other.warn),
116            deny: merged(&self.deny, &other.deny),
117            forbid: merged(&self.forbid, &other.forbid),
118        }
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash)]
123pub struct Std {
124    pub target: TargetSelection,
125    config: LintConfig,
126    /// Whether to lint only a subset of crates.
127    crates: Vec<String>,
128}
129
130impl Step for Std {
131    type Output = ();
132    const DEFAULT: bool = true;
133
134    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
135        run.crate_or_deps("sysroot").path("library")
136    }
137
138    fn make_run(run: RunConfig<'_>) {
139        let crates = std_crates_for_run_make(&run);
140        let config = LintConfig::new(run.builder);
141        run.builder.ensure(Std { target: run.target, config, crates });
142    }
143
144    fn run(self, builder: &Builder<'_>) {
145        let target = self.target;
146        let compiler = builder.compiler(builder.top_stage, builder.config.host_target);
147
148        let mut cargo = builder::Cargo::new(
149            builder,
150            compiler,
151            Mode::Std,
152            SourceType::InTree,
153            target,
154            Kind::Clippy,
155        );
156
157        std_cargo(builder, target, compiler.stage, &mut cargo);
158
159        for krate in &*self.crates {
160            cargo.arg("-p").arg(krate);
161        }
162
163        let _guard =
164            builder.msg_clippy(format_args!("library{}", crate_description(&self.crates)), target);
165
166        run_cargo(
167            builder,
168            cargo,
169            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
170            &build_stamp::libstd_stamp(builder, compiler, target),
171            vec![],
172            true,
173            false,
174        );
175    }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, Hash)]
179pub struct Rustc {
180    pub target: TargetSelection,
181    config: LintConfig,
182    /// Whether to lint only a subset of crates.
183    crates: Vec<String>,
184}
185
186impl Step for Rustc {
187    type Output = ();
188    const ONLY_HOSTS: bool = true;
189    const DEFAULT: bool = true;
190
191    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
192        run.crate_or_deps("rustc-main").path("compiler")
193    }
194
195    fn make_run(run: RunConfig<'_>) {
196        let crates = run.make_run_crates(Alias::Compiler);
197        let config = LintConfig::new(run.builder);
198        run.builder.ensure(Rustc { target: run.target, config, crates });
199    }
200
201    /// Lints the compiler.
202    ///
203    /// This will lint the compiler for a particular stage of the build using
204    /// the `compiler` targeting the `target` architecture.
205    fn run(self, builder: &Builder<'_>) {
206        let compiler = builder.compiler(builder.top_stage, builder.config.host_target);
207        let target = self.target;
208
209        if !builder.download_rustc() {
210            if compiler.stage != 0 {
211                // If we're not in stage 0, then we won't have a std from the beta
212                // compiler around. That means we need to make sure there's one in
213                // the sysroot for the compiler to find. Otherwise, we're going to
214                // fail when building crates that need to generate code (e.g., build
215                // scripts and their dependencies).
216                builder.std(compiler, compiler.host);
217                builder.std(compiler, target);
218            } else {
219                builder.ensure(check::Std::new(compiler, target));
220            }
221        }
222
223        let mut cargo = builder::Cargo::new(
224            builder,
225            compiler,
226            Mode::Rustc,
227            SourceType::InTree,
228            target,
229            Kind::Clippy,
230        );
231
232        rustc_cargo(builder, &mut cargo, target, &compiler, &self.crates);
233
234        // Explicitly pass -p for all compiler crates -- this will force cargo
235        // to also lint the tests/benches/examples for these crates, rather
236        // than just the leaf crate.
237        for krate in &*self.crates {
238            cargo.arg("-p").arg(krate);
239        }
240
241        let _guard =
242            builder.msg_clippy(format_args!("compiler{}", crate_description(&self.crates)), target);
243
244        run_cargo(
245            builder,
246            cargo,
247            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
248            &build_stamp::librustc_stamp(builder, compiler, target),
249            vec![],
250            true,
251            false,
252        );
253    }
254}
255
256macro_rules! lint_any {
257    ($(
258        $name:ident, $path:expr, $readable_name:expr
259        $(,lint_by_default = $lint_by_default:expr)*
260        ;
261    )+) => {
262        $(
263
264        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
265        pub struct $name {
266            pub target: TargetSelection,
267            config: LintConfig,
268        }
269
270        impl Step for $name {
271            type Output = ();
272            const DEFAULT: bool = if false $(|| $lint_by_default)* { true } else { false };
273
274            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
275                run.path($path)
276            }
277
278            fn make_run(run: RunConfig<'_>) {
279                let config = LintConfig::new(run.builder);
280                run.builder.ensure($name {
281                    target: run.target,
282                    config,
283                });
284            }
285
286            fn run(self, builder: &Builder<'_>) -> Self::Output {
287                let compiler = builder.compiler(builder.top_stage, builder.config.host_target);
288                let target = self.target;
289
290                if !builder.download_rustc() {
291                    builder.ensure(check::Rustc::new(builder, compiler, target));
292                };
293
294                let cargo = prepare_tool_cargo(
295                    builder,
296                    compiler,
297                    Mode::ToolRustc,
298                    target,
299                    Kind::Clippy,
300                    $path,
301                    SourceType::InTree,
302                    &[],
303                );
304
305                let _guard = builder.msg_tool(
306                    Kind::Clippy,
307                    Mode::ToolRustc,
308                    $readable_name,
309                    compiler.stage,
310                    &compiler.host,
311                    &target,
312                );
313
314                let stringified_name = stringify!($name).to_lowercase();
315                let stamp = BuildStamp::new(&builder.cargo_out(compiler, Mode::ToolRustc, target))
316                    .with_prefix(&format!("{}-check", stringified_name));
317
318                run_cargo(
319                    builder,
320                    cargo,
321                    lint_args(builder, &self.config, &[]),
322                    &stamp,
323                    vec![],
324                    true,
325                    false,
326                );
327            }
328        }
329        )+
330    }
331}
332
333lint_any!(
334    Bootstrap, "src/bootstrap", "bootstrap";
335    BuildHelper, "src/build_helper", "build_helper";
336    BuildManifest, "src/tools/build-manifest", "build-manifest";
337    CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri";
338    Clippy, "src/tools/clippy", "clippy";
339    CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata";
340    CodegenGcc, "compiler/rustc_codegen_gcc", "rustc-codegen-gcc";
341    Compiletest, "src/tools/compiletest", "compiletest";
342    CoverageDump, "src/tools/coverage-dump", "coverage-dump";
343    Jsondocck, "src/tools/jsondocck", "jsondocck";
344    Jsondoclint, "src/tools/jsondoclint", "jsondoclint";
345    LintDocs, "src/tools/lint-docs", "lint-docs";
346    LlvmBitcodeLinker, "src/tools/llvm-bitcode-linker", "llvm-bitcode-linker";
347    Miri, "src/tools/miri", "miri";
348    MiroptTestTools, "src/tools/miropt-test-tools", "miropt-test-tools";
349    OptDist, "src/tools/opt-dist", "opt-dist";
350    RemoteTestClient, "src/tools/remote-test-client", "remote-test-client";
351    RemoteTestServer, "src/tools/remote-test-server", "remote-test-server";
352    RustAnalyzer, "src/tools/rust-analyzer", "rust-analyzer";
353    Rustdoc, "src/librustdoc", "clippy";
354    Rustfmt, "src/tools/rustfmt", "rustfmt";
355    RustInstaller, "src/tools/rust-installer", "rust-installer";
356    Tidy, "src/tools/tidy", "tidy";
357    TestFloatParse, "src/tools/test-float-parse", "test-float-parse";
358);
359
360#[derive(Debug, Clone, PartialEq, Eq, Hash)]
361pub struct CI {
362    target: TargetSelection,
363    config: LintConfig,
364}
365
366impl Step for CI {
367    type Output = ();
368    const DEFAULT: bool = false;
369
370    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
371        run.alias("ci")
372    }
373
374    fn make_run(run: RunConfig<'_>) {
375        let config = LintConfig::new(run.builder);
376        run.builder.ensure(CI { target: run.target, config });
377    }
378
379    fn run(self, builder: &Builder<'_>) -> Self::Output {
380        builder.ensure(Bootstrap {
381            target: self.target,
382            config: self.config.merge(&LintConfig {
383                allow: vec![],
384                warn: vec![],
385                deny: vec!["warnings".into()],
386                forbid: vec![],
387            }),
388        });
389        let library_clippy_cfg = LintConfig {
390            allow: vec!["clippy::all".into()],
391            warn: vec![],
392            deny: vec![
393                "clippy::correctness".into(),
394                "clippy::char_lit_as_u8".into(),
395                "clippy::four_forward_slashes".into(),
396                "clippy::needless_bool".into(),
397                "clippy::needless_bool_assign".into(),
398                "clippy::non_minimal_cfg".into(),
399                "clippy::print_literal".into(),
400                "clippy::same_item_push".into(),
401                "clippy::single_char_add_str".into(),
402                "clippy::to_string_in_format_args".into(),
403            ],
404            forbid: vec![],
405        };
406        builder.ensure(Std {
407            target: self.target,
408            config: self.config.merge(&library_clippy_cfg),
409            crates: vec![],
410        });
411
412        let compiler_clippy_cfg = LintConfig {
413            allow: vec!["clippy::all".into()],
414            warn: vec![],
415            deny: vec![
416                "clippy::correctness".into(),
417                "clippy::char_lit_as_u8".into(),
418                "clippy::clone_on_ref_ptr".into(),
419                "clippy::format_in_format_args".into(),
420                "clippy::four_forward_slashes".into(),
421                "clippy::needless_bool".into(),
422                "clippy::needless_bool_assign".into(),
423                "clippy::non_minimal_cfg".into(),
424                "clippy::print_literal".into(),
425                "clippy::same_item_push".into(),
426                "clippy::single_char_add_str".into(),
427                "clippy::to_string_in_format_args".into(),
428            ],
429            forbid: vec![],
430        };
431        builder.ensure(Rustc {
432            target: self.target,
433            config: self.config.merge(&compiler_clippy_cfg),
434            crates: vec![],
435        });
436
437        let rustc_codegen_gcc = LintConfig {
438            allow: vec![],
439            warn: vec![],
440            deny: vec!["warnings".into()],
441            forbid: vec![],
442        };
443        builder.ensure(CodegenGcc {
444            target: self.target,
445            config: self.config.merge(&rustc_codegen_gcc),
446        });
447    }
448}