From f9a4638065694f65f1b02525a569316e5404acd7 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 16 Oct 2024 17:53:25 -0500 Subject: [PATCH] Add `--group` and `--only-group` to `uv sync` and includes all groups in `uv lock` (#8110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #8090 Adds the ability to include a group (`--group`) in the sync or _only_ sync a group (`--only-group`). Includes all groups in the resolution, which will have the same limitations as extras as described in #6981. There's a great deal of refactoring of the "development" concept into "groups" behind the scenes that I am continuing to defer here to minimize the diff. Additionally, this does not yet resolve interactions with the existing `dev` group — we'll tackle that separately as well. I probably won't merge the stack until that design is resolved. The current proposal is that we'll just "combine' the `dev-dependencies` contents into the `dev` group. --- crates/uv-cli/src/lib.rs | 14 +++++ crates/uv-configuration/src/dev.rs | 36 +++++++++++ crates/uv/src/commands/project/lock.rs | 8 ++- crates/uv/src/commands/project/sync.rs | 6 +- crates/uv/src/settings.rs | 14 +++-- crates/uv/tests/it/edit.rs | 27 ++++++--- crates/uv/tests/it/lock.rs | 2 +- crates/uv/tests/it/sync.rs | 82 ++++++++++++++++++++++++++ docs/reference/cli.md | 10 ++++ 9 files changed, 179 insertions(+), 20 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index ea64e7339096..64276796ed90 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2788,6 +2788,20 @@ pub struct SyncArgs { #[arg(long, conflicts_with("no_dev"))] pub only_dev: bool, + /// Include dependencies from the specified local dependency group. + /// + /// May be provided multiple times. + #[arg(long, conflicts_with("only_group"))] + pub group: Vec, + + /// Only include dependencies from the specified local dependency group. + /// + /// May be provided multiple times. + /// + /// The project itself will also be omitted. + #[arg(long, conflicts_with("group"))] + pub only_group: Vec, + /// Install any editable dependencies, including the project and any workspace members, as /// non-editable. #[arg(long)] diff --git a/crates/uv-configuration/src/dev.rs b/crates/uv-configuration/src/dev.rs index ba4be17210c5..b0a0cf9029b2 100644 --- a/crates/uv-configuration/src/dev.rs +++ b/crates/uv-configuration/src/dev.rs @@ -61,3 +61,39 @@ impl From for DevSpecification { } } } + +impl DevSpecification { + /// Determine the [`DevSpecification`] policy from the command-line arguments. + pub fn from_args( + dev: bool, + no_dev: bool, + only_dev: bool, + group: Vec, + only_group: Vec, + ) -> Self { + let from_mode = DevSpecification::from(DevMode::from_args(dev, no_dev, only_dev)); + if !group.is_empty() { + match from_mode { + DevSpecification::Exclude => Self::Include(group), + DevSpecification::Include(dev) => { + Self::Include(group.into_iter().chain(dev).collect()) + } + DevSpecification::Only(_) => { + unreachable!("cannot specify both `--only-dev` and `--group`") + } + } + } else if !only_group.is_empty() { + match from_mode { + DevSpecification::Exclude => Self::Only(only_group), + DevSpecification::Only(dev) => { + Self::Only(only_group.into_iter().chain(dev).collect()) + } + // TODO(zanieb): `dev` defaults to true we can't tell if `--dev` was provided in + // conflict with `--only-group` here + DevSpecification::Include(_) => Self::Only(only_group), + } + } else { + from_mode + } + } +} diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index bc5bb44822e8..eba093b83a6e 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -302,7 +302,13 @@ async fn do_lock( let requirements = workspace.non_project_requirements().collect::>(); let overrides = workspace.overrides().into_iter().collect::>(); let constraints = workspace.constraints(); - let dev = vec![DEV_DEPENDENCIES.clone()]; + let dev: Vec<_> = workspace + .pyproject_toml() + .dependency_groups + .iter() + .flat_map(|groups| groups.keys().cloned()) + .chain(std::iter::once(DEV_DEPENDENCIES.clone())) + .collect(); let source_trees = vec![]; // Collect the list of members. diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index e6136e8e5436..1ccc84855d7c 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -8,7 +8,7 @@ use uv_auth::store_credentials; use uv_cache::Cache; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - Concurrency, Constraints, DevMode, DevSpecification, EditableMode, ExtrasSpecification, + Concurrency, Constraints, DevSpecification, EditableMode, ExtrasSpecification, HashCheckingMode, InstallOptions, LowerBound, }; use uv_dispatch::BuildDispatch; @@ -43,7 +43,7 @@ pub(crate) async fn sync( frozen: bool, package: Option, extras: ExtrasSpecification, - dev: DevMode, + dev: DevSpecification, editable: EditableMode, install_options: InstallOptions, modifications: Modifications, @@ -155,7 +155,7 @@ pub(crate) async fn sync( &venv, &lock, &extras, - &DevSpecification::from(dev), + &dev, editable, install_options, modifications, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 7123491b6527..96496b0094d0 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -19,10 +19,10 @@ use uv_cli::{ }; use uv_client::Connectivity; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, DevMode, EditableMode, ExportFormat, - ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType, - NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, SourceStrategy, TargetTriple, - TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem, + BuildOptions, Concurrency, ConfigSettings, DevMode, DevSpecification, EditableMode, + ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, + KeyringProviderType, NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, + SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem, }; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations}; use uv_install_wheel::linker::LinkMode; @@ -693,7 +693,7 @@ pub(crate) struct SyncSettings { pub(crate) locked: bool, pub(crate) frozen: bool, pub(crate) extras: ExtrasSpecification, - pub(crate) dev: DevMode, + pub(crate) dev: DevSpecification, pub(crate) editable: EditableMode, pub(crate) install_options: InstallOptions, pub(crate) modifications: Modifications, @@ -714,6 +714,8 @@ impl SyncSettings { dev, no_dev, only_dev, + group, + only_group, no_editable, inexact, exact, @@ -741,7 +743,7 @@ impl SyncSettings { flag(all_extras, no_all_extras).unwrap_or_default(), extra.unwrap_or_default(), ), - dev: DevMode::from_args(dev, no_dev, only_dev), + dev: DevSpecification::from_args(dev, no_dev, only_dev, group, only_group), editable: EditableMode::from_args(no_editable), install_options: InstallOptions::new( no_install_project, diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index aa3841764f70..40731e20e417 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -4247,8 +4247,12 @@ fn add_group() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolved 1 package in [TIME] - Audited in [TIME] + Resolved 4 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==3.7.0 + + idna==3.6 + + sniffio==1.3.1 "###); let pyproject_toml = context.read("pyproject.toml"); @@ -4272,14 +4276,19 @@ fn add_group() -> Result<()> { ); }); - uv_snapshot!(context.filters(), context.add().arg("trio").arg("--group").arg("test"), @r###" + uv_snapshot!(context.filters(), context.add().arg("requests").arg("--group").arg("test"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Resolved 1 package in [TIME] - Audited in [TIME] + Resolved 8 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + certifi==2024.2.2 + + charset-normalizer==3.3.2 + + requests==2.31.0 + + urllib3==2.2.1 "###); let pyproject_toml = context.read("pyproject.toml"); @@ -4298,7 +4307,7 @@ fn add_group() -> Result<()> { [dependency-groups] test = [ "anyio==3.7.0", - "trio", + "requests>=2.31.0", ] "### ); @@ -4310,8 +4319,8 @@ fn add_group() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolved 1 package in [TIME] - Audited in [TIME] + Resolved 8 packages in [TIME] + Audited 3 packages in [TIME] "###); let pyproject_toml = context.read("pyproject.toml"); @@ -4330,7 +4339,7 @@ fn add_group() -> Result<()> { [dependency-groups] test = [ "anyio==3.7.0", - "trio", + "requests>=2.31.0", ] second = [ "anyio==3.7.0", diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index dda1d6d6b2b0..39bb7d1acdc2 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -12846,7 +12846,7 @@ fn lock_named_index_cli() -> Result<()> { ----- stderr ----- error: Failed to build: `project @ file://[TEMP_DIR]/` - Caused by: Failed to parse entry for: `jinja2` + Caused by: Failed to parse entry: `jinja2` Caused by: Package `jinja2` references an undeclared index: `pytorch` "###); diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 095c0b4fee43..e61e391c1e36 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1012,6 +1012,88 @@ fn sync_dev() -> Result<()> { Ok(()) } +#[test] +fn sync_group() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["typing-extensions"] + + [dependency-groups] + foo = ["anyio"] + bar = ["requests"] + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + typing-extensions==4.10.0 + "###); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "###); + + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + Prepared 4 packages in [TIME] + Uninstalled 3 packages in [TIME] + Installed 4 packages in [TIME] + - anyio==4.3.0 + + certifi==2024.2.2 + + charset-normalizer==3.3.2 + + requests==2.31.0 + - sniffio==1.3.1 + - typing-extensions==4.10.0 + + urllib3==2.2.1 + "###); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 9 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + sniffio==1.3.1 + + typing-extensions==4.10.0 + "###); + + Ok(()) +} + /// Regression test for . /// /// Previously, we would read metadata statically from pyproject.toml and write that to `uv.lock`. In diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 0ff684fbd84a..34a83c5ac640 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1416,6 +1416,10 @@ uv sync [OPTIONS]

Instead of checking if the lockfile is up-to-date, uses the versions in the lockfile as the source of truth. If the lockfile is missing, uv will exit with an error. If the pyproject.toml includes changes to dependencies that have not been included in the lockfile yet, they will not be present in the environment.

May also be set with the UV_FROZEN environment variable.

+
--group group

Include dependencies from the specified local dependency group.

+ +

May be provided multiple times.

+
--help, -h

Display the concise help for this command

--index index

The URLs to use when resolving dependencies, in addition to the default index.

@@ -1555,6 +1559,12 @@ uv sync [OPTIONS]

The project itself will also be omitted.

+
--only-group only-group

Only include dependencies from the specified local dependency group.

+ +

May be provided multiple times.

+ +

The project itself will also be omitted.

+
--package package

Sync for a specific package in the workspace.

The workspace’s environment (.venv) is updated to reflect the subset of dependencies declared by the specified workspace member package.