Activate source selection contexts during sync

This commit is contained in:
Charlie Marsh
2026-06-02 14:41:15 -04:00
parent a4c489ff3b
commit debc223da7
2 changed files with 56 additions and 9 deletions
+17 -9
View File
@@ -64,6 +64,15 @@ pub trait Installable<'lock> {
let mut activated_projects: Vec<&PackageName> = vec![];
let mut activated_extras: Vec<(&PackageName, &ExtraName)> = vec![];
let mut activated_groups: Vec<(&PackageName, &GroupName)> = vec![];
let needs_activation_context = !self.lock().conflicts().is_empty()
|| self.lock().packages.iter().any(|package| {
package
.dependencies
.iter()
.chain(package.optional_dependencies.values().flatten())
.chain(package.dependency_groups.values().flatten())
.any(|dependency| !dependency.complexified_marker.conflict().is_true())
});
let root = petgraph.add_node(Node::Root);
@@ -74,7 +83,7 @@ pub trait Installable<'lock> {
// marker. But at that point, we don't know the full set of activated extras; this is only
// computed below. We somehow need to add the dependency groups _after_ we've computed all
// enabled extras, but the groups themselves could depend on the set of enabled extras.
if !self.lock().conflicts().is_empty() {
if needs_activation_context {
for root_name in self.roots() {
let dist = self
.lock()
@@ -347,7 +356,7 @@ pub trait Installable<'lock> {
// activated extras. This includes the extras explicitly enabled on
// the CLI (which were gathered above) and the extras enabled via
// dependency specifications like `foo[extra]`. We need to do this
// to correctly support conflicting extras.
// to correctly support conflicting extras and source-selection markers.
//
// In particular, the way conflicting extras works is by forking the
// resolver based on the extras that are declared as conflicting. But
@@ -366,12 +375,7 @@ pub trait Installable<'lock> {
// if it's enabled, we need to traverse the entire dependency graph
// first to inspect which extras are enabled!
//
// Of course, we don't need to do this at all if there aren't any
// conflicts. In which case, we skip all of this and just do the one
// traversal below.
if !self.lock().conflicts().is_empty() {
let mut activated_extras_set: BTreeSet<(&PackageName, &ExtraName)> =
activated_extras.iter().copied().collect();
if needs_activation_context {
let mut queue = queue.clone();
let mut seen = seen.clone();
while let Some((package, extra)) = queue.pop_front() {
@@ -421,7 +425,6 @@ pub trait Installable<'lock> {
// there are, we raise an error. ---AG
for key in additional_activated_extras {
activated_extras_set.insert(key);
activated_extras.push(key);
}
let dep_dist = self.lock().find_by_id(&dep.package_id);
@@ -436,6 +439,11 @@ pub trait Installable<'lock> {
}
}
}
}
if !self.lock().conflicts().is_empty() {
let activated_extras_set: BTreeSet<(&PackageName, &ExtraName)> =
activated_extras.iter().copied().collect();
// At time of writing, it's somewhat expected that the set of
// conflicting extras is pretty small. With that said, the
// time complexity of the following routine is pretty gross.
+39
View File
@@ -10463,6 +10463,45 @@ fn sync_multiple_sources_extra_url_conflict() -> Result<()> {
Ok(())
}
#[test]
fn sync_multiple_sources_group_url_without_conflicts() -> Result<()> {
let context = uv_test::test_context!("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>=2"]
[dependency-groups]
alt = ["iniconfig"]
[tool.uv.sources]
iniconfig = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", group = "alt" },
]
"#,
)?;
context.lock().assert().success();
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("alt"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
");
Ok(())
}
#[test]
fn sync_derivation_chain() -> Result<()> {
let context = uv_test::test_context!("3.12");