bootstrap/core/build_steps/
clean.rs1use 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 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 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 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 if all {
112 rm_rf(&build.out);
113 return;
114 }
115
116 if let Some(stage) = stage {
118 clean_specific_stage(build, stage);
119 return;
120 }
121
122 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 !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 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 #[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 #[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 #[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 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}