Skip to content

Commit

Permalink
Show messages for builds and large downloads in non-interactive mode (#…
Browse files Browse the repository at this point in the history
…11165)

When stderr is not a tty, we currently don't show any messages for build
or large downloads, since indicatif is hidden. We can improve this by
showing a message for:

* Starting and finishing a large download (>1MB)
* Starting and finishing a build

Downloads are limited to 1MB or unknown size to keep the logs concise
and not scroll the entire terminal away for a download that finishes
almost immediately.

These messages are not captured in the tests since their order is
non-deterministic (downloads and builds race to finish).

There are no "tick" messages for large downloads yet, we could e.g. show
an update on runnning downloads every n seconds.

Part of #11121

**Test Plan**

```
$ uv venv && FORCE_COLOR=1 cargo run -q pip install numpy --no-binary :all: --no-cache 2>&1 | tee a.txt
  Using CPython 3.13.0
  Creating virtual environment at: .venv
  Activate with: source .venv/bin/activate
  Resolved 1 package in 221ms
     Building numpy==2.2.2
        Built numpy==2.2.2
  Prepared 1 package in 2m 34s
  Installed 1 package in 6ms
   + numpy==2.2.2
```


![image](https://github.com/user-attachments/assets/f4b64313-afa7-449f-9e5b-2b1b7026bef3)


```
$ uv venv && FORCE_COLOR=1 cargo run -q pip install torch --no-cache 2>&1 | tee b.txt
  Using CPython 3.13.0
  Creating virtual environment at: .venv
  Activate with: source .venv/bin/activate
  Resolved 24 packages in 648ms
  Downloading setuptools (1.2MiB)
  Downloading nvidia-cuda-cupti-cu12 (13.2MiB)
  Downloading torch (731.1MiB)
  Downloading nvidia-nvjitlink-cu12 (20.1MiB)
  Downloading nvidia-cufft-cu12 (201.7MiB)
  Downloading nvidia-cuda-nvrtc-cu12 (23.5MiB)
  Downloading nvidia-curand-cu12 (53.7MiB)
  Downloading nvidia-nccl-cu12 (179.9MiB)
  Downloading nvidia-cudnn-cu12 (634.0MiB)
  Downloading nvidia-cublas-cu12 (346.6MiB)
  Downloading sympy (5.9MiB)
  Downloading nvidia-cusparse-cu12 (197.8MiB)
  Downloading nvidia-cusparselt-cu12 (143.1MiB)
  Downloading networkx (1.6MiB)
  Downloading nvidia-cusolver-cu12 (122.0MiB)
  Downloading triton (241.4MiB)
   Downloaded setuptools
   Downloaded networkx
   Downloaded sympy
   Downloaded nvidia-cuda-cupti-cu12
   Downloaded nvidia-nvjitlink-cu12
   Downloaded nvidia-cuda-nvrtc-cu12
   Downloaded nvidia-curand-cu12
[...]
```


![image](https://github.com/user-attachments/assets/71918d94-c5c0-44ce-bea8-aaba6cd80ef7)
  • Loading branch information
konstin authored Feb 5, 2025
1 parent b83e25a commit 59b46bc
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 21 deletions.
2 changes: 1 addition & 1 deletion crates/uv-git/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl GitSource {
// Report the checkout operation to the reporter.
if let Some(task) = task {
if let Some(reporter) = self.reporter.as_ref() {
reporter.on_checkout_complete(remote.url(), short_id.as_str(), task);
reporter.on_checkout_complete(remote.url(), actual_rev.as_str(), task);
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,10 @@ impl EnvVars {
#[attr_hidden]
pub const UV_TEST_INDEX_URL: &'static str = "UV_TEST_INDEX_URL";

/// Hide progress messages with non-deterministic order in tests.
#[attr_hidden]
pub const UV_TEST_NO_CLI_PROGRESS: &'static str = "UV_TEST_NO_CLI_PROGRESS";

/// `.env` files from which to load environment variables when executing `uv run` commands.
pub const UV_ENV_FILE: &'static str = "UV_ENV_FILE";

Expand Down
107 changes: 87 additions & 20 deletions crates/uv/src/commands/reporters.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::env;
use std::fmt::Write;
use std::sync::LazyLock;
use std::sync::{Arc, Mutex};
use std::time::Duration;

Expand All @@ -7,6 +9,8 @@ use owo_colors::OwoColorize;
use rustc_hash::FxHashMap;
use url::Url;

use crate::commands::human_readable_bytes;
use crate::printer::Printer;
use uv_cache::Removal;
use uv_distribution_types::{
BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef,
Expand All @@ -16,7 +20,10 @@ use uv_pep440::Version;
use uv_python::PythonInstallationKey;
use uv_static::EnvVars;

use crate::printer::Printer;
/// Since downloads, fetches and builds run in parallel, their message output order is
/// non-deterministic, so can't capture them in test output.
static HAS_UV_TEST_NO_CLI_PROGRESS: LazyLock<bool> =
LazyLock::new(|| env::var(EnvVars::UV_TEST_NO_CLI_PROGRESS).is_ok());

#[derive(Debug)]
struct ProgressReporter {
Expand Down Expand Up @@ -44,6 +51,8 @@ struct BarState {
sizes: Vec<u64>,
/// A map of progress bars, by ID.
bars: FxHashMap<usize, ProgressBar>,
/// The download size, if known, by ID.
download_size: FxHashMap<usize, Option<u64>>,
/// A monotonic counter for bar IDs.
id: usize,
}
Expand Down Expand Up @@ -95,19 +104,27 @@ impl ProgressReporter {
);

progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
progress.set_message(format!(
"{} {}",
let message = format!(
" {} {}",
"Building".bold().cyan(),
source.to_color_string()
));
);
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS {
let _ = writeln!(self.printer.stderr(), "{message}");
}
progress.set_message(message);

state.headers += 1;
state.bars.insert(id, progress);
id
}

fn on_build_complete(&self, source: &BuildableSource, id: usize) {
let ProgressMode::Multi { state, .. } = &self.mode else {
let ProgressMode::Multi {
state,
multi_progress,
} = &self.mode
else {
return;
};

Expand All @@ -117,11 +134,15 @@ impl ProgressReporter {
state.bars.remove(&id).unwrap()
};

progress.finish_with_message(format!(
" {} {}",
let message = format!(
" {} {}",
"Built".bold().green(),
source.to_color_string()
));
);
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS {
let _ = writeln!(self.printer.stderr(), "{message}");
}
progress.finish_with_message(message);
}

fn on_download_start(&self, name: String, size: Option<u64>) -> usize {
Expand All @@ -145,7 +166,7 @@ impl ProgressReporter {
ProgressBar::with_draw_target(size, self.printer.target()),
);

if size.is_some() {
if let Some(size) = size {
// We're using binary bytes to match `human_readable_bytes`.
progress.set_style(
ProgressStyle::with_template(
Expand All @@ -154,15 +175,36 @@ impl ProgressReporter {
.unwrap()
.progress_chars("--"),
);
// If the download is larger than 1MB, show a message to indicate that this may take
// a while keeping the log concise.
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS && size > 1024 * 1024 {
let (bytes, unit) = human_readable_bytes(size);
let _ = writeln!(
self.printer.stderr(),
"{} {} {}",
"Downloading".bold().cyan(),
name,
format!("({bytes:.1}{unit})").dimmed()
);
}
progress.set_message(name);
} else {
progress.set_style(ProgressStyle::with_template("{wide_msg:.dim} ....").unwrap());
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS {
let _ = writeln!(
self.printer.stderr(),
"{} {}",
"Downloading".bold().cyan(),
name
);
}
progress.set_message(name);
progress.finish();
}

let id = state.id();
state.bars.insert(id, progress);
state.download_size.insert(id, size);
id
}

Expand All @@ -175,11 +217,29 @@ impl ProgressReporter {
}

fn on_download_complete(&self, id: usize) {
let ProgressMode::Multi { state, .. } = &self.mode else {
let ProgressMode::Multi {
state,
multi_progress,
} = &self.mode
else {
return;
};

let progress = state.lock().unwrap().bars.remove(&id).unwrap();

let size = state.lock().unwrap().download_size[&id];
if multi_progress.is_hidden()
&& !*HAS_UV_TEST_NO_CLI_PROGRESS
&& size.is_none_or(|size| size > 1024 * 1024)
{
let _ = writeln!(
self.printer.stderr(),
" {} {}",
"Downloaded".bold().green(),
progress.message()
);
}

progress.finish_and_clear();
}

Expand All @@ -201,12 +261,11 @@ impl ProgressReporter {
);

progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
progress.set_message(format!(
"{} {} ({})",
"Updating".bold().cyan(),
url,
rev.dimmed()
));
let message = format!(" {} {} ({})", "Updating".bold().cyan(), url, rev.dimmed());
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS {
let _ = writeln!(self.printer.stderr(), "{message}");
}
progress.set_message(message);
progress.finish();

state.headers += 1;
Expand All @@ -215,7 +274,11 @@ impl ProgressReporter {
}

fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
let ProgressMode::Multi { state, .. } = &self.mode else {
let ProgressMode::Multi {
state,
multi_progress,
} = &self.mode
else {
return;
};

Expand All @@ -225,12 +288,16 @@ impl ProgressReporter {
state.bars.remove(&id).unwrap()
};

progress.finish_with_message(format!(
" {} {} ({})",
let message = format!(
" {} {} ({})",
"Updated".bold().green(),
url,
rev.dimmed()
));
);
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS {
let _ = writeln!(self.printer.stderr(), "{message}");
}
progress.finish_with_message(message);
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/uv/tests/it/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,9 @@ impl TestContext {
.env(EnvVars::UV_PYTHON_DOWNLOADS, "never")
.env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path())
.env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER)
// Since downloads, fetches and builds run in parallel, their message output order is
// non-deterministic, so can't capture them in test output.
.env(EnvVars::UV_TEST_NO_CLI_PROGRESS, "1")
.env_remove(EnvVars::UV_CACHE_DIR)
.env_remove(EnvVars::UV_TOOL_BIN_DIR)
.current_dir(self.temp_dir.path());
Expand Down

0 comments on commit 59b46bc

Please sign in to comment.