]>
Commit | Line | Data |
---|---|---|
92bf2c36 | 1 | #![warn(rust_2018_idioms)] // while we're getting used to 2018 |
10eb56f2 | 2 | #![allow(clippy::all)] |
1ca6830e | 3 | |
1c5e68b4 | 4 | use cargo::util::toml::StringOrVec; |
88810035 EH |
5 | use cargo::util::CliError; |
6 | use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config}; | |
7 | use cargo_util::{ProcessBuilder, ProcessError}; | |
0ab79d7a | 8 | use std::collections::BTreeMap; |
ee5e24ff | 9 | use std::env; |
a6dad622 | 10 | use std::fs; |
ef4c09f9 | 11 | use std::path::{Path, PathBuf}; |
ba273af5 | 12 | |
38f81e05 | 13 | mod cli; |
66bb9dc3 | 14 | mod commands; |
7acd343b | 15 | |
04ddd4d0 | 16 | use crate::command_prelude::*; |
c3422cfc | 17 | |
3512d997 | 18 | fn main() { |
aa8eff88 | 19 | #[cfg(feature = "pretty-env-logger")] |
782266aa | 20 | pretty_env_logger::init_custom_env("CARGO_LOG"); |
aa8eff88 | 21 | #[cfg(not(feature = "pretty-env-logger"))] |
782266aa | 22 | env_logger::init_from_env("CARGO_LOG"); |
3b93c575 | 23 | |
eda1652b | 24 | let mut config = cli::LazyConfig::new(); |
3b93c575 | 25 | |
ddc21ea4 | 26 | let result = if let Some(lock_addr) = cargo::ops::fix_get_proxy_lock_addr() { |
eda1652b | 27 | cargo::ops::fix_exec_rustc(config.get(), &lock_addr).map_err(|e| CliError::from(e)) |
ddc21ea4 EP |
28 | } else { |
29 | let _token = cargo::util::job::setup(); | |
30 | cli::main(&mut config) | |
16bde4e0 AK |
31 | }; |
32 | ||
33 | match result { | |
eda1652b | 34 | Err(e) => cargo::exit_with_error(e, &mut config.get_mut().shell()), |
ef4c09f9 | 35 | Ok(()) => {} |
3b93c575 | 36 | } |
3512d997 | 37 | } |
3a15f5b7 | 38 | |
2ae8df65 C |
39 | /// Table for defining the aliases which come builtin in `Cargo`. |
40 | /// The contents are structured as: `(alias, aliased_command, description)`. | |
82636e86 | 41 | const BUILTIN_ALIASES: [(&str, &str, &str); 5] = [ |
2ae8df65 C |
42 | ("b", "build", "alias: build"), |
43 | ("c", "check", "alias: check"), | |
82636e86 | 44 | ("d", "doc", "alias: doc"), |
2ae8df65 C |
45 | ("r", "run", "alias: run"), |
46 | ("t", "test", "alias: test"), | |
26beca06 C |
47 | ]; |
48 | ||
49 | /// Function which contains the list of all of the builtin aliases and it's | |
50 | /// corresponding execs represented as &str. | |
2ae8df65 C |
51 | fn builtin_aliases_execs(cmd: &str) -> Option<&(&str, &str, &str)> { |
52 | BUILTIN_ALIASES.iter().find(|alias| alias.0 == cmd) | |
26beca06 C |
53 | } |
54 | ||
23591fe5 | 55 | fn aliased_command(config: &Config, command: &str) -> CargoResult<Option<Vec<String>>> { |
66739f1c | 56 | let alias_name = format!("alias.{}", command); |
e8f37dae EH |
57 | let user_alias = match config.get_string(&alias_name) { |
58 | Ok(Some(record)) => Some( | |
59 | record | |
60 | .val | |
61 | .split_whitespace() | |
62 | .map(|s| s.to_string()) | |
63 | .collect(), | |
64 | ), | |
65 | Ok(None) => None, | |
5bba4261 | 66 | Err(_) => config.get::<Option<Vec<String>>>(&alias_name)?, |
e8f37dae | 67 | }; |
26beca06 | 68 | |
e58c544f EH |
69 | let result = user_alias.or_else(|| { |
70 | builtin_aliases_execs(command).map(|command_str| vec![command_str.1.to_string()]) | |
e8f37dae EH |
71 | }); |
72 | Ok(result) | |
66739f1c SBI |
73 | } |
74 | ||
e68c682a | 75 | /// List all runnable commands |
0ab79d7a | 76 | fn list_commands(config: &Config) -> BTreeMap<String, CommandInfo> { |
e68c682a AK |
77 | let prefix = "cargo-"; |
78 | let suffix = env::consts::EXE_SUFFIX; | |
0ab79d7a | 79 | let mut commands = BTreeMap::new(); |
e68c682a AK |
80 | for dir in search_directories(config) { |
81 | let entries = match fs::read_dir(dir) { | |
82 | Ok(entries) => entries, | |
83 | _ => continue, | |
84 | }; | |
85 | for entry in entries.filter_map(|e| e.ok()) { | |
86 | let path = entry.path(); | |
87 | let filename = match path.file_name().and_then(|s| s.to_str()) { | |
88 | Some(filename) => filename, | |
89 | _ => continue, | |
90 | }; | |
91 | if !filename.starts_with(prefix) || !filename.ends_with(suffix) { | |
92 | continue; | |
93 | } | |
94 | if is_executable(entry.path()) { | |
95 | let end = filename.len() - suffix.len(); | |
0ab79d7a NK |
96 | commands.insert( |
97 | filename[prefix.len()..end].to_string(), | |
98 | CommandInfo::External { path: path.clone() }, | |
99 | ); | |
e68c682a AK |
100 | } |
101 | } | |
102 | } | |
103 | ||
104 | for cmd in commands::builtin() { | |
0ab79d7a NK |
105 | commands.insert( |
106 | cmd.get_name().to_string(), | |
107 | CommandInfo::BuiltIn { | |
f17ecafc | 108 | about: cmd.get_about().map(|s| s.to_string()), |
0ab79d7a NK |
109 | }, |
110 | ); | |
e68c682a | 111 | } |
2ae8df65 C |
112 | |
113 | // Add the builtin_aliases and them descriptions to the | |
0ab79d7a | 114 | // `commands` `BTreeMap`. |
26beca06 | 115 | for command in &BUILTIN_ALIASES { |
0ab79d7a NK |
116 | commands.insert( |
117 | command.0.to_string(), | |
118 | CommandInfo::BuiltIn { | |
119 | about: Some(command.2.to_string()), | |
120 | }, | |
121 | ); | |
26beca06 | 122 | } |
e68c682a | 123 | |
1c5e68b4 DM |
124 | // Add the user-defined aliases |
125 | if let Ok(aliases) = config.get::<BTreeMap<String, StringOrVec>>("alias") { | |
126 | for (name, target) in aliases.iter() { | |
0ab79d7a NK |
127 | commands.insert( |
128 | name.to_string(), | |
129 | CommandInfo::Alias { | |
130 | target: target.clone(), | |
131 | }, | |
132 | ); | |
1c5e68b4 | 133 | } |
ff3e880c | 134 | } |
1c5e68b4 | 135 | |
b655a896 EH |
136 | // `help` is special, so it needs to be inserted separately. |
137 | commands.insert( | |
138 | "help".to_string(), | |
139 | CommandInfo::BuiltIn { | |
140 | about: Some("Displays help for a cargo subcommand".to_string()), | |
141 | }, | |
142 | ); | |
143 | ||
1c5e68b4 | 144 | commands |
ff3e880c ZL |
145 | } |
146 | ||
5bfd345e | 147 | fn find_external_subcommand(config: &Config, cmd: &str) -> Option<PathBuf> { |
20b768e6 | 148 | let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX); |
5bfd345e | 149 | search_directories(config) |
ef4c09f9 | 150 | .iter() |
151 | .map(|dir| dir.join(&command_exe)) | |
5bfd345e BH |
152 | .find(|file| is_executable(file)) |
153 | } | |
154 | ||
155 | fn execute_external_subcommand(config: &Config, cmd: &str, args: &[&str]) -> CliResult { | |
156 | let path = find_external_subcommand(config, cmd); | |
20b768e6 | 157 | let command = match path { |
7ed31fab | 158 | Some(command) => command, |
12f5de8e | 159 | None => { |
b2f44de8 | 160 | let err = if cmd.starts_with('+') { |
91bc002f | 161 | anyhow::format_err!( |
162 | "no such subcommand: `{}`\n\n\t\ | |
163 | Cargo does not handle `+toolchain` directives.\n\t\ | |
8f5f2ed2 | 164 | Did you mean to invoke `cargo` through `rustup` instead?", |
91bc002f | 165 | cmd |
166 | ) | |
43507dd9 | 167 | } else { |
b2f44de8 | 168 | let suggestions = list_commands(config); |
169 | let did_you_mean = closest_msg(cmd, suggestions.keys(), |c| c); | |
170 | ||
91bc002f | 171 | anyhow::format_err!( |
172 | "no such subcommand: `{}`{}\n\n\t\ | |
173 | View all installed commands with `cargo --list`", | |
174 | cmd, | |
175 | did_you_mean | |
176 | ) | |
43507dd9 | 177 | }; |
178 | ||
1e682848 | 179 | return Err(CliError::new(err, 101)); |
12f5de8e | 180 | } |
8cce8996 | 181 | }; |
015a08a0 VK |
182 | |
183 | let cargo_exe = config.cargo_exe()?; | |
c1b90b97 WL |
184 | let mut cmd = ProcessBuilder::new(&command); |
185 | cmd.env(cargo::CARGO_ENV, cargo_exe).args(args); | |
186 | if let Some(client) = config.jobserver_from_env() { | |
187 | cmd.inherit_jobserver(client); | |
188 | } | |
189 | let err = match cmd.exec_replace() { | |
fdc5b07c AC |
190 | Ok(()) => return Ok(()), |
191 | Err(e) => e, | |
192 | }; | |
193 | ||
37cffbe0 | 194 | if let Some(perr) = err.downcast_ref::<ProcessError>() { |
cc5e9df6 | 195 | if let Some(code) = perr.code { |
e95044e3 | 196 | return Err(CliError::code(code)); |
197 | } | |
fdc5b07c | 198 | } |
48446574 | 199 | Err(CliError::new(err, 101)) |
a3f6a404 | 200 | } |
201 | ||
a6dad622 | 202 | #[cfg(unix)] |
7ed31fab | 203 | fn is_executable<P: AsRef<Path>>(path: P) -> bool { |
a6dad622 | 204 | use std::os::unix::prelude::*; |
ef4c09f9 | 205 | fs::metadata(path) |
206 | .map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0) | |
207 | .unwrap_or(false) | |
a6dad622 AC |
208 | } |
209 | #[cfg(windows)] | |
7ed31fab | 210 | fn is_executable<P: AsRef<Path>>(path: P) -> bool { |
4367ec4d | 211 | path.as_ref().is_file() |
a3f6a404 | 212 | } |
213 | ||
20b768e6 | 214 | fn search_directories(config: &Config) -> Vec<PathBuf> { |
c712f088 DC |
215 | let mut path_dirs = if let Some(val) = env::var_os("PATH") { |
216 | env::split_paths(&val).collect() | |
217 | } else { | |
218 | vec![] | |
219 | }; | |
220 | ||
221 | let home_bin = config.home().clone().into_path_unlocked().join("bin"); | |
222 | ||
223 | // If any of that PATH elements contains `home_bin`, do not | |
224 | // add it again. This is so that the users can control priority | |
225 | // of it using PATH, while preserving the historical | |
226 | // behavior of preferring it over system global directories even | |
227 | // when not in PATH at all. | |
228 | // See https://github.com/rust-lang/cargo/issues/11020 for details. | |
229 | // | |
230 | // Note: `p == home_bin` will ignore trailing slash, but we don't | |
231 | // `canonicalize` the paths. | |
232 | if !path_dirs.iter().any(|p| p == &home_bin) { | |
233 | path_dirs.insert(0, home_bin); | |
234 | }; | |
235 | ||
236 | path_dirs | |
3a15f5b7 | 237 | } |
b20b0f6b AC |
238 | |
239 | fn init_git_transports(config: &Config) { | |
9f932e11 JG |
240 | // Only use a custom transport if any HTTP options are specified, |
241 | // such as proxies or custom certificate authorities. The custom | |
242 | // transport, however, is not as well battle-tested. | |
243 | ||
244 | match cargo::ops::needs_custom_http_transport(config) { | |
8eb28ad6 | 245 | Ok(true) => {} |
ef4c09f9 | 246 | _ => return, |
b20b0f6b AC |
247 | } |
248 | ||
249 | let handle = match cargo::ops::http_handle(config) { | |
250 | Ok(handle) => handle, | |
251 | Err(..) => return, | |
252 | }; | |
253 | ||
254 | // The unsafety of the registration function derives from two aspects: | |
255 | // | |
256 | // 1. This call must be synchronized with all other registration calls as | |
257 | // well as construction of new transports. | |
258 | // 2. The argument is leaked. | |
259 | // | |
260 | // We're clear on point (1) because this is only called at the start of this | |
261 | // binary (we know what the state of the world looks like) and we're mostly | |
262 | // clear on point (2) because we'd only free it after everything is done | |
263 | // anyway | |
264 | unsafe { | |
265 | git2_curl::register(handle); | |
266 | } | |
222e0e51 JG |
267 | |
268 | // Disabling the owner validation in git can, in theory, lead to code execution | |
269 | // vulnerabilities. However, libgit2 does not launch executables, which is the foundation of | |
270 | // the original security issue. Meanwhile, issues with refusing to load git repos in | |
271 | // `CARGO_HOME` for example will likely be very frustrating for users. So, we disable the | |
272 | // validation. | |
273 | // | |
274 | // For further discussion of Cargo's current interactions with git, see | |
275 | // | |
276 | // https://github.com/rust-lang/rfcs/pull/3279 | |
277 | // | |
278 | // and in particular the subsection on "Git support". | |
279 | // | |
280 | // Note that we only disable this when Cargo is run as a binary. If Cargo is used as a library, | |
281 | // this code won't be invoked. Instead, developers will need to explicitly disable the | |
282 | // validation in their code. This is inconvenient, but won't accidentally open consuming | |
283 | // applications up to security issues if they use git2 to open repositories elsewhere in their | |
284 | // code. | |
285 | unsafe { | |
286 | if git2::opts::set_verify_owner_validation(false).is_err() { | |
287 | return; | |
288 | } | |
289 | } | |
b20b0f6b | 290 | } |