From 963a7b2ab5b5c1948fe76ce634ab6ca71c7e4a12 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 26 Jun 2024 13:46:07 -0400 Subject: [PATCH] Add `--package` argument to `uv add` and `uv remove` (#4556) ## Summary Closes https://github.com/astral-sh/uv/issues/4550. --- crates/uv-cli/src/lib.rs | 16 ++++++++++++---- crates/uv/src/commands/project/add.rs | 17 +++++++++++++---- crates/uv/src/commands/project/remove.rs | 22 ++++++++++++++-------- crates/uv/src/main.rs | 2 ++ crates/uv/src/settings.rs | 12 +++++++++--- crates/uv/tests/edit.rs | 4 +++- 6 files changed, 53 insertions(+), 20 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 23bff8e7b0e4..3ef7711df995 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1639,6 +1639,10 @@ pub struct RunArgs { #[command(flatten)] pub refresh: RefreshArgs, + /// Run the command in a specific package in the workspace. + #[arg(long, conflicts_with = "isolated")] + pub package: Option, + /// The Python interpreter to use to build the run environment. /// /// By default, `uv` uses the virtual environment in the current working directory or any parent @@ -1652,10 +1656,6 @@ pub struct RunArgs { /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. #[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)] pub python: Option, - - /// Run the command in a different package in the workspace. - #[arg(long, conflicts_with = "isolated")] - pub package: Option, } #[derive(Args)] @@ -1784,6 +1784,10 @@ pub struct AddArgs { #[command(flatten)] pub refresh: RefreshArgs, + /// Add the dependency to a specific package in the workspace. + #[arg(long, conflicts_with = "isolated")] + pub package: Option, + /// The Python interpreter into which packages should be installed. /// /// By default, `uv` installs into the virtual environment in the current working directory or @@ -1811,6 +1815,10 @@ pub struct RemoveArgs { #[arg(long)] pub dev: bool, + /// Remove the dependency from a specific package in the workspace. + #[arg(long, conflicts_with = "isolated")] + pub package: Option, + /// The Python interpreter into which packages should be installed. /// /// By default, `uv` installs into the virtual environment in the current working directory or diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index c827218786bc..243862cb9701 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_dispatch::BuildDispatch; use uv_distribution::pyproject::{Source, SourceError}; @@ -11,7 +11,8 @@ use uv_types::{BuildIsolation, HashStrategy, InFlight}; use uv_cache::Cache; use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode, SetupPyStrategy}; -use uv_distribution::{DistributionDatabase, ProjectWorkspace}; +use uv_distribution::{DistributionDatabase, ProjectWorkspace, Workspace}; +use uv_normalize::PackageName; use uv_warnings::warn_user; use crate::commands::pip::operations::Modifications; @@ -32,6 +33,7 @@ pub(crate) async fn add( rev: Option, tag: Option, branch: Option, + package: Option, python: Option, settings: ResolverInstallerSettings, toolchain_preference: ToolchainPreference, @@ -46,8 +48,15 @@ pub(crate) async fn add( warn_user!("`uv add` is experimental and may change without warning."); } - // Find the project requirements. - let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?; + // Find the project in the workspace. + let project = if let Some(package) = package { + Workspace::discover(&std::env::current_dir()?, None) + .await? + .with_current_project(package.clone()) + .with_context(|| format!("Package `{package}` not found in workspace"))? + } else { + ProjectWorkspace::discover(&std::env::current_dir()?, None).await? + }; // Discover or create the virtual environment. let venv = project::init_environment( diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index da95a14984cd..a245aea1ee7f 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -1,11 +1,11 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use pep508_rs::PackageName; use uv_cache::Cache; use uv_client::Connectivity; use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode}; use uv_distribution::pyproject_mut::PyProjectTomlMut; -use uv_distribution::ProjectWorkspace; +use uv_distribution::{ProjectWorkspace, Workspace}; use uv_toolchain::{ToolchainPreference, ToolchainRequest}; use uv_warnings::warn_user; @@ -19,6 +19,7 @@ use crate::settings::{InstallerSettings, ResolverSettings}; pub(crate) async fn remove( requirements: Vec, dev: bool, + package: Option, python: Option, toolchain_preference: ToolchainPreference, preview: PreviewMode, @@ -32,8 +33,15 @@ pub(crate) async fn remove( warn_user!("`uv remove` is experimental and may change without warning."); } - // Find the project requirements. - let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?; + // Find the project in the workspace. + let project = if let Some(package) = package { + Workspace::discover(&std::env::current_dir()?, None) + .await? + .with_current_project(package.clone()) + .with_context(|| format!("Package `{package}` not found in workspace"))? + } else { + ProjectWorkspace::discover(&std::env::current_dir()?, None).await? + }; let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?; for req in requirements { @@ -47,7 +55,7 @@ pub(crate) async fn remove( .filter(|deps| !deps.is_empty()) .is_some() { - uv_warnings::warn_user!("`{req}` is not a development dependency; try calling `uv remove` without the `--dev` flag"); + warn_user!("`{req}` is not a development dependency; try calling `uv remove` without the `--dev` flag"); } anyhow::bail!("The dependency `{req}` could not be found in `dev-dependencies`"); @@ -65,9 +73,7 @@ pub(crate) async fn remove( .filter(|deps| !deps.is_empty()) .is_some() { - uv_warnings::warn_user!( - "`{req}` is a development dependency; try calling `uv remove --dev`" - ); + warn_user!("`{req}` is a development dependency; try calling `uv remove --dev`"); } anyhow::bail!("The dependency `{req}` could not be found in `dependencies`"); diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 559dd8432e65..53026baf5e7e 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -726,6 +726,7 @@ async fn run() -> Result { args.rev, args.tag, args.branch, + args.package, args.python, args.settings, globals.toolchain_preference, @@ -749,6 +750,7 @@ async fn run() -> Result { commands::remove( args.requirements, args.dev, + args.package, args.python, globals.toolchain_preference, globals.preview, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index ff51351a7b37..dff5e674e34c 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -147,8 +147,8 @@ pub(crate) struct RunSettings { pub(crate) dev: bool, pub(crate) command: ExternalCommand, pub(crate) with: Vec, - pub(crate) python: Option, pub(crate) package: Option, + pub(crate) python: Option, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, } @@ -168,8 +168,8 @@ impl RunSettings { installer, build, refresh, - python, package, + python, } = args; Self { @@ -180,8 +180,8 @@ impl RunSettings { dev: flag(dev, no_dev).unwrap_or(true), command, with, - python, package, + python, refresh: Refresh::from(refresh), settings: ResolverInstallerSettings::combine( resolver_installer_options(installer, build), @@ -438,6 +438,7 @@ pub(crate) struct AddSettings { pub(crate) rev: Option, pub(crate) tag: Option, pub(crate) branch: Option, + pub(crate) package: Option, pub(crate) python: Option, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, @@ -459,6 +460,7 @@ impl AddSettings { installer, build, refresh, + package, python, } = args; @@ -476,6 +478,7 @@ impl AddSettings { rev, tag, branch, + package, python, refresh: Refresh::from(refresh), settings: ResolverInstallerSettings::combine( @@ -492,6 +495,7 @@ impl AddSettings { pub(crate) struct RemoveSettings { pub(crate) requirements: Vec, pub(crate) dev: bool, + pub(crate) package: Option, pub(crate) python: Option, } @@ -502,12 +506,14 @@ impl RemoveSettings { let RemoveArgs { dev, requirements, + package, python, } = args; Self { requirements, dev, + package, python, } } diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 48c598f7b5b0..a2da486fbadb 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -740,7 +740,9 @@ fn add_remove_workspace() -> Result<()> { add_cmd .arg("--preview") .arg("--workspace") - .current_dir(&child1); + .arg("--package") + .arg("child1") + .current_dir(&context.temp_dir); uv_snapshot!(context.filters(), add_cmd, @r###" success: true