Skip to content

Commit fc77c56

Browse files
committed
Add --no-group support to CLI (#8477)
## Summary Now that `default-groups` can include more than just `"dev"`, it makes sense to allow users to remove groups with `--no-group`.
1 parent f9457f7 commit fc77c56

File tree

7 files changed

+278
-26
lines changed

7 files changed

+278
-26
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,6 +2617,12 @@ pub struct RunArgs {
26172617
#[arg(long, conflicts_with("only_group"))]
26182618
pub group: Vec<GroupName>,
26192619

2620+
/// Exclude dependencies from the specified local dependency group.
2621+
///
2622+
/// May be provided multiple times.
2623+
#[arg(long)]
2624+
pub no_group: Vec<GroupName>,
2625+
26202626
/// Only include dependencies from the specified local dependency group.
26212627
///
26222628
/// May be provided multiple times.
@@ -2808,6 +2814,12 @@ pub struct SyncArgs {
28082814
#[arg(long, conflicts_with("only_group"))]
28092815
pub group: Vec<GroupName>,
28102816

2817+
/// Exclude dependencies from the specified local dependency group.
2818+
///
2819+
/// May be provided multiple times.
2820+
#[arg(long)]
2821+
pub no_group: Vec<GroupName>,
2822+
28112823
/// Only include dependencies from the specified local dependency group.
28122824
///
28132825
/// May be provided multiple times.
@@ -3198,6 +3210,12 @@ pub struct TreeArgs {
31983210
#[arg(long, conflicts_with("only_group"))]
31993211
pub group: Vec<GroupName>,
32003212

3213+
/// Exclude dependencies from the specified local dependency group.
3214+
///
3215+
/// May be provided multiple times.
3216+
#[arg(long)]
3217+
pub no_group: Vec<GroupName>,
3218+
32013219
/// Only include dependencies from the specified local dependency group.
32023220
///
32033221
/// May be provided multiple times.
@@ -3313,6 +3331,12 @@ pub struct ExportArgs {
33133331
#[arg(long, conflicts_with("only_group"))]
33143332
pub group: Vec<GroupName>,
33153333

3334+
/// Exclude dependencies from the specified local dependency group.
3335+
///
3336+
/// May be provided multiple times.
3337+
#[arg(long)]
3338+
pub no_group: Vec<GroupName>,
3339+
33163340
/// Only include dependencies from the specified local dependency group.
33173341
///
33183342
/// May be provided multiple times.

crates/uv-configuration/src/dev.rs

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,38 +68,76 @@ pub struct DevGroupsSpecification {
6868
#[derive(Debug, Clone)]
6969
pub enum GroupsSpecification {
7070
/// Include dependencies from the specified groups.
71-
Include(Vec<GroupName>),
71+
///
72+
/// The `include` list is guaranteed to omit groups in the `exclude` list (i.e., they have an
73+
/// empty intersection).
74+
Include {
75+
include: Vec<GroupName>,
76+
exclude: Vec<GroupName>,
77+
},
7278
/// Only include dependencies from the specified groups, exclude all other dependencies.
73-
Only(Vec<GroupName>),
79+
///
80+
/// The `include` list is guaranteed to omit groups in the `exclude` list (i.e., they have an
81+
/// empty intersection).
82+
Only {
83+
include: Vec<GroupName>,
84+
exclude: Vec<GroupName>,
85+
},
7486
}
7587

7688
impl GroupsSpecification {
89+
/// Create a [`GroupsSpecification`] that includes the given group.
90+
pub fn from_group(group: GroupName) -> Self {
91+
Self::Include {
92+
include: vec![group],
93+
exclude: Vec::new(),
94+
}
95+
}
96+
7797
/// Returns `true` if the specification allows for production dependencies.
7898
pub fn prod(&self) -> bool {
79-
matches!(self, Self::Include(_))
99+
matches!(self, Self::Include { .. })
80100
}
81101

82102
/// Returns `true` if the specification is limited to a select set of groups.
83103
pub fn only(&self) -> bool {
84-
matches!(self, Self::Only(_))
104+
matches!(self, Self::Only { .. })
85105
}
86106

87107
/// Returns the option that was used to request the groups, if any.
88108
pub fn as_flag(&self) -> Option<Cow<'_, str>> {
89109
match self {
90-
Self::Include(groups) => match groups.as_slice() {
91-
[] => None,
110+
Self::Include { include, exclude } => match include.as_slice() {
111+
[] => match exclude.as_slice() {
112+
[] => None,
113+
[group] => Some(Cow::Owned(format!("--no-group {group}"))),
114+
[..] => Some(Cow::Borrowed("--no-group")),
115+
},
92116
[group] => Some(Cow::Owned(format!("--group {group}"))),
93117
[..] => Some(Cow::Borrowed("--group")),
94118
},
95-
Self::Only(groups) => match groups.as_slice() {
96-
[] => None,
119+
Self::Only { include, exclude } => match include.as_slice() {
120+
[] => match exclude.as_slice() {
121+
[] => None,
122+
[group] => Some(Cow::Owned(format!("--no-group {group}"))),
123+
[..] => Some(Cow::Borrowed("--no-group")),
124+
},
97125
[group] => Some(Cow::Owned(format!("--only-group {group}"))),
98126
[..] => Some(Cow::Borrowed("--only-group")),
99127
},
100128
}
101129
}
102130

131+
/// Iterate over all groups referenced in the [`DevGroupsSpecification`].
132+
pub fn names(&self) -> impl Iterator<Item = &GroupName> {
133+
match self {
134+
GroupsSpecification::Include { include, exclude }
135+
| GroupsSpecification::Only { include, exclude } => {
136+
include.iter().chain(exclude.iter())
137+
}
138+
}
139+
}
140+
103141
/// Iterate over the group names to include.
104142
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
105143
<&Self as IntoIterator>::into_iter(self)
@@ -112,9 +150,14 @@ impl<'a> IntoIterator for &'a GroupsSpecification {
112150

113151
fn into_iter(self) -> Self::IntoIter {
114152
match self {
115-
GroupsSpecification::Include(groups) | GroupsSpecification::Only(groups) => {
116-
groups.iter()
153+
GroupsSpecification::Include {
154+
include,
155+
exclude: _,
117156
}
157+
| GroupsSpecification::Only {
158+
include,
159+
exclude: _,
160+
} => include.iter(),
118161
}
119162
}
120163
}
@@ -125,8 +168,9 @@ impl DevGroupsSpecification {
125168
dev: bool,
126169
no_dev: bool,
127170
only_dev: bool,
128-
group: Vec<GroupName>,
129-
only_group: Vec<GroupName>,
171+
mut group: Vec<GroupName>,
172+
no_group: Vec<GroupName>,
173+
mut only_group: Vec<GroupName>,
130174
) -> Self {
131175
let dev = if only_dev {
132176
Some(DevMode::Only)
@@ -142,12 +186,31 @@ impl DevGroupsSpecification {
142186
if matches!(dev, Some(DevMode::Only)) {
143187
unreachable!("cannot specify both `--only-dev` and `--group`")
144188
};
145-
Some(GroupsSpecification::Include(group))
189+
190+
// Ensure that `--no-group` and `--group` are mutually exclusive.
191+
group.retain(|group| !no_group.contains(group));
192+
193+
Some(GroupsSpecification::Include {
194+
include: group,
195+
exclude: no_group,
196+
})
146197
} else if !only_group.is_empty() {
147198
if matches!(dev, Some(DevMode::Include)) {
148199
unreachable!("cannot specify both `--dev` and `--only-group`")
149200
};
150-
Some(GroupsSpecification::Only(only_group))
201+
202+
// Ensure that `--no-group` and `--only-group` are mutually exclusive.
203+
only_group.retain(|group| !no_group.contains(group));
204+
205+
Some(GroupsSpecification::Only {
206+
include: only_group,
207+
exclude: no_group,
208+
})
209+
} else if !no_group.is_empty() {
210+
Some(GroupsSpecification::Include {
211+
include: Vec::new(),
212+
exclude: no_group,
213+
})
151214
} else {
152215
None
153216
};
@@ -270,8 +333,24 @@ impl DevGroupsManifest {
270333
.iter()
271334
.chain(self.defaults.iter().filter(|default| {
272335
// If `--no-dev` was provided, exclude the `dev` group from the list of defaults.
273-
!matches!(self.spec.dev_mode(), Some(DevMode::Exclude))
274-
|| *default != &*DEV_DEPENDENCIES
336+
if matches!(self.spec.dev_mode(), Some(DevMode::Exclude)) {
337+
if *default == &*DEV_DEPENDENCIES {
338+
return false;
339+
};
340+
}
341+
342+
// If `--no-group` was provided, exclude the group from the list of defaults.
343+
if let Some(GroupsSpecification::Include {
344+
include: _,
345+
exclude,
346+
}) = self.spec.groups()
347+
{
348+
if exclude.contains(default) {
349+
return false;
350+
}
351+
}
352+
353+
true
275354
})),
276355
)
277356
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,7 @@ async fn lock_and_sync(
823823
DependencyType::Group(ref group_name) => {
824824
let extras = ExtrasSpecification::None;
825825
let dev =
826-
DevGroupsSpecification::from(GroupsSpecification::Include(
827-
vec![group_name.clone()],
828-
));
826+
DevGroupsSpecification::from(GroupsSpecification::from_group(group_name.clone()));
829827
(extras, dev)
830828
}
831829
};

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use tracing::debug;
88
use uv_cache::Cache;
99
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
1010
use uv_configuration::{
11-
Concurrency, Constraints, DevGroupsSpecification, ExtrasSpecification, LowerBound, Reinstall,
12-
Upgrade,
11+
Concurrency, Constraints, DevGroupsSpecification, ExtrasSpecification, GroupsSpecification,
12+
LowerBound, Reinstall, Upgrade,
1313
};
1414
use uv_dispatch::BuildDispatch;
1515
use uv_distribution::DistributionDatabase;
@@ -1361,7 +1361,11 @@ pub(crate) fn validate_dependency_groups(
13611361
pyproject_toml: &PyProjectToml,
13621362
dev: &DevGroupsSpecification,
13631363
) -> Result<(), ProjectError> {
1364-
for group in dev.groups().into_iter().flatten() {
1364+
for group in dev
1365+
.groups()
1366+
.into_iter()
1367+
.flat_map(GroupsSpecification::names)
1368+
{
13651369
if !pyproject_toml
13661370
.dependency_groups
13671371
.as_ref()

crates/uv/src/settings.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ impl RunSettings {
253253
dev,
254254
no_dev,
255255
group,
256+
no_group,
256257
only_group,
257258
module: _,
258259
only_dev,
@@ -282,7 +283,9 @@ impl RunSettings {
282283
flag(all_extras, no_all_extras).unwrap_or_default(),
283284
extra.unwrap_or_default(),
284285
),
285-
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
286+
dev: DevGroupsSpecification::from_args(
287+
dev, no_dev, only_dev, group, no_group, only_group,
288+
),
286289
editable: EditableMode::from_args(no_editable),
287290
with,
288291
with_editable,
@@ -718,6 +721,7 @@ impl SyncSettings {
718721
only_dev,
719722
group,
720723
only_group,
724+
no_group,
721725
no_editable,
722726
inexact,
723727
exact,
@@ -745,7 +749,9 @@ impl SyncSettings {
745749
flag(all_extras, no_all_extras).unwrap_or_default(),
746750
extra.unwrap_or_default(),
747751
),
748-
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
752+
dev: DevGroupsSpecification::from_args(
753+
dev, no_dev, only_dev, group, no_group, only_group,
754+
),
749755
editable: EditableMode::from_args(no_editable),
750756
install_options: InstallOptions::new(
751757
no_install_project,
@@ -1025,6 +1031,7 @@ impl TreeSettings {
10251031
only_dev,
10261032
no_dev,
10271033
group,
1034+
no_group,
10281035
only_group,
10291036
locked,
10301037
frozen,
@@ -1036,7 +1043,9 @@ impl TreeSettings {
10361043
} = args;
10371044

10381045
Self {
1039-
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
1046+
dev: DevGroupsSpecification::from_args(
1047+
dev, no_dev, only_dev, group, no_group, only_group,
1048+
),
10401049
locked,
10411050
frozen,
10421051
universal,
@@ -1087,6 +1096,7 @@ impl ExportSettings {
10871096
no_dev,
10881097
only_dev,
10891098
group,
1099+
no_group,
10901100
only_group,
10911101
header,
10921102
no_header,
@@ -1112,7 +1122,9 @@ impl ExportSettings {
11121122
flag(all_extras, no_all_extras).unwrap_or_default(),
11131123
extra.unwrap_or_default(),
11141124
),
1115-
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
1125+
dev: DevGroupsSpecification::from_args(
1126+
dev, no_dev, only_dev, group, no_group, only_group,
1127+
),
11161128
editable: EditableMode::from_args(no_editable),
11171129
hashes: flag(hashes, no_hashes).unwrap_or(true),
11181130
install_options: InstallOptions::new(

0 commit comments

Comments
 (0)