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