diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 06b5dff9f235..7c96ac7384d5 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3849,9 +3849,9 @@ pub struct ToolUninstallArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct ToolUpgradeArgs { - /// The name of the tool to upgrade. + /// The name of the tool to upgrade, along with an optional version specifier. #[arg(required = true)] - pub name: Vec, + pub name: Vec, /// Upgrade all tools. #[arg(long, conflicts_with("name"))] diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 8b5f0a3af360..c09e99022f68 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -338,9 +338,25 @@ impl RequirementsSpecification { } } + /// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`], including + /// constraints. + pub fn from_constraints(requirements: Vec, constraints: Vec) -> Self { + Self { + requirements: requirements + .into_iter() + .map(UnresolvedRequirementSpecification::from) + .collect(), + constraints: constraints + .into_iter() + .map(NameRequirementSpecification::from) + .collect(), + ..Self::default() + } + } + /// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`], including /// constraints and overrides. - pub fn from_constraints( + pub fn from_overrides( requirements: Vec, constraints: Vec, overrides: Vec, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index e9c038c5023c..d1ac35ed98e3 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -316,7 +316,7 @@ pub(crate) async fn run( .collect::, _>>()?; let spec = - RequirementsSpecification::from_constraints(requirements, constraints, overrides); + RequirementsSpecification::from_overrides(requirements, constraints, overrides); let result = CachedEnvironment::get_or_create( EnvironmentSpecification::from(spec), interpreter, diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 277f69826059..b77d21f334e3 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -1,14 +1,16 @@ -use std::{collections::BTreeSet, fmt::Write}; - use anyhow::Result; use itertools::Itertools; use owo_colors::OwoColorize; +use std::collections::BTreeMap; +use std::fmt::Write; use tracing::debug; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity}; use uv_configuration::{Concurrency, TrustedHost}; +use uv_fs::CWD; use uv_normalize::PackageName; +use uv_pypi_types::Requirement; use uv_python::{ EnvironmentPreference, Interpreter, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest, @@ -31,7 +33,7 @@ use crate::settings::ResolverInstallerSettings; /// Upgrade a tool. pub(crate) async fn upgrade( - name: Vec, + names: Vec, python: Option, install_mirrors: PythonInstallMirrors, connectivity: Connectivity, @@ -48,17 +50,24 @@ pub(crate) async fn upgrade( let installed_tools = InstalledTools::from_settings()?.init()?; let _lock = installed_tools.lock().await?; - // Collect the tools to upgrade. - let names: BTreeSet = { - if name.is_empty() { + // Collect the tools to upgrade, along with any constraints. + let names: BTreeMap> = { + if names.is_empty() { installed_tools .tools() .unwrap_or_default() .into_iter() - .map(|(name, _)| name) + .map(|(name, _)| (name, Vec::new())) .collect() } else { - name.into_iter().collect() + let mut map = BTreeMap::new(); + for name in names { + let requirement = Requirement::from(uv_pep508::Requirement::parse(&name, &*CWD)?); + map.entry(requirement.name.clone()) + .or_insert_with(Vec::new) + .push(requirement); + } + map } }; @@ -102,10 +111,11 @@ pub(crate) async fn upgrade( let mut did_upgrade_environment = vec![]; let mut errors = Vec::new(); - for name in &names { + for (name, constraints) in &names { debug!("Upgrading tool: `{name}`"); let result = upgrade_tool( name, + constraints, interpreter.as_ref(), printer, &installed_tools, @@ -194,6 +204,7 @@ enum UpgradeOutcome { /// Upgrade a specific tool. async fn upgrade_tool( name: &PackageName, + constraints: &[Requirement], interpreter: Option<&Interpreter>, printer: Printer, installed_tools: &InstalledTools, @@ -255,7 +266,8 @@ async fn upgrade_tool( // Resolve the requirements. let requirements = existing_tool_receipt.requirements(); - let spec = RequirementsSpecification::from_requirements(requirements.to_vec()); + let spec = + RequirementsSpecification::from_constraints(requirements.to_vec(), constraints.to_vec()); // Initialize any shared state. let state = SharedState::default(); @@ -267,7 +279,7 @@ async fn upgrade_tool( { // If we're using a new interpreter, re-create the environment for each tool. let resolution = resolve_environment( - RequirementsSpecification::from_requirements(requirements.to_vec()).into(), + spec.into(), interpreter, settings.as_ref().into(), &state, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index faa49d30e087..19c4221304b5 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1002,7 +1002,7 @@ async fn run(mut cli: Cli) -> Result { let cache = cache.init()?.with_refresh(Refresh::All(Timestamp::now())); Box::pin(commands::tool_upgrade( - args.name, + args.names, args.python, args.install_mirrors, globals.connectivity, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 3b7b9ff60de4..6429b9f1c90a 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -534,7 +534,7 @@ impl ToolInstallSettings { #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct ToolUpgradeSettings { - pub(crate) name: Vec, + pub(crate) names: Vec, pub(crate) python: Option, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) args: ResolverInstallerOptions, @@ -574,6 +574,9 @@ impl ToolUpgradeSettings { if upgrade { warn_user_once!("`--upgrade` is enabled by default on `uv tool upgrade`"); } + if !upgrade_package.is_empty() { + warn_user_once!("`--upgrade-package` is enabled by default on `uv tool upgrade`"); + } // Enable `--upgrade` by default. let installer = ResolverInstallerArgs { @@ -611,7 +614,7 @@ impl ToolUpgradeSettings { .unwrap_or_default(); Self { - name: if all { vec![] } else { name }, + names: if all { vec![] } else { name }, python: python.and_then(Maybe::into_option), args, filesystem: top_level, diff --git a/crates/uv/tests/it/tool_upgrade.rs b/crates/uv/tests/it/tool_upgrade.rs index 91b08b18ee6a..de8bf3dc6fe6 100644 --- a/crates/uv/tests/it/tool_upgrade.rs +++ b/crates/uv/tests/it/tool_upgrade.rs @@ -467,7 +467,28 @@ fn tool_upgrade_constraint() { Installed 1 executable: pybabel "###); - // Upgrade `babel`, but apply a constraint. + // Upgrade `babel`, but apply a constraint inline. + uv_snapshot!(context.filters(), context.tool_upgrade() + .arg("babel<2.12.0") + .arg("--index-url") + .arg("https://pypi.org/simple/") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Updated babel v2.6.0 -> v2.11.0 + - babel==2.6.0 + + babel==2.11.0 + - pytz==2018.5 + + pytz==2024.1 + Installed 1 executable: pybabel + "###); + + // Upgrade `babel`, but apply a constraint via `--upgrade-package`. uv_snapshot!(context.filters(), context.tool_upgrade() .arg("babel") .arg("--index-url") @@ -482,10 +503,11 @@ fn tool_upgrade_constraint() { ----- stdout ----- ----- stderr ----- - Updated babel v2.6.0 -> v2.13.1 - - babel==2.6.0 + warning: `--upgrade-package` is enabled by default on `uv tool upgrade` + Updated babel v2.11.0 -> v2.13.1 + - babel==2.11.0 + babel==2.13.1 - - pytz==2018.5 + - pytz==2024.1 + setuptools==69.2.0 Installed 1 executable: pybabel "###); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 5ff6e145446a..d2d31ef151c7 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -3495,7 +3495,7 @@ uv tool upgrade [OPTIONS] ...

Arguments

-
NAME

The name of the tool to upgrade

+
NAME

The name of the tool to upgrade, along with an optional version specifier