1use std::{
4 env,
5 ffi::OsStr,
6 iter,
7 path::{Path, PathBuf},
8 process::Command,
9};
10
11use camino::{Utf8Path, Utf8PathBuf};
12
13#[derive(Copy, Clone)]
14pub enum Tool {
15 Cargo,
16 Rustc,
17 Rustup,
18 Rustfmt,
19}
20
21impl Tool {
22 pub fn proxy(self) -> Option<Utf8PathBuf> {
23 cargo_proxy(self.name())
24 }
25
26 pub fn prefer_proxy(self) -> Utf8PathBuf {
40 invoke(&[cargo_proxy, lookup_as_env_var, lookup_in_path], self.name())
41 }
42
43 pub fn path(self) -> Utf8PathBuf {
57 invoke(&[lookup_as_env_var, lookup_in_path, cargo_proxy], self.name())
58 }
59
60 pub fn path_in(self, path: &Utf8Path) -> Option<Utf8PathBuf> {
61 probe_for_binary(path.join(self.name()))
62 }
63
64 pub fn name(self) -> &'static str {
65 match self {
66 Tool::Cargo => "cargo",
67 Tool::Rustc => "rustc",
68 Tool::Rustup => "rustup",
69 Tool::Rustfmt => "rustfmt",
70 }
71 }
72}
73
74pub const NO_RUSTUP_AUTO_INSTALL_ENV: (&str, &str) = ("RUSTUP_AUTO_INSTALL", "0");
76
77#[allow(clippy::disallowed_types)] pub fn command<H>(
79 cmd: impl AsRef<OsStr>,
80 working_directory: impl AsRef<Path>,
81 extra_env: &std::collections::HashMap<String, Option<String>, H>,
82) -> Command {
83 #[allow(clippy::disallowed_methods)]
85 let mut cmd = Command::new(cmd);
86 cmd.current_dir(working_directory);
87 cmd.env(NO_RUSTUP_AUTO_INSTALL_ENV.0, NO_RUSTUP_AUTO_INSTALL_ENV.1);
88 for env in extra_env {
89 match env {
90 (key, Some(val)) => cmd.env(key, val),
91 (key, None) => cmd.env_remove(key),
92 };
93 }
94 cmd
95}
96
97fn invoke(list: &[fn(&str) -> Option<Utf8PathBuf>], executable: &str) -> Utf8PathBuf {
98 list.iter().find_map(|it| it(executable)).unwrap_or_else(|| executable.into())
99}
100
101fn lookup_as_env_var(executable_name: &str) -> Option<Utf8PathBuf> {
103 env::var_os(executable_name.to_ascii_uppercase())
104 .map(PathBuf::from)
105 .map(Utf8PathBuf::try_from)
106 .and_then(Result::ok)
107}
108
109fn cargo_proxy(executable_name: &str) -> Option<Utf8PathBuf> {
111 let mut path = get_cargo_home()?;
112 path.push("bin");
113 path.push(executable_name);
114 probe_for_binary(path)
115}
116
117fn get_cargo_home() -> Option<Utf8PathBuf> {
118 if let Some(path) = env::var_os("CARGO_HOME") {
119 return Utf8PathBuf::try_from(PathBuf::from(path)).ok();
120 }
121
122 if let Some(mut path) = home::home_dir() {
123 path.push(".cargo");
124 return Utf8PathBuf::try_from(path).ok();
125 }
126
127 None
128}
129
130fn lookup_in_path(exec: &str) -> Option<Utf8PathBuf> {
131 let paths = env::var_os("PATH").unwrap_or_default();
132 env::split_paths(&paths)
133 .map(|path| path.join(exec))
134 .map(Utf8PathBuf::try_from)
135 .filter_map(Result::ok)
136 .find_map(probe_for_binary)
137}
138
139pub fn probe_for_binary(path: Utf8PathBuf) -> Option<Utf8PathBuf> {
140 let with_extension = match env::consts::EXE_EXTENSION {
141 "" => None,
142 it => Some(path.with_extension(it)),
143 };
144 iter::once(path).chain(with_extension).find(|it| it.is_file())
145}