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