Skip to content

Commit a2ff2b4

Browse files
committed
Move dependency group normalization into specification
1 parent 2d8dda3 commit a2ff2b4

File tree

10 files changed

+65
-80
lines changed

10 files changed

+65
-80
lines changed

crates/uv-normalize/src/group_name.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::fmt::{Display, Formatter};
2-
use std::path::{Path, PathBuf};
2+
use std::path::PathBuf;
33
use std::str::FromStr;
44
use std::sync::LazyLock;
55

@@ -98,17 +98,6 @@ pub struct PipGroupName {
9898
pub name: GroupName,
9999
}
100100

101-
impl PipGroupName {
102-
/// Gets the path to use, applying the default if it's missing
103-
pub fn path(&self) -> &Path {
104-
if let Some(path) = &self.path {
105-
path
106-
} else {
107-
Path::new("pyproject.toml")
108-
}
109-
}
110-
}
111-
112101
impl FromStr for PipGroupName {
113102
type Err = InvalidPipGroupError;
114103

crates/uv-requirements/src/specification.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use uv_distribution_types::{
4343
UnresolvedRequirementSpecification,
4444
};
4545
use uv_fs::{CWD, Simplified};
46-
use uv_normalize::{ExtraName, GroupName, PackageName};
46+
use uv_normalize::{ExtraName, PackageName, PipGroupName};
4747
use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement};
4848
use uv_warnings::warn_user;
4949
use uv_workspace::pyproject::PyProjectToml;
@@ -215,7 +215,7 @@ impl RequirementsSpecification {
215215
requirements: &[RequirementsSource],
216216
constraints: &[RequirementsSource],
217217
overrides: &[RequirementsSource],
218-
groups: BTreeMap<PathBuf, Vec<GroupName>>,
218+
groups: Option<&GroupsSpecification>,
219219
client_builder: &BaseClientBuilder<'_>,
220220
) -> Result<Self> {
221221
let mut spec = Self::default();
@@ -272,7 +272,7 @@ impl RequirementsSpecification {
272272
"Cannot specify constraints with a `pylock.toml` file"
273273
));
274274
}
275-
if !groups.is_empty() {
275+
if groups.is_some_and(|groups| !groups.groups.is_empty()) {
276276
return Err(anyhow::anyhow!(
277277
"Cannot specify groups with a `pylock.toml` file"
278278
));
@@ -287,9 +287,24 @@ impl RequirementsSpecification {
287287
}
288288

289289
// pip `--group` flags specify their own sources, which we need to process here
290-
if !groups.is_empty() {
290+
if let Some(groups) = groups {
291+
// First, we collect all groups by their path.
292+
let mut groups_by_path = BTreeMap::new();
293+
for group in &groups.groups {
294+
// If there's no path provided, expect a pyproject.toml in the project-dir
295+
// (Which is typically the current working directory, matching pip's behaviour)
296+
let pyproject_path = group
297+
.path
298+
.clone()
299+
.unwrap_or_else(|| groups.root.join("pyproject.toml"));
300+
groups_by_path
301+
.entry(pyproject_path)
302+
.or_insert_with(Vec::new)
303+
.push(group.name.clone());
304+
}
305+
291306
let mut group_specs = BTreeMap::new();
292-
for (path, groups) in groups {
307+
for (path, groups) in groups_by_path {
293308
let group_spec = DependencyGroups::from_args(
294309
false,
295310
false,
@@ -426,7 +441,7 @@ impl RequirementsSpecification {
426441
requirements: &[RequirementsSource],
427442
client_builder: &BaseClientBuilder<'_>,
428443
) -> Result<Self> {
429-
Self::from_sources(requirements, &[], &[], BTreeMap::default(), client_builder).await
444+
Self::from_sources(requirements, &[], &[], None, client_builder).await
430445
}
431446

432447
/// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`].
@@ -485,3 +500,12 @@ impl RequirementsSpecification {
485500
self.requirements.is_empty() && self.source_trees.is_empty() && self.overrides.is_empty()
486501
}
487502
}
503+
504+
#[derive(Debug, Default, Clone)]
505+
pub struct GroupsSpecification {
506+
/// The path to the project root, relative to which the default `pyproject.toml` file is
507+
/// located.
508+
pub root: PathBuf,
509+
/// The enabled groups.
510+
pub groups: Vec<PipGroupName>,
511+
}

crates/uv/src/commands/pip/compile.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use std::collections::{BTreeMap, BTreeSet};
1+
use std::collections::BTreeSet;
22
use std::env;
33
use std::ffi::OsStr;
4-
use std::path::{Path, PathBuf};
4+
use std::path::Path;
55
use std::str::FromStr;
66

77
use anyhow::{Result, anyhow};
@@ -26,15 +26,16 @@ use uv_distribution_types::{
2626
use uv_fs::{CWD, Simplified};
2727
use uv_git::ResolvedRepositoryReference;
2828
use uv_install_wheel::LinkMode;
29-
use uv_normalize::{GroupName, PackageName};
29+
use uv_normalize::PackageName;
3030
use uv_pypi_types::{Conflicts, SupportedEnvironments};
3131
use uv_python::{
3232
EnvironmentPreference, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest,
3333
PythonVersion, VersionRequest,
3434
};
3535
use uv_requirements::upgrade::{LockedRequirements, read_pylock_toml_requirements};
3636
use uv_requirements::{
37-
RequirementsSource, RequirementsSpecification, is_pylock_toml, upgrade::read_requirements_txt,
37+
GroupsSpecification, RequirementsSource, RequirementsSpecification, is_pylock_toml,
38+
upgrade::read_requirements_txt,
3839
};
3940
use uv_resolver::{
4041
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, ForkStrategy,
@@ -64,7 +65,7 @@ pub(crate) async fn pip_compile(
6465
build_constraints_from_workspace: Vec<Requirement>,
6566
environments: SupportedEnvironments,
6667
extras: ExtrasSpecification,
67-
groups: BTreeMap<PathBuf, Vec<GroupName>>,
68+
groups: GroupsSpecification,
6869
output_file: Option<&Path>,
6970
format: Option<ExportFormat>,
7071
resolution_mode: ResolutionMode,
@@ -207,7 +208,7 @@ pub(crate) async fn pip_compile(
207208
requirements,
208209
constraints,
209210
overrides,
210-
groups,
211+
Some(&groups),
211212
&client_builder,
212213
)
213214
.await?;

crates/uv/src/commands/pip/install.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use std::collections::{BTreeMap, BTreeSet};
1+
use std::collections::BTreeSet;
22
use std::fmt::Write;
3-
use std::path::PathBuf;
43

54
use anyhow::Context;
65
use itertools::Itertools;
@@ -23,14 +22,13 @@ use uv_distribution_types::{
2322
use uv_fs::Simplified;
2423
use uv_install_wheel::LinkMode;
2524
use uv_installer::{SatisfiesResult, SitePackages};
26-
use uv_normalize::GroupName;
2725
use uv_pep508::PackageName;
2826
use uv_pypi_types::Conflicts;
2927
use uv_python::{
3028
EnvironmentPreference, Prefix, PythonEnvironment, PythonInstallation, PythonPreference,
3129
PythonRequest, PythonVersion, Target,
3230
};
33-
use uv_requirements::{RequirementsSource, RequirementsSpecification};
31+
use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification};
3432
use uv_resolver::{
3533
DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml,
3634
PythonRequirement, ResolutionMode, ResolverEnvironment,
@@ -59,7 +57,7 @@ pub(crate) async fn pip_install(
5957
overrides_from_workspace: Vec<Requirement>,
6058
build_constraints_from_workspace: Vec<Requirement>,
6159
extras: &ExtrasSpecification,
62-
groups: BTreeMap<PathBuf, Vec<GroupName>>,
60+
groups: &GroupsSpecification,
6361
resolution_mode: ResolutionMode,
6462
prerelease_mode: PrereleaseMode,
6563
dependency_mode: DependencyMode,
@@ -128,7 +126,7 @@ pub(crate) async fn pip_install(
128126
constraints,
129127
overrides,
130128
extras,
131-
groups,
129+
Some(groups),
132130
&client_builder,
133131
)
134132
.await?;

crates/uv/src/commands/pip/operations.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ use uv_distribution_types::{
2727
use uv_fs::Simplified;
2828
use uv_install_wheel::LinkMode;
2929
use uv_installer::{Plan, Planner, Preparer, SitePackages};
30-
use uv_normalize::{GroupName, PackageName};
30+
use uv_normalize::PackageName;
3131
use uv_pep508::{MarkerEnvironment, RequirementOrigin};
3232
use uv_platform_tags::Tags;
3333
use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment};
3434
use uv_python::{PythonEnvironment, PythonInstallation};
3535
use uv_requirements::{
36-
LookaheadResolver, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification,
37-
SourceTreeResolver,
36+
GroupsSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource,
37+
RequirementsSpecification, SourceTreeResolver,
3838
};
3939
use uv_resolver::{
4040
DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, Preference,
@@ -55,7 +55,7 @@ pub(crate) async fn read_requirements(
5555
constraints: &[RequirementsSource],
5656
overrides: &[RequirementsSource],
5757
extras: &ExtrasSpecification,
58-
groups: BTreeMap<PathBuf, Vec<GroupName>>,
58+
groups: Option<&GroupsSpecification>,
5959
client_builder: &BaseClientBuilder<'_>,
6060
) -> Result<RequirementsSpecification, Error> {
6161
// If the user requests `extras` but does not provide a valid source (e.g., a `pyproject.toml`),
@@ -91,15 +91,11 @@ pub(crate) async fn read_constraints(
9191
constraints: &[RequirementsSource],
9292
client_builder: &BaseClientBuilder<'_>,
9393
) -> Result<Vec<NameRequirementSpecification>, Error> {
94-
Ok(RequirementsSpecification::from_sources(
95-
&[],
96-
constraints,
97-
&[],
98-
BTreeMap::default(),
99-
client_builder,
94+
Ok(
95+
RequirementsSpecification::from_sources(&[], constraints, &[], None, client_builder)
96+
.await?
97+
.constraints,
10098
)
101-
.await?
102-
.constraints)
10399
}
104100

105101
/// Resolve a set of requirements, similar to running `pip compile`.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::{BTreeMap, BTreeSet};
1+
use std::collections::BTreeSet;
22
use std::fmt::Write;
33

44
use anyhow::{Context, Result};
@@ -92,7 +92,7 @@ pub(crate) async fn pip_sync(
9292
// Initialize a few defaults.
9393
let overrides = &[];
9494
let extras = ExtrasSpecification::default();
95-
let groups = BTreeMap::default();
95+
let groups = None;
9696
let upgrade = Upgrade::default();
9797
let resolution_mode = ResolutionMode::default();
9898
let prerelease_mode = PrereleaseMode::default();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ pub(crate) async fn add(
345345
&requirements,
346346
&constraints,
347347
&[],
348-
BTreeMap::default(),
348+
None,
349349
&client_builder,
350350
)
351351
.await?;

crates/uv/src/commands/tool/install.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::collections::BTreeMap;
21
use std::fmt::Write;
32
use std::str::FromStr;
43

@@ -261,7 +260,7 @@ pub(crate) async fn install(
261260
with,
262261
constraints,
263262
overrides,
264-
BTreeMap::default(),
263+
None,
265264
&client_builder,
266265
)
267266
.await?;

crates/uv/src/commands/tool/run.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::collections::BTreeMap;
21
use std::fmt::Display;
32
use std::fmt::Write;
43
use std::path::Path;
@@ -871,7 +870,7 @@ async fn get_or_create_environment(
871870
with,
872871
constraints,
873872
overrides,
874-
BTreeMap::default(),
873+
None,
875874
&client_builder,
876875
)
877876
.await?;

crates/uv/src/lib.rs

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::borrow::Cow;
2-
use std::collections::BTreeMap;
32
use std::ffi::OsString;
43
use std::fmt::Write;
54
use std::io::stdout;
@@ -36,7 +35,7 @@ use uv_pep440::release_specifiers_to_ranges;
3635
use uv_pep508::VersionOrUrl;
3736
use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl};
3837
use uv_python::PythonRequest;
39-
use uv_requirements::RequirementsSource;
38+
use uv_requirements::{GroupsSpecification, RequirementsSource};
4039
use uv_requirements_txt::RequirementsTxtRequirement;
4140
use uv_scripts::{Pep723Error, Pep723Item, Pep723ItemRef, Pep723Metadata, Pep723Script};
4241
use uv_settings::{Combine, EnvironmentOptions, FilesystemOptions, Options};
@@ -472,20 +471,10 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
472471
.into_iter()
473472
.map(RequirementsSource::from_constraints_txt)
474473
.collect::<Result<Vec<_>, _>>()?;
475-
476-
let mut groups = BTreeMap::new();
477-
for group in args.settings.groups {
478-
// If there's no path provided, expect a pyproject.toml in the project-dir
479-
// (Which is typically the current working directory, matching pip's behaviour)
480-
let pyproject_path = group
481-
.path
482-
.clone()
483-
.unwrap_or_else(|| project_dir.join("pyproject.toml"));
484-
groups
485-
.entry(pyproject_path)
486-
.or_insert_with(Vec::new)
487-
.push(group.name.clone());
488-
}
474+
let groups = GroupsSpecification {
475+
root: project_dir.to_path_buf(),
476+
groups: args.settings.groups,
477+
};
489478

490479
commands::pip_compile(
491480
&requirements,
@@ -657,20 +646,10 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
657646
.into_iter()
658647
.map(RequirementsSource::from_overrides_txt)
659648
.collect::<Result<Vec<_>, _>>()?;
660-
661-
let mut groups = BTreeMap::new();
662-
for group in args.settings.groups {
663-
// If there's no path provided, expect a pyproject.toml in the project-dir
664-
// (Which is typically the current working directory, matching pip's behaviour)
665-
let pyproject_path = group
666-
.path
667-
.clone()
668-
.unwrap_or_else(|| project_dir.join("pyproject.toml"));
669-
groups
670-
.entry(pyproject_path)
671-
.or_insert_with(Vec::new)
672-
.push(group.name.clone());
673-
}
649+
let groups = GroupsSpecification {
650+
root: project_dir.to_path_buf(),
651+
groups: args.settings.groups,
652+
};
674653

675654
// Special-case: any source trees specified on the command-line are automatically
676655
// reinstalled. This matches user expectations: `uv pip install .` should always
@@ -730,7 +709,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
730709
args.overrides_from_workspace,
731710
args.build_constraints_from_workspace,
732711
&args.settings.extras,
733-
groups,
712+
&groups,
734713
args.settings.resolution,
735714
args.settings.prerelease,
736715
args.settings.dependency_mode,

0 commit comments

Comments
 (0)