| //! bootstrap, the Rust build system | |
| //! | |
| //! This is the entry point for the build system used to compile the `rustc` | |
| //! compiler. Lots of documentation can be found in the `README.md` file in the | |
| //! parent directory, and otherwise documentation can be found throughout the `build` | |
| //! directory in each respective module. | |
| use std::fs::{self, OpenOptions, TryLockError}; | |
| use std::io::{self, BufRead, BufReader, IsTerminal, Read, Write}; | |
| use std::path::Path; | |
| use std::str::FromStr; | |
| use std::sync::Once; | |
| use std::time::Instant; | |
| use std::{env, process}; | |
| use crate::core::builder::StepStack; | |
| use crate::core::config::flags::{Flags, Subcommand}; | |
| use crate::core::config::{ChangeId, Config}; | |
| use crate::core::session::Session; | |
| use crate::debug; | |
| use crate::utils::change_tracker::{ | |
| CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes, | |
| }; | |
| use crate::utils::helpers::t; | |
| fn is_tracing_enabled() -> bool { | |
| cfg!(feature = "tracing") | |
| } | |
| pub fn main() { | |
| #[cfg(feature = "tracing")] | |
| let guard = crate::utils::tracing::setup_tracing("BOOTSTRAP_TRACING"); | |
| let _start_time = Instant::now(); | |
| let default_panic_hook = std::panic::take_hook(); | |
| std::panic::set_hook(Box::new(move |info| { | |
| static BACKTRACE_LOCK: Once = Once::new(); | |
| // Always print backtraces to provide richer errors, to help debug hard-to-reproduce panics | |
| // when the user didn't specify RUST_BACKTRACE | |
| // Note that we only override this variable in the panic handler, because bootstrap might | |
| // manually capture backtraces when a command is executed, and in that case we do not want | |
| // to always force backtraces. | |
| BACKTRACE_LOCK.call_once(|| { | |
| if std::env::var("RUST_BACKTRACE").is_err() { | |
| unsafe { | |
| std::env::set_var("RUST_BACKTRACE", "1"); | |
| } | |
| } | |
| }); | |
| default_panic_hook(info); | |
| StepStack::with_current(|stack| { | |
| eprintln!("\nBootstrap has panicked, currently active steps:"); | |
| for step in stack.get_active_steps() { | |
| eprintln!("{} at {}", step.info, step.location); | |
| } | |
| }); | |
| })); | |
| let args = env::args().skip(1).collect::<Vec<_>>(); | |
| if Flags::try_parse_verbose_help(&args) { | |
| return; | |
| } | |
| debug!("parsing flags"); | |
| let flags = Flags::parse(&args); | |
| debug!("parsing config based on flags"); | |
| let config = Config::parse(flags); | |
| let mut build_lock; | |
| if !config.bypass_bootstrap_lock { | |
| // Display PID of process holding the lock | |
| // PID will be stored in a lock file | |
| let lock_path = config.out.join("lock"); | |
| build_lock = t!(fs::OpenOptions::new() | |
| .read(true) | |
| .write(true) | |
| .create(true) | |
| .truncate(false) | |
| .open(&lock_path)); | |
| t!(build_lock.try_lock().or_else(|e| { | |
| if let TryLockError::Error(e) = e { | |
| return Err(e); | |
| } | |
| let mut pid = String::new(); | |
| t!(build_lock.read_to_string(&mut pid)); | |
| // #135972: We can reach this point when the lock has been taken, | |
| // but the locker has not yet written its PID to the file | |
| if !pid.is_empty() { | |
| println!("WARNING: build directory locked by process {pid}, waiting for lock"); | |
| } else { | |
| println!("WARNING: build directory locked, waiting for lock"); | |
| } | |
| build_lock.lock() | |
| })); | |
| t!(build_lock.set_len(0)); | |
| t!(build_lock.write_all(process::id().to_string().as_bytes())); | |
| } | |
| // check_version warnings are not printed during setup, or during CI | |
| let changelog_suggestion = if matches!(config.cmd, Subcommand::Setup { .. }) | |
| || config.is_running_on_ci() | |
| || config.dry_run() | |
| { | |
| None | |
| } else { | |
| check_version(&config) | |
| }; | |
| // NOTE: Since `./configure` generates a `bootstrap.toml`, distro maintainers will see the | |
| // changelog warning, not the `x.py setup` message. | |
| let suggest_setup = config.config.is_none() && !matches!(config.cmd, Subcommand::Setup { .. }); | |
| if suggest_setup { | |
| println!("WARNING: you have not made a `bootstrap.toml`"); | |
| println!( | |
| "HELP: consider running `./x.py setup` or copying `bootstrap.example.toml` by running \ | |
| `cp bootstrap.example.toml bootstrap.toml`" | |
| ); | |
| } else if let Some(suggestion) = &changelog_suggestion { | |
| println!("{suggestion}"); | |
| } | |
| let pre_commit = config.src.join(".git").join("hooks").join("pre-commit"); | |
| let dump_bootstrap_shims = config.dump_bootstrap_shims; | |
| let out_dir = config.out.clone(); | |
| let tracing_enabled = is_tracing_enabled(); | |
| // Prepare a directory for tracing output | |
| // Also store a symlink named "latest" to point to the latest tracing directory. | |
| let tracing_dir = out_dir.join("bootstrap-trace").join(std::process::id().to_string()); | |
| let latest_trace_dir = tracing_dir.parent().unwrap().join("latest"); | |
| if tracing_enabled { | |
| let _ = std::fs::remove_dir_all(&tracing_dir); | |
| std::fs::create_dir_all(&tracing_dir).unwrap(); | |
| #[cfg(windows)] | |
| let _ = std::fs::remove_dir(&latest_trace_dir); | |
| #[cfg(not(windows))] | |
| let _ = std::fs::remove_file(&latest_trace_dir); | |
| #[cfg(not(windows))] | |
| fn symlink_dir_inner(original: &Path, link: &Path) -> io::Result<()> { | |
| use std::os::unix::fs; | |
| fs::symlink(original, link) | |
| } | |
| #[cfg(windows)] | |
| fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> { | |
| junction::create(target, junction) | |
| } | |
| t!(symlink_dir_inner(&tracing_dir, &latest_trace_dir)); | |
| } | |
| debug!("creating new session based on config"); | |
| let mut sess = Session::new(config); | |
| sess.build(); | |
| if suggest_setup { | |
| println!("WARNING: you have not made a `bootstrap.toml`"); | |
| println!( | |
| "HELP: consider running `./x.py setup` or copying `bootstrap.example.toml` by running \ | |
| `cp bootstrap.example.toml bootstrap.toml`" | |
| ); | |
| } else if let Some(suggestion) = &changelog_suggestion { | |
| println!("{suggestion}"); | |
| } | |
| // Give a warning if the pre-commit script is in pre-commit and not pre-push. | |
| // HACK: Since the commit script uses hard links, we can't actually tell if it was installed by x.py setup or not. | |
| // We could see if it's identical to src/etc/pre-push.sh, but pre-push may have been modified in the meantime. | |
| // Instead, look for this comment, which is almost certainly not in any custom hook. | |
| if fs::read_to_string(pre_commit).is_ok_and(|contents| { | |
| contents.contains("https://github.com/rust-lang/rust/issues/77620#issuecomment-705144570") | |
| }) { | |
| println!( | |
| "WARNING: You have the pre-push script installed to .git/hooks/pre-commit. \ | |
| Consider moving it to .git/hooks/pre-push instead, which runs less often." | |
| ); | |
| } | |
| if suggest_setup || changelog_suggestion.is_some() { | |
| println!("NOTE: this message was printed twice to make it more likely to be seen"); | |
| } | |
| if dump_bootstrap_shims { | |
| let dump_dir = out_dir.join("bootstrap-shims-dump"); | |
| assert!(dump_dir.exists()); | |
| for entry in walkdir::WalkDir::new(&dump_dir) { | |
| let entry = t!(entry); | |
| if !entry.file_type().is_file() { | |
| continue; | |
| } | |
| let file = t!(fs::File::open(entry.path())); | |
| // To ensure deterministic results we must sort the dump lines. | |
| // This is necessary because the order of rustc invocations different | |
| // almost all the time. | |
| let mut lines: Vec<String> = t!(BufReader::new(&file).lines().collect()); | |
| lines.sort_by_key(|t| t.to_lowercase()); | |
| let mut file = t!(OpenOptions::new().write(true).truncate(true).open(entry.path())); | |
| t!(file.write_all(lines.join("\n").as_bytes())); | |
| } | |
| } | |
| #[cfg(feature = "tracing")] | |
| { | |
| sess.report_summary(&tracing_dir.join("command-stats.txt"), _start_time); | |
| sess.report_step_graph(&tracing_dir); | |
| guard.copy_to_dir(&tracing_dir); | |
| eprintln!("Tracing/profiling output has been written to {}", latest_trace_dir.display()); | |
| } | |
| } | |
| fn check_version(config: &Config) -> Option<String> { | |
| let mut msg = String::new(); | |
| let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id; | |
| let warned_id_path = config.out.join("bootstrap").join(".last-warned-change-id"); | |
| let mut id = match config.change_id { | |
| Some(ChangeId::Id(id)) if id == latest_change_id => return None, | |
| Some(ChangeId::Ignore) => return None, | |
| Some(ChangeId::Id(id)) => id, | |
| None => { | |
| 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"); | |
| msg.push_str("NOTE: to silence this warning, "); | |
| msg.push_str(&format!( | |
| "add `change-id = {latest_change_id}` or `change-id = \"ignore\"` at the top of `bootstrap.toml`" | |
| )); | |
| return Some(msg); | |
| } | |
| }; | |
| // Always try to use `change-id` from .last-warned-change-id first. If it doesn't exist, | |
| // then use the one from the bootstrap.toml. This way we never show the same warnings | |
| // more than once. | |
| if let Ok(t) = fs::read_to_string(&warned_id_path) { | |
| let last_warned_id = usize::from_str(&t) | |
| .unwrap_or_else(|_| panic!("{} is corrupted.", warned_id_path.display())); | |
| // We only use the last_warned_id if it exists in `CONFIG_CHANGE_HISTORY`. | |
| // Otherwise, we may retrieve all the changes if it's not the highest value. | |
| // For better understanding, refer to `change_tracker::find_recent_config_change_ids`. | |
| if CONFIG_CHANGE_HISTORY.iter().any(|config| config.change_id == last_warned_id) { | |
| id = last_warned_id; | |
| } | |
| }; | |
| let changes = find_recent_config_change_ids(id); | |
| if changes.is_empty() { | |
| return None; | |
| } | |
| msg.push_str("There have been changes to x.py since you last updated:\n"); | |
| msg.push_str(&human_readable_changes(changes)); | |
| msg.push_str("NOTE: to silence this warning, "); | |
| msg.push_str(&format!( | |
| "update `bootstrap.toml` to use `change-id = {latest_change_id}` or `change-id = \"ignore\"` instead" | |
| )); | |
| if io::stdout().is_terminal() { | |
| t!(fs::write(warned_id_path, latest_change_id.to_string())); | |
| } | |
| Some(msg) | |
| } |