blob: e15a914cc8beb2096680daa71068498ce62b0d86 [file] [log] [blame]
#![cfg_attr(test, deny(warnings))]
#![warn(rust_2018_idioms)]
// While we're getting used to 2018:
// Clippy isn't enforced by CI (@alexcrichton isn't a fan).
#![allow(clippy::boxed_local)] // bug rust-lang-nursery/rust-clippy#1123
#![allow(clippy::cyclomatic_complexity)] // large project
#![allow(clippy::derive_hash_xor_eq)] // there's an intentional incoherence
#![allow(clippy::explicit_into_iter_loop)] // explicit loops are clearer
#![allow(clippy::explicit_iter_loop)] // explicit loops are clearer
#![allow(clippy::identity_op)] // used for vertical alignment
#![allow(clippy::implicit_hasher)] // large project
#![allow(clippy::large_enum_variant)] // large project
#![allow(clippy::redundant_closure_call)] // closures over try catch blocks
#![allow(clippy::too_many_arguments)] // large project
#![allow(clippy::type_complexity)] // there's an exceptionally complex type
#![allow(clippy::wrong_self_convention)] // perhaps `Rc` should be special-cased in Clippy?
#![warn(clippy::needless_borrow)]
#![warn(clippy::redundant_clone)]
use std::fmt;
use failure::Error;
use log::debug;
use serde::ser;
use crate::core::shell::Verbosity::Verbose;
use crate::core::Shell;
pub use crate::util::errors::Internal;
pub use crate::util::{CargoResult, CliError, CliResult, Config};
pub const CARGO_ENV: &str = "CARGO";
#[macro_use]
mod macros;
pub mod core;
pub mod ops;
pub mod sources;
pub mod util;
pub struct CommitInfo {
pub short_commit_hash: String,
pub commit_hash: String,
pub commit_date: String,
}
pub struct CfgInfo {
// Information about the Git repository we may have been built from.
pub commit_info: Option<CommitInfo>,
// The release channel we were built for.
pub release_channel: String,
}
pub struct VersionInfo {
pub major: u8,
pub minor: u8,
pub patch: u8,
pub pre_release: Option<String>,
// Information that's only available when we were built with
// configure/make, rather than Cargo itself.
pub cfg_info: Option<CfgInfo>,
}
impl fmt::Display for VersionInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "cargo {}.{}.{}", self.major, self.minor, self.patch)?;
if let Some(channel) = self.cfg_info.as_ref().map(|ci| &ci.release_channel) {
if channel != "stable" {
write!(f, "-{}", channel)?;
let empty = String::new();
write!(f, "{}", self.pre_release.as_ref().unwrap_or(&empty))?;
}
};
if let Some(ref cfg) = self.cfg_info {
if let Some(ref ci) = cfg.commit_info {
write!(f, " ({} {})", ci.short_commit_hash, ci.commit_date)?;
}
};
Ok(())
}
}
pub fn print_json<T: ser::Serialize>(obj: &T) {
let encoded = serde_json::to_string(&obj).unwrap();
println!("{}", encoded);
}
pub fn exit_with_error(err: CliError, shell: &mut Shell) -> ! {
debug!("exit_with_error; err={:?}", err);
if let Some(ref err) = err.error {
if let Some(clap_err) = err.downcast_ref::<clap::Error>() {
clap_err.exit()
}
}
let CliError {
error,
exit_code,
unknown,
} = err;
// `exit_code` of 0 means non-fatal error (e.g., docopt version info).
let fatal = exit_code != 0;
let hide = unknown && shell.verbosity() != Verbose;
if let Some(error) = error {
if hide {
drop(shell.error("An unknown error occurred"))
} else if fatal {
drop(shell.error(&error))
} else {
println!("{}", error);
}
if !handle_cause(&error, shell) || hide {
drop(writeln!(
shell.err(),
"\nTo learn more, run the command again \
with --verbose."
));
}
}
std::process::exit(exit_code)
}
pub fn handle_error(err: &failure::Error, shell: &mut Shell) {
debug!("handle_error; err={:?}", err);
let _ignored_result = shell.error(err);
handle_cause(err, shell);
}
fn handle_cause(cargo_err: &Error, shell: &mut Shell) -> bool {
fn print(error: &str, shell: &mut Shell) {
drop(writeln!(shell.err(), "\nCaused by:"));
drop(writeln!(shell.err(), " {}", error));
}
let verbose = shell.verbosity();
if verbose == Verbose {
// The first error has already been printed to the shell.
// Print all remaining errors.
for err in cargo_err.iter_causes() {
print(&err.to_string(), shell);
}
} else {
// The first error has already been printed to the shell.
// Print remaining errors until one marked as `Internal` appears.
for err in cargo_err.iter_causes() {
if err.downcast_ref::<Internal>().is_some() {
return false;
}
print(&err.to_string(), shell);
}
}
true
}
pub fn version() -> VersionInfo {
macro_rules! option_env_str {
($name:expr) => {
option_env!($name).map(|s| s.to_string())
};
}
// So this is pretty horrible...
// There are two versions at play here:
// - version of cargo-the-binary, which you see when you type `cargo --version`
// - version of cargo-the-library, which you download from crates.io for use
// in your packages.
//
// We want to make the `binary` version the same as the corresponding Rust/rustc release.
// At the same time, we want to keep the library version at `0.x`, because Cargo as
// a library is (and probably will always be) unstable.
//
// Historically, Cargo used the same version number for both the binary and the library.
// Specifically, rustc 1.x.z was paired with cargo 0.x+1.w.
// We continue to use this scheme for the library, but transform it to 1.x.w for the purposes
// of `cargo --version`.
let major = 1;
let minor = env!("CARGO_PKG_VERSION_MINOR").parse::<u8>().unwrap() - 1;
let patch = env!("CARGO_PKG_VERSION_PATCH").parse::<u8>().unwrap();
match option_env!("CFG_RELEASE_CHANNEL") {
// We have environment variables set up from configure/make.
Some(_) => {
let commit_info = option_env!("CFG_COMMIT_HASH").map(|s| CommitInfo {
commit_hash: s.to_string(),
short_commit_hash: option_env_str!("CFG_SHORT_COMMIT_HASH").unwrap(),
commit_date: option_env_str!("CFG_COMMIT_DATE").unwrap(),
});
VersionInfo {
major,
minor,
patch,
pre_release: option_env_str!("CARGO_PKG_VERSION_PRE"),
cfg_info: Some(CfgInfo {
release_channel: option_env_str!("CFG_RELEASE_CHANNEL").unwrap(),
commit_info,
}),
}
}
// We are being compiled by Cargo itself.
None => VersionInfo {
major,
minor,
patch,
pre_release: option_env_str!("CARGO_PKG_VERSION_PRE"),
cfg_info: None,
},
}
}