Skip to main content

bootstrap/utils/
shared_helpers.rs

1//! This module serves two purposes:
2//!
3//! 1. It is part of the `utils` module and used in other parts of bootstrap.
4//! 2. It is embedded inside bootstrap shims to avoid a dependency on the bootstrap library.
5//!    Therefore, this module should never use any other bootstrap module. This reduces binary size
6//!    and improves compilation time by minimizing linking time.
7
8// # Note on tests
9//
10// If we were to declare a tests submodule here, the shim binaries that include this module via
11// `#[path]` would fail to find it, which breaks `./x check bootstrap`. So instead the unit tests
12// for this module are in `super::tests::shared_helpers_tests`.
13
14#![allow(dead_code)]
15
16use std::env;
17use std::ffi::OsString;
18use std::fs::OpenOptions;
19use std::io::{BufRead, Write};
20use std::path::Path;
21use std::process::Command;
22use std::str::FromStr;
23
24/// Returns the environment variable which the dynamic library lookup path
25/// resides in for this platform.
26pub fn dylib_path_var() -> &'static str {
27    if cfg!(any(target_os = "windows", target_os = "cygwin")) {
28        "PATH"
29    } else if cfg!(target_vendor = "apple") {
30        "DYLD_LIBRARY_PATH"
31    } else if cfg!(target_os = "haiku") {
32        "LIBRARY_PATH"
33    } else if cfg!(target_os = "aix") {
34        "LIBPATH"
35    } else {
36        "LD_LIBRARY_PATH"
37    }
38}
39
40/// Parses the `dylib_path_var()` environment variable, returning a list of
41/// paths that are members of this lookup path.
42pub fn dylib_path() -> Vec<std::path::PathBuf> {
43    let var = match std::env::var_os(dylib_path_var()) {
44        Some(v) => v,
45        None => return vec![],
46    };
47    std::env::split_paths(&var).collect()
48}
49
50/// Given an executable called `name`, return the filename for the
51/// executable for a particular target.
52pub fn exe(name: &str, target: &str) -> String {
53    // On Cygwin, the decision to append .exe or not is not as straightforward.
54    // Executable files do actually have .exe extensions so on hosts other than
55    // Cygwin it is necessary.  But on a Cygwin host there is magic happening
56    // that redirects requests for file X to file X.exe if it exists, and
57    // furthermore /proc/self/exe (and thus std::env::current_exe) always
58    // returns the name *without* the .exe extension.  For comparisons against
59    // that to match, we therefore do not append .exe for Cygwin targets on
60    // a Cygwin host.
61    if target.contains("windows") || (cfg!(not(target_os = "cygwin")) && target.contains("cygwin"))
62    {
63        format!("{name}.exe")
64    } else if target.contains("uefi") {
65        format!("{name}.efi")
66    } else if target.contains("wasm") {
67        format!("{name}.wasm")
68    } else {
69        name.to_string()
70    }
71}
72
73/// Parses the value of the "RUSTC_VERBOSE" environment variable and returns it as a `usize`.
74/// If it was not defined, returns 0 by default.
75///
76/// Panics if "RUSTC_VERBOSE" is defined with the value that is not an unsigned integer.
77pub fn parse_rustc_verbose() -> usize {
78    match env::var("RUSTC_VERBOSE") {
79        Ok(s) => usize::from_str(&s).expect("RUSTC_VERBOSE should be an integer"),
80        Err(_) => 0,
81    }
82}
83
84/// Parses the value of the "RUSTC_STAGE" environment variable and returns it as a `String`.
85/// This is the stage of the *build compiler*, which we are wrapping using a rustc/rustdoc wrapper.
86///
87/// If "RUSTC_STAGE" was not set, the program will be terminated with 101.
88pub fn parse_rustc_stage() -> u32 {
89    env::var("RUSTC_STAGE").ok().and_then(|v| v.parse().ok()).unwrap_or_else(|| {
90        // Don't panic here; it's reasonable to try and run these shims directly. Give a helpful error instead.
91        eprintln!("rustc shim: FATAL: RUSTC_STAGE was not set");
92        eprintln!("rustc shim: NOTE: use `x.py build -vvv` to see all environment variables set by bootstrap");
93        std::process::exit(101);
94    })
95}
96
97/// Writes the command invocation to a file if `DUMP_BOOTSTRAP_SHIMS` is set during bootstrap.
98///
99/// Before writing it, replaces user-specific values to create generic dumps for cross-environment
100/// comparisons.
101pub fn maybe_dump(dump_name: String, cmd: &Command) {
102    if let Ok(dump_dir) = env::var("DUMP_BOOTSTRAP_SHIMS") {
103        let dump_file = format!("{dump_dir}/{dump_name}");
104
105        let mut file = OpenOptions::new().create(true).append(true).open(dump_file).unwrap();
106
107        let cmd_dump = format!("{cmd:?}\n");
108        let cmd_dump = cmd_dump.replace(&env::var("BUILD_OUT").unwrap(), "${BUILD_OUT}");
109        let cmd_dump = cmd_dump.replace(&env::var("CARGO_HOME").unwrap(), "${CARGO_HOME}");
110
111        file.write_all(cmd_dump.as_bytes()).expect("Unable to write file");
112    }
113}
114
115/// Finds `key` and returns its value from the given list of arguments `args`.
116pub fn parse_value_from_args<'a>(args: &'a [OsString], key: &str) -> Option<&'a str> {
117    let mut args = args.iter();
118    while let Some(arg) = args.next() {
119        let arg = arg.to_str().unwrap();
120
121        if let Some(value) = arg.strip_prefix(&format!("{key}=")) {
122            return Some(value);
123        } else if arg == key {
124            return args.next().map(|v| v.to_str().unwrap());
125        }
126    }
127
128    None
129}
130
131/// Collect all the command line arguments, including the arguments from any `@argfile`
132pub fn collect_args() -> Vec<OsString> {
133    let mut args = Vec::with_capacity(env::args_os().len());
134    for arg in env::args_os().skip(1) {
135        if let Some(s) = arg.to_str()
136            && let Some(path) = s.strip_prefix('@')
137        {
138            args.extend(args_from_argfile(Path::new(path)));
139        } else {
140            args.push(arg)
141        }
142    }
143    args
144}
145
146/// Reads all the arguments from argfile given by `path`.
147/// Each argument should be on a line by itself
148fn args_from_argfile(path: &Path) -> Vec<OsString> {
149    fn collect_lines(path: &Path) -> Result<Vec<OsString>, std::io::Error> {
150        let file = std::fs::File::open(path)?;
151        let lines: Result<Vec<OsString>, std::io::Error> =
152            std::io::BufReader::new(file).lines().map(|r| r.map(OsString::from)).collect();
153        lines
154    }
155    collect_lines(path).expect("read args from argfile {path:?}")
156}