Skip to main content

cargo/util/
mod.rs

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