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
//! # toolchain
//!
//! Toolchain related utilify functions.
//!

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

use cargo_metadata::Version;
use semver::Prerelease;

use crate::types::{CommandSpec, ToolchainSpecifier};
use std::process::{Command, Stdio};

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

    let mut rustup_args = vec![
        "run".to_string(),
        toolchain.channel().to_string(),
        command.to_string(),
    ];

    match args {
        Some(array) => {
            for arg in array.iter() {
                rustup_args.push(arg.to_string());
            }
        }
        None => (),
    };

    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 output = Command::new("rustup")
        .args(&["run", toolchain.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.",
            &toolchain
        );
        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.",
                toolchain.channel(),
                &spec_min_version,
                rustc_version,
            );
        }
    }
}