Model groups as a property of requirements (#9545)

## Summary

Today, our dependency group implementation is a little awkward... For
each package `P`, we check if `P` contains dependencies for each enabled
group, then add a dependency on `P` with the group enabled. There are a
few issues here:

1. It's sort of backwards... We add a dependency from the base package
`P` to `P` with the group enabled. Then `P` with the group enabled adds
a dependency on the base package.
2. We can't, e.g., enable different groups for different packages. (We
don't have a way for users to specify this on the CLI, but there's no
reason that it should be _impossible_ in the resolver.)
3. It's inconsistent with how extras work, which leads to confusing
differences in the resolver.

Instead, our internal requirement type can now include dependency
groups, which makes dependency groups look much, much more like extras
in the resolver.
This commit is contained in:
Charlie Marsh 2024-12-03 19:55:51 -05:00 committed by GitHub
parent 7d1308876e
commit 1ecdc1a31e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 180 additions and 146 deletions

View file

@ -287,6 +287,63 @@ impl Workspace {
Some(Requirement {
name: member.pyproject_toml.project.as_ref()?.name.clone(),
extras: vec![],
groups: vec![],
marker: MarkerTree::TRUE,
source: if member.pyproject_toml.is_package() {
RequirementSource::Directory {
install_path: member.root.clone(),
editable: true,
r#virtual: false,
url,
}
} else {
RequirementSource::Directory {
install_path: member.root.clone(),
editable: false,
r#virtual: true,
url,
}
},
origin: None,
})
})
}
/// Returns the set of requirements that include all packages in the workspace.
pub fn group_requirements(&self) -> impl Iterator<Item = Requirement> + '_ {
self.packages.values().filter_map(|member| {
let url = VerbatimUrl::from_absolute_path(&member.root)
.expect("path is valid URL")
.with_given(member.root.to_string_lossy());
let groups = {
let mut groups = member
.pyproject_toml
.dependency_groups
.as_ref()
.map(|groups| groups.keys().cloned().collect::<Vec<_>>())
.unwrap_or_default();
if member
.pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.dev_dependencies.as_ref())
.is_some()
{
groups.push(DEV_DEPENDENCIES.clone());
groups.sort_unstable();
}
groups
};
if groups.is_empty() {
return None;
}
Some(Requirement {
name: member.pyproject_toml.project.as_ref()?.name.clone(),
extras: vec![],
groups,
marker: MarkerTree::TRUE,
source: if member.pyproject_toml.is_package() {
RequirementSource::Directory {