Skip to main content

bootstrap/core/build_steps/
clean.rs

1//! `./x.py clean`
2//!
3//! Responsible for cleaning out a build directory of all old and stale
4//! artifacts to prepare for a fresh build. Currently doesn't remove the
5//! `build/cache` directory (download cache) or the `build/$target/llvm`
6//! directory unless the `--all` flag is present.
7
8use std::fs;
9use std::io::{self, ErrorKind};
10use std::path::Path;
11
12use crate::core::builder::{
13    Builder, CommandLineStep, Kind, RunConfig, ShouldRun, crate_description,
14};
15use crate::core::config::Subcommand;
16use crate::utils::build_stamp::BuildStamp;
17use crate::utils::helpers::t;
18use crate::{Build, Compiler, Mode};
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub struct CleanAll {}
22
23impl CommandLineStep for CleanAll {
24    type Output = ();
25
26    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
27        // Normally this step is invoked implicitly via `./x clean`, but all
28        // steps are required to register at least one explicit path/alias.
29        run.alias("default")
30    }
31
32    fn is_default_step(_builder: &Builder<'_>) -> bool {
33        true
34    }
35
36    fn make_run(run: RunConfig<'_>) {
37        run.builder.ensure(CleanAll {})
38    }
39
40    fn run(self, builder: &Builder<'_>) -> Self::Output {
41        let Subcommand::Clean { all, stage } = builder.config.cmd else {
42            unreachable!("wrong subcommand?")
43        };
44
45        if all && stage.is_some() {
46            panic!("--all and --stage can't be used at the same time for `x clean`");
47        }
48
49        clean(builder.build, all, stage)
50    }
51}
52
53macro_rules! clean_crate_tree {
54    ( $( $name:ident, $mode:path, $root_crate:literal);+ $(;)? ) => { $(
55        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
56        pub struct $name {
57            compiler: Compiler,
58            crates: Vec<String>,
59        }
60
61        impl CommandLineStep for $name {
62            type Output = ();
63
64            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
65                run.crate_or_deps($root_crate)
66            }
67
68            fn make_run(run: RunConfig<'_>) {
69                let builder = run.builder;
70                let compiler = builder.compiler(builder.top_stage, run.target);
71                builder.ensure(Self { crates: run.cargo_crates_in_set(), compiler });
72            }
73
74            fn run(self, builder: &Builder<'_>) -> Self::Output {
75                let compiler = self.compiler;
76                let target = compiler.host;
77                let mut cargo = builder.bare_cargo(compiler, $mode, target, Kind::Clean);
78
79                // Since https://github.com/rust-lang/rust/pull/111076 enables
80                // unstable cargo feature (`public-dependency`), we need to ensure
81                // that unstable features are enabled before reading libstd Cargo.toml.
82                cargo.env("RUSTC_BOOTSTRAP", "1");
83
84                for krate in &*self.crates {
85                    cargo.arg("-p");
86                    cargo.arg(krate);
87                }
88
89                builder.info(&format!(
90                    "Cleaning{} stage{} {} artifacts ({} -> {})",
91                    crate_description(&self.crates), compiler.stage, stringify!($name).to_lowercase(), &compiler.host, target,
92                ));
93
94                // NOTE: doesn't use `run_cargo` because we don't want to save a stamp file,
95                // and doesn't use `stream_cargo` to avoid passing `--message-format` which `clean` doesn't accept.
96                cargo.run(builder);
97            }
98        }
99    )+ }
100}
101
102clean_crate_tree! {
103    Rustc, Mode::Rustc, "rustc-main";
104    Std, Mode::Std, "sysroot";
105}
106
107fn clean(build: &Build, all: bool, stage: Option<u32>) {
108    if build.config.dry_run() {
109        return;
110    }
111
112    rm_rf("tmp".as_ref());
113
114    // Clean the entire build directory
115    if all {
116        rm_rf(&build.out);
117        return;
118    }
119
120    // Clean the target stage artifacts
121    if let Some(stage) = stage {
122        clean_specific_stage(build, stage);
123        return;
124    }
125
126    // Follow the default behaviour
127    clean_default(build);
128}
129
130fn clean_specific_stage(build: &Build, stage: u32) {
131    for host in &build.hosts {
132        let entries = match build.out.join(host).read_dir() {
133            Ok(iter) => iter,
134            Err(_) => continue,
135        };
136
137        for entry in entries {
138            let entry = t!(entry);
139            let stage_prefix = format!("stage{}", stage + 1);
140
141            // if current entry is not related with the target stage, continue
142            if !entry.file_name().to_str().unwrap_or("").contains(&stage_prefix) {
143                continue;
144            }
145
146            let path = t!(entry.path().canonicalize());
147            rm_rf(&path);
148        }
149    }
150}
151
152fn clean_default(build: &Build) {
153    rm_rf(&build.out.join("tmp"));
154    rm_rf(&build.out.join("dist"));
155    rm_rf(&build.out.join("bootstrap").join(".last-warned-change-id"));
156    rm_rf(&build.out.join("bootstrap-shims-dump"));
157    rm_rf(BuildStamp::new(&build.out).with_prefix("rustfmt").path());
158
159    let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t)).collect();
160    // After cross-compilation, artifacts of the host architecture (which may differ from build.host)
161    // might not get removed.
162    // Adding its path (linked one for easier accessibility) will solve this problem.
163    hosts.push(build.out.join("host"));
164
165    for host in hosts {
166        let entries = match host.read_dir() {
167            Ok(iter) => iter,
168            Err(_) => continue,
169        };
170
171        for entry in entries {
172            let entry = t!(entry);
173            if entry.file_name().to_str() == Some("llvm") {
174                continue;
175            }
176            let path = t!(entry.path().canonicalize());
177            rm_rf(&path);
178        }
179    }
180}
181
182fn rm_rf(path: &Path) {
183    match fs::remove_dir_all(path) {
184        Ok(()) => return,
185        // Already deleted, nothing for us to do.
186        Err(e) if e.kind() == ErrorKind::NotFound => return,
187        _ => {}
188    }
189
190    // If remove_dir_all fails then retry.
191    // We do so manually so we can provide better diagnostics,
192    // e.g. pointing to the exact file that failed.
193    match path.symlink_metadata() {
194        Err(e) => {
195            if e.kind() == ErrorKind::NotFound {
196                return;
197            }
198            panic!("failed to get metadata for file {}: {}", path.display(), e);
199        }
200        Ok(metadata) => {
201            if !metadata.file_type().is_dir() {
202                do_op(path, "remove file", |p| match fs::remove_file(p) {
203                    #[cfg(windows)]
204                    Err(e)
205                        if e.kind() == std::io::ErrorKind::PermissionDenied
206                            && p.file_name().and_then(std::ffi::OsStr::to_str)
207                                == Some("bootstrap.exe") =>
208                    {
209                        eprintln!("WARNING: failed to delete '{}'.", p.display());
210                        Ok(())
211                    }
212                    r => r,
213                });
214
215                return;
216            }
217
218            for file in t!(fs::read_dir(path)) {
219                rm_rf(&t!(file).path());
220            }
221
222            do_op(path, "remove dir", |p| match fs::remove_dir(p) {
223                // Check for dir not empty on Windows
224                #[cfg(windows)]
225                Err(e) if e.kind() == ErrorKind::DirectoryNotEmpty => Ok(()),
226                r => r,
227            });
228        }
229    };
230}
231
232fn do_op<F>(path: &Path, desc: &str, mut f: F)
233where
234    F: FnMut(&Path) -> io::Result<()>,
235{
236    match f(path) {
237        Ok(()) => {}
238        // On windows we can't remove a readonly file, and git will often clone files as readonly.
239        // As a result, we have some special logic to remove readonly files on windows.
240        // This is also the reason that we can't use things like fs::remove_dir_all().
241        #[cfg(windows)]
242        Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
243            let m = t!(path.symlink_metadata());
244            let mut p = m.permissions();
245            // this os not unix, so clippy gives FP
246            #[expect(clippy::permissions_set_readonly_false)]
247            p.set_readonly(false);
248            t!(fs::set_permissions(path, p));
249            f(path).unwrap_or_else(|e| {
250                // Delete symlinked directories on Windows
251                if fs::remove_dir(path).is_ok() {
252                    return;
253                }
254                panic!("failed to {} {}: {}", desc, path.display(), e);
255            });
256        }
257        Err(e) => {
258            panic!("failed to {} {}: {}", desc, path.display(), e);
259        }
260    }
261}