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
//! # cargo-toolchain
//!
//! `cargo-toolchain` is a utility to get the currently active and default
//! [rustup toolchains](https://doc.rust-lang.org/stable/book/appendix-07-nightly-rust.html#rustup-and-the-role-of-rust-nightly).
//!
//! It requires that [rustup](https://rustup.rs/) is installed.
//!
//! ## Usage as a CLI
//!
//! ```shell
//! cargo install cargo-toolchain
//!
//! cargo toolchain # prints the currently active cargo toolchain, e.g. 'stable'
//!
//! cargo toolchain -d # prints the default toolchain for the directory
//!
//! cargo toolchain -h # print help message
//! ```

use anyhow::Result;
use std::env;
use std::process::Command;

const RUSTUP_TOOLCHAIN_VAR: &str = "RUSTUP_TOOLCHAIN";
const DEFAULT_PROFILES: [&str; 3] = ["stable", "beta", "nightly"];

/// Get the profile that was used to call the current executable.
///
/// Affected by directory overrides and command line flags (e.g. `cargo +nightly ...`)
///
/// # Errors
///
/// If the `RUSTUP_TOOLCHAIN` variable is not set, `get_active_toolchain` falls back to [`get_directory_toolchain`](./fn.get_directory_toolchain.html).
/// That function returns an error if running `rustup` or parsing its output as utf-8 fails.
pub fn get_active_toolchain() -> Result<String> {
    match env::var(RUSTUP_TOOLCHAIN_VAR) {
        Ok(toolchain) => Ok(truncate_toolchain(toolchain)),
        _ => get_directory_toolchain(),
    }
}

/// Get the default rustup toolchain for the current directory.
///
/// Affected by directory overrides, but not command line flags (e.g. `cargo +nightly ...`)
///
/// # Errors
///
/// If the `rustup` command fails or its output cannot be interpreted as utf-8,
/// an error is returned.
pub fn get_directory_toolchain() -> Result<String> {
    let output = Command::new("rustup")
        .args(&["show", "active-toolchain"])
        .env_remove(RUSTUP_TOOLCHAIN_VAR)
        .output()?;
    let toolchain = String::from_utf8(output.stdout)?;

    Ok(truncate_toolchain(toolchain))
}

/// Truncate the triple from a toolchain name if it's one of the defaults.
///
/// # Examples
///
/// ```rust
/// let toolchain = cargo_toolchain::truncate_toolchain(String::from("nightly-x86_64-unknown-linux-gnu"));
///
/// assert_eq!(toolchain, "nightly");
///
/// let toolchain = cargo_toolchain::truncate_toolchain(String::from("my-custom-toolchain"));
///
/// assert_eq!(toolchain, "my-custom-toolchain");
/// ```
///
pub fn truncate_toolchain(toolchain: String) -> String {
    for default in &DEFAULT_PROFILES {
        if toolchain.starts_with(default) {
            return String::from(*default);
        }
    }

    toolchain
}

/// Wrapper around [`get_active_toolchain`](./fn.get_active_toolchain.html)
/// and [`get_directory_toolchain`](./fn.get_directory_toolchain.html)
pub fn get_toolchain(directory_default: bool) -> Result<String> {
    if directory_default {
        return get_directory_toolchain();
    }

    get_active_toolchain()
}