]> git.proxmox.com Git - cargo.git/blame - src/bin/cargo/main.rs
refactor(cli): Make help behave like other subcommands
[cargo.git] / src / bin / cargo / main.rs
CommitLineData
92bf2c36 1#![warn(rust_2018_idioms)] // while we're getting used to 2018
10eb56f2 2#![allow(clippy::all)]
1ca6830e 3
88810035 4use cargo::core::shell::Shell;
1c5e68b4 5use cargo::util::toml::StringOrVec;
88810035
EH
6use cargo::util::CliError;
7use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config};
8use cargo_util::{ProcessBuilder, ProcessError};
0ab79d7a 9use std::collections::BTreeMap;
ee5e24ff 10use std::env;
a6dad622 11use std::fs;
ef4c09f9 12use std::path::{Path, PathBuf};
ba273af5 13
38f81e05 14mod cli;
66bb9dc3 15mod commands;
7acd343b 16
04ddd4d0 17use crate::command_prelude::*;
c3422cfc 18
3512d997 19fn 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 50const 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
60fn builtin_aliases_execs(cmd: &str) -> Option<&(&str, &str, &str)> {
61 BUILTIN_ALIASES.iter().find(|alias| alias.0 == cmd)
26beca06
C
62}
63
23591fe5 64fn 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 85fn 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 156fn 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
164fn 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 211fn 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 218fn is_executable<P: AsRef<Path>>(path: P) -> bool {
4367ec4d 219 path.as_ref().is_file()
a3f6a404 220}
221
20b768e6 222fn 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
230fn 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}