summaryrefslogtreecommitdiffstats
path: root/src/tools/x/src/main.rs
blob: 5da8a2888ec8385c6c6ab54a0189a787182e72c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! 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::{self, consts::EXE_EXTENSION},
    io,
    path::Path,
    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) {
        // `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() {
    match env::args().skip(1).next().as_deref() {
        Some("--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);
}