#![warn(rust_2018_idioms)] #![allow(clippy::all)]
use cargo::core::shell::Shell;
use cargo::util::toml::StringOrVec;
use cargo::util::CliError;
use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config};
use cargo_util::{ProcessBuilder, ProcessError};
use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
mod cli;
mod commands;
use crate::command_prelude::*;
fn main() {
#[cfg(feature = "pretty-env-logger")]
pretty_env_logger::init_custom_env("CARGO_LOG");
#[cfg(not(feature = "pretty-env-logger"))]
env_logger::init_from_env("CARGO_LOG");
let mut config = match Config::default() {
Ok(cfg) => cfg,
Err(e) => {
let mut shell = Shell::new();
cargo::exit_with_error(e.into(), &mut shell)
}
};
let result = match cargo::ops::fix_maybe_exec_rustc(&config) {
Ok(true) => Ok(()),
Ok(false) => {
let _token = cargo::util::job::setup();
cli::main(&mut config)
}
Err(e) => Err(CliError::from(e)),
};
match result {
Err(e) => cargo::exit_with_error(e, &mut *config.shell()),
Ok(()) => {}
}
}
const BUILTIN_ALIASES: [(&str, &str, &str); 5] = [
("b", "build", "alias: build"),
("c", "check", "alias: check"),
("d", "doc", "alias: doc"),
("r", "run", "alias: run"),
("t", "test", "alias: test"),
];
fn builtin_aliases_execs(cmd: &str) -> Option<&(&str, &str, &str)> {
BUILTIN_ALIASES.iter().find(|alias| alias.0 == cmd)
}
fn aliased_command(config: &Config, command: &str) -> CargoResult<Option<Vec<String>>> {
let alias_name = format!("alias.{}", command);
let user_alias = match config.get_string(&alias_name) {
Ok(Some(record)) => Some(
record
.val
.split_whitespace()
.map(|s| s.to_string())
.collect(),
),
Ok(None) => None,
Err(_) => config.get::<Option<Vec<String>>>(&alias_name)?,
};
let result = user_alias.or_else(|| {
builtin_aliases_execs(command).map(|command_str| vec![command_str.1.to_string()])
});
Ok(result)
}
fn list_commands(config: &Config) -> BTreeMap<String, CommandInfo> {
let prefix = "cargo-";
let suffix = env::consts::EXE_SUFFIX;
let mut commands = BTreeMap::new();
for dir in search_directories(config) {
let entries = match fs::read_dir(dir) {
Ok(entries) => entries,
_ => continue,
};
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
let filename = match path.file_name().and_then(|s| s.to_str()) {
Some(filename) => filename,
_ => continue,
};
if !filename.starts_with(prefix) || !filename.ends_with(suffix) {
continue;
}
if is_executable(entry.path()) {
let end = filename.len() - suffix.len();
commands.insert(
filename[prefix.len()..end].to_string(),
CommandInfo::External { path: path.clone() },
);
}
}
}
for cmd in commands::builtin() {
commands.insert(
cmd.get_name().to_string(),
CommandInfo::BuiltIn {
about: cmd.get_about().map(|s| s.to_string()),
},
);
}
for command in &BUILTIN_ALIASES {
commands.insert(
command.0.to_string(),
CommandInfo::BuiltIn {
about: Some(command.2.to_string()),
},
);
}
if let Ok(aliases) = config.get::<BTreeMap<String, StringOrVec>>("alias") {
for (name, target) in aliases.iter() {
commands.insert(
name.to_string(),
CommandInfo::Alias {
target: target.clone(),
},
);
}
}
commands.insert(
"help".to_string(),
CommandInfo::BuiltIn {
about: Some("Displays help for a cargo subcommand".to_string()),
},
);
commands
}
fn find_external_subcommand(config: &Config, cmd: &str) -> Option<PathBuf> {
let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX);
search_directories(config)
.iter()
.map(|dir| dir.join(&command_exe))
.find(|file| is_executable(file))
}
fn execute_external_subcommand(config: &Config, cmd: &str, args: &[&str]) -> CliResult {
let path = find_external_subcommand(config, cmd);
let command = match path {
Some(command) => command,
None => {
let suggestions = list_commands(config);
let did_you_mean = closest_msg(cmd, suggestions.keys(), |c| c);
let err = anyhow::format_err!("no such subcommand: `{}`{}", cmd, did_you_mean);
return Err(CliError::new(err, 101));
}
};
let cargo_exe = config.cargo_exe()?;
let err = match ProcessBuilder::new(&command)
.env(cargo::CARGO_ENV, cargo_exe)
.args(args)
.exec_replace()
{
Ok(()) => return Ok(()),
Err(e) => e,
};
if let Some(perr) = err.downcast_ref::<ProcessError>() {
if let Some(code) = perr.code {
return Err(CliError::code(code));
}
}
Err(CliError::new(err, 101))
}
#[cfg(unix)]
fn is_executable<P: AsRef<Path>>(path: P) -> bool {
use std::os::unix::prelude::*;
fs::metadata(path)
.map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(windows)]
fn is_executable<P: AsRef<Path>>(path: P) -> bool {
path.as_ref().is_file()
}
fn search_directories(config: &Config) -> Vec<PathBuf> {
let mut dirs = vec![config.home().clone().into_path_unlocked().join("bin")];
if let Some(val) = env::var_os("PATH") {
dirs.extend(env::split_paths(&val));
}
dirs
}
fn init_git_transports(config: &Config) {
match cargo::ops::needs_custom_http_transport(config) {
Ok(true) => {}
_ => return,
}
let handle = match cargo::ops::http_handle(config) {
Ok(handle) => handle,
Err(..) => return,
};
unsafe {
git2_curl::register(handle);
}
}