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