diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index e1084f035..472bacb16 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1542,6 +1542,15 @@ pub struct PipSyncArgs { #[arg(long, group = "sources")] pub group: Vec, + /// Include dependencies from all dependency groups. + /// + /// Only applies to `pylock.toml` sources and the `pyproject.toml` in the working directory. + #[arg(long, group = "sources", overrides_with = "no_all_groups")] + pub all_groups: bool, + + #[arg(long, overrides_with("all_groups"), hide = true)] + pub no_all_groups: bool, + #[command(flatten)] pub installer: InstallerArgs, @@ -1844,6 +1853,15 @@ pub struct PipInstallArgs { #[arg(long, group = "sources")] pub group: Vec, + /// Include dependencies from all dependency groups. + /// + /// Only applies to `pylock.toml` sources and the `pyproject.toml` in the working directory. + #[arg(long, group = "sources", overrides_with = "no_all_groups")] + pub all_groups: bool, + + #[arg(long, overrides_with("all_groups"), hide = true)] + pub no_all_groups: bool, + #[command(flatten)] pub installer: ResolverInstallerArgs, diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 88a5eba21..3896ba186 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -289,7 +289,21 @@ impl RequirementsSpecification { names.push(group.name.clone()); } - if !names.is_empty() { + if groups.all_groups { + spec.groups.insert( + pylock_toml.clone(), + DependencyGroups::from_args( + false, + false, + false, + Vec::new(), + Vec::new(), + false, + Vec::new(), + true, + ), + ); + } else if !names.is_empty() { spec.groups.insert( pylock_toml.clone(), DependencyGroups::from_args( @@ -323,7 +337,7 @@ impl RequirementsSpecification { } let mut group_specs = BTreeMap::new(); - for (path, groups) in groups_by_path { + for (path, group_names) in groups_by_path { let group_spec = DependencyGroups::from_args( false, false, @@ -331,11 +345,29 @@ impl RequirementsSpecification { Vec::new(), Vec::new(), false, - groups, + group_names, false, ); group_specs.insert(path, group_spec); } + + // If `--all-groups` was specified, we assume it refers to the root project. + if groups.all_groups { + group_specs.insert( + groups.root.join("pyproject.toml"), + DependencyGroups::from_args( + false, + false, + false, + Vec::new(), + Vec::new(), + false, + Vec::new(), + true, + ), + ); + } + spec.groups = group_specs; } @@ -534,4 +566,6 @@ pub struct GroupsSpecification { pub root: PathBuf, /// The enabled groups. pub groups: Vec, + /// Whether to include all groups. + pub all_groups: bool, } diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 9eb765a1e..cc63f4cad 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -1181,6 +1181,17 @@ pub struct PipOptions { "# )] pub group: Option>, + /// Include dependencies from all dependency groups. + /// + /// Only applies to `pylock.toml` sources and the `pyproject.toml` in the working directory. + #[option( + default = "false", + value_type = "bool", + example = r#" + all-groups = true + "# + )] + pub all_groups: Option, /// Allow `uv pip sync` with empty requirements, which will clear the environment of all /// packages. #[option( diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9a67bb877..676edcfc3 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -473,7 +473,8 @@ async fn run(mut cli: Cli) -> Result { .collect::, _>>()?; let groups = GroupsSpecification { root: project_dir.to_path_buf(), - groups: args.settings.groups, + groups: args.settings.groups.clone(), + all_groups: args.settings.all_groups, }; commands::pip_compile( @@ -568,7 +569,8 @@ async fn run(mut cli: Cli) -> Result { .collect::, _>>()?; let groups = GroupsSpecification { root: project_dir.to_path_buf(), - groups: args.settings.groups, + groups: args.settings.groups.clone(), + all_groups: args.settings.all_groups, }; commands::pip_sync( @@ -654,7 +656,8 @@ async fn run(mut cli: Cli) -> Result { .collect::, _>>()?; let groups = GroupsSpecification { root: project_dir.to_path_buf(), - groups: args.settings.groups, + groups: args.settings.groups.clone(), + all_groups: args.settings.all_groups, }; // Special-case: any source trees specified on the command-line are automatically diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 534640f94..f2cd1cc85 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2062,6 +2062,8 @@ impl PipSyncSettings { all_extras, no_all_extras, group, + all_groups, + no_all_groups, installer, refresh, require_hashes, @@ -2129,6 +2131,7 @@ impl PipSyncSettings { extra, all_extras: flag(all_extras, no_all_extras, "all-extras"), group: Some(group), + all_groups: flag(all_groups, no_all_groups, "all-groups"), torch_backend, ..PipOptions::from(installer) }, @@ -2174,6 +2177,8 @@ impl PipInstallSettings { no_deps, deps, group, + all_groups, + no_all_groups, require_hashes, no_require_hashes, verify_hashes, @@ -2286,6 +2291,7 @@ impl PipInstallSettings { extra, all_extras: flag(all_extras, no_all_extras, "all-extras"), group: Some(group), + all_groups: flag(all_groups, no_all_groups, "all-groups"), no_deps: flag(no_deps, deps, "deps"), python_version, python_platform, @@ -2907,6 +2913,7 @@ pub(crate) struct PipSettings { pub(crate) system: bool, pub(crate) extras: ExtrasSpecification, pub(crate) groups: Vec, + pub(crate) all_groups: bool, pub(crate) break_system_packages: bool, pub(crate) target: Option, pub(crate) prefix: Option, @@ -3022,6 +3029,7 @@ impl PipSettings { upgrade_package, reinstall, reinstall_package, + all_groups, } = pip.unwrap_or_default(); let ResolverInstallerOptions { @@ -3114,6 +3122,7 @@ impl PipSettings { ), groups: args.group.combine(group).unwrap_or_default(), + all_groups: args.all_groups.combine(all_groups).unwrap_or_default(), dependency_mode: if args.no_deps.combine(no_deps).unwrap_or_default() { DependencyMode::Direct } else { diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 936f77aff..2f5b37fe2 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -9754,6 +9754,25 @@ fn dependency_group() -> Result<()> { + typing-extensions==4.10.0 "); + // all groups at once + context = new_context()?; + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r").arg("pyproject.toml") + .arg("--all-groups"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + iniconfig==2.0.0 + + sniffio==1.3.1 + + sortedcontainers==2.4.0 + + typing-extensions==4.10.0 + "); + Ok(()) } @@ -11393,11 +11412,12 @@ fn pep_751_multiple_sources() -> Result<()> { #[test] fn pep_751_groups() -> Result<()> { - let context = TestContext::new("3.13"); + fn new_context() -> Result { + let context = TestContext::new("3.13"); - let pylock_toml = context.temp_dir.child("pylock.toml"); - pylock_toml.write_str( - r#" + let pylock_toml = context.temp_dir.child("pylock.toml"); + pylock_toml.write_str( + r#" lock-version = "1.0" requires-python = "==3.13.*" environments = [ @@ -11496,9 +11516,13 @@ hashes = {sha256 = "51795362d337720c28bd6c3a26eb33751f2b69590261f599ffb4172ee2c4 [[tool.pdm.targets]] requires_python = "==3.13.*" "#, - )?; + )?; + + Ok(context) + } // By default, only `iniconfig` should be installed, since it's in the default group. + let context = new_context()?; uv_snapshot!(context.filters(), context.pip_install() .arg("--preview") .arg("-r") @@ -11514,7 +11538,8 @@ requires_python = "==3.13.*" " ); - // With `--extra async`, `anyio` should be installed. + // With `--extra async`, `anyio` should be installed along with `iniconfig` (default group). + let context = new_context()?; uv_snapshot!(context.filters(), context.pip_install() .arg("--preview") .arg("-r") @@ -11526,15 +11551,17 @@ requires_python = "==3.13.*" ----- stdout ----- ----- stderr ----- - Prepared 3 packages in [TIME] - Installed 3 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + anyio==4.9.0 + idna==3.10 + + iniconfig==2.1.0 + sniffio==1.3.1 " ); - // With `--group test`, `pygments` should be installed. + // With `--group test`, only `pygments` should be installed. + let context = new_context()?; uv_snapshot!(context.filters(), context.pip_install() .arg("--preview") .arg("-r") @@ -11552,7 +11579,8 @@ requires_python = "==3.13.*" " ); - // With `--all-extras`, `blinker` should be installed. + // With `--all-extras`, `blinker` and `anyio` should be installed, along with `iniconfig` (default group). + let context = new_context()?; uv_snapshot!(context.filters(), context.pip_install() .arg("--preview") .arg("-r") @@ -11563,13 +11591,38 @@ requires_python = "==3.13.*" ----- stdout ----- ----- stderr ----- - Prepared 1 package in [TIME] - Installed 1 package in [TIME] + Prepared 5 packages in [TIME] + Installed 5 packages in [TIME] + + anyio==4.9.0 + blinker==1.9.0 + + idna==3.10 + + iniconfig==2.1.0 + + sniffio==1.3.1 " ); - // `--group pylock.toml:test` should be rejeceted. + // With `--all-groups`, both `iniconfig` (default group) and `pygments` (test group) should be + // installed. + let context = new_context()?; + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--all-groups"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 2 packages in [TIME] + Installed 2 packages in [TIME] + + iniconfig==2.1.0 + + pygments==2.19.2 + " + ); + + // `--group pylock.toml:test` should be rejected. + let context = new_context()?; uv_snapshot!(context.filters(), context.pip_install() .arg("--preview") .arg("-r") diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index bbcddd2b1..bb8b9133b 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -172,6 +172,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -357,6 +358,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -543,6 +545,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -761,6 +764,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -914,6 +918,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -1111,6 +1116,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -1356,6 +1362,7 @@ fn resolve_index_url() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -1611,6 +1618,7 @@ fn resolve_index_url() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -1821,6 +1829,7 @@ fn resolve_find_links() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -1996,6 +2005,7 @@ fn resolve_top_level() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -2231,6 +2241,7 @@ fn resolve_top_level() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -2449,6 +2460,7 @@ fn resolve_top_level() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -2623,6 +2635,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -2781,6 +2794,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -2939,6 +2953,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -3099,6 +3114,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -3447,6 +3463,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -3673,6 +3690,7 @@ fn resolve_both() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -3903,6 +3921,7 @@ fn resolve_both_special_fields() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -4212,6 +4231,7 @@ fn resolve_config_file() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -4463,6 +4483,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -4624,6 +4645,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -4804,6 +4826,7 @@ fn allow_insecure_host() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -5045,6 +5068,7 @@ fn index_priority() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -5265,6 +5289,7 @@ fn index_priority() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -5491,6 +5516,7 @@ fn index_priority() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -5712,6 +5738,7 @@ fn index_priority() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -5940,6 +5967,7 @@ fn index_priority() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -6161,6 +6189,7 @@ fn index_priority() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -6326,6 +6355,7 @@ fn verify_hashes() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -6477,6 +6507,7 @@ fn verify_hashes() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -6626,6 +6657,7 @@ fn verify_hashes() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -6777,6 +6809,7 @@ fn verify_hashes() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -6926,6 +6959,7 @@ fn verify_hashes() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, @@ -7076,6 +7110,7 @@ fn verify_hashes() -> anyhow::Result<()> { }, ), groups: [], + all_groups: false, break_system_packages: false, target: None, prefix: None, diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 409ef5911..e142509c2 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -3639,6 +3639,8 @@ uv pip sync [OPTIONS] ...
--all-extras

Include all optional dependencies.

Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

+
--all-groups

Include dependencies from all dependency groups.

+

Only applies to pylock.toml sources and the pyproject.toml in the working directory.

--allow-empty-requirements

Allow sync of empty requirements, which will clear the environment of all packages

--allow-insecure-host, --trusted-host allow-insecure-host

Allow insecure connections to a host.

Can be provided multiple times.

@@ -3883,7 +3885,7 @@ Install packages into an environment

Usage

``` -uv pip install [OPTIONS] |--editable |--group > +uv pip install [OPTIONS] |--editable |--group |--all-groups> ```

Arguments

@@ -3896,6 +3898,8 @@ uv pip install [OPTIONS] |--editable
--all-extras

Include all optional dependencies.

Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

+
--all-groups

Include dependencies from all dependency groups.

+

Only applies to pylock.toml sources and the pyproject.toml in the working directory.

--allow-insecure-host, --trusted-host allow-insecure-host

Allow insecure connections to a host.

Can be provided multiple times.

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 55d3f8ae4..4f634944c 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -2121,6 +2121,34 @@ Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. --- +#### [`all-groups`](#pip_all-groups) {: #pip_all-groups } + + +Include dependencies from all dependency groups. + +Only applies to `pylock.toml` sources and the `pyproject.toml` in the working directory. + +**Default value**: `false` + +**Type**: `bool` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.pip] + all-groups = true + ``` +=== "uv.toml" + + ```toml + [pip] + all-groups = true + ``` + +--- + #### [`allow-empty-requirements`](#pip_allow-empty-requirements) {: #pip_allow-empty-requirements } diff --git a/uv.schema.json b/uv.schema.json index 22b30cd06..47abf4016 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -1160,6 +1160,13 @@ "null" ] }, + "all-groups": { + "description": "Include dependencies from all dependency groups.\n\nOnly applies to `pylock.toml` sources and the `pyproject.toml` in the working directory.", + "type": [ + "boolean", + "null" + ] + }, "allow-empty-requirements": { "description": "Allow `uv pip sync` with empty requirements, which will clear the environment of all\npackages.", "type": [