Skip to content

Commit a706fd9

Browse files
zaniebcharliermarsh
authored andcommitted
Add --group and --only-group to uv sync and includes all groups in uv lock (#8110)
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.
1 parent 5066670 commit a706fd9

File tree

9 files changed

+179
-20
lines changed

9 files changed

+179
-20
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,6 +2788,20 @@ pub struct SyncArgs {
27882788
#[arg(long, conflicts_with("no_dev"))]
27892789
pub only_dev: bool,
27902790

2791+
/// Include dependencies from the specified local dependency group.
2792+
///
2793+
/// May be provided multiple times.
2794+
#[arg(long, conflicts_with("only_group"))]
2795+
pub group: Vec<GroupName>,
2796+
2797+
/// Only include dependencies from the specified local dependency group.
2798+
///
2799+
/// May be provided multiple times.
2800+
///
2801+
/// The project itself will also be omitted.
2802+
#[arg(long, conflicts_with("group"))]
2803+
pub only_group: Vec<GroupName>,
2804+
27912805
/// Install any editable dependencies, including the project and any workspace members, as
27922806
/// non-editable.
27932807
#[arg(long)]

crates/uv-configuration/src/dev.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,39 @@ impl From<DevMode> for DevSpecification {
6161
}
6262
}
6363
}
64+
65+
impl DevSpecification {
66+
/// Determine the [`DevSpecification`] policy from the command-line arguments.
67+
pub fn from_args(
68+
dev: bool,
69+
no_dev: bool,
70+
only_dev: bool,
71+
group: Vec<GroupName>,
72+
only_group: Vec<GroupName>,
73+
) -> Self {
74+
let from_mode = DevSpecification::from(DevMode::from_args(dev, no_dev, only_dev));
75+
if !group.is_empty() {
76+
match from_mode {
77+
DevSpecification::Exclude => Self::Include(group),
78+
DevSpecification::Include(dev) => {
79+
Self::Include(group.into_iter().chain(dev).collect())
80+
}
81+
DevSpecification::Only(_) => {
82+
unreachable!("cannot specify both `--only-dev` and `--group`")
83+
}
84+
}
85+
} else if !only_group.is_empty() {
86+
match from_mode {
87+
DevSpecification::Exclude => Self::Only(only_group),
88+
DevSpecification::Only(dev) => {
89+
Self::Only(only_group.into_iter().chain(dev).collect())
90+
}
91+
// TODO(zanieb): `dev` defaults to true we can't tell if `--dev` was provided in
92+
// conflict with `--only-group` here
93+
DevSpecification::Include(_) => Self::Only(only_group),
94+
}
95+
} else {
96+
from_mode
97+
}
98+
}
99+
}

