bootstrap/core/build_steps/
clean.rs1use std::fs;
9use std::io::{self, ErrorKind};
10use std::path::Path;
11
12use crate::core::builder::{
13 Builder, CommandLineStep, Kind, RunConfig, ShouldRun, crate_description,
14};
15use crate::core::compiler::Compiler;
16use crate::core::config::flags::Subcommand;
17use crate::utils::build_stamp::BuildStamp;
18use crate::utils::helpers::t;
19use crate::{Build, Mode};
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub struct CleanAll {}
23
24impl CommandLineStep for CleanAll {
25 type Output = ();
26
27 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
28 run.alias("default")
31 }
32
33 fn is_default_step(_builder: &Builder<'_>) -> bool {
34 true
35 }
36
37 fn make_run(run: RunConfig<'_>) {
38 run.builder.ensure(CleanAll {})
39 }
40
41 fn run(self, builder: &Builder<'_>) -> Self::Output {
42 let Subcommand::Clean { all, stage } = builder.config.cmd else {
43 unreachable!("wrong subcommand?")
44 };
45
46 if all && stage.is_some() {
47 panic!("--all and --stage can't be used at the same time for `x clean`");
48 }
49
50 clean(builder.build, all, stage)
51 }
52}
53
54macro_rules! clean_crate_tree {
55 ( $( $name:ident, $mode:path, $root_crate:literal);+ $(;)? ) => { $(
56 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
57 pub struct $name {
58 compiler: Compiler,
59 crates: Vec<String>,
60 }
61
62 impl CommandLineStep for $name {
63 type Output = ();
64
65 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
66 run.crate_or_deps($root_crate)
67 }
68
69 fn make_run(run: RunConfig<'_>) {
70 let builder = run.builder;
71 let compiler = builder.compiler(builder.top_stage, run.target);
72 builder.ensure(Self { crates: run.cargo_crates_in_set(), compiler });
73 }
74
75 fn run(self, builder: &Builder<'_>) -> Self::Output {
76 let compiler = self.compiler;
77 let target = compiler.host;
78 let mut cargo = builder.bare_cargo(compiler, $mode, target, Kind::Clean);
79
80 cargo.env("RUSTC_BOOTSTRAP", "1");
84
85 for krate in &*self.crates {
86 cargo.arg("-p");
87 cargo.arg(krate);
88 }
89
90 builder.info(&format!(
91 "Cleaning{} stage{} {} artifacts ({} -> {})",
92 crate_description(&self.crates), compiler.stage, stringify!($name).to_lowercase(), &compiler.host, target,
93 ));
94
95 cargo.run(builder);
98 }
99 }
100 )+ }
101}
102
103clean_crate_tree! {
104 Rustc, Mode::Rustc, "rustc-main";
105 Std, Mode::Std, "sysroot";
106}
107
108fn clean(build: &Build, all: bool, stage: Option<u32>) {
109 if build.config.dry_run() {
110 return;
111 }
112
113 rm_rf("tmp".as_ref());
114
115 if all {
117 rm_rf(&build.out);
118 return;
119 }
120
121 if let Some(stage) = stage {
123 clean_specific_stage(build, stage);
124 return;
125 }
126
127 clean_default(build);
129}
130
131fn clean_specific_stage(build: &Build, stage: u32) {
132 for host in &build.hosts {
133 let entries = match build.out.join(host).read_dir() {
134 Ok(iter) => iter,
135 Err(_) => continue,
136 };
137
138 for entry in entries {
139 let entry = t!(entry);
140 let stage_prefix = format!("stage{}", stage + 1);
141
142 if !entry.file_name().to_str().unwrap_or("").contains(&stage_prefix) {
144 continue;
145 }
146
147 let path = t!(entry.path().canonicalize());
148 rm_rf(&path);
149 }
150 }
151}
152
153fn clean_default(build: &Build) {
154 rm_rf(&build.out.join("tmp"));
155 rm_rf(&build.out.join("dist"));
156 rm_rf(&build.out.join("bootstrap").join(".last-warned-change-id"));
157 rm_rf(&build.out.join("bootstrap-shims-dump"));
158 rm_rf(BuildStamp::new(&build.out).with_prefix("rustfmt").path());
159
160 let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t)).collect();
161 hosts.push(build.out.join("host"));
165
166 for host in hosts {
167 let entries = match host.read_dir() {
168 Ok(iter) => iter,
169 Err(_) => continue,
170 };
171
172 for entry in entries {
173 let entry = t!(entry);
174 if entry.file_name().to_str() == Some("llvm") {
175 continue;
176 }
177 let path = t!(entry.path().canonicalize());
178 rm_rf(&path);
179 }
180 }
181}
182
183fn rm_rf(path: &Path) {
184 match fs::remove_dir_all(path) {
185 Ok(()) => return,
186 Err(e) if e.kind() == ErrorKind::NotFound => return,
188 _ => {}
189 }
190
191 match path.symlink_metadata() {
195 Err(e) => {
196 if e.kind() == ErrorKind::NotFound {
197 return;
198 }
199 panic!("failed to get metadata for file {}: {}", path.display(), e);
200 }
201 Ok(metadata) => {
202 if !metadata.file_type().is_dir() {
203 do_op(path, "remove file", |p| match fs::remove_file(p) {
204 #[cfg(windows)]
205 Err(e)
206 if e.kind() == std::io::ErrorKind::PermissionDenied
207 && p.file_name().and_then(std::ffi::OsStr::to_str)
208 == Some("bootstrap.exe") =>
209 {
210 eprintln!("WARNING: failed to delete '{}'.", p.display());
211 Ok(())
212 }
213 r => r,
214 });
215
216 return;
217 }
218
219 for file in t!(fs::read_dir(path)) {
220 rm_rf(&t!(file).path());
221 }
222
223 do_op(path, "remove dir", |p| match fs::remove_dir(p) {
224 #[cfg(windows)]
226 Err(e) if e.kind() == ErrorKind::DirectoryNotEmpty => Ok(()),
227 r => r,
228 });
229 }
230 };
231}
232
233fn do_op<F>(path: &Path, desc: &str, mut f: F)
234where
235 F: FnMut(&Path) -> io::Result<()>,
236{
237 match f(path) {
238 Ok(()) => {}
239 #[cfg(windows)]
243 Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
244 let m = t!(path.symlink_metadata());
245 let mut p = m.permissions();
246 #[expect(clippy::permissions_set_readonly_false)]
248 p.set_readonly(false);
249 t!(fs::set_permissions(path, p));
250 f(path).unwrap_or_else(|e| {
251 if fs::remove_dir(path).is_ok() {
253 return;
254 }
255 panic!("failed to {} {}: {}", desc, path.display(), e);
256 });
257 }
258 Err(e) => {
259 panic!("failed to {} {}: {}", desc, path.display(), e);
260 }
261 }
262}