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 let crates = run.builder.in_tree_crates($root_crate, None);
62 run.crates(crates)
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 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 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 if all {
113 rm_rf(&build.out);
114 return;
115 }
116
117 if let Some(stage) = stage {
119 clean_specific_stage(build, stage);
120 return;
121 }
122
123 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 !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 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 path.symlink_metadata() {
181 Err(e) => {
182 if e.kind() == ErrorKind::NotFound {
183 return;
184 }
185 panic!("failed to get metadata for file {}: {}", path.display(), e);
186 }
187 Ok(metadata) => {
188 if !metadata.file_type().is_dir() {
189 do_op(path, "remove file", |p| match fs::remove_file(p) {
190 #[cfg(windows)]
191 Err(e)
192 if e.kind() == std::io::ErrorKind::PermissionDenied
193 && p.file_name().and_then(std::ffi::OsStr::to_str)
194 == Some("bootstrap.exe") =>
195 {
196 eprintln!("WARNING: failed to delete '{}'.", p.display());
197 Ok(())
198 }
199 r => r,
200 });
201
202 return;
203 }
204
205 for file in t!(fs::read_dir(path)) {
206 rm_rf(&t!(file).path());
207 }
208
209 do_op(path, "remove dir", |p| match fs::remove_dir(p) {
210 #[cfg(windows)]
212 Err(e) if e.kind() == ErrorKind::DirectoryNotEmpty => Ok(()),
213 r => r,
214 });
215 }
216 };
217}
218
219fn do_op<F>(path: &Path, desc: &str, mut f: F)
220where
221 F: FnMut(&Path) -> io::Result<()>,
222{
223 match f(path) {
224 Ok(()) => {}
225 #[cfg(windows)]
229 Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
230 let m = t!(path.symlink_metadata());
231 let mut p = m.permissions();
232 #[expect(clippy::permissions_set_readonly_false)]
234 p.set_readonly(false);
235 t!(fs::set_permissions(path, p));
236 f(path).unwrap_or_else(|e| {
237 if m.file_type().is_symlink() && path.is_dir() && fs::remove_dir(path).is_ok() {
239 return;
240 }
241 panic!("failed to {} {}: {}", desc, path.display(), e);
242 });
243 }
244 Err(e) => {
245 panic!("failed to {} {}: {}", desc, path.display(), e);
246 }
247 }
248}