bootstrap/core/build_steps/
clippy.rs

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