Skip to content

Commit d2e1f18

Browse files
committed
Refactor development dependency configuration (#8309)
Part of #8090 Unblocks #8274 Refactors `DevMode` and `DevSpecification` into a shared type `DevGroupsSpecification` that allows us to track if `--dev` was implicitly or explicitly provided.
1 parent fc2e79c commit d2e1f18

File tree

11 files changed

+165
-109
lines changed

11 files changed

+165
-109
lines changed

crates/uv-configuration/src/dev.rs

Lines changed: 102 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,44 @@ pub enum DevMode {
1313
}
1414

1515
impl DevMode {
16-
/// Determine the [`DevMode`] policy from the command-line arguments.
17-
pub fn from_args(dev: bool, no_dev: bool, only_dev: bool) -> Self {
18-
if only_dev {
19-
Self::Only
20-
} else if no_dev {
21-
Self::Exclude
22-
} else if dev {
23-
Self::Include
24-
} else {
25-
Self::default()
16+
/// Iterate over the group names to include.
17+
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
18+
match self {
19+
Self::Exclude => Either::Left(std::iter::empty()),
20+
Self::Include | Self::Only => Either::Right(std::iter::once(&*DEV_DEPENDENCIES)),
2621
}
2722
}
23+
24+
/// Returns `true` if the specification allows for production dependencies.
25+
pub fn prod(&self) -> bool {
26+
matches!(self, Self::Exclude | Self::Include)
27+
}
2828
}
2929

3030
#[derive(Debug, Clone)]
31-
pub enum DevSpecification {
32-
/// Include dev dependencies from the specified group.
31+
pub struct DevGroupsSpecification {
32+
/// Legacy option for `dependency-group.dev` and `tool.uv.dev-dependencies`.
33+
///
34+
/// Requested via the `--dev`, `--no-dev`, and `--only-dev` flags.
35+
dev: Option<DevMode>,
36+
37+
/// The groups to include.
38+
///
39+
/// Requested via the `--group` and `--only-group` options.
40+
groups: GroupsSpecification,
41+
}
42+
43+
#[derive(Debug, Clone)]
44+
pub enum GroupsSpecification {
45+
/// Include dependencies from the specified groups.
3346
Include(Vec<GroupName>),
34-
/// Do not include dev dependencies.
47+
/// Do not include dependencies from groups.
3548
Exclude,
36-
/// Include dev dependencies from the specified groups, and exclude all non-dev dependencies.
49+
/// Only include dependencies from the specified groups, exclude all other dependencies.
3750
Only(Vec<GroupName>),
3851
}
3952

40-
impl DevSpecification {
53+
impl GroupsSpecification {
4154
/// Returns an [`Iterator`] over the group names to include.
4255
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
4356
match self {
@@ -52,48 +65,92 @@ impl DevSpecification {
5265
}
5366
}
5467

55-
impl From<DevMode> for DevSpecification {
56-
fn from(mode: DevMode) -> Self {
57-
match mode {
58-
DevMode::Include => Self::Include(vec![DEV_DEPENDENCIES.clone()]),
59-
DevMode::Exclude => Self::Exclude,
60-
DevMode::Only => Self::Only(vec![DEV_DEPENDENCIES.clone()]),
68+
impl DevGroupsSpecification {
69+
/// Returns an [`Iterator`] over the group names to include.
70+
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
71+
match self.dev {
72+
None => Either::Left(self.groups.iter()),
73+
Some(ref dev_mode) => Either::Right(self.groups.iter().chain(dev_mode.iter())),
6174
}
6275
}
63-
}
6476

65-
impl DevSpecification {
66-
/// Determine the [`DevSpecification`] policy from the command-line arguments.
77+
/// Determine the [`DevGroupsSpecification`] policy from the command-line arguments.
6778
pub fn from_args(
6879
dev: bool,
6980
no_dev: bool,
7081
only_dev: bool,
7182
group: Vec<GroupName>,
7283
only_group: Vec<GroupName>,
7384
) -> 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+
let dev_mode = if only_dev {
86+
Some(DevMode::Only)
87+
} else if no_dev {
88+
Some(DevMode::Exclude)
89+
} else if dev {
90+
Some(DevMode::Include)
91+
} else {
92+
None
93+
};
94+
95+
let groups = if !group.is_empty() {
96+
if matches!(dev_mode, Some(DevMode::Only)) {
97+
unreachable!("cannot specify both `--only-dev` and `--group`")
98+
};
99+
GroupsSpecification::Include(group)
85100
} 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-
}
101+
if matches!(dev_mode, Some(DevMode::Include)) {
102+
unreachable!("cannot specify both `--dev` and `--only-group`")
103+
};
104+
GroupsSpecification::Only(only_group)
95105
} else {
96-
from_mode
106+
GroupsSpecification::Exclude
107+
};
108+
109+
Self {
110+
dev: dev_mode,
111+
groups,
112+
}
113+
}
114+
115+
/// Return a new [`DevGroupsSpecification`] with development dependencies included by default.
116+
///
117+
/// This is appropriate in projects, where the `dev` group is synced by default.
118+
#[must_use]
119+
pub fn with_default_dev(self) -> Self {
120+
match self.dev {
121+
Some(_) => self,
122+
None => match self.groups {
123+
// Only include the default `dev` group if `--only-group` wasn't used
124+
GroupsSpecification::Only(_) => self,
125+
GroupsSpecification::Exclude | GroupsSpecification::Include(_) => Self {
126+
dev: Some(DevMode::Include),
127+
..self
128+
},
129+
},
97130
}
98131
}
132+
133+
/// Returns `true` if the specification allows for production dependencies.
134+
pub fn prod(&self) -> bool {
135+
(self.dev.is_none() || self.dev.as_ref().is_some_and(DevMode::prod)) && self.groups.prod()
136+
}
137+
138+
pub fn dev_mode(&self) -> Option<&DevMode> {
139+
self.dev.as_ref()
140+
}
141+
}
142+
143+
impl From<DevMode> for DevGroupsSpecification {
144+
fn from(dev: DevMode) -> Self {
145+
Self {
146+
dev: Some(dev),
147+
groups: GroupsSpecification::Exclude,
148+
}
149+
}
150+
}
151+
152+
impl From<GroupsSpecification> for DevGroupsSpecification {
153+
fn from(groups: GroupsSpecification) -> Self {
154+
Self { dev: None, groups }
155+
}
99156
}

crates/uv-resolver/src/lock/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use toml_edit::{value, Array, ArrayOfTables, InlineTable, Item, Table, Value};
1515
use url::Url;
1616

1717
use uv_cache_key::RepositoryUrl;
18-
use uv_configuration::{BuildOptions, DevSpecification, ExtrasSpecification, InstallOptions};
18+
use uv_configuration::{BuildOptions, DevGroupsSpecification, ExtrasSpecification, InstallOptions};
1919
use uv_distribution::DistributionDatabase;
2020
use uv_distribution_filename::{DistExtension, ExtensionError, SourceDistExtension, WheelFilename};
2121
use uv_distribution_types::{
@@ -580,7 +580,7 @@ impl Lock {
580580
marker_env: &ResolverMarkerEnvironment,
581581
tags: &Tags,
582582
extras: &ExtrasSpecification,
583-
dev: &DevSpecification,
583+
dev: &DevGroupsSpecification,
584584
build_options: &BuildOptions,
585585
install_options: &InstallOptions,
586586
) -> Result<Resolution, LockError> {

crates/uv-resolver/src/lock/requirements_txt.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use petgraph::{Directed, Graph};
1010
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
1111
use url::Url;
1212

13-
use uv_configuration::{DevSpecification, EditableMode, ExtrasSpecification, InstallOptions};
13+
use uv_configuration::{DevGroupsSpecification, EditableMode, ExtrasSpecification, InstallOptions};
1414
use uv_distribution_filename::{DistExtension, SourceDistExtension};
1515
use uv_fs::Simplified;
1616
use uv_git::GitReference;
@@ -43,7 +43,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
4343
lock: &'lock Lock,
4444
root_name: &PackageName,
4545
extras: &ExtrasSpecification,
46-
dev: &DevSpecification,
46+
dev: &DevGroupsSpecification,
4747
editable: EditableMode,
4848
hashes: bool,
4949
install_options: &'lock InstallOptions,

crates/uv-resolver/src/lock/tree.rs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use petgraph::visit::Dfs;
77
use petgraph::Direction;
88
use rustc_hash::{FxHashMap, FxHashSet};
99

10-
use uv_configuration::DevMode;
10+
use uv_configuration::DevGroupsSpecification;
1111
use uv_normalize::{ExtraName, GroupName, PackageName};
1212
use uv_pypi_types::ResolverMarkerEnvironment;
1313

@@ -34,7 +34,7 @@ impl<'env> TreeDisplay<'env> {
3434
depth: usize,
3535
prune: &[PackageName],
3636
packages: &[PackageName],
37-
dev: DevMode,
37+
dev: &DevGroupsSpecification,
3838
no_dedupe: bool,
3939
invert: bool,
4040
) -> Self {
@@ -134,8 +134,6 @@ impl<'env> TreeDisplay<'env> {
134134
}
135135
}
136136

137-
let mut modified = false;
138-
139137
// Step 1: Filter out packages that aren't reachable on this platform.
140138
if let Some(environment_markers) = markers {
141139
let mut reachable = FxHashSet::default();
@@ -167,12 +165,11 @@ impl<'env> TreeDisplay<'env> {
167165

168166
// Remove the unreachable nodes from the graph.
169167
graph.retain_nodes(|_, index| reachable.contains(&index));
170-
modified = true;
171168
}
172169

173170
// Step 2: Filter the graph to those that are reachable in production or development, if
174171
// `--no-dev` or `--only-dev` were specified, respectively.
175-
if dev != DevMode::Include {
172+
{
176173
let mut reachable = FxHashSet::default();
177174

178175
// Perform a DFS from the root nodes to find the reachable nodes, following only the
@@ -189,27 +186,24 @@ impl<'env> TreeDisplay<'env> {
189186
while let Some(node) = stack.pop_front() {
190187
reachable.insert(node);
191188
for edge in graph.edges_directed(node, Direction::Outgoing) {
192-
if matches!(edge.weight(), Edge::Prod(_) | Edge::Optional(_, _)) {
189+
let include = match edge.weight() {
190+
Edge::Prod(_) => dev.prod(),
191+
Edge::Optional(_, _) => dev.prod(),
192+
Edge::Dev(group, _) => dev.iter().contains(*group),
193+
};
194+
if include {
193195
stack.push_back(edge.target());
194196
}
195197
}
196198
}
197199

198200
// Remove the unreachable nodes from the graph.
199-
graph.retain_nodes(|_, index| {
200-
if reachable.contains(&index) {
201-
dev != DevMode::Only
202-
} else {
203-
dev != DevMode::Exclude
204-
}
205-
});
206-
modified = true;
201+
graph.retain_nodes(|_, index| reachable.contains(&index));
207202
}
208203

209204
// Step 3: Reverse the graph.
210205
if invert {
211206
graph.reverse();
212-
modified = true;
213207
}
214208

215209
// Step 4: Filter the graph to those nodes reachable from the target packages.
@@ -230,11 +224,10 @@ impl<'env> TreeDisplay<'env> {
230224

231225
// Remove the unreachable nodes from the graph.
232226
graph.retain_nodes(|_, index| reachable.contains(&index));
233-
modified = true;
234227
}
235228

236-
// If the graph was modified, re-create the inverse map.
237-
if modified {
229+
// Re-create the inverse map.
230+
{
238231
inverse.clear();
239232
for node in graph.node_indices() {
240233
inverse.insert(graph[node], node);

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use uv_cache::Cache;
1313
use uv_cache_key::RepositoryUrl;
1414
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
1515
use uv_configuration::{
16-
Concurrency, Constraints, DevMode, DevSpecification, EditableMode, ExtrasSpecification,
17-
InstallOptions, LowerBound, SourceStrategy,
16+
Concurrency, Constraints, DevGroupsSpecification, DevMode, EditableMode, ExtrasSpecification,
17+
GroupsSpecification, InstallOptions, LowerBound, SourceStrategy,
1818
};
1919
use uv_dispatch::BuildDispatch;
2020
use uv_distribution::DistributionDatabase;
@@ -811,22 +811,25 @@ async fn lock_and_sync(
811811
let (extras, dev) = match dependency_type {
812812
DependencyType::Production => {
813813
let extras = ExtrasSpecification::None;
814-
let dev = DevSpecification::from(DevMode::Exclude);
814+
let dev = DevGroupsSpecification::from(DevMode::Exclude);
815815
(extras, dev)
816816
}
817817
DependencyType::Dev => {
818818
let extras = ExtrasSpecification::None;
819-
let dev = DevSpecification::from(DevMode::Include);
819+
let dev = DevGroupsSpecification::from(DevMode::Include);
820820
(extras, dev)
821821
}
822822
DependencyType::Optional(ref extra_name) => {
823823
let extras = ExtrasSpecification::Some(vec![extra_name.clone()]);
824-
let dev = DevSpecification::from(DevMode::Exclude);
824+
let dev = DevGroupsSpecification::from(DevMode::Exclude);
825825
(extras, dev)
826826
}
827827
DependencyType::Group(ref group_name) => {
828828
let extras = ExtrasSpecification::None;
829-
let dev = DevSpecification::Include(vec![group_name.clone()]);
829+
let dev =
830+
DevGroupsSpecification::from(GroupsSpecification::Include(
831+
vec![group_name.clone()],
832+
));
830833
(extras, dev)
831834
}
832835
};

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
88
use uv_cache::Cache;
99
use uv_client::Connectivity;
1010
use uv_configuration::{
11-
Concurrency, DevMode, DevSpecification, EditableMode, ExportFormat, ExtrasSpecification,
11+
Concurrency, DevGroupsSpecification, EditableMode, ExportFormat, ExtrasSpecification,
1212
InstallOptions, LowerBound,
1313
};
1414
use uv_normalize::PackageName;
@@ -33,7 +33,7 @@ pub(crate) async fn export(
3333
install_options: InstallOptions,
3434
output_file: Option<PathBuf>,
3535
extras: ExtrasSpecification,
36-
dev: DevMode,
36+
dev: DevGroupsSpecification,
3737
editable: EditableMode,
3838
locked: bool,
3939
frozen: bool,
@@ -142,7 +142,7 @@ pub(crate) async fn export(
142142
&lock,
143143
project.project_name(),
144144
&extras,
145-
&DevSpecification::from(dev),
145+
&dev.with_default_dev(),
146146
editable,
147147
hashes,
148148
&install_options,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use owo_colors::OwoColorize;
66
use uv_cache::Cache;
77
use uv_client::Connectivity;
88
use uv_configuration::{
9-
Concurrency, DevMode, DevSpecification, EditableMode, ExtrasSpecification, InstallOptions,
10-
LowerBound,
9+
Concurrency, DevGroupsSpecification, DevMode, EditableMode, ExtrasSpecification,
10+
InstallOptions, LowerBound,
1111
};
1212
use uv_fs::Simplified;
1313
use uv_pep508::PackageName;
@@ -216,7 +216,7 @@ pub(crate) async fn remove(
216216
&venv,
217217
&lock,
218218
&extras,
219-
&DevSpecification::from(dev),
219+
&DevGroupsSpecification::from(dev),
220220
EditableMode::Editable,
221221
install_options,
222222
Modifications::Exact,

0 commit comments

Comments
 (0)