1use std::fs::{self, OpenOptions, TryLockError};
9use std::io::{self, BufRead, BufReader, IsTerminal, Read, Write};
10use std::path::Path;
11use std::str::FromStr;
12use std::sync::Once;
13use std::time::Instant;
14use std::{env, process};
15
16use crate::core::builder::StepStack;
17use crate::core::config::flags::Flags;
18use crate::core::config::{ChangeId, Config, Subcommand};
19use crate::utils::change_tracker::{
20 CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes,
21};
22use crate::utils::helpers::t;
23use crate::{Build, debug};
24
25fn is_tracing_enabled() -> bool {
26 cfg!(feature = "tracing")
27}
28
29pub fn main() {
30 #[cfg(feature = "tracing")]
31 let guard = crate::utils::tracing::setup_tracing("BOOTSTRAP_TRACING");
32
33 let _start_time = Instant::now();
34
35 let default_panic_hook = std::panic::take_hook();
36 std::panic::set_hook(Box::new(move |info| {
37 static BACKTRACE_LOCK: Once = Once::new();
38
39 BACKTRACE_LOCK.call_once(|| {
45 if std::env::var("RUST_BACKTRACE").is_err() {
46 unsafe {
47 std::env::set_var("RUST_BACKTRACE", "1");
48 }
49 }
50 });
51
52 default_panic_hook(info);
53 StepStack::with_current(|stack| {
54 eprintln!("\nBootstrap has panicked, currently active steps:");
55 for step in stack.get_active_steps() {
56 eprintln!("{} at {}", step.info, step.location);
57 }
58 });
59 }));
60
61 let args = env::args().skip(1).collect::<Vec<_>>();
62
63 if Flags::try_parse_verbose_help(&args) {
64 return;
65 }
66
67 debug!("parsing flags");
68 let flags = Flags::parse(&args);
69 debug!("parsing config based on flags");
70 let config = Config::parse(flags);
71
72 let mut build_lock;
73
74 if !config.bypass_bootstrap_lock {
75 let lock_path = config.out.join("lock");
78 build_lock = t!(fs::OpenOptions::new()
79 .read(true)
80 .write(true)
81 .create(true)
82 .truncate(false)
83 .open(&lock_path));
84 t!(build_lock.try_lock().or_else(|e| {
85 if let TryLockError::Error(e) = e {
86 return Err(e);
87 }
88 let mut pid = String::new();
89 t!(build_lock.read_to_string(&mut pid));
90 if !pid.is_empty() {
93 println!("WARNING: build directory locked by process {pid}, waiting for lock");
94 } else {
95 println!("WARNING: build directory locked, waiting for lock");
96 }
97 build_lock.lock()
98 }));
99 t!(build_lock.set_len(0));
100 t!(build_lock.write_all(process::id().to_string().as_bytes()));
101 }
102
103 let changelog_suggestion = if matches!(config.cmd, Subcommand::Setup { .. })
105 || config.is_running_on_ci()
106 || config.dry_run()
107 {
108 None
109 } else {
110 check_version(&config)
111 };
112
113 let suggest_setup = config.config.is_none() && !matches!(config.cmd, Subcommand::Setup { .. });
116 if suggest_setup {
117 println!("WARNING: you have not made a `bootstrap.toml`");
118 println!(
119 "HELP: consider running `./x.py setup` or copying `bootstrap.example.toml` by running \
120 `cp bootstrap.example.toml bootstrap.toml`"
121 );
122 } else if let Some(suggestion) = &changelog_suggestion {
123 println!("{suggestion}");
124 }
125
126 let pre_commit = config.src.join(".git").join("hooks").join("pre-commit");
127 let dump_bootstrap_shims = config.dump_bootstrap_shims;
128 let out_dir = config.out.clone();
129
130 let tracing_enabled = is_tracing_enabled();
131
132 let tracing_dir = out_dir.join("bootstrap-trace").join(std::process::id().to_string());
135 let latest_trace_dir = tracing_dir.parent().unwrap().join("latest");
136 if tracing_enabled {
137 let _ = std::fs::remove_dir_all(&tracing_dir);
138 std::fs::create_dir_all(&tracing_dir).unwrap();
139
140 #[cfg(windows)]
141 let _ = std::fs::remove_dir(&latest_trace_dir);
142 #[cfg(not(windows))]
143 let _ = std::fs::remove_file(&latest_trace_dir);
144
145 #[cfg(not(windows))]
146 fn symlink_dir_inner(original: &Path, link: &Path) -> io::Result<()> {
147 use std::os::unix::fs;
148 fs::symlink(original, link)
149 }
150
151 #[cfg(windows)]
152 fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
153 junction::create(target, junction)
154 }
155
156 t!(symlink_dir_inner(&tracing_dir, &latest_trace_dir));
157 }
158
159 debug!("creating new build based on config");
160 let mut build = Build::new(config);
161 build.build();
162
163 if suggest_setup {
164 println!("WARNING: you have not made a `bootstrap.toml`");
165 println!(
166 "HELP: consider running `./x.py setup` or copying `bootstrap.example.toml` by running \
167 `cp bootstrap.example.toml bootstrap.toml`"
168 );
169 } else if let Some(suggestion) = &changelog_suggestion {
170 println!("{suggestion}");
171 }
172
173 if fs::read_to_string(pre_commit).is_ok_and(|contents| {
178 contents.contains("https://github.com/rust-lang/rust/issues/77620#issuecomment-705144570")
179 }) {
180 println!(
181 "WARNING: You have the pre-push script installed to .git/hooks/pre-commit. \
182 Consider moving it to .git/hooks/pre-push instead, which runs less often."
183 );
184 }
185
186 if suggest_setup || changelog_suggestion.is_some() {
187 println!("NOTE: this message was printed twice to make it more likely to be seen");
188 }
189
190 if dump_bootstrap_shims {
191 let dump_dir = out_dir.join("bootstrap-shims-dump");
192 assert!(dump_dir.exists());
193
194 for entry in walkdir::WalkDir::new(&dump_dir) {
195 let entry = t!(entry);
196
197 if !entry.file_type().is_file() {
198 continue;
199 }
200
201 let file = t!(fs::File::open(entry.path()));
202
203 let mut lines: Vec<String> = t!(BufReader::new(&file).lines().collect());
207 lines.sort_by_key(|t| t.to_lowercase());
208 let mut file = t!(OpenOptions::new().write(true).truncate(true).open(entry.path()));
209 t!(file.write_all(lines.join("\n").as_bytes()));
210 }
211 }
212
213 #[cfg(feature = "tracing")]
214 {
215 build.report_summary(&tracing_dir.join("command-stats.txt"), _start_time);
216 build.report_step_graph(&tracing_dir);
217 guard.copy_to_dir(&tracing_dir);
218 eprintln!("Tracing/profiling output has been written to {}", latest_trace_dir.display());
219 }
220}
221
222fn check_version(config: &Config) -> Option<String> {
223 let mut msg = String::new();
224
225 let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id;
226 let warned_id_path = config.out.join("bootstrap").join(".last-warned-change-id");
227
228 let mut id = match config.change_id {
229 Some(ChangeId::Id(id)) if id == latest_change_id => return None,
230 Some(ChangeId::Ignore) => return None,
231 Some(ChangeId::Id(id)) => id,
232 None => {
233 msg.push_str("WARNING: The `change-id` is missing in the `bootstrap.toml`. This means that you will not be able to track the major changes made to the bootstrap configurations.\n");
234 msg.push_str("NOTE: to silence this warning, ");
235 msg.push_str(&format!(
236 "add `change-id = {latest_change_id}` or `change-id = \"ignore\"` at the top of `bootstrap.toml`"
237 ));
238 return Some(msg);
239 }
240 };
241
242 if let Ok(t) = fs::read_to_string(&warned_id_path) {
246 let last_warned_id = usize::from_str(&t)
247 .unwrap_or_else(|_| panic!("{} is corrupted.", warned_id_path.display()));
248
249 if CONFIG_CHANGE_HISTORY.iter().any(|config| config.change_id == last_warned_id) {
253 id = last_warned_id;
254 }
255 };
256
257 let changes = find_recent_config_change_ids(id);
258
259 if changes.is_empty() {
260 return None;
261 }
262
263 msg.push_str("There have been changes to x.py since you last updated:\n");
264 msg.push_str(&human_readable_changes(changes));
265
266 msg.push_str("NOTE: to silence this warning, ");
267 msg.push_str(&format!(
268 "update `bootstrap.toml` to use `change-id = {latest_change_id}` or `change-id = \"ignore\"` instead"
269 ));
270
271 if io::stdout().is_terminal() {
272 t!(fs::write(warned_id_path, latest_change_id.to_string()));
273 }
274
275 Some(msg)
276}