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