|
| 1 | +//! This crate contains a single public function |
| 2 | +//! [`get_path_for_executable`](fn.get_path_for_executable.html). |
| 3 | +//! See docs there for more information. |
| 4 | +
|
| 5 | +use anyhow::{bail, Result}; |
| 6 | +use std::env; |
| 7 | +use std::path::{Path, PathBuf}; |
| 8 | +use std::process::Command; |
| 9 | + |
| 10 | +/// Return a `PathBuf` to use for the given executable. |
| 11 | +/// |
| 12 | +/// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that |
| 13 | +/// gives a valid Cargo executable; or it may return a full path to a valid |
| 14 | +/// Cargo. |
| 15 | +pub fn get_path_for_executable(executable_name: impl AsRef<str>) -> Result<PathBuf> { |
| 16 | + // The current implementation checks three places for an executable to use: |
| 17 | + // 1) Appropriate environment variable (erroring if this is set but not a usable executable) |
| 18 | + // example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc |
| 19 | + // 2) `<executable_name>` |
| 20 | + // example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH |
| 21 | + // 3) `~/.cargo/bin/<executable_name>` |
| 22 | + // example: for cargo, this tries ~/.cargo/bin/cargo |
| 23 | + // It seems that this is a reasonable place to try for cargo, rustc, and rustup |
| 24 | + let executable_name = executable_name.as_ref(); |
| 25 | + let env_var = executable_name.to_ascii_uppercase(); |
| 26 | + if let Ok(path) = env::var(&env_var) { |
| 27 | + if is_valid_executable(&path) { |
| 28 | + Ok(path.into()) |
| 29 | + } else { |
| 30 | + bail!( |
| 31 | + "`{}` environment variable points to something that's not a valid executable", |
| 32 | + env_var |
| 33 | + ) |
| 34 | + } |
| 35 | + } else { |
| 36 | + if is_valid_executable(executable_name) { |
| 37 | + return Ok(executable_name.into()); |
| 38 | + } |
| 39 | + if let Some(mut path) = ::home::home_dir() { |
| 40 | + path.push(".cargo"); |
| 41 | + path.push("bin"); |
| 42 | + path.push(executable_name); |
| 43 | + if is_valid_executable(&path) { |
| 44 | + return Ok(path); |
| 45 | + } |
| 46 | + } |
| 47 | + // This error message may also be caused by $PATH or $CARGO/$RUSTC/etc not being set correctly |
| 48 | + // for VSCode, even if they are set correctly in a terminal. |
| 49 | + // On macOS in particular, launching VSCode from terminal with `code <dirname>` causes VSCode |
| 50 | + // to inherit environment variables including $PATH, $CARGO, $RUSTC, etc from that terminal; |
| 51 | + // but launching VSCode from Dock does not inherit environment variables from a terminal. |
| 52 | + // For more discussion, see #3118. |
| 53 | + bail!( |
| 54 | + "Failed to find `{}` executable. Make sure `{}` is in `$PATH`, or set `${}` to point to a valid executable.", |
| 55 | + executable_name, executable_name, env_var |
| 56 | + ) |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +/// Does the given `Path` point to a usable executable? |
| 61 | +/// |
| 62 | +/// (assumes the executable takes a `--version` switch and writes to stdout, |
| 63 | +/// which is true for `cargo`, `rustc`, and `rustup`) |
| 64 | +fn is_valid_executable(p: impl AsRef<Path>) -> bool { |
| 65 | + Command::new(p.as_ref()).arg("--version").output().is_ok() |
| 66 | +} |
0 commit comments