cargo/util/
mod.rs

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