Add a progress bar to uv tree --outdated and uv pip list --outdated (#9284)

## Summary

Closes https://github.com/astral-sh/uv/issues/9282.
This commit is contained in:
Charlie Marsh 2024-11-20 12:29:57 -05:00 committed by GitHub
parent a0de83001c
commit b19ccb6b97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 125 additions and 60 deletions

View file

@ -27,6 +27,7 @@ use uv_resolver::{ExcludeNewer, PrereleaseMode, RequiresPython};
use crate::commands::pip::latest::LatestClient;
use crate::commands::pip::operations::report_target_environment;
use crate::commands::reporters::LatestVersionReporter;
use crate::commands::ExitStatus;
use crate::printer::Printer;
@ -78,7 +79,7 @@ pub(crate) async fn pip_list(
.collect_vec();
// Determine the latest version for each package.
let latest = if outdated {
let latest = if outdated && !results.is_empty() {
let capabilities = IndexCapabilities::default();
// Initialize the registry client.
@ -110,6 +111,8 @@ pub(crate) async fn pip_list(
requires_python: &requires_python,
};
let reporter = LatestVersionReporter::from(printer).with_length(results.len() as u64);
// Fetch the latest version for each package.
let mut fetches = futures::stream::iter(&results)
.map(|dist| async {
@ -120,8 +123,14 @@ pub(crate) async fn pip_list(
let mut map = FxHashMap::default();
while let Some((package, version)) = fetches.next().await.transpose()? {
if let Some(version) = version.as_ref() {
reporter.on_fetch_version(package, version.version());
} else {
reporter.on_fetch_progress();
}
map.insert(package, version);
}
reporter.on_fetch_complete();
map
} else {
FxHashMap::default()

View file

@ -24,6 +24,7 @@ use crate::commands::project::lock::{do_safe_lock, LockMode};
use crate::commands::project::{
default_dependency_groups, DependencyGroupsTarget, ProjectError, ProjectInterpreter,
};
use crate::commands::reporters::LatestVersionReporter;
use crate::commands::{diagnostics, ExitStatus, SharedState};
use crate::printer::Printer;
use crate::settings::ResolverSettings;
@ -142,73 +143,89 @@ pub(crate) async fn tree(
// If necessary, look up the latest version of each package.
let latest = if outdated {
let ResolverSettings {
index_locations: _,
index_strategy: _,
keyring_provider,
resolution: _,
prerelease: _,
dependency_metadata: _,
config_setting: _,
no_build_isolation: _,
no_build_isolation_package: _,
exclude_newer: _,
link_mode: _,
upgrade: _,
build_options: _,
sources: _,
} = &settings;
// Filter to packages that are derived from a registry.
let packages = lock
.packages()
.iter()
.filter_map(|package| {
let index = match package.index(workspace.install_path()) {
Ok(Some(index)) => index,
Ok(None) => return None,
Err(err) => return Some(Err(err)),
};
Some(Ok((package, index)))
})
.collect::<Result<Vec<_>, _>>()?;
let capabilities = IndexCapabilities::default();
if packages.is_empty() {
PackageMap::default()
} else {
let ResolverSettings {
index_locations: _,
index_strategy: _,
keyring_provider,
resolution: _,
prerelease: _,
dependency_metadata: _,
config_setting: _,
no_build_isolation: _,
no_build_isolation_package: _,
exclude_newer: _,
link_mode: _,
upgrade: _,
build_options: _,
sources: _,
} = &settings;
// Initialize the registry client.
let client =
RegistryClientBuilder::new(cache.clone().with_refresh(Refresh::All(Timestamp::now())))
.native_tls(native_tls)
.connectivity(connectivity)
.keyring(*keyring_provider)
.allow_insecure_host(allow_insecure_host.to_vec())
.build();
let capabilities = IndexCapabilities::default();
// Initialize the client to fetch the latest version of each package.
let client = LatestClient {
client: &client,
capabilities: &capabilities,
prerelease: lock.prerelease_mode(),
exclude_newer: lock.exclude_newer(),
requires_python: lock.requires_python(),
tags: None,
};
// Initialize the registry client.
let client = RegistryClientBuilder::new(
cache.clone().with_refresh(Refresh::All(Timestamp::now())),
)
.native_tls(native_tls)
.connectivity(connectivity)
.keyring(*keyring_provider)
.allow_insecure_host(allow_insecure_host.to_vec())
.build();
// Fetch the latest version for each package.
let mut fetches = futures::stream::iter(lock.packages().iter().filter_map(|package| {
// Filter to packages that are derived from a registry.
let index = match package.index(workspace.install_path()) {
Ok(Some(index)) => index,
Ok(None) => return None,
Err(err) => return Some(Err(err)),
// Initialize the client to fetch the latest version of each package.
let client = LatestClient {
client: &client,
capabilities: &capabilities,
prerelease: lock.prerelease_mode(),
exclude_newer: lock.exclude_newer(),
requires_python: lock.requires_python(),
tags: None,
};
Some(Ok((package, index)))
}))
.map(|result| async move {
let (package, index) = result?;
let Some(filename) = client.find_latest(package.name(), Some(&index)).await? else {
return Ok(None);
};
if filename.version() == package.version() {
return Ok(None);
}
Ok::<Option<_>, Error>(Some((package, filename.into_version())))
})
.buffer_unordered(concurrency.downloads);
let mut map = PackageMap::default();
while let Some(entry) = fetches.next().await.transpose()? {
if let Some((package, version)) = entry {
map.insert(package.clone(), version);
let reporter = LatestVersionReporter::from(printer).with_length(packages.len() as u64);
// Fetch the latest version for each package.
let mut fetches = futures::stream::iter(packages)
.map(|(package, index)| async move {
let Some(filename) = client.find_latest(package.name(), Some(&index)).await?
else {
return Ok(None);
};
Ok::<Option<_>, Error>(Some((package, filename.into_version())))
})
.buffer_unordered(concurrency.downloads);
let mut map = PackageMap::default();
while let Some(entry) = fetches.next().await.transpose()? {
let Some((package, version)) = entry else {
reporter.on_fetch_progress();
continue;
};
reporter.on_fetch_version(package.name(), &version);
if version > *package.version() {
map.insert(package.clone(), version);
}
}
reporter.on_fetch_complete();
map
}
map
} else {
PackageMap::default()
};

View file

@ -11,6 +11,7 @@ use uv_distribution_types::{
BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef,
};
use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_python::PythonInstallationKey;
use uv_static::EnvVars;
@ -528,6 +529,44 @@ impl uv_publish::Reporter for PublishReporter {
}
}
#[derive(Debug)]
pub(crate) struct LatestVersionReporter {
progress: ProgressBar,
}
impl From<Printer> for LatestVersionReporter {
fn from(printer: Printer) -> Self {
let progress = ProgressBar::with_draw_target(None, printer.target());
progress.set_style(
ProgressStyle::with_template("{bar:20} [{pos}/{len}] {wide_msg:.dim}").unwrap(),
);
progress.set_message("Fetching latest versions...");
Self { progress }
}
}
impl LatestVersionReporter {
#[must_use]
pub(crate) fn with_length(self, length: u64) -> Self {
self.progress.set_length(length);
self
}
pub(crate) fn on_fetch_progress(&self) {
self.progress.inc(1);
}
pub(crate) fn on_fetch_version(&self, name: &PackageName, version: &Version) {
self.progress.set_message(format!("{name} v{version}"));
self.progress.inc(1);
}
pub(crate) fn on_fetch_complete(&self) {
self.progress.set_message("");
self.progress.finish_and_clear();
}
}
#[derive(Debug)]
pub(crate) struct CleaningDirectoryReporter {
bar: ProgressBar,