Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2617,6 +2617,12 @@ pub struct RunArgs {
#[arg(long, conflicts_with("only_group"))]
pub group: Vec<GroupName>,

/// Exclude dependencies from the specified local dependency group.
///
/// May be provided multiple times.
#[arg(long)]
pub no_group: Vec<GroupName>,

/// Only include dependencies from the specified local dependency group.
///
/// May be provided multiple times.
Expand Down Expand Up @@ -2808,6 +2814,12 @@ pub struct SyncArgs {
#[arg(long, conflicts_with("only_group"))]
pub group: Vec<GroupName>,

/// Exclude dependencies from the specified local dependency group.
///
/// May be provided multiple times.
#[arg(long)]
pub no_group: Vec<GroupName>,

/// Only include dependencies from the specified local dependency group.
///
/// May be provided multiple times.
Expand Down Expand Up @@ -3198,6 +3210,12 @@ pub struct TreeArgs {
#[arg(long, conflicts_with("only_group"))]
pub group: Vec<GroupName>,

/// Exclude dependencies from the specified local dependency group.
///
/// May be provided multiple times.
#[arg(long)]
pub no_group: Vec<GroupName>,

/// Only include dependencies from the specified local dependency group.
///
/// May be provided multiple times.
Expand Down Expand Up @@ -3313,6 +3331,12 @@ pub struct ExportArgs {
#[arg(long, conflicts_with("only_group"))]
pub group: Vec<GroupName>,

/// Exclude dependencies from the specified local dependency group.
///
/// May be provided multiple times.
#[arg(long)]
pub no_group: Vec<GroupName>,

/// Only include dependencies from the specified local dependency group.
///
/// May be provided multiple times.
Expand Down
111 changes: 95 additions & 16 deletions crates/uv-configuration/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,38 +68,76 @@ pub struct DevGroupsSpecification {
#[derive(Debug, Clone)]
pub enum GroupsSpecification {
/// Include dependencies from the specified groups.
Include(Vec<GroupName>),
///
/// The `include` list is guaranteed to omit groups in the `exclude` list (i.e., they have an
/// empty intersection).
Include {
include: Vec<GroupName>,
exclude: Vec<GroupName>,
},
/// Only include dependencies from the specified groups, exclude all other dependencies.
Only(Vec<GroupName>),
///
/// The `include` list is guaranteed to omit groups in the `exclude` list (i.e., they have an
/// empty intersection).
Only {
include: Vec<GroupName>,
exclude: Vec<GroupName>,
},
}

impl GroupsSpecification {
/// Create a [`GroupsSpecification`] that includes the given group.
pub fn from_group(group: GroupName) -> Self {
Self::Include {
include: vec![group],
exclude: Vec::new(),
}
}

/// Returns `true` if the specification allows for production dependencies.
pub fn prod(&self) -> bool {
matches!(self, Self::Include(_))
matches!(self, Self::Include { .. })
}

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

/// Returns the option that was used to request the groups, if any.
pub fn as_flag(&self) -> Option<Cow<'_, str>> {
match self {
Self::Include(groups) => match groups.as_slice() {
[] => None,
Self::Include { include, exclude } => match include.as_slice() {
[] => match exclude.as_slice() {
[] => None,
[group] => Some(Cow::Owned(format!("--no-group {group}"))),
[..] => Some(Cow::Borrowed("--no-group")),
},
[group] => Some(Cow::Owned(format!("--group {group}"))),
[..] => Some(Cow::Borrowed("--group")),
},
Self::Only(groups) => match groups.as_slice() {
[] => None,
Self::Only { include, exclude } => match include.as_slice() {
[] => match exclude.as_slice() {
[] => None,
[group] => Some(Cow::Owned(format!("--no-group {group}"))),
[..] => Some(Cow::Borrowed("--no-group")),
},
[group] => Some(Cow::Owned(format!("--only-group {group}"))),
[..] => Some(Cow::Borrowed("--only-group")),
},
}
}

/// Iterate over all groups referenced in the [`DevGroupsSpecification`].
pub fn names(&self) -> impl Iterator<Item = &GroupName> {
match self {
GroupsSpecification::Include { include, exclude }
| GroupsSpecification::Only { include, exclude } => {
include.iter().chain(exclude.iter())
}
}
}

/// Iterate over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
<&Self as IntoIterator>::into_iter(self)
Expand All @@ -112,9 +150,14 @@ impl<'a> IntoIterator for &'a GroupsSpecification {

fn into_iter(self) -> Self::IntoIter {
match self {
GroupsSpecification::Include(groups) | GroupsSpecification::Only(groups) => {
groups.iter()
GroupsSpecification::Include {
include,
exclude: _,
}
| GroupsSpecification::Only {
include,
exclude: _,
} => include.iter(),
}
}
}
Expand All @@ -125,8 +168,9 @@ impl DevGroupsSpecification {
dev: bool,
no_dev: bool,
only_dev: bool,
group: Vec<GroupName>,
only_group: Vec<GroupName>,
mut group: Vec<GroupName>,
no_group: Vec<GroupName>,
mut only_group: Vec<GroupName>,
) -> Self {
let dev = if only_dev {
Some(DevMode::Only)
Expand All @@ -142,12 +186,31 @@ impl DevGroupsSpecification {
if matches!(dev, Some(DevMode::Only)) {
unreachable!("cannot specify both `--only-dev` and `--group`")
};
Some(GroupsSpecification::Include(group))

// Ensure that `--no-group` and `--group` are mutually exclusive.
group.retain(|group| !no_group.contains(group));

Some(GroupsSpecification::Include {
include: group,
exclude: no_group,
})
} else if !only_group.is_empty() {
if matches!(dev, Some(DevMode::Include)) {
unreachable!("cannot specify both `--dev` and `--only-group`")
};
Some(GroupsSpecification::Only(only_group))

// Ensure that `--no-group` and `--only-group` are mutually exclusive.
only_group.retain(|group| !no_group.contains(group));

Some(GroupsSpecification::Only {
include: only_group,
exclude: no_group,
})
} else if !no_group.is_empty() {
Some(GroupsSpecification::Include {
include: Vec::new(),
exclude: no_group,
})
} else {
None
};
Expand Down Expand Up @@ -270,8 +333,24 @@ impl DevGroupsManifest {
.iter()
.chain(self.defaults.iter().filter(|default| {
// If `--no-dev` was provided, exclude the `dev` group from the list of defaults.
!matches!(self.spec.dev_mode(), Some(DevMode::Exclude))
|| *default != &*DEV_DEPENDENCIES
if matches!(self.spec.dev_mode(), Some(DevMode::Exclude)) {
if *default == &*DEV_DEPENDENCIES {
return false;
};
}

// If `--no-group` was provided, exclude the group from the list of defaults.
if let Some(GroupsSpecification::Include {
include: _,
exclude,
}) = self.spec.groups()
{
if exclude.contains(default) {
return false;
}
}

true
})),
)
}
Expand Down
4 changes: 1 addition & 3 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -823,9 +823,7 @@ async fn lock_and_sync(
DependencyType::Group(ref group_name) => {
let extras = ExtrasSpecification::None;
let dev =
DevGroupsSpecification::from(GroupsSpecification::Include(
vec![group_name.clone()],
));
DevGroupsSpecification::from(GroupsSpecification::from_group(group_name.clone()));
(extras, dev)
}
};
Expand Down
10 changes: 7 additions & 3 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use tracing::debug;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DevGroupsSpecification, ExtrasSpecification, LowerBound, Reinstall,
Upgrade,
Concurrency, Constraints, DevGroupsSpecification, ExtrasSpecification, GroupsSpecification,
LowerBound, Reinstall, Upgrade,
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
Expand Down Expand Up @@ -1361,7 +1361,11 @@ pub(crate) fn validate_dependency_groups(
pyproject_toml: &PyProjectToml,
dev: &DevGroupsSpecification,
) -> Result<(), ProjectError> {
for group in dev.groups().into_iter().flatten() {
for group in dev
.groups()
.into_iter()
.flat_map(GroupsSpecification::names)
{
if !pyproject_toml
.dependency_groups
.as_ref()
Expand Down
20 changes: 16 additions & 4 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ impl RunSettings {
dev,
no_dev,
group,
no_group,
only_group,
module: _,
only_dev,
Expand Down Expand Up @@ -282,7 +283,9 @@ impl RunSettings {
flag(all_extras, no_all_extras).unwrap_or_default(),
extra.unwrap_or_default(),
),
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
dev: DevGroupsSpecification::from_args(
dev, no_dev, only_dev, group, no_group, only_group,
),
editable: EditableMode::from_args(no_editable),
with,
with_editable,
Expand Down Expand Up @@ -715,6 +718,7 @@ impl SyncSettings {
only_dev,
group,
only_group,
no_group,
no_editable,
inexact,
exact,
Expand Down Expand Up @@ -742,7 +746,9 @@ impl SyncSettings {
flag(all_extras, no_all_extras).unwrap_or_default(),
extra.unwrap_or_default(),
),
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
dev: DevGroupsSpecification::from_args(
dev, no_dev, only_dev, group, no_group, only_group,
),
editable: EditableMode::from_args(no_editable),
install_options: InstallOptions::new(
no_install_project,
Expand Down Expand Up @@ -1022,6 +1028,7 @@ impl TreeSettings {
only_dev,
no_dev,
group,
no_group,
only_group,
locked,
frozen,
Expand All @@ -1033,7 +1040,9 @@ impl TreeSettings {
} = args;

Self {
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
dev: DevGroupsSpecification::from_args(
dev, no_dev, only_dev, group, no_group, only_group,
),
locked,
frozen,
universal,
Expand Down Expand Up @@ -1084,6 +1093,7 @@ impl ExportSettings {
no_dev,
only_dev,
group,
no_group,
only_group,
header,
no_header,
Expand All @@ -1109,7 +1119,9 @@ impl ExportSettings {
flag(all_extras, no_all_extras).unwrap_or_default(),
extra.unwrap_or_default(),
),
dev: DevGroupsSpecification::from_args(dev, no_dev, only_dev, group, only_group),
dev: DevGroupsSpecification::from_args(
dev, no_dev, only_dev, group, no_group, only_group,
),
editable: EditableMode::from_args(no_editable),
hashes: flag(hashes, no_hashes).unwrap_or(true),
install_options: InstallOptions::new(
Expand Down
Loading
Loading