Skip to content

Commit

Permalink
Add a progress bar to uv tree --outdated and `uv pip list --outdate…
Browse files Browse the repository at this point in the history
…d` (#9284)

## Summary

Closes #9282.
  • Loading branch information
charliermarsh authored Nov 20, 2024
1 parent a0de830 commit b19ccb6
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 65 deletions.
11 changes: 10 additions & 1 deletion crates/uv/src/commands/pip/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
Expand Down
145 changes: 81 additions & 64 deletions crates/uv/src/commands/project/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

let capabilities = IndexCapabilities::default();

// 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();

// 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,
};

// 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)),
};
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);
// 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<_>, _>>()?;

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;

let capabilities = IndexCapabilities::default();

// 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();

// 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,
};
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()
};
Expand Down
39 changes: 39 additions & 0 deletions crates/uv/src/commands/reporters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit b19ccb6

Please sign in to comment.