Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 42 additions & 25 deletions crates/uv/src/commands/tool/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ use uv_cache_info::Timestamp;
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
use uv_configuration::Concurrency;
use uv_distribution_filename::DistFilename;
use uv_distribution_types::IndexCapabilities;
use uv_distribution_types::{IndexCapabilities, RequiresPython};
use uv_fs::Simplified;
use uv_normalize::PackageName;
use uv_python::LenientImplementationName;
use uv_resolver::{ExcludeNewer, PrereleaseMode};
use uv_settings::ResolverInstallerOptions;
use uv_tool::InstalledTools;
use uv_warnings::warn_user;

use crate::commands::ExitStatus;
use crate::commands::pip::latest::LatestClient;
use crate::commands::reporters::LatestVersionReporter;
use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings;

/// List installed tools.
#[expect(clippy::fn_params_excessive_bools)]
Expand Down Expand Up @@ -114,35 +115,51 @@ pub(crate) async fn list(
let latest: FxHashMap<PackageName, Option<DistFilename>> = if outdated
&& !valid_tools.is_empty()
{
let capabilities = IndexCapabilities::default();

// Initialize the registry client.
let client = RegistryClientBuilder::new(
client_builder,
cache.clone().with_refresh(Refresh::All(Timestamp::now())),
)
.build();
let download_concurrency = concurrency.downloads_semaphore.clone();

// Initialize the client to fetch the latest version of each package.
let latest_client = LatestClient {
client: &client,
capabilities: &capabilities,
prerelease: PrereleaseMode::default(),
exclude_newer: &ExcludeNewer::default(),
tags: None,
requires_python: None,
};

let reporter = LatestVersionReporter::from(printer).with_length(valid_tools.len() as u64);

// Fetch the latest version for each tool.
let mut fetches = futures::stream::iter(&valid_tools)
.map(async |(name, _tool, _tool_env, _version)| {
let latest = latest_client
.find_latest(name, None, &download_concurrency)
.await?;
Ok::<(&PackageName, Option<DistFilename>), uv_client::Error>((name, latest))
.map(|(name, tool, tool_env, _version)| {
let client_builder = client_builder.clone();
let download_concurrency = download_concurrency.clone();
async move {
let capabilities = IndexCapabilities::default();
let settings = ResolverInstallerSettings::from(ResolverInstallerOptions::from(
tool.options().clone(),
));
let interpreter = tool_env.environment().interpreter();

let client = RegistryClientBuilder::new(
client_builder
.clone()
.keyring(settings.resolver.keyring_provider),
cache.clone().with_refresh(Refresh::All(Timestamp::now())),
)
.index_locations(settings.resolver.index_locations.clone())
.index_strategy(settings.resolver.index_strategy)
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();

let requires_python = RequiresPython::greater_than_equal_version(
interpreter.python_full_version(),
);
let latest_client = LatestClient {
client: &client,
capabilities: &capabilities,
prerelease: settings.resolver.prerelease,
exclude_newer: &settings.resolver.exclude_newer,
tags: None,
requires_python: Some(&requires_python),
};

let latest = latest_client
.find_latest(name, None, &download_concurrency)
.await?;
Ok::<(&PackageName, Option<DistFilename>), anyhow::Error>((name, latest))
}
})
.buffer_unordered(concurrency.downloads);

Expand Down
31 changes: 31 additions & 0 deletions crates/uv/tests/it/tool_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,37 @@ fn tool_list_outdated() {
);
}

#[test]
fn tool_list_outdated_respects_exclude_newer() {
let context = uv_test::test_context!("3.12").with_filtered_exe_suffix();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");

// Install `black` with a persisted `exclude-newer` cutoff.
context
.tool_install()
.arg("black")
.arg("--exclude-newer")
.arg("2024-03-25T00:00:00Z")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.assert()
.success();

// `--outdated` should respect the stored tool settings and avoid flagging upgrades that
// `uv tool upgrade` would intentionally skip.
uv_snapshot!(context.filters(), context.tool_list()
.arg("--outdated")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
");
}

#[test]
fn tool_list_missing_receipt() {
let context = uv_test::test_context!("3.12").with_filtered_exe_suffix();
Expand Down
Loading