#
cargo/util/
mod.rs

    1use std::path::{Path, PathBuf};
    2use std::time::Duration;
    3
    4pub use self::canonical_url::CanonicalUrl;
    5pub use self::context::{ConfigValue, GlobalContext, homedir};
    6pub(crate) use self::counter::MetricsCounter;
    7pub use self::dependency_queue::DependencyQueue;
    8pub use self::diagnostic_server::RustfixDiagnosticServer;
    9pub use self::edit_distance::{closest, closest_msg, edit_distance};
    10pub use self::errors::CliError;
    11pub use self::errors::{CargoResult, CliResult, internal};
    12pub use self::flock::{FileLock, Filesystem};
    13pub use self::graph::Graph;
    14pub use self::hasher::StableHasher;
    15pub use self::hex::{hash_u64, short_hash, to_hex};
    16pub use self::hostname::hostname;
    17pub use self::into_url::IntoUrl;
    18pub use self::into_url_with_base::IntoUrlWithBase;
    19pub(crate) use self::io::LimitErrorReader;
    20pub use self::lockserver::{LockServer, LockServerClient, LockServerStarted};
    21pub use self::once::OnceExt;
    22pub use self::progress::{Progress, ProgressStyle};
    23pub use self::queue::Queue;
    24pub use self::rustc::Rustc;
    25pub use self::semver_ext::{OptVersionReq, VersionExt};
    26pub use self::vcs::{FossilRepo, GitRepo, HgRepo, PijulRepo, existing_vcs_repo};
    27pub use self::workspace::{
    28    add_path_args, path_args, print_available_benches, print_available_binaries,
    29    print_available_examples, print_available_packages, print_available_tests,
    30};
    31
    32pub mod auth;
    33pub mod cache_lock;
    34mod canonical_url;
    35pub mod command_prelude;
    36pub mod context;
    37mod counter;
    38pub mod cpu;
    39pub mod credential;
    40mod dependency_queue;
    41pub mod diagnostic_server;
    42pub mod edit_distance;
    43pub mod errors;
    44mod flock;
    45pub mod frontmatter;
    46pub mod graph;
    47mod hasher;
    48pub mod hex;
    49mod hostname;
    50pub mod important_paths;
    51pub mod interning;
    52pub mod into_url;
    53mod into_url_with_base;
    54mod io;
    55pub mod job;
    56pub mod lints;
    57mod lockserver;
    58pub mod machine_message;
    59pub mod network;
    60mod once;
    61mod progress;
    62mod queue;
    63pub mod restricted_names;
    64pub mod rustc;
    65mod semver_eval_ext;
    66mod semver_ext;
    67pub mod sqlite;
    68pub mod style;
    69pub mod toml;
    70pub mod toml_mut;
    71mod vcs;
    72mod workspace;
    73
    74pub fn is_rustup() -> bool {
    75    // ALLOWED: `RUSTUP_HOME` should only be read from process env, otherwise
    76    // other tools may point to executables from incompatible distributions.
    77    #[allow(clippy::disallowed_methods)]
    78    std::env::var_os("RUSTUP_HOME").is_some()
    79}
    80
    81pub fn elapsed(duration: Duration) -> String {
    82    let secs = duration.as_secs();
    83
    84    if secs >= 60 {
    85        format!("{}m {:02}s", secs / 60, secs % 60)
    86    } else {
    87        format!("{}.{:02}s", secs, duration.subsec_nanos() / 10_000_000)
    88    }
    89}
    90
    91/// Formats a number of bytes into a human readable SI-prefixed size.
    92pub struct HumanBytes(pub u64);
    93
    94impl std::fmt::Display for HumanBytes {
    95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    96        const UNITS: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
    97        let bytes = self.0 as f32;
    98        let i = ((bytes.log2() / 10.0) as usize).min(UNITS.len() - 1);
    99        let unit = UNITS[i];
    100        let size = bytes / 1024_f32.powi(i as i32);
    101
    102        // Don't show a fractional number of bytes.
    103        if i == 0 {
    104            return write!(f, "{size}{unit}");
    105        }
    106
    107        let Some(precision) = f.precision() else {
    108            return write!(f, "{size}{unit}");
    109        };
    110        write!(f, "{size:.precision$}{unit}",)
    111    }
    112}
    113
    114pub fn indented_lines(text: &str) -> String {
    115    text.lines()
    116        .map(|line| {
    117            if line.is_empty() {
    118                String::from("\n")
    119            } else {
    120                format!("  {}\n", line)
    121            }
    122        })
    123        .collect()
    124}
    125
    126pub fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
    127    // We should truncate at grapheme-boundary and compute character-widths,
    128    // yet the dependencies on unicode-segmentation and unicode-width are
    129    // not worth it.
    130    let mut chars = s.chars();
    131    let mut prefix = (&mut chars).take(max_width - 1).collect::<String>();
    132    if chars.next().is_some() {
    133        prefix.push('…');
    134    }
    135    prefix
    136}
    137
    138#[cfg(not(windows))]
    139#[inline]
    140pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
    141    std::fs::canonicalize(&path)
    142}
    143
    144#[cfg(windows)]
    145#[inline]
    146pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
    147    use std::io::Error;
    148    use std::io::ErrorKind;
    149
    150    // On Windows `canonicalize` may fail, so we fall back to getting an absolute path.
    151    std::fs::canonicalize(&path).or_else(|_| {
    152        // Return an error if a file does not exist for better compatibility with `canonicalize`
    153        if !path.as_ref().try_exists()? {
    154            return Err(Error::new(ErrorKind::NotFound, "the path was not found"));
    155        }
    156        std::path::absolute(&path)
    157    })
    158}
    159
    160/// Get the current [`umask`] value.
    161///
    162/// [`umask`]: https://man7.org/linux/man-pages/man2/umask.2.html
    163#[cfg(unix)]
    164pub fn get_umask() -> u32 {
    165    use std::sync::OnceLock;
    166    static UMASK: OnceLock<libc::mode_t> = OnceLock::new();
    167    // SAFETY: Syscalls are unsafe. Calling `umask` twice is even unsafer for
    168    // multithreading program, since it doesn't provide a way to retrieve the
    169    // value without modifications. We use a static `OnceLock` here to ensure
    170    // it only gets call once during the entire program lifetime.
    171    *UMASK.get_or_init(|| unsafe {
    172        let umask = libc::umask(0o022);
    173        libc::umask(umask);
    174        umask
    175    }) as u32 // it is u16 on macos
    176}
    177
    178#[cfg(test)]
    179mod test {
    180    use super::*;
    181
    182    #[track_caller]
    183    fn t(bytes: u64, expected: &str) {
    184        assert_eq!(&HumanBytes(bytes).to_string(), expected);
    185    }
    186
    187    #[test]
    188    fn test_human_readable_bytes() {
    189        t(0, "0B");
    190        t(8, "8B");
    191        t(1000, "1000B");
    192        t(1024, "1KiB");
    193        t(1024 * 420 + 512, "420.5KiB");
    194        t(1024 * 1024, "1MiB");
    195        t(1024 * 1024 + 1024 * 256, "1.25MiB");
    196        t(1024 * 1024 * 1024, "1GiB");
    197        t((1024. * 1024. * 1024. * 1.2345) as u64, "1.2345GiB");
    198        t(1024 * 1024 * 1024 * 1024, "1TiB");
    199        t(1024 * 1024 * 1024 * 1024 * 1024, "1PiB");
    200        t(1024 * 1024 * 1024 * 1024 * 1024 * 1024, "1EiB");
    201        t(u64::MAX, "16EiB");
    202
    203        assert_eq!(
    204            &format!("{:.3}", HumanBytes((1024. * 1.23456) as u64)),
    205            "1.234KiB"
    206        );
    207    }
    208}