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