crates/uv/src/commands/project/lock.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,13 @@ async fn do_lock(
272272
let requirements = workspace.non_project_requirements().collect::<Vec<_>>();
273273
let overrides = workspace.overrides().into_iter().collect::<Vec<_>>();
274274
let constraints = workspace.constraints();
275-
let dev = vec![DEV_DEPENDENCIES.clone()];
275+
let dev: Vec<_> = workspace
276+
.pyproject_toml()
277+
.dependency_groups
278+
.iter()
279+
.flat_map(|groups| groups.keys().cloned())
280+
.chain(std::iter::once(DEV_DEPENDENCIES.clone()))
281+
.collect();
276282
let source_trees = vec![];
277283

278284
// Collect the list of members.

crates/uv/src/commands/project/sync.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use uv_auth::store_credentials;
88
use uv_cache::Cache;
99
use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder};
1010
use uv_configuration::{
11-
Concurrency, Constraints, DevMode, DevSpecification, EditableMode, ExtrasSpecification,
11+
Concurrency, Constraints, DevSpecification, EditableMode, ExtrasSpecification,
1212
HashCheckingMode, InstallOptions, LowerBound,
1313
};
1414
use uv_dispatch::BuildDispatch;
@@ -43,7 +43,7 @@ pub(crate) async fn sync(
4343
frozen: bool,
4444
package: Option<PackageName>,
4545
extras: ExtrasSpecification,
46-
dev: DevMode,
46+
dev: DevSpecification,
4747
editable: EditableMode,
4848
install_options: InstallOptions,
4949
modifications: Modifications,
@@ -154,7 +154,7 @@ pub(crate) async fn sync(
154154
&venv,
155155
&lock,
156156
&extras,
157-
&DevSpecification::from(dev),
157+
&dev,
158158
editable,
159159
install_options,
160160
modifications,

crates/uv/src/settings.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use uv_cli::{
1919
};
2020
use uv_client::Connectivity;
2121
use uv_configuration::{
22-
BuildOptions, Concurrency, ConfigSettings, DevMode, EditableMode, ExportFormat,
23-
ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType,
24-
NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, SourceStrategy, TargetTriple,
25-
TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem,
22+
BuildOptions, Concurrency, ConfigSettings, DevMode, DevSpecification, EditableMode,
23+
ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions,
24+
KeyringProviderType, NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall,
25+
SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem,
2626
};
2727
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations};
2828
use uv_install_wheel::linker::LinkMode;
@@ -690,7 +690,7 @@ pub(crate) struct SyncSettings {
690690
pub(crate) locked: bool,
691691
pub(crate) frozen: bool,
692692
pub(crate) extras: ExtrasSpecification,
693-
pub(crate) dev: DevMode,
693+
pub(crate) dev: DevSpecification,
694694
pub(crate) editable: EditableMode,
695695
pub(crate) install_options: InstallOptions,
696696
pub(crate) modifications: Modifications,
@@ -711,6 +711,8 @@ impl SyncSettings {
711711
dev,
712712
no_dev,
713713
only_dev,
714+
group,
715+
only_group,
714716
no_editable,
715717
inexact,
716718
exact,
@@ -738,7 +740,7 @@ impl SyncSettings {
738740
flag(all_extras, no_all_extras).unwrap_or_default(),
739741
extra.unwrap_or_default(),
740742
),
741-
dev: DevMode::from_args(dev, no_dev, only_dev),
743+
dev: DevSpecification::from_args(dev, no_dev, only_dev, group, only_group),
742744
editable: EditableMode::from_args(no_editable),
743745
install_options: InstallOptions::new(
744746
no_install_project,

crates/uv/tests/it/edit.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4247,8 +4247,12 @@ fn add_group() -> Result<()> {
42474247
----- stdout -----
42484248
42494249
----- stderr -----
4250-
Resolved 1 package in [TIME]
4251-
Audited in [TIME]
4250+
Resolved 4 packages in [TIME]
4251+
Prepared 3 packages in [TIME]
4252+
Installed 3 packages in [TIME]
4253+
+ anyio==3.7.0
4254+
+ idna==3.6
4255+
+ sniffio==1.3.1
42524256
"###);
42534257

42544258
let pyproject_toml = context.read("pyproject.toml");
@@ -4272,14 +4276,19 @@ fn add_group() -> Result<()> {
42724276
);
42734277
});
42744278

4275-
uv_snapshot!(context.filters(), context.add().arg("trio").arg("--group").arg("test"), @r###"
4279+
uv_snapshot!(context.filters(), context.add().arg("requests").arg("--group").arg("test"), @r###"
42764280
success: true
42774281
exit_code: 0
42784282
----- stdout -----
42794283
42804284
----- stderr -----
4281-
Resolved 1 package in [TIME]
4282-
Audited in [TIME]
4285+
Resolved 8 packages in [TIME]
4286+
Prepared 4 packages in [TIME]
4287+
Installed 4 packages in [TIME]
4288+
+ certifi==2024.2.2
4289+
+ charset-normalizer==3.3.2
4290+
+ requests==2.31.0
4291+
+ urllib3==2.2.1
42834292
"###);
42844293

42854294
let pyproject_toml = context.read("pyproject.toml");
@@ -4298,7 +4307,7 @@ fn add_group() -> Result<()> {
42984307
[dependency-groups]
42994308
test = [
43004309
"anyio==3.7.0",
4301-
"trio",
4310+
"requests>=2.31.0",
43024311
]
43034312
"###
43044313
);
@@ -4310,8 +4319,8 @@ fn add_group() -> Result<()> {
43104319
----- stdout -----
43114320
43124321
----- stderr -----
4313-
Resolved 1 package in [TIME]
4314-
Audited in [TIME]
4322+
Resolved 8 packages in [TIME]
4323+
Audited 3 packages in [TIME]
43154324
"###);
43164325

43174326
let pyproject_toml = context.read("pyproject.toml");
@@ -4330,7 +4339,7 @@ fn add_group() -> Result<()> {
43304339
[dependency-groups]
43314340
test = [
43324341
"anyio==3.7.0",
4333-
"trio",
4342+
"requests>=2.31.0",
43344343
]
43354344
second = [
43364345
"anyio==3.7.0",

crates/uv/tests/it/lock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12678,7 +12678,7 @@ fn lock_named_index_cli() -> Result<()> {
1267812678

1267912679
----- stderr -----
1268012680
error: Failed to build: `project @ file://[TEMP_DIR]/`
12681-
Caused by: Failed to parse entry for: `jinja2`
12681+
Caused by: Failed to parse entry: `jinja2`
1268212682
Caused by: Package `jinja2` references an undeclared index: `pytorch`
1268312683
"###);
1268412684

crates/uv/tests/it/sync.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,88 @@ fn sync_dev() -> Result<()> {
10121012
Ok(())
10131013
}
10141014

1015+
#[test]
1016+
fn sync_group() -> Result<()> {
1017+
let context = TestContext::new("3.12");
1018+
1019+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
1020+
pyproject_toml.write_str(
1021+
r#"
1022+
[project]
1023+
name = "project"
1024+
version = "0.1.0"
1025+
requires-python = ">=3.12"
1026+
dependencies = ["typing-extensions"]
1027+
1028+
[dependency-groups]
1029+
foo = ["anyio"]
1030+
bar = ["requests"]
1031+
"#,
1032+
)?;
1033+
1034+
context.lock().assert().success();
1035+
1036+
uv_snapshot!(context.filters(), context.sync(), @r###"
1037+
success: true
1038+
exit_code: 0
1039+
----- stdout -----
1040+
1041+
----- stderr -----
1042+
Resolved 9 packages in [TIME]
1043+
Prepared 1 package in [TIME]
1044+
Installed 1 package in [TIME]
1045+
+ typing-extensions==4.10.0
1046+
"###);
1047+
1048+
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###"
1049+
success: true
1050+
exit_code: 0
1051+
----- stdout -----
1052+
1053+
----- stderr -----
1054+
Resolved 9 packages in [TIME]
1055+
Prepared 3 packages in [TIME]
1056+
Installed 3 packages in [TIME]
1057+
+ anyio==4.3.0
1058+
+ idna==3.6
1059+
+ sniffio==1.3.1
1060+
"###);
1061+
1062+
uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###"
1063+
success: true
1064+
exit_code: 0
1065+
----- stdout -----
1066+
1067+
----- stderr -----
1068+
Resolved 9 packages in [TIME]
1069+
Prepared 4 packages in [TIME]
1070+
Uninstalled 3 packages in [TIME]
1071+
Installed 4 packages in [TIME]
1072+
- anyio==4.3.0
1073+
+ certifi==2024.2.2
1074+
+ charset-normalizer==3.3.2
1075+
+ requests==2.31.0
1076+
- sniffio==1.3.1
1077+
- typing-extensions==4.10.0
1078+
+ urllib3==2.2.1
1079+
"###);
1080+
1081+
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###"
1082+
success: true
1083+
exit_code: 0
1084+
----- stdout -----
1085+
1086+
----- stderr -----
1087+
Resolved 9 packages in [TIME]
1088+
Installed 3 packages in [TIME]
1089+
+ anyio==4.3.0
1090+
+ sniffio==1.3.1
1091+
+ typing-extensions==4.10.0
1092+
"###);
1093+
1094+
Ok(())
1095+
}
1096+
10151097
/// Regression test for <https://github.com/astral-sh/uv/issues/6316>.
10161098
///
10171099
/// Previously, we would read metadata statically from pyproject.toml and write that to `uv.lock`. In

docs/reference/cli.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,10 @@ uv sync [OPTIONS]
14161416
<p>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 <code>pyproject.toml</code> includes changes to dependencies that have not been included in the lockfile yet, they will not be present in the environment.</p>
14171417

14181418
<p>May also be set with the <code>UV_FROZEN</code> environment variable.</p>
1419+
</dd><dt><code>--group</code> <i>group</i></dt><dd><p>Include dependencies from the specified local dependency group.</p>
1420+
1421+
<p>May be provided multiple times.</p>
1422+
14191423
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
14201424

14211425
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
@@ -1555,6 +1559,12 @@ uv sync [OPTIONS]
15551559

15561560
<p>The project itself will also be omitted.</p>
15571561

1562+
</dd><dt><code>--only-group</code> <i>only-group</i></dt><dd><p>Only include dependencies from the specified local dependency group.</p>
1563+
1564+
<p>May be provided multiple times.</p>
1565+
1566+
<p>The project itself will also be omitted.</p>
1567+
15581568
</dd><dt><code>--package</code> <i>package</i></dt><dd><p>Sync for a specific package in the workspace.</p>
15591569

15601570
<p>The workspace&#8217;s environment (<code>.venv</code>) is updated to reflect the subset of dependencies declared by the specified workspace member package.</p>

0 commit comments

Comments
 (0)