build_helper/
util.rs

1use std::fs::File;
2use std::io::{BufRead, BufReader};
3use std::path::Path;
4use std::process::Command;
5
6use crate::ci::CiEnv;
7
8/// Invokes `build_helper::util::detail_exit` with `cfg!(test)`
9///
10/// This is a macro instead of a function so that it uses `cfg(test)` in the *calling* crate, not in build helper.
11#[macro_export]
12macro_rules! exit {
13    ($code:expr) => {
14        $crate::util::detail_exit($code, cfg!(test));
15    };
16}
17
18/// If code is not 0 (successful exit status), exit status is 101 (rust's default error code.)
19/// If `is_test` true and code is an error code, it will cause a panic.
20pub fn detail_exit(code: i32, is_test: bool) -> ! {
21    // if in test and code is an error code, panic with status code provided
22    if is_test {
23        panic!("status code: {code}");
24    } else {
25        // If we're in CI, print the current bootstrap invocation command, to make it easier to
26        // figure out what exactly has failed.
27        if CiEnv::is_ci() {
28            // Skip the first argument, as it will be some absolute path to the bootstrap binary.
29            let bootstrap_args =
30                std::env::args().skip(1).map(|a| a.to_string()).collect::<Vec<_>>().join(" ");
31            eprintln!("Bootstrap failed while executing `{bootstrap_args}`");
32        }
33
34        // otherwise, exit with provided status code
35        std::process::exit(code);
36    }
37}
38
39pub fn fail(s: &str) -> ! {
40    eprintln!("\n\n{s}\n\n");
41    detail_exit(1, cfg!(test));
42}
43
44pub fn try_run(cmd: &mut Command, print_cmd_on_fail: bool) -> Result<(), ()> {
45    let status = match cmd.status() {
46        Ok(status) => status,
47        Err(e) => fail(&format!("failed to execute command: {cmd:?}\nerror: {e}")),
48    };
49    if !status.success() {
50        if print_cmd_on_fail {
51            println!(
52                "\n\ncommand did not execute successfully: {cmd:?}\n\
53                 expected success, got: {status}\n\n"
54            );
55        }
56        Err(())
57    } else {
58        Ok(())
59    }
60}
61
62/// Returns the submodule paths from the `.gitmodules` file in the given directory.
63pub fn parse_gitmodules(target_dir: &Path) -> Vec<String> {
64    let gitmodules = target_dir.join(".gitmodules");
65    assert!(gitmodules.exists(), "'{}' file is missing.", gitmodules.display());
66
67    let file = File::open(gitmodules).unwrap();
68
69    let mut submodules_paths = vec![];
70    for line in BufReader::new(file).lines().map_while(Result::ok) {
71        let line = line.trim();
72        if line.starts_with("path") {
73            let actual_path = line.split(' ').next_back().expect("Couldn't get value of path");
74            submodules_paths.push(actual_path.to_owned());
75        }
76    }
77
78    submodules_paths
79}