Skip to content

Commit

Permalink
Add support for default-groups (#8471)
Browse files Browse the repository at this point in the history
This PR adds support for `tool.uv.default-groups`, which defaults to
`["dev"]` for backwards-compatibility. These represent the groups we
sync by default.
  • Loading branch information
charliermarsh authored and zanieb committed Oct 25, 2024
1 parent 2e028cd commit 291c4c4
Show file tree
Hide file tree
Showing 19 changed files with 479 additions and 171 deletions.
201 changes: 145 additions & 56 deletions crates/uv-configuration/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,16 @@ pub enum DevMode {
}

impl DevMode {
/// Iterate over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
match self {
Self::Exclude => Either::Left(std::iter::empty()),
Self::Include | Self::Only => Either::Right(std::iter::once(&*DEV_DEPENDENCIES)),
}
}

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

/// Returns `true` if the specification only includes development dependencies.
pub fn only(&self) -> bool {
matches!(self, Self::Only)
}

/// Returns the flag that was used to request development dependencies.
pub fn as_flag(&self) -> &'static str {
match self {
Expand All @@ -36,9 +33,26 @@ impl DevMode {
Self::Only => "--only-dev",
}
}

/// Iterate over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
<&Self as IntoIterator>::into_iter(self)
}
}

impl<'a> IntoIterator for &'a DevMode {
type Item = &'a GroupName;
type IntoIter = Either<std::iter::Empty<&'a GroupName>, std::iter::Once<&'a GroupName>>;

fn into_iter(self) -> Self::IntoIter {
match self {
DevMode::Exclude => Either::Left(std::iter::empty()),
DevMode::Include | DevMode::Only => Either::Right(std::iter::once(&*DEV_DEPENDENCIES)),
}
}
}

#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
pub struct DevGroupsSpecification {
/// Legacy option for `dependency-group.dev` and `tool.uv.dev-dependencies`.
///
Expand All @@ -48,37 +62,31 @@ pub struct DevGroupsSpecification {
/// The groups to include.
///
/// Requested via the `--group` and `--only-group` options.
groups: GroupsSpecification,
groups: Option<GroupsSpecification>,
}

#[derive(Debug, Clone)]
pub enum GroupsSpecification {
/// Include dependencies from the specified groups.
Include(Vec<GroupName>),
/// Do not include dependencies from groups.
Exclude,
/// Only include dependencies from the specified groups, exclude all other dependencies.
Only(Vec<GroupName>),
}

impl GroupsSpecification {
/// Returns an [`Iterator`] over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
match self {
Self::Exclude => Either::Left(std::iter::empty()),
Self::Include(groups) | Self::Only(groups) => Either::Right(groups.iter()),
}
}

/// Returns `true` if the specification allows for production dependencies.
pub fn prod(&self) -> bool {
matches!(self, Self::Exclude | 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(_))
}

/// Returns the option that was used to request the groups, if any.
pub fn as_flag(&self) -> Option<Cow<'_, str>> {
match self {
Self::Exclude => None,
Self::Include(groups) => match groups.as_slice() {
[] => None,
[group] => Some(Cow::Owned(format!("--group {group}"))),
Expand All @@ -91,17 +99,27 @@ impl GroupsSpecification {
},
}
}
}

impl DevGroupsSpecification {
/// Returns an [`Iterator`] over the group names to include.
/// Iterate over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
match self.dev {
None => Either::Left(self.groups.iter()),
Some(ref dev_mode) => Either::Right(self.groups.iter().chain(dev_mode.iter())),
<&Self as IntoIterator>::into_iter(self)
}
}

impl<'a> IntoIterator for &'a GroupsSpecification {
type Item = &'a GroupName;
type IntoIter = std::slice::Iter<'a, GroupName>;

fn into_iter(self) -> Self::IntoIter {
match self {
GroupsSpecification::Include(groups) | GroupsSpecification::Only(groups) => {
groups.iter()
}
}
}
}

impl DevGroupsSpecification {
/// Determine the [`DevGroupsSpecification`] policy from the command-line arguments.
pub fn from_args(
dev: bool,
Expand All @@ -110,7 +128,7 @@ impl DevGroupsSpecification {
group: Vec<GroupName>,
only_group: Vec<GroupName>,
) -> Self {
let dev_mode = if only_dev {
let dev = if only_dev {
Some(DevMode::Only)
} else if no_dev {
Some(DevMode::Exclude)
Expand All @@ -121,70 +139,141 @@ impl DevGroupsSpecification {
};

let groups = if !group.is_empty() {
if matches!(dev_mode, Some(DevMode::Only)) {
if matches!(dev, Some(DevMode::Only)) {
unreachable!("cannot specify both `--only-dev` and `--group`")
};
GroupsSpecification::Include(group)
Some(GroupsSpecification::Include(group))
} else if !only_group.is_empty() {
if matches!(dev_mode, Some(DevMode::Include)) {
if matches!(dev, Some(DevMode::Include)) {
unreachable!("cannot specify both `--dev` and `--only-group`")
};
GroupsSpecification::Only(only_group)
Some(GroupsSpecification::Only(only_group))
} else {
GroupsSpecification::Exclude
None
};

Self {
dev: dev_mode,
groups,
}
Self { dev, groups }
}

/// Return a new [`DevGroupsSpecification`] with development dependencies included by default.
///
/// This is appropriate in projects, where the `dev` group is synced by default.
#[must_use]
pub fn with_default_dev(self) -> Self {
match self.dev {
Some(_) => self,
None => match self.groups {
// Only include the default `dev` group if `--only-group` wasn't used
GroupsSpecification::Only(_) => self,
GroupsSpecification::Exclude | GroupsSpecification::Include(_) => Self {
dev: Some(DevMode::Include),
..self
},
},
pub fn with_defaults(self, defaults: Vec<GroupName>) -> DevGroupsManifest {
DevGroupsManifest {
spec: self,
defaults,
}
}

/// Returns `true` if the specification allows for production dependencies.
pub fn prod(&self) -> bool {
(self.dev.is_none() || self.dev.as_ref().is_some_and(DevMode::prod)) && self.groups.prod()
self.dev.as_ref().map_or(true, DevMode::prod)
&& self.groups.as_ref().map_or(true, GroupsSpecification::prod)
}

/// Returns the flag that was used to request development dependencies.
/// Returns `true` if the specification is limited to a select set of groups.
pub fn only(&self) -> bool {
self.dev.as_ref().is_some_and(DevMode::only)
|| self.groups.as_ref().is_some_and(GroupsSpecification::only)
}

/// Returns the flag that was used to request development dependencies, if specified.
pub fn dev_mode(&self) -> Option<&DevMode> {
self.dev.as_ref()
}

/// Returns the list of groups to include.
pub fn groups(&self) -> &GroupsSpecification {
&self.groups
/// Returns the list of groups to include, if specified.
pub fn groups(&self) -> Option<&GroupsSpecification> {
self.groups.as_ref()
}

/// Returns an [`Iterator`] over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
<&Self as IntoIterator>::into_iter(self)
}
}

impl<'a> IntoIterator for &'a DevGroupsSpecification {
type Item = &'a GroupName;
type IntoIter = std::iter::Chain<
std::iter::Flatten<std::option::IntoIter<&'a DevMode>>,
std::iter::Flatten<std::option::IntoIter<&'a GroupsSpecification>>,
>;

fn into_iter(self) -> Self::IntoIter {
self.dev
.as_ref()
.into_iter()
.flatten()
.chain(self.groups.as_ref().into_iter().flatten())
}
}

impl From<DevMode> for DevGroupsSpecification {
fn from(dev: DevMode) -> Self {
Self {
dev: Some(dev),
groups: GroupsSpecification::Exclude,
groups: None,
}
}
}

impl From<GroupsSpecification> for DevGroupsSpecification {
fn from(groups: GroupsSpecification) -> Self {
Self { dev: None, groups }
Self {
dev: None,
groups: Some(groups),
}
}
}

/// The manifest of `dependency-groups` to include, taking into account the user-provided
/// [`DevGroupsSpecification`] and the project-specific default groups.
#[derive(Debug, Clone)]
pub struct DevGroupsManifest {
/// The specification for the development dependencies.
pub(crate) spec: DevGroupsSpecification,
/// The default groups to include.
pub(crate) defaults: Vec<GroupName>,
}

impl DevGroupsManifest {
/// Returns a new [`DevGroupsManifest`] with the given default groups.
pub fn from_defaults(defaults: Vec<GroupName>) -> Self {
Self {
spec: DevGroupsSpecification::default(),
defaults,
}
}

/// Returns a new [`DevGroupsManifest`] with the given specification.
pub fn from_spec(spec: DevGroupsSpecification) -> Self {
Self {
spec,
defaults: Vec::new(),
}
}

/// Returns `true` if the specification allows for production dependencies.
pub fn prod(&self) -> bool {
self.spec.prod()
}

/// Returns an [`Iterator`] over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
if self.spec.only() {
Either::Left(self.spec.iter())
} else {
Either::Right(
self.spec
.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
})),
)
}
}
}
4 changes: 2 additions & 2 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use toml_edit::{value, Array, ArrayOfTables, InlineTable, Item, Table, Value};
use url::Url;

use uv_cache_key::RepositoryUrl;
use uv_configuration::{BuildOptions, DevGroupsSpecification, ExtrasSpecification, InstallOptions};
use uv_configuration::{BuildOptions, DevGroupsManifest, ExtrasSpecification, InstallOptions};
use uv_distribution::DistributionDatabase;
use uv_distribution_filename::{DistExtension, ExtensionError, SourceDistExtension, WheelFilename};
use uv_distribution_types::{
Expand Down Expand Up @@ -580,7 +580,7 @@ impl Lock {
marker_env: &ResolverMarkerEnvironment,
tags: &Tags,
extras: &ExtrasSpecification,
dev: &DevGroupsSpecification,
dev: &DevGroupsManifest,
build_options: &BuildOptions,
install_options: &InstallOptions,
) -> Result<Resolution, LockError> {
Expand Down
4 changes: 2 additions & 2 deletions crates/uv-resolver/src/lock/requirements_txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use petgraph::{Directed, Graph};
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use url::Url;

use uv_configuration::{DevGroupsSpecification, EditableMode, ExtrasSpecification, InstallOptions};
use uv_configuration::{DevGroupsManifest, EditableMode, ExtrasSpecification, InstallOptions};
use uv_distribution_filename::{DistExtension, SourceDistExtension};
use uv_fs::Simplified;
use uv_git::GitReference;
Expand Down Expand Up @@ -43,7 +43,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
lock: &'lock Lock,
root_name: &PackageName,
extras: &ExtrasSpecification,
dev: &DevGroupsSpecification,
dev: &DevGroupsManifest,
editable: EditableMode,
hashes: bool,
install_options: &'lock InstallOptions,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv-resolver/src/lock/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use petgraph::visit::Dfs;
use petgraph::Direction;
use rustc_hash::{FxHashMap, FxHashSet};

use uv_configuration::DevGroupsSpecification;
use uv_configuration::DevGroupsManifest;
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pypi_types::ResolverMarkerEnvironment;

Expand All @@ -34,7 +34,7 @@ impl<'env> TreeDisplay<'env> {
depth: usize,
prune: &[PackageName],
packages: &[PackageName],
dev: &DevGroupsSpecification,
dev: &DevGroupsManifest,
no_dedupe: bool,
invert: bool,
) -> Self {
Expand Down
9 changes: 6 additions & 3 deletions crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1563,11 +1563,13 @@ pub struct OptionsWire {
#[allow(dead_code)]
sources: Option<serde::de::IgnoredAny>,
#[allow(dead_code)]
dev_dependencies: Option<serde::de::IgnoredAny>,
#[allow(dead_code)]
managed: Option<serde::de::IgnoredAny>,
#[allow(dead_code)]
r#package: Option<serde::de::IgnoredAny>,
#[allow(dead_code)]
default_groups: Option<serde::de::IgnoredAny>,
#[allow(dead_code)]
dev_dependencies: Option<serde::de::IgnoredAny>,
}

impl From<OptionsWire> for Options {
Expand Down Expand Up @@ -1618,9 +1620,10 @@ impl From<OptionsWire> for Options {
trusted_publishing,
workspace: _,
sources: _,
dev_dependencies: _,
managed: _,
package: _,
default_groups: _,
dev_dependencies: _,
} = value;

Self {
Expand Down
Loading

0 comments on commit 291c4c4

Please sign in to comment.