about summary refs log tree commit diff
path: root/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs
blob: b1faa27acc4a2362176ea33279a53f8a261d80f6 (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
use std::collections::HashMap;
use std::ffi::OsStr;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::path::PathBuf;

use crate::config::ConfigInfo;
use crate::utils::{get_toolchain, rustc_toolchain_version_info, rustc_version_info};

fn args(command: &str) -> Result<Option<Vec<String>>, String> {
    // We skip the binary and the "cargo"/"rustc" option.
    if let Some("--help") = std::env::args().nth(2).as_deref() {
        usage(command);
        return Ok(None);
    }
    let args = std::env::args().skip(2).collect::<Vec<_>>();
    if args.is_empty() {
        return Err(format!(
            "Expected at least one argument for `{command}` subcommand, found none"
        ));
    }
    Ok(Some(args))
}

fn usage(command: &str) {
    println!(
        r#"
`{command}` command help:

    [args]     : Arguments to be passed to the cargo command
    --help     : Show this help
"#,
    )
}

struct RustcTools {
    env: HashMap<String, String>,
    args: Vec<String>,
    toolchain: String,
    config: ConfigInfo,
}

impl RustcTools {
    fn new(command: &str) -> Result<Option<Self>, String> {
        let Some(args) = args(command)? else { return Ok(None) };

        // We first need to go to the original location to ensure that the config setup will go as
        // expected.
        let current_dir = std::env::current_dir()
            .and_then(|path| path.canonicalize())
            .map_err(|error| format!("Failed to get current directory path: {error:?}"))?;
        let current_exe = std::env::current_exe()
            .and_then(|path| path.canonicalize())
            .map_err(|error| format!("Failed to get current exe path: {error:?}"))?;
        let mut parent_dir =
            current_exe.components().map(|comp| comp.as_os_str()).collect::<Vec<_>>();
        // We run this script from "build_system/target/release/y", so we need to remove these elements.
        for to_remove in &["y", "release", "target", "build_system"] {
            if parent_dir.last().map(|part| part == to_remove).unwrap_or(false) {
                parent_dir.pop();
            } else {
                return Err(format!(
                    "Build script not executed from `build_system/target/release/y` (in path {})",
                    current_exe.display(),
                ));
            }
        }
        let parent_dir = PathBuf::from(parent_dir.join(OsStr::new("/")));
        std::env::set_current_dir(&parent_dir).map_err(|error| {
            format!("Failed to go to `{}` folder: {:?}", parent_dir.display(), error)
        })?;

        let mut env: HashMap<String, String> = std::env::vars().collect();
        let mut config = ConfigInfo::default();
        config.setup(&mut env, false)?;
        let toolchain = get_toolchain()?;

        let toolchain_version = rustc_toolchain_version_info(&toolchain)?;
        let default_version = rustc_version_info(None)?;
        if toolchain_version != default_version {
            println!(
                "rustc_codegen_gcc is built for {} but the default rustc version is {}.",
                toolchain_version.short, default_version.short,
            );
            println!("Using {}.", toolchain_version.short);
        }

        // We go back to the original folder since we now have set up everything we needed.
        std::env::set_current_dir(&current_dir).map_err(|error| {
            format!("Failed to go back to `{}` folder: {:?}", current_dir.display(), error)
        })?;
        let toolchain = format!("+{toolchain}");
        Ok(Some(Self { toolchain, args, env, config }))
    }
}

fn exec(input: &[&dyn AsRef<OsStr>], env: &HashMap<String, String>) -> Result<(), String> {
    #[cfg(unix)]
    {
        // We use `exec` to call the `execvp` syscall instead of creating a new process where the
        // command will be executed because very few signals can actually kill a current process,
        // so if segmentation fault (SIGSEGV signal) happens and we raise to the current process,
        // it will simply do nothing and we won't have the nice error message for the shell.
        let error = crate::utils::get_command_inner(input, None, Some(env)).exec();
        eprintln!("execvp syscall failed: {error:?}");
        std::process::exit(1);
    }
    #[cfg(not(unix))]
    {
        if crate::utils::run_command_with_output_and_env_no_err(input, None, Some(env)).is_err() {
            std::process::exit(1);
        }
        Ok(())
    }
}

pub fn run_cargo() -> Result<(), String> {
    let Some(mut tools) = RustcTools::new("cargo")? else { return Ok(()) };
    let rustflags = tools.env.get("RUSTFLAGS").cloned().unwrap_or_default();
    tools.env.insert("RUSTDOCFLAGS".to_string(), rustflags);
    let mut command: Vec<&dyn AsRef<OsStr>> = vec![&"cargo", &tools.toolchain];
    for arg in &tools.args {
        command.push(arg);
    }
    exec(&command, &tools.env)
}

pub fn run_rustc() -> Result<(), String> {
    let Some(tools) = RustcTools::new("rustc")? else { return Ok(()) };
    let mut command = tools.config.rustc_command_vec();
    for arg in &tools.args {
        command.push(arg);
    }
    exec(&command, &tools.env)
}