bootstrap/core/build_steps/
clean.rs
1use 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() }
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 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 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 if all {
109 rm_rf(&build.out);
110 return;
111 }
112
113 if let Some(stage) = stage {
115 clean_specific_stage(build, stage);
116 return;
117 }
118
119 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 !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 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 #[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 #[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 #[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 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}