cli/
toolchain.rs

1//! # toolchain
2//!
3//! Toolchain related utilify functions.
4//!
5
6#[cfg(test)]
7#[path = "toolchain_test.rs"]
8mod toolchain_test;
9
10use crate::environment::expand_value;
11use crate::types::{CommandSpec, ToolchainSpecifier};
12use semver::{Prerelease, Version};
13use std::process::{Command, Stdio};
14
15pub(crate) fn get_channel(toolchain: &ToolchainSpecifier) -> String {
16    let channel = toolchain.channel().to_string();
17    expand_value(&channel)
18}
19
20pub(crate) fn wrap_command(
21    toolchain: &ToolchainSpecifier,
22    command: &str,
23    args: &Option<Vec<String>>,
24) -> CommandSpec {
25    check_toolchain(toolchain);
26
27    let channel = get_channel(&toolchain);
28    if channel.is_empty() {
29        let mut command_args = vec![];
30        if let Some(array) = args {
31            for arg in array.iter() {
32                command_args.push(arg.to_string());
33            }
34        };
35
36        CommandSpec {
37            command: command.to_string(),
38            args: Some(command_args),
39        }
40    } else {
41        let mut rustup_args = vec!["run".to_string(), channel, command.to_string()];
42        if let Some(array) = args {
43            for arg in array.iter() {
44                rustup_args.push(arg.to_string());
45            }
46        };
47
48        CommandSpec {
49            command: "rustup".to_string(),
50            args: Some(rustup_args),
51        }
52    }
53}
54
55fn get_specified_min_version(toolchain: &ToolchainSpecifier) -> Option<Version> {
56    let min_version = toolchain.min_version()?;
57    let spec_min_version = min_version.parse::<Version>();
58    if let Err(_) = spec_min_version {
59        warn!("Unable to parse min version value: {}", &min_version);
60    }
61    spec_min_version.ok()
62}
63
64fn check_toolchain(toolchain: &ToolchainSpecifier) {
65    let channel = get_channel(&toolchain);
66
67    if channel.is_empty() {
68        return;
69    }
70
71    let output = Command::new("rustup")
72        .args(&["run", &channel, "rustc", "--version"])
73        .stderr(Stdio::null())
74        .stdout(Stdio::piped())
75        .output()
76        .expect("Failed to check rustup toolchain");
77    if !output.status.success() {
78        error!(
79            "Missing toolchain {}! Please install it using rustup.",
80            &channel
81        );
82        return;
83    }
84
85    let spec_min_version = get_specified_min_version(toolchain);
86    if let Some(ref spec_min_version) = spec_min_version {
87        let rustc_version = String::from_utf8_lossy(&output.stdout);
88        let rustc_version = rustc_version
89            .split(" ")
90            .nth(1)
91            .expect("expected a version in rustc output");
92        let mut rustc_version = rustc_version
93            .parse::<Version>()
94            .expect("unexpected version format");
95        // Remove prerelease identifiers from the output of rustc. Specifying a toolchain
96        // channel means the user actively chooses beta or nightly (or a custom one).
97        //
98        // Direct comparison with rustc output would otherwise produce unintended results:
99        // `{ channel = "beta", min_version = "1.56" }` is expected to work with
100        // `rustup run beta rustc --version` ==> "rustc 1.56.0-beta.4 (e6e620e1c 2021-10-04)"
101        // so we would have 1.56.0-beta.4 < 1.56 according to semver
102        rustc_version.pre = Prerelease::EMPTY;
103
104        if &rustc_version < spec_min_version {
105            error!(
106                "Installed toolchain {} is required to satisfy version {}, found {}! Please upgrade it using rustup.",
107                &channel,
108                &spec_min_version,
109                rustc_version,
110            );
111        }
112    }
113}
114
115pub(crate) fn get_cargo_binary_path(toolchain: &ToolchainSpecifier) -> Option<String> {
116    let command_spec = wrap_command(
117        toolchain,
118        "rustup",
119        &Some(vec!["which".to_string(), "cargo".to_string()]),
120    );
121    let mut command = Command::new(&command_spec.command);
122    match command_spec.args {
123        Some(ref args_vec) => {
124            command.args(args_vec);
125        }
126        None => debug!("No command args defined."),
127    };
128
129    let output = command
130        .stderr(Stdio::null())
131        .stdout(Stdio::piped())
132        .output()
133        .expect("Failed to check rustup toolchain");
134    if !output.status.success() {
135        error!(
136            "Missing toolchain {}! Please install it using rustup.",
137            &toolchain
138        );
139        return None;
140    }
141
142    let binary_path = String::from_utf8_lossy(&output.stdout);
143    if binary_path.is_empty() {
144        None
145    } else {
146        Some(binary_path.to_string())
147    }
148}
149
150pub(crate) fn remove_rust_env_vars() {
151    envmnt::remove_all(&vec!["CARGO", "RUSTC", "RUSTDOC"]);
152}