fix: Prevent `rust` module from installing toolchains (#426)
Prevents the rust module from installing rustup toolchains. Previously, the rust module would trigger rustup toolchain installations in some cases, leading to frozen prompts while rustup downloads/installs components. This commit changes the behaviour to avoid this.
This commit is contained in:
parent
d335abca3d
commit
b7dc6c5e96
|
@ -1,5 +1,8 @@
|
|||
use ansi_term::Color;
|
||||
use std::process::Command;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Output};
|
||||
use std::{env, fs};
|
||||
|
||||
use super::{Context, Module};
|
||||
|
||||
|
@ -9,6 +12,8 @@ use super::{Context, Module};
|
|||
/// - Current directory contains a file with a `.rs` extension
|
||||
/// - Current directory contains a `Cargo.toml` file
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
const RUST_CHAR: &str = "🦀 ";
|
||||
|
||||
let is_rs_project = context
|
||||
.try_begin_scan()?
|
||||
.set_files(&["Cargo.toml"])
|
||||
|
@ -19,27 +24,137 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||
return None;
|
||||
}
|
||||
|
||||
match get_rust_version() {
|
||||
Some(rust_version) => {
|
||||
const RUST_CHAR: &str = "🦀 ";
|
||||
|
||||
let mut module = context.new_module("rust");
|
||||
let module_style = module
|
||||
.config_value_style("style")
|
||||
.unwrap_or_else(|| Color::Red.bold());
|
||||
module.set_style(module_style);
|
||||
|
||||
let formatted_version = format_rustc_version(rust_version);
|
||||
module.new_segment("symbol", RUST_CHAR);
|
||||
module.new_segment("version", &formatted_version);
|
||||
|
||||
Some(module)
|
||||
// `$CARGO_HOME/bin/rustc(.exe) --version` may attempt installing a rustup toolchain.
|
||||
// https://github.com/starship/starship/issues/417
|
||||
//
|
||||
// To display appropriate versions preventing `rustc` from downloading toolchains, we have to
|
||||
// check
|
||||
// 1. `$RUSTUP_TOOLCHAIN`
|
||||
// 2. `rustup override list`
|
||||
// 3. `rust-toolchain` in `.` or parent directories
|
||||
// as `rustup` does.
|
||||
// https://github.com/rust-lang/rustup.rs/tree/eb694fcada7becc5d9d160bf7c623abe84f8971d#override-precedence
|
||||
//
|
||||
// Probably we have no other way to know whether any toolchain override is specified for the
|
||||
// current directory. The following commands also cause toolchain installations.
|
||||
// - `rustup show`
|
||||
// - `rustup show active-toolchain`
|
||||
// - `rustup which`
|
||||
let module_version = if let Some(toolchain) = env_rustup_toolchain()
|
||||
.or_else(|| execute_rustup_override_list(&context.current_dir))
|
||||
.or_else(|| find_rust_toolchain_file(&context))
|
||||
{
|
||||
match execute_rustup_run_rustc_version(&toolchain) {
|
||||
RustupRunRustcVersionOutcome::RustcVersion(stdout) => format_rustc_version(stdout),
|
||||
RustupRunRustcVersionOutcome::ToolchainName(toolchain) => toolchain,
|
||||
RustupRunRustcVersionOutcome::RustupNotWorking => {
|
||||
// If `rustup` is not in `$PATH` or cannot be executed for other reasons, we can
|
||||
// safely execute `rustc --version`.
|
||||
format_rustc_version(execute_rustc_version()?)
|
||||
}
|
||||
RustupRunRustcVersionOutcome::Err => return None,
|
||||
}
|
||||
None => None,
|
||||
} else {
|
||||
format_rustc_version(execute_rustc_version()?)
|
||||
};
|
||||
|
||||
let mut module = context.new_module("rust");
|
||||
|
||||
let module_style = module
|
||||
.config_value_style("style")
|
||||
.unwrap_or_else(|| Color::Red.bold());
|
||||
module.set_style(module_style);
|
||||
module.new_segment("symbol", RUST_CHAR);
|
||||
module.new_segment("version", &module_version);
|
||||
|
||||
Some(module)
|
||||
}
|
||||
|
||||
fn env_rustup_toolchain() -> Option<String> {
|
||||
let val = env::var("RUSTUP_TOOLCHAIN").ok()?;
|
||||
Some(val.trim().to_owned())
|
||||
}
|
||||
|
||||
fn execute_rustup_override_list(cwd: &Path) -> Option<String> {
|
||||
let Output { stdout, .. } = Command::new("rustup")
|
||||
.args(&["override", "list"])
|
||||
.output()
|
||||
.ok()?;
|
||||
let stdout = String::from_utf8(stdout).ok()?;
|
||||
extract_toolchain_from_rustup_override_list(&stdout, cwd)
|
||||
}
|
||||
|
||||
fn extract_toolchain_from_rustup_override_list(stdout: &str, cwd: &Path) -> Option<String> {
|
||||
if stdout == "no overrides\n" {
|
||||
return None;
|
||||
}
|
||||
stdout
|
||||
.lines()
|
||||
.flat_map(|line| {
|
||||
let mut words = line.split_whitespace();
|
||||
let dir = words.next()?;
|
||||
let toolchain = words.next()?;
|
||||
Some((dir, toolchain))
|
||||
})
|
||||
.find(|(dir, _)| cwd.starts_with(dir))
|
||||
.map(|(_, toolchain)| toolchain.to_owned())
|
||||
}
|
||||
|
||||
fn find_rust_toolchain_file(context: &Context) -> Option<String> {
|
||||
// Look for 'rust-toolchain' as rustup does.
|
||||
// https://github.com/rust-lang/rustup.rs/blob/d84e6e50126bccd84649e42482fc35a11d019401/src/config.rs#L320-L358
|
||||
|
||||
fn read_first_line(path: &Path) -> Option<String> {
|
||||
let content = fs::read_to_string(path).ok()?;
|
||||
let line = content.lines().next()?;
|
||||
Some(line.trim().to_owned())
|
||||
}
|
||||
|
||||
if let Some(path) = context
|
||||
.get_dir_files()
|
||||
.ok()?
|
||||
.iter()
|
||||
.find(|p| p.file_name() == Some(OsStr::new("rust-toolchain")))
|
||||
{
|
||||
if let Some(toolchain) = read_first_line(path) {
|
||||
return Some(toolchain);
|
||||
}
|
||||
}
|
||||
|
||||
let mut dir = &*context.current_dir;
|
||||
loop {
|
||||
if let Some(toolchain) = read_first_line(&dir.join("rust-toolchain")) {
|
||||
return Some(toolchain);
|
||||
}
|
||||
dir = dir.parent()?;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rust_version() -> Option<String> {
|
||||
fn execute_rustup_run_rustc_version(toolchain: &str) -> RustupRunRustcVersionOutcome {
|
||||
Command::new("rustup")
|
||||
.args(&["run", toolchain, "rustc", "--version"])
|
||||
.output()
|
||||
.map(extract_toolchain_from_rustup_run_rustc_version)
|
||||
.unwrap_or(RustupRunRustcVersionOutcome::RustupNotWorking)
|
||||
}
|
||||
|
||||
fn extract_toolchain_from_rustup_run_rustc_version(output: Output) -> RustupRunRustcVersionOutcome {
|
||||
if output.status.success() {
|
||||
if let Ok(output) = String::from_utf8(output.stdout) {
|
||||
return RustupRunRustcVersionOutcome::RustcVersion(output);
|
||||
}
|
||||
} else if let Ok(stderr) = String::from_utf8(output.stderr) {
|
||||
if stderr.starts_with("error: toolchain '") && stderr.ends_with("' is not installed\n") {
|
||||
let stderr = stderr
|
||||
["error: toolchain '".len()..stderr.len() - "' is not installed\n".len()]
|
||||
.to_owned();
|
||||
return RustupRunRustcVersionOutcome::ToolchainName(stderr);
|
||||
}
|
||||
}
|
||||
RustupRunRustcVersionOutcome::Err
|
||||
}
|
||||
|
||||
fn execute_rustc_version() -> Option<String> {
|
||||
match Command::new("rustc").arg("--version").output() {
|
||||
Ok(output) => Some(String::from_utf8(output.stdout).unwrap()),
|
||||
Err(_) => None,
|
||||
|
@ -53,10 +168,112 @@ fn format_rustc_version(mut rustc_stdout: String) -> String {
|
|||
format!("v{}", formatted_version.replace("rustc", "").trim())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum RustupRunRustcVersionOutcome {
|
||||
RustcVersion(String),
|
||||
ToolchainName(String),
|
||||
RustupNotWorking,
|
||||
Err,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use once_cell::sync::Lazy;
|
||||
use std::process::{ExitStatus, Output};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_toolchain_from_rustup_override_list() {
|
||||
static NO_OVERRIDES_INPUT: &str = "no overrides\n";
|
||||
static NO_OVERRIDES_CWD: &str = "";
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_override_list(
|
||||
NO_OVERRIDES_INPUT,
|
||||
NO_OVERRIDES_CWD.as_ref(),
|
||||
),
|
||||
None,
|
||||
);
|
||||
|
||||
static OVERRIDES_INPUT: &str =
|
||||
"/home/user/src/a beta-x86_64-unknown-linux-gnu\n\
|
||||
/home/user/src/b nightly-x86_64-unknown-linux-gnu\n";
|
||||
static OVERRIDES_CWD_A: &str = "/home/user/src/a/src";
|
||||
static OVERRIDES_CWD_B: &str = "/home/user/src/b/tests";
|
||||
static OVERRIDES_CWD_C: &str = "/home/user/src/c/examples";
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_A.as_ref()),
|
||||
Some("beta-x86_64-unknown-linux-gnu".to_owned()),
|
||||
);
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_B.as_ref()),
|
||||
Some("nightly-x86_64-unknown-linux-gnu".to_owned()),
|
||||
);
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_override_list(OVERRIDES_INPUT, OVERRIDES_CWD_C.as_ref()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(unix, windows))]
|
||||
#[test]
|
||||
fn test_extract_toolchain_from_rustup_run_rustc_version() {
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::ExitStatusExt as _;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::ExitStatusExt as _;
|
||||
|
||||
static RUSTC_VERSION: Lazy<Output> = Lazy::new(|| Output {
|
||||
status: ExitStatus::from_raw(0),
|
||||
stdout: b"rustc 1.34.0\n"[..].to_owned(),
|
||||
stderr: vec![],
|
||||
});
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_run_rustc_version(RUSTC_VERSION.clone()),
|
||||
RustupRunRustcVersionOutcome::RustcVersion("rustc 1.34.0\n".to_owned()),
|
||||
);
|
||||
|
||||
static TOOLCHAIN_NAME: Lazy<Output> = Lazy::new(|| Output {
|
||||
status: ExitStatus::from_raw(1),
|
||||
stdout: vec![],
|
||||
stderr: b"error: toolchain 'channel-triple' is not installed\n"[..].to_owned(),
|
||||
});
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_run_rustc_version(TOOLCHAIN_NAME.clone()),
|
||||
RustupRunRustcVersionOutcome::ToolchainName("channel-triple".to_owned()),
|
||||
);
|
||||
|
||||
static INVALID_STDOUT: Lazy<Output> = Lazy::new(|| Output {
|
||||
status: ExitStatus::from_raw(0),
|
||||
stdout: b"\xc3\x28"[..].to_owned(),
|
||||
stderr: vec![],
|
||||
});
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_run_rustc_version(INVALID_STDOUT.clone()),
|
||||
RustupRunRustcVersionOutcome::Err,
|
||||
);
|
||||
|
||||
static INVALID_STDERR: Lazy<Output> = Lazy::new(|| Output {
|
||||
status: ExitStatus::from_raw(1),
|
||||
stdout: vec![],
|
||||
stderr: b"\xc3\x28"[..].to_owned(),
|
||||
});
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_run_rustc_version(INVALID_STDERR.clone()),
|
||||
RustupRunRustcVersionOutcome::Err,
|
||||
);
|
||||
|
||||
static UNEXPECTED_FORMAT_OF_ERROR: Lazy<Output> = Lazy::new(|| Output {
|
||||
status: ExitStatus::from_raw(1),
|
||||
stdout: vec![],
|
||||
stderr: b"error:"[..].to_owned(),
|
||||
});
|
||||
assert_eq!(
|
||||
extract_toolchain_from_rustup_run_rustc_version(UNEXPECTED_FORMAT_OF_ERROR.clone()),
|
||||
RustupRunRustcVersionOutcome::Err,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_rustc_version() {
|
||||
let nightly_input = String::from("rustc 1.34.0-nightly (b139669f3 2019-04-10)");
|
||||
|
|
Loading…
Reference in New Issue