diff --git a/.gitignore b/.gitignore index 1c50d9b054d..5f7135e38d1 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ __pycache__/ /inst/ /llvm/ /mingw-build/ +/src/tools/x/target # Created by default with `src/ci/docker/run.sh`: /obj/ /unicode-downloads diff --git a/Cargo.toml b/Cargo.toml index c27e5c469cf..e1a36d88086 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,11 +29,17 @@ members = [ "src/tools/unicode-table-generator", "src/tools/expand-yaml-anchors", ] + exclude = [ "build", "compiler/rustc_codegen_cranelift", # HACK(eddyb) This hardcodes the fact that our CI uses `/checkout/obj`. "obj", + # The `x` binary is a thin wrapper that calls `x.py`, which initializes + # submodules, before which workspace members cannot be invoked because + # not all `Cargo.toml` files are available, so we exclude the `x` binary, + # so it can be invoked before the current checkout is set up. + "src/tools/x", ] [profile.release.package.compiler_builtins] diff --git a/src/tools/x/Cargo.lock b/src/tools/x/Cargo.lock new file mode 100644 index 00000000000..723d6cb25ed --- /dev/null +++ b/src/tools/x/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "x" +version = "0.1.0" diff --git a/src/tools/x/Cargo.toml b/src/tools/x/Cargo.toml new file mode 100644 index 00000000000..72c4948c617 --- /dev/null +++ b/src/tools/x/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "x" +version = "0.1.0" +description = "Run x.py slightly more conveniently" +authors = ["Casey Rodarmor "] +edition = "2018" +publish = false diff --git a/src/tools/x/README.md b/src/tools/x/README.md new file mode 100644 index 00000000000..3b3cf2847c2 --- /dev/null +++ b/src/tools/x/README.md @@ -0,0 +1,3 @@ +# x + +`x` invokes `x.py` from any subdirectory. diff --git a/src/tools/x/src/main.rs b/src/tools/x/src/main.rs new file mode 100644 index 00000000000..6c0311433d6 --- /dev/null +++ b/src/tools/x/src/main.rs @@ -0,0 +1,92 @@ +//! Run `x.py` 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 `python`, `python3`, or `python2` as the python interpreter to run +//! `x.py`, in that order of preference. + +use std::{ + env, io, + process::{self, Command, ExitStatus}, +}; + +const PYTHON: &str = "python"; +const PYTHON2: &str = "python2"; +const PYTHON3: &str = "python3"; + +fn python() -> &'static str { + let val = match env::var_os("PATH") { + Some(val) => val, + None => return PYTHON, + }; + + let mut python2 = false; + let mut python3 = false; + + for dir in env::split_paths(&val) { + if dir.join(PYTHON).exists() { + return PYTHON; + } + + python2 |= dir.join(PYTHON2).exists(); + python3 |= dir.join(PYTHON3).exists(); + } + + if python3 { + PYTHON3 + } else if python2 { + PYTHON2 + } else { + PYTHON + } +} + +#[cfg(unix)] +fn exec_or_status(command: &mut Command) -> io::Result { + use std::os::unix::process::CommandExt; + Err(command.exec()) +} + +#[cfg(not(unix))] +fn exec_or_status(command: &mut Command) -> io::Result { + command.status() +} + +fn main() { + 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 mut python = Command::new(python()); + + python.arg(&candidate).args(env::args().skip(1)).current_dir(dir); + + let result = exec_or_status(&mut python); + + match result { + Err(error) => { + eprintln!("Failed to invoke `{}`: {}", candidate.display(), error); + } + Ok(status) => { + process::exit(status.code().unwrap_or(1)); + } + } + } + } + + eprintln!( + "x.py not found. Please run inside of a checkout of `https://github.com/rust-lang/rust`." + ); + + process::exit(1); +}