]>
Commit | Line | Data |
---|---|---|
92bf2c36 | 1 | #![warn(rust_2018_idioms)] // while we're getting used to 2018 |
9ed82b57 | 2 | #![allow(clippy::redundant_closure)] // there's a false positive |
ef0b4776 | 3 | #![warn(clippy::needless_borrow)] |
a91a12b4 | 4 | #![warn(clippy::redundant_clone)] |
1ca6830e | 5 | |
0f157f52 | 6 | use std::collections::{BTreeMap, BTreeSet}; |
ee5e24ff | 7 | use std::env; |
a6dad622 | 8 | use std::fs; |
ef4c09f9 | 9 | use std::path::{Path, PathBuf}; |
ba273af5 | 10 | |
901065f5 | 11 | use cargo::core::shell::Shell; |
7d7fe679 | 12 | use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config}; |
37cffbe0 | 13 | use cargo::util::{CliError, ProcessError}; |
3a15f5b7 | 14 | |
38f81e05 | 15 | mod cli; |
66bb9dc3 | 16 | mod commands; |
7acd343b | 17 | |
04ddd4d0 | 18 | use crate::command_prelude::*; |
c3422cfc | 19 | |
3512d997 | 20 | fn main() { |
aa8eff88 | 21 | #[cfg(feature = "pretty-env-logger")] |
782266aa | 22 | pretty_env_logger::init_custom_env("CARGO_LOG"); |
aa8eff88 | 23 | #[cfg(not(feature = "pretty-env-logger"))] |
782266aa | 24 | env_logger::init_from_env("CARGO_LOG"); |
75bb1906 | 25 | cargo::core::maybe_allow_nightly_features(); |
3b93c575 | 26 | |
89c09e20 | 27 | let mut config = match Config::default() { |
3b93c575 AK |
28 | Ok(cfg) => cfg, |
29 | Err(e) => { | |
f8fb0a02 | 30 | let mut shell = Shell::new(); |
ef043577 | 31 | cargo::exit_with_error(e.into(), &mut shell) |
3b93c575 AK |
32 | } |
33 | }; | |
34 | ||
b02ba377 AC |
35 | let result = match cargo::ops::fix_maybe_exec_rustc() { |
36 | Ok(true) => Ok(()), | |
37 | Ok(false) => { | |
b02ba377 AC |
38 | let _token = cargo::util::job::setup(); |
39 | cli::main(&mut config) | |
40 | } | |
41 | Err(e) => Err(CliError::from(e)), | |
16bde4e0 AK |
42 | }; |
43 | ||
44 | match result { | |
ef043577 | 45 | Err(e) => cargo::exit_with_error(e, &mut *config.shell()), |
ef4c09f9 | 46 | Ok(()) => {} |
3b93c575 | 47 | } |
3512d997 | 48 | } |
3a15f5b7 | 49 | |
26beca06 C |
50 | const BUILTIN_ALIASES: [(&str, &str); 4] = [ |
51 | ("b", "alias: build"), | |
52 | ("c", "alias: check"), | |
53 | ("r", "alias: run"), | |
54 | ("t", "alias: test"), | |
55 | ]; | |
56 | ||
57 | /// Function which contains the list of all of the builtin aliases and it's | |
58 | /// corresponding execs represented as &str. | |
59 | fn builtin_aliases_execs(cmd: &str) -> Option<&str> { | |
60 | match cmd { | |
61 | "b" => Some("build"), | |
62 | "c" => Some("check"), | |
63 | "r" => Some("run"), | |
64 | "t" => Some("test"), | |
65 | _ => None, | |
66 | } | |
67 | } | |
68 | ||
23591fe5 | 69 | fn aliased_command(config: &Config, command: &str) -> CargoResult<Option<Vec<String>>> { |
66739f1c | 70 | let alias_name = format!("alias.{}", command); |
e8f37dae EH |
71 | let user_alias = match config.get_string(&alias_name) { |
72 | Ok(Some(record)) => Some( | |
73 | record | |
74 | .val | |
75 | .split_whitespace() | |
76 | .map(|s| s.to_string()) | |
77 | .collect(), | |
78 | ), | |
79 | Ok(None) => None, | |
5bba4261 | 80 | Err(_) => config.get::<Option<Vec<String>>>(&alias_name)?, |
e8f37dae | 81 | }; |
26beca06 C |
82 | |
83 | let result = user_alias.or_else(|| match builtin_aliases_execs(command) { | |
84 | Some(command_str) => Some(vec![command_str.to_string()]), | |
85 | None => None, | |
e8f37dae EH |
86 | }); |
87 | Ok(result) | |
66739f1c SBI |
88 | } |
89 | ||
e68c682a | 90 | /// List all runnable commands |
c3422cfc | 91 | fn list_commands(config: &Config) -> BTreeSet<CommandInfo> { |
e68c682a AK |
92 | let prefix = "cargo-"; |
93 | let suffix = env::consts::EXE_SUFFIX; | |
94 | let mut commands = BTreeSet::new(); | |
95 | for dir in search_directories(config) { | |
96 | let entries = match fs::read_dir(dir) { | |
97 | Ok(entries) => entries, | |
98 | _ => continue, | |
99 | }; | |
100 | for entry in entries.filter_map(|e| e.ok()) { | |
101 | let path = entry.path(); | |
102 | let filename = match path.file_name().and_then(|s| s.to_str()) { | |
103 | Some(filename) => filename, | |
104 | _ => continue, | |
105 | }; | |
106 | if !filename.starts_with(prefix) || !filename.ends_with(suffix) { | |
107 | continue; | |
108 | } | |
109 | if is_executable(entry.path()) { | |
110 | let end = filename.len() - suffix.len(); | |
c3422cfc DW |
111 | commands.insert(CommandInfo::External { |
112 | name: filename[prefix.len()..end].to_string(), | |
113 | path: path.clone(), | |
114 | }); | |
e68c682a AK |
115 | } |
116 | } | |
117 | } | |
118 | ||
119 | for cmd in commands::builtin() { | |
c3422cfc DW |
120 | commands.insert(CommandInfo::BuiltIn { |
121 | name: cmd.get_name().to_string(), | |
af2c3555 | 122 | about: cmd.p.meta.about.map(|s| s.to_string()), |
c3422cfc | 123 | }); |
e68c682a | 124 | } |
26beca06 C |
125 | for command in &BUILTIN_ALIASES { |
126 | commands.insert(CommandInfo::BuiltIn { | |
127 | name: command.0.to_string(), | |
128 | about: Some(command.1.to_string()), | |
129 | }); | |
130 | } | |
e68c682a AK |
131 | |
132 | commands | |
133 | } | |
134 | ||
ff3e880c ZL |
135 | /// List all runnable aliases |
136 | fn list_aliases(config: &Config) -> Vec<String> { | |
0f157f52 ZL |
137 | match config.get::<BTreeMap<String, String>>("alias") { |
138 | Ok(aliases) => aliases.keys().map(|a| a.to_string()).collect(), | |
ff3e880c ZL |
139 | Err(_) => Vec::new(), |
140 | } | |
141 | } | |
142 | ||
81ce3e1b | 143 | fn execute_external_subcommand(config: &Config, cmd: &str, args: &[&str]) -> CliResult { |
20b768e6 AC |
144 | let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX); |
145 | let path = search_directories(config) | |
ef4c09f9 | 146 | .iter() |
147 | .map(|dir| dir.join(&command_exe)) | |
148 | .find(|file| is_executable(file)); | |
20b768e6 | 149 | let command = match path { |
7ed31fab | 150 | Some(command) => command, |
12f5de8e | 151 | None => { |
ff3e880c ZL |
152 | let commands: Vec<String> = list_commands(config) |
153 | .iter() | |
154 | .map(|c| c.name().to_string()) | |
155 | .collect(); | |
156 | let aliases = list_aliases(config); | |
157 | let suggestions = commands.iter().chain(aliases.iter()); | |
158 | let did_you_mean = closest_msg(cmd, suggestions, |c| c); | |
3a18c89a | 159 | let err = anyhow::format_err!("no such subcommand: `{}`{}", cmd, did_you_mean); |
1e682848 | 160 | return Err(CliError::new(err, 101)); |
12f5de8e | 161 | } |
8cce8996 | 162 | }; |
015a08a0 VK |
163 | |
164 | let cargo_exe = config.cargo_exe()?; | |
165 | let err = match util::process(&command) | |
166 | .env(cargo::CARGO_ENV, cargo_exe) | |
deb1c1e1 | 167 | .args(args) |
1e682848 AC |
168 | .exec_replace() |
169 | { | |
fdc5b07c AC |
170 | Ok(()) => return Ok(()), |
171 | Err(e) => e, | |
172 | }; | |
173 | ||
37cffbe0 | 174 | if let Some(perr) = err.downcast_ref::<ProcessError>() { |
e95044e3 | 175 | if let Some(code) = perr.exit.as_ref().and_then(|c| c.code()) { |
176 | return Err(CliError::code(code)); | |
177 | } | |
fdc5b07c | 178 | } |
48446574 | 179 | Err(CliError::new(err, 101)) |
a3f6a404 | 180 | } |
181 | ||
a6dad622 | 182 | #[cfg(unix)] |
7ed31fab | 183 | fn is_executable<P: AsRef<Path>>(path: P) -> bool { |
a6dad622 | 184 | use std::os::unix::prelude::*; |
ef4c09f9 | 185 | fs::metadata(path) |
186 | .map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0) | |
187 | .unwrap_or(false) | |
a6dad622 AC |
188 | } |
189 | #[cfg(windows)] | |
7ed31fab | 190 | fn is_executable<P: AsRef<Path>>(path: P) -> bool { |
4367ec4d | 191 | path.as_ref().is_file() |
a3f6a404 | 192 | } |
193 | ||
20b768e6 | 194 | fn search_directories(config: &Config) -> Vec<PathBuf> { |
8eac1d62 | 195 | let mut dirs = vec![config.home().clone().into_path_unlocked().join("bin")]; |
1384050e | 196 | if let Some(val) = env::var_os("PATH") { |
ee5e24ff AC |
197 | dirs.extend(env::split_paths(&val)); |
198 | } | |
a1b3d153 | 199 | dirs |
3a15f5b7 | 200 | } |
b20b0f6b AC |
201 | |
202 | fn init_git_transports(config: &Config) { | |
9f932e11 JG |
203 | // Only use a custom transport if any HTTP options are specified, |
204 | // such as proxies or custom certificate authorities. The custom | |
205 | // transport, however, is not as well battle-tested. | |
206 | ||
207 | match cargo::ops::needs_custom_http_transport(config) { | |
8eb28ad6 | 208 | Ok(true) => {} |
ef4c09f9 | 209 | _ => return, |
b20b0f6b AC |
210 | } |
211 | ||
212 | let handle = match cargo::ops::http_handle(config) { | |
213 | Ok(handle) => handle, | |
214 | Err(..) => return, | |
215 | }; | |
216 | ||
217 | // The unsafety of the registration function derives from two aspects: | |
218 | // | |
219 | // 1. This call must be synchronized with all other registration calls as | |
220 | // well as construction of new transports. | |
221 | // 2. The argument is leaked. | |
222 | // | |
223 | // We're clear on point (1) because this is only called at the start of this | |
224 | // binary (we know what the state of the world looks like) and we're mostly | |
225 | // clear on point (2) because we'd only free it after everything is done | |
226 | // anyway | |
227 | unsafe { | |
228 | git2_curl::register(handle); | |
229 | } | |
230 | } |