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
142
143
144
145
146
147
148
149
150
151
152
153
//! # toolchain
//!
//! Toolchain related utilify functions.
//!

#[cfg(test)]
#[path = "toolchain_test.rs"]
mod toolchain_test;

use crate::environment::expand_value;
use crate::types::{CommandSpec, ToolchainSpecifier};
use envmnt;
use semver::{Prerelease, Version};
use std::process::{Command, Stdio};

pub(crate) fn get_channel(toolchain: &ToolchainSpecifier) -> String {
    let channel = toolchain.channel().to_string();
    expand_value(&channel)
}

pub(crate) fn wrap_command(
    toolchain: &ToolchainSpecifier,
    command: &str,
    args: &Option<Vec<String>>,
) -> CommandSpec {
    check_toolchain(toolchain);

    let channel = get_channel(&toolchain);
    if channel.is_empty() {
        let mut command_args = vec![];
        if let Some(array) = args {
            for arg in array.iter() {
                command_args.push(arg.to_string());
            }
        };

        CommandSpec {
            command: command.to_string(),
            args: Some(command_args),
        }
    } else {
        let mut rustup_args = vec!["run".to_string(), channel, command.to_string()];
        if let Some(array) = args {
            for arg in array.iter() {
                rustup_args.push(arg.to_string());
            }
        };

        CommandSpec {
            command: "rustup".to_string(),
            args: Some(rustup_args),
        }
    }
}

fn get_specified_min_version(toolchain: &ToolchainSpecifier) -> Option<Version> {
    let min_version = toolchain.min_version()?;
    let spec_min_version = min_version.parse::<Version>();
    if let Err(_) = spec_min_version {
        warn!("Unable to parse min version value: {}", &min_version);
    }
    spec_min_version.ok()
}

fn check_toolchain(toolchain: &ToolchainSpecifier) {
    let channel = get_channel(&toolchain);

    if channel.is_empty() {
        return;
    }

    let output = Command::new("rustup")
        .args(&["run", &channel, "rustc", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::piped())
        .output()
        .expect("Failed to check rustup toolchain");
    if !output.status.success() {
        error!(
            "Missing toolchain {}! Please install it using rustup.",
            &channel
        );
        return;
    }

    let spec_min_version = get_specified_min_version(toolchain);
    if let Some(ref spec_min_version) = spec_min_version {
        let rustc_version = String::from_utf8_lossy(&output.stdout);
        let rustc_version = rustc_version
            .split(" ")
            .nth(1)
            .expect("expected a version in rustc output");
        let mut rustc_version = rustc_version
            .parse::<Version>()
            .expect("unexpected version format");
        // Remove prerelease identifiers from the output of rustc. Specifying a toolchain
        // channel means the user actively chooses beta or nightly (or a custom one).
        //
        // Direct comparison with rustc output would otherwise produce unintended results:
        // `{ channel = "beta", min_version = "1.56" }` is expected to work with
        // `rustup run beta rustc --version` ==> "rustc 1.56.0-beta.4 (e6e620e1c 2021-10-04)"
        // so we would have 1.56.0-beta.4 < 1.56 according to semver
        rustc_version.pre = Prerelease::EMPTY;

        if &rustc_version < spec_min_version {
            error!(
                "Installed toolchain {} is required to satisfy version {}, found {}! Please upgrade it using rustup.",
                &channel,
                &spec_min_version,
                rustc_version,
            );
        }
    }
}

pub(crate) fn get_cargo_binary_path(toolchain: &ToolchainSpecifier) -> Option<String> {
    let command_spec = wrap_command(
        toolchain,
        "rustup",
        &Some(vec!["which".to_string(), "cargo".to_string()]),
    );
    let mut command = Command::new(&command_spec.command);
    match command_spec.args {
        Some(ref args_vec) => {
            command.args(args_vec);
        }
        None => debug!("No command args defined."),
    };

    let output = command
        .stderr(Stdio::null())
        .stdout(Stdio::piped())
        .output()
        .expect("Failed to check rustup toolchain");
    if !output.status.success() {
        error!(
            "Missing toolchain {}! Please install it using rustup.",
            &toolchain
        );
        return None;
    }

    let binary_path = String::from_utf8_lossy(&output.stdout);
    if binary_path.is_empty() {
        None
    } else {
        Some(binary_path.to_string())
    }
}

pub(crate) fn remove_rust_env_vars() {
    envmnt::remove_all(&vec!["RUSTC", "RUSTDOC", "RUSTFLAGS"]);
}