cargo/
lib.rs

1#![cfg_attr(test, deny(warnings))]
2// While we're getting used to 2018:
3#![warn(rust_2018_idioms)]
4// Clippy isn't enforced by CI (@alexcrichton isn't a fan).
5#![allow(clippy::blacklisted_name)] // frequently used in tests
6#![allow(clippy::cognitive_complexity)] // large project
7#![allow(clippy::derive_hash_xor_eq)] // there's an intentional incoherence
8#![allow(clippy::explicit_into_iter_loop)] // explicit loops are clearer
9#![allow(clippy::explicit_iter_loop)] // explicit loops are clearer
10#![allow(clippy::identity_op)] // used for vertical alignment
11#![allow(clippy::implicit_hasher)] // large project
12#![allow(clippy::large_enum_variant)] // large project
13#![allow(clippy::new_without_default)] // explicit is maybe clearer
14#![allow(clippy::redundant_closure)] // closures can be less verbose
15#![allow(clippy::redundant_closure_call)] // closures over try catch blocks
16#![allow(clippy::too_many_arguments)] // large project
17#![allow(clippy::type_complexity)] // there's an exceptionally complex type
18#![allow(clippy::wrong_self_convention)] // perhaps `Rc` should be special-cased in Clippy?
19#![allow(clippy::write_with_newline)] // too pedantic
20#![allow(clippy::inefficient_to_string)] // this causes suggestions that result in `(*s).to_string()`
21#![warn(clippy::needless_borrow)]
22#![warn(clippy::redundant_clone)]
23// Unit is now interned, and would probably be better as pass-by-copy, but
24// doing so causes a lot of & and * shenanigans that makes the code arguably
25// less clear and harder to read.
26#![allow(clippy::trivially_copy_pass_by_ref)]
27// exhaustively destructuring ensures future fields are handled
28#![allow(clippy::unneeded_field_pattern)]
29// false positives in target-specific code, for details see
30// https://github.com/rust-lang/cargo/pull/7251#pullrequestreview-274914270
31#![allow(clippy::identity_conversion)]
32
33use crate::core::shell::Verbosity::Verbose;
34use crate::core::Shell;
35use anyhow::Error;
36use log::debug;
37use serde::ser;
38use std::fmt;
39
40pub use crate::util::errors::{InternalError, VerboseError};
41pub use crate::util::{CargoResult, CliError, CliResult, Config};
42
43pub const CARGO_ENV: &str = "CARGO";
44
45#[macro_use]
46mod macros;
47
48pub mod core;
49pub mod ops;
50pub mod sources;
51pub mod util;
52
53pub struct CommitInfo {
54    pub short_commit_hash: String,
55    pub commit_hash: String,
56    pub commit_date: String,
57}
58
59pub struct CfgInfo {
60    // Information about the Git repository we may have been built from.
61    pub commit_info: Option<CommitInfo>,
62    // The release channel we were built for.
63    pub release_channel: String,
64}
65
66pub struct VersionInfo {
67    pub major: u8,
68    pub minor: u8,
69    pub patch: u8,
70    pub pre_release: Option<String>,
71    // Information that's only available when we were built with
72    // configure/make, rather than Cargo itself.
73    pub cfg_info: Option<CfgInfo>,
74}
75
76impl fmt::Display for VersionInfo {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "cargo {}.{}.{}", self.major, self.minor, self.patch)?;
79        if let Some(channel) = self.cfg_info.as_ref().map(|ci| &ci.release_channel) {
80            if channel != "stable" {
81                write!(f, "-{}", channel)?;
82                let empty = String::new();
83                write!(f, "{}", self.pre_release.as_ref().unwrap_or(&empty))?;
84            }
85        };
86
87        if let Some(ref cfg) = self.cfg_info {
88            if let Some(ref ci) = cfg.commit_info {
89                write!(f, " ({} {})", ci.short_commit_hash, ci.commit_date)?;
90            }
91        };
92        Ok(())
93    }
94}
95
96pub fn print_json<T: ser::Serialize>(obj: &T) {
97    let encoded = serde_json::to_string(&obj).unwrap();
98    println!("{}", encoded);
99}
100
101pub fn exit_with_error(err: CliError, shell: &mut Shell) -> ! {
102    debug!("exit_with_error; err={:?}", err);
103    if let Some(ref err) = err.error {
104        if let Some(clap_err) = err.downcast_ref::<clap::Error>() {
105            clap_err.exit()
106        }
107    }
108
109    let CliError { error, exit_code } = err;
110    if let Some(error) = error {
111        display_error(&error, shell);
112    }
113
114    std::process::exit(exit_code)
115}
116
117/// Displays an error, and all its causes, to stderr.
118pub fn display_error(err: &Error, shell: &mut Shell) {
119    debug!("display_error; err={:?}", err);
120    let has_verbose = _display_error(err, shell);
121    if has_verbose {
122        drop(writeln!(
123            shell.err(),
124            "\nTo learn more, run the command again with --verbose."
125        ));
126    }
127    if err
128        .chain()
129        .any(|e| e.downcast_ref::<InternalError>().is_some())
130    {
131        drop(shell.note("this is an unexpected cargo internal error"));
132        drop(
133            shell.note(
134                "we would appreciate a bug report: https://github.com/rust-lang/cargo/issues/",
135            ),
136        );
137        drop(shell.note(format!("{}", version())));
138        // Once backtraces are stabilized, this should print out a backtrace
139        // if it is available.
140    }
141}
142
143fn _display_error(err: &Error, shell: &mut Shell) -> bool {
144    let verbosity = shell.verbosity();
145    let is_verbose = |e: &(dyn std::error::Error + 'static)| -> bool {
146        verbosity != Verbose && e.downcast_ref::<VerboseError>().is_some()
147    };
148    // Generally the top error shouldn't be verbose, but check it anyways.
149    if is_verbose(err.as_ref()) {
150        return true;
151    }
152    drop(shell.error(&err));
153    for cause in err.chain().skip(1) {
154        // If we're not in verbose mode then print remaining errors until one
155        // marked as `VerboseError` appears.
156        if is_verbose(cause) {
157            return true;
158        }
159        drop(writeln!(shell.err(), "\nCaused by:"));
160        drop(writeln!(shell.err(), "  {}", cause));
161    }
162    false
163}
164
165pub fn version() -> VersionInfo {
166    macro_rules! option_env_str {
167        ($name:expr) => {
168            option_env!($name).map(|s| s.to_string())
169        };
170    }
171
172    // So this is pretty horrible...
173    // There are two versions at play here:
174    //   - version of cargo-the-binary, which you see when you type `cargo --version`
175    //   - version of cargo-the-library, which you download from crates.io for use
176    //     in your packages.
177    //
178    // We want to make the `binary` version the same as the corresponding Rust/rustc release.
179    // At the same time, we want to keep the library version at `0.x`, because Cargo as
180    // a library is (and probably will always be) unstable.
181    //
182    // Historically, Cargo used the same version number for both the binary and the library.
183    // Specifically, rustc 1.x.z was paired with cargo 0.x+1.w.
184    // We continue to use this scheme for the library, but transform it to 1.x.w for the purposes
185    // of `cargo --version`.
186    let major = 1;
187    let minor = env!("CARGO_PKG_VERSION_MINOR").parse::<u8>().unwrap() - 1;
188    let patch = env!("CARGO_PKG_VERSION_PATCH").parse::<u8>().unwrap();
189
190    match option_env!("CFG_RELEASE_CHANNEL") {
191        // We have environment variables set up from configure/make.
192        Some(_) => {
193            let commit_info = option_env!("CFG_COMMIT_HASH").map(|s| CommitInfo {
194                commit_hash: s.to_string(),
195                short_commit_hash: option_env_str!("CFG_SHORT_COMMIT_HASH").unwrap(),
196                commit_date: option_env_str!("CFG_COMMIT_DATE").unwrap(),
197            });
198            VersionInfo {
199                major,
200                minor,
201                patch,
202                pre_release: option_env_str!("CARGO_PKG_VERSION_PRE"),
203                cfg_info: Some(CfgInfo {
204                    release_channel: option_env_str!("CFG_RELEASE_CHANNEL").unwrap(),
205                    commit_info,
206                }),
207            }
208        }
209        // We are being compiled by Cargo itself.
210        None => VersionInfo {
211            major,
212            minor,
213            patch,
214            pre_release: option_env_str!("CARGO_PKG_VERSION_PRE"),
215            cfg_info: None,
216        },
217    }
218}