Enable constraints in uv tool upgrade CLI (#9375)

## Summary

Closes https://github.com/astral-sh/uv/issues/9321.
This commit is contained in:
Charlie Marsh 2024-11-25 17:22:30 -05:00 committed by GitHub
parent 0158717ae6
commit 5759cb9891
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 76 additions and 23 deletions

View file

@ -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<PackageName>,
pub name: Vec<String>,
/// Upgrade all tools.
#[arg(long, conflicts_with("name"))]

View file

@ -351,9 +351,25 @@ impl RequirementsSpecification {
}
}
/// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`], including
/// constraints.
pub fn from_constraints(requirements: Vec<Requirement>, constraints: Vec<Requirement>) -> 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<Requirement>,
constraints: Vec<Requirement>,
overrides: Vec<Requirement>,

View file

@ -316,7 +316,7 @@ pub(crate) async fn run(
.collect::<Result<Vec<_>, _>>()?;
let spec =
RequirementsSpecification::from_constraints(requirements, constraints, overrides);
RequirementsSpecification::from_overrides(requirements, constraints, overrides);
let result = CachedEnvironment::get_or_create(
EnvironmentSpecification::from(spec),
interpreter,

View file

@ -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<PackageName>,
names: Vec<String>,
python: Option<String>,
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<PackageName> = {
if name.is_empty() {
// Collect the tools to upgrade, along with any constraints.
let names: BTreeMap<PackageName, Vec<Requirement>> = {
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,

View file

@ -1002,7 +1002,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
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,

View file

@ -534,7 +534,7 @@ impl ToolInstallSettings {
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct ToolUpgradeSettings {
pub(crate) name: Vec<PackageName>,
pub(crate) names: Vec<String>,
pub(crate) python: Option<String>,
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,

View file

@ -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
"###);

View file

@ -3495,7 +3495,7 @@ uv tool upgrade [OPTIONS] <NAME>...
<h3 class="cli-reference">Arguments</h3>
<dl class="cli-reference"><dt><code>NAME</code></dt><dd><p>The name of the tool to upgrade</p>
<dl class="cli-reference"><dt><code>NAME</code></dt><dd><p>The name of the tool to upgrade, along with an optional version specifier</p>
</dd></dl>