]> git.proxmox.com Git - cargo.git/blame - src/bin/cargo/main.rs
New upstream version 0.66.0
[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
1c5e68b4 4use cargo::util::toml::StringOrVec;
88810035
EH
5use cargo::util::CliError;
6use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config};
7use cargo_util::{ProcessBuilder, ProcessError};
0ab79d7a 8use std::collections::BTreeMap;
ee5e24ff 9use std::env;
a6dad622 10use std::fs;
ef4c09f9 11use std::path::{Path, PathBuf};
ba273af5 12
38f81e05 13mod cli;
66bb9dc3 14mod commands;
7acd343b 15
04ddd4d0 16use crate::command_prelude::*;
c3422cfc 17
3512d997 18fn 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 41const 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
51fn builtin_aliases_execs(cmd: &str) -> Option<&(&str, &str, &str)> {
52 BUILTIN_ALIASES.iter().find(|alias| alias.0 == cmd)
26beca06
C
53}
54
23591fe5 55fn 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 76fn 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 147fn 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
155fn 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 203fn 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 210fn is_executable<P: AsRef<Path>>(path: P) -> bool {
4367ec4d 211 path.as_ref().is_file()
a3f6a404 212}
213
20b768e6 214fn 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
239fn 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}