mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Show messages for builds and large downloads in non-interactive mode (#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 ```  ``` $ 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 [...] ``` 
This commit is contained in:
parent
b83e25a911
commit
59b46bc216
4 changed files with 95 additions and 21 deletions
|
@ -133,7 +133,7 @@ impl GitSource {
|
||||||
// Report the checkout operation to the reporter.
|
// Report the checkout operation to the reporter.
|
||||||
if let Some(task) = task {
|
if let Some(task) = task {
|
||||||
if let Some(reporter) = self.reporter.as_ref() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -579,6 +579,10 @@ impl EnvVars {
|
||||||
#[attr_hidden]
|
#[attr_hidden]
|
||||||
pub const UV_TEST_INDEX_URL: &'static str = "UV_TEST_INDEX_URL";
|
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.
|
/// `.env` files from which to load environment variables when executing `uv run` commands.
|
||||||
pub const UV_ENV_FILE: &'static str = "UV_ENV_FILE";
|
pub const UV_ENV_FILE: &'static str = "UV_ENV_FILE";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -7,6 +9,8 @@ use owo_colors::OwoColorize;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::commands::human_readable_bytes;
|
||||||
|
use crate::printer::Printer;
|
||||||
use uv_cache::Removal;
|
use uv_cache::Removal;
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef,
|
BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef,
|
||||||
|
@ -16,7 +20,10 @@ use uv_pep440::Version;
|
||||||
use uv_python::PythonInstallationKey;
|
use uv_python::PythonInstallationKey;
|
||||||
use uv_static::EnvVars;
|
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)]
|
#[derive(Debug)]
|
||||||
struct ProgressReporter {
|
struct ProgressReporter {
|
||||||
|
@ -44,6 +51,8 @@ struct BarState {
|
||||||
sizes: Vec<u64>,
|
sizes: Vec<u64>,
|
||||||
/// A map of progress bars, by ID.
|
/// A map of progress bars, by ID.
|
||||||
bars: FxHashMap<usize, ProgressBar>,
|
bars: FxHashMap<usize, ProgressBar>,
|
||||||
|
/// The download size, if known, by ID.
|
||||||
|
download_size: FxHashMap<usize, Option<u64>>,
|
||||||
/// A monotonic counter for bar IDs.
|
/// A monotonic counter for bar IDs.
|
||||||
id: usize,
|
id: usize,
|
||||||
}
|
}
|
||||||
|
@ -95,11 +104,15 @@ impl ProgressReporter {
|
||||||
);
|
);
|
||||||
|
|
||||||
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
||||||
progress.set_message(format!(
|
let message = format!(
|
||||||
"{} {}",
|
" {} {}",
|
||||||
"Building".bold().cyan(),
|
"Building".bold().cyan(),
|
||||||
source.to_color_string()
|
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.headers += 1;
|
||||||
state.bars.insert(id, progress);
|
state.bars.insert(id, progress);
|
||||||
|
@ -107,7 +120,11 @@ impl ProgressReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_build_complete(&self, source: &BuildableSource, id: usize) {
|
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;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,11 +134,15 @@ impl ProgressReporter {
|
||||||
state.bars.remove(&id).unwrap()
|
state.bars.remove(&id).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
progress.finish_with_message(format!(
|
let message = format!(
|
||||||
" {} {}",
|
" {} {}",
|
||||||
"Built".bold().green(),
|
"Built".bold().green(),
|
||||||
source.to_color_string()
|
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 {
|
fn on_download_start(&self, name: String, size: Option<u64>) -> usize {
|
||||||
|
@ -145,7 +166,7 @@ impl ProgressReporter {
|
||||||
ProgressBar::with_draw_target(size, self.printer.target()),
|
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`.
|
// We're using binary bytes to match `human_readable_bytes`.
|
||||||
progress.set_style(
|
progress.set_style(
|
||||||
ProgressStyle::with_template(
|
ProgressStyle::with_template(
|
||||||
|
@ -154,15 +175,36 @@ impl ProgressReporter {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.progress_chars("--"),
|
.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);
|
progress.set_message(name);
|
||||||
} else {
|
} else {
|
||||||
progress.set_style(ProgressStyle::with_template("{wide_msg:.dim} ....").unwrap());
|
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.set_message(name);
|
||||||
progress.finish();
|
progress.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = state.id();
|
let id = state.id();
|
||||||
state.bars.insert(id, progress);
|
state.bars.insert(id, progress);
|
||||||
|
state.download_size.insert(id, size);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,11 +217,29 @@ impl ProgressReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_download_complete(&self, id: usize) {
|
fn on_download_complete(&self, id: usize) {
|
||||||
let ProgressMode::Multi { state, .. } = &self.mode else {
|
let ProgressMode::Multi {
|
||||||
|
state,
|
||||||
|
multi_progress,
|
||||||
|
} = &self.mode
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let progress = state.lock().unwrap().bars.remove(&id).unwrap();
|
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();
|
progress.finish_and_clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,12 +261,11 @@ impl ProgressReporter {
|
||||||
);
|
);
|
||||||
|
|
||||||
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
progress.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
|
||||||
progress.set_message(format!(
|
let message = format!(" {} {} ({})", "Updating".bold().cyan(), url, rev.dimmed());
|
||||||
"{} {} ({})",
|
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS {
|
||||||
"Updating".bold().cyan(),
|
let _ = writeln!(self.printer.stderr(), "{message}");
|
||||||
url,
|
}
|
||||||
rev.dimmed()
|
progress.set_message(message);
|
||||||
));
|
|
||||||
progress.finish();
|
progress.finish();
|
||||||
|
|
||||||
state.headers += 1;
|
state.headers += 1;
|
||||||
|
@ -215,7 +274,11 @@ impl ProgressReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
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;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -225,12 +288,16 @@ impl ProgressReporter {
|
||||||
state.bars.remove(&id).unwrap()
|
state.bars.remove(&id).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
progress.finish_with_message(format!(
|
let message = format!(
|
||||||
" {} {} ({})",
|
" {} {} ({})",
|
||||||
"Updated".bold().green(),
|
"Updated".bold().green(),
|
||||||
url,
|
url,
|
||||||
rev.dimmed()
|
rev.dimmed()
|
||||||
));
|
);
|
||||||
|
if multi_progress.is_hidden() && !*HAS_UV_TEST_NO_CLI_PROGRESS {
|
||||||
|
let _ = writeln!(self.printer.stderr(), "{message}");
|
||||||
|
}
|
||||||
|
progress.finish_with_message(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -555,6 +555,9 @@ impl TestContext {
|
||||||
.env(EnvVars::UV_PYTHON_DOWNLOADS, "never")
|
.env(EnvVars::UV_PYTHON_DOWNLOADS, "never")
|
||||||
.env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path())
|
.env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path())
|
||||||
.env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER)
|
.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_CACHE_DIR)
|
||||||
.env_remove(EnvVars::UV_TOOL_BIN_DIR)
|
.env_remove(EnvVars::UV_TOOL_BIN_DIR)
|
||||||
.current_dir(self.temp_dir.path());
|
.current_dir(self.temp_dir.path());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue