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