From 882431f8f8e9de2d372d8cfaedbdf27a619d8117 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 10 Oct 2024 16:17:39 -0500 Subject: [PATCH] Add `--group` and `--only-group` to `uv sync` and includes all groups in `uv lock` --- 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 | 25 +++++--- crates/uv/tests/it/sync.rs | 80 ++++++++++++++++++++++++++ docs/reference/cli.md | 10 ++++ 8 files changed, 175 insertions(+), 18 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 79f2ba47c3f41..5443a955f8b78 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2719,6 +2719,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 ba4be17210c59..b0a0cf9029b2a 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 056908b3008f3..0b76e175bbc91 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -267,7 +267,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 aeffa1d705964..7337ff6f0de6a 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -14,7 +14,7 @@ use std::str::FromStr; 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, }; use uv_dispatch::BuildDispatch; @@ -40,7 +40,7 @@ pub(crate) async fn sync( frozen: bool, package: Option, extras: ExtrasSpecification, - dev: DevMode, + dev: DevSpecification, editable: EditableMode, install_options: InstallOptions, modifications: Modifications, @@ -150,7 +150,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 85775ad58c199..6bf4b29e1bd93 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, Reinstall, SourceStrategy, TargetTriple, TrustedHost, - TrustedPublishing, Upgrade, VersionControlSystem, + BuildOptions, Concurrency, ConfigSettings, DevMode, DevSpecification, EditableMode, + ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, + KeyringProviderType, NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, TargetTriple, + TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem, }; use uv_distribution_types::{DependencyMetadata, IndexLocations}; use uv_install_wheel::linker::LinkMode; @@ -686,7 +686,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, @@ -707,6 +707,8 @@ impl SyncSettings { dev, no_dev, only_dev, + group, + only_group, no_editable, inexact, exact, @@ -734,7 +736,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 7172cf4cb7b16..d4399418d56f9 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -4076,8 +4076,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"); @@ -4107,8 +4111,13 @@ fn add_group() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolved 1 package in [TIME] - Audited in [TIME] + Resolved 10 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + attrs==23.2.0 + + outcome==1.3.0.post0 + + sortedcontainers==2.4.0 + + trio==0.25.0 "###); let pyproject_toml = context.read("pyproject.toml"); @@ -4127,7 +4136,7 @@ fn add_group() -> Result<()> { [dependency-groups] test = [ "anyio==3.7.0", - "trio", + "trio>=0.25.0", ] "### ); @@ -4139,8 +4148,8 @@ fn add_group() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolved 1 package in [TIME] - Audited in [TIME] + Resolved 10 packages in [TIME] + Audited 3 packages in [TIME] "###); let pyproject_toml = context.read("pyproject.toml"); @@ -4159,7 +4168,7 @@ fn add_group() -> Result<()> { [dependency-groups] test = [ "anyio==3.7.0", - "trio", + "trio>=0.25.0", ] second = [ "anyio==3.7.0", diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index b7d834c7ee19d..0114464ed14f9 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1010,6 +1010,86 @@ 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 = ["trio"] + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 11 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 11 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 11 packages in [TIME] + Prepared 4 packages in [TIME] + Uninstalled 2 packages in [TIME] + Installed 4 packages in [TIME] + - anyio==4.3.0 + + attrs==23.2.0 + + outcome==1.3.0.post0 + + sortedcontainers==2.4.0 + + trio==0.25.0 + - typing-extensions==4.10.0 + "###); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 11 packages in [TIME] + Installed 2 packages in [TIME] + + anyio==4.3.0 + + 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 72da9d406f0ad..5cad1f92c7ba1 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1341,6 +1341,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.

+
--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-strategy index-strategy

The strategy to use when resolving against multiple index URLs.

@@ -1472,6 +1476,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.