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