| //! Run bootstrap from any subdirectory of a rust compiler checkout. |
| //! |
| //! We prefer `exec`, to avoid adding an extra process in the process tree. |
| //! However, since `exec` isn't available on Windows, we indirect through |
| //! `exec_or_status`, which will call `exec` on unix and `status` on Windows. |
| //! |
| //! We use `powershell.exe x.ps1` on Windows, and `sh -c x` on Unix, those are |
| //! the ones that call `x.py`. We use `sh -c` on Unix, because it is a standard. |
| //! We also don't use `pwsh` on Windows, because it is not installed by default; |
| |
| use std::env::consts::EXE_EXTENSION; |
| use std::env::{self}; |
| use std::io; |
| use std::path::Path; |
| use std::process::{self, Command, ExitStatus}; |
| |
| const PYTHON: &str = "python"; |
| const PYTHON2: &str = "python2"; |
| const PYTHON3: &str = "python3"; |
| |
| fn python() -> &'static str { |
| let Some(path) = env::var_os("PATH") else { |
| return PYTHON; |
| }; |
| |
| let mut python2 = false; |
| let mut python3 = false; |
| |
| for dir in env::split_paths(&path) { |
| // `python` should always take precedence over python2 / python3 if it exists |
| if dir.join(PYTHON).with_extension(EXE_EXTENSION).exists() { |
| return PYTHON; |
| } |
| |
| python2 |= dir.join(PYTHON2).with_extension(EXE_EXTENSION).exists(); |
| python3 |= dir.join(PYTHON3).with_extension(EXE_EXTENSION).exists(); |
| } |
| |
| // try 3 before 2 |
| if python3 { |
| PYTHON3 |
| } else if python2 { |
| PYTHON2 |
| } else { |
| // Python was not found on path, so exit |
| eprintln!("Unable to find python in your PATH. Please check it is installed."); |
| process::exit(1); |
| } |
| } |
| |
| #[cfg(windows)] |
| fn x_command(dir: &Path) -> Command { |
| let mut cmd = Command::new("powershell.exe"); |
| cmd.args([ |
| "-NoLogo", |
| "-NoProfile", |
| "-NonInteractive", |
| "-ExecutionPolicy", |
| "RemoteSigned", |
| "-Command", |
| "./x.ps1", |
| ]) |
| .current_dir(dir); |
| cmd |
| } |
| |
| #[cfg(unix)] |
| fn x_command(dir: &Path) -> Command { |
| Command::new(dir.join("x")) |
| } |
| |
| #[cfg(not(any(windows, unix)))] |
| fn x_command(_dir: &Path) -> Command { |
| compile_error!("Unsupported platform"); |
| } |
| |
| #[cfg(unix)] |
| fn exec_or_status(command: &mut Command) -> io::Result<ExitStatus> { |
| use std::os::unix::process::CommandExt; |
| Err(command.exec()) |
| } |
| |
| #[cfg(not(unix))] |
| fn exec_or_status(command: &mut Command) -> io::Result<ExitStatus> { |
| command.status() |
| } |
| |
| fn handle_result(result: io::Result<ExitStatus>, cmd: Command) { |
| match result { |
| Err(error) => { |
| eprintln!("Failed to invoke `{cmd:?}`: {error}"); |
| } |
| Ok(status) => { |
| process::exit(status.code().unwrap_or(1)); |
| } |
| } |
| } |
| |
| fn main() { |
| if env::args().nth(1).is_some_and(|s| s == "--wrapper-version") { |
| let version = env!("CARGO_PKG_VERSION"); |
| println!("{version}"); |
| return; |
| } |
| |
| let current = match env::current_dir() { |
| Ok(dir) => dir, |
| Err(err) => { |
| eprintln!("Failed to get current directory: {err}"); |
| process::exit(1); |
| } |
| }; |
| for dir in current.ancestors() { |
| let candidate = dir.join("x.py"); |
| if candidate.exists() { |
| let shell_script_candidate = dir.join("x"); |
| let mut cmd: Command; |
| if shell_script_candidate.exists() { |
| cmd = x_command(dir); |
| cmd.args(env::args().skip(1)).current_dir(dir); |
| } else { |
| // For older checkouts that do not have the x shell script, default to python |
| cmd = Command::new(python()); |
| cmd.arg(&candidate).args(env::args().skip(1)).current_dir(dir); |
| } |
| let result = exec_or_status(&mut cmd); |
| handle_result(result, cmd); |
| } |
| } |
| |
| eprintln!( |
| "x.py not found. Please run inside of a checkout of `https://github.com/rust-lang/rust`." |
| ); |
| |
| process::exit(1); |
| } |