Skip to content

Commit 27253f2

Browse files
authored
fix calculated markers if a dependency is required by several groups with different markers (#10613)
This also fixes the case where a dependency is required in an extra and a group.
1 parent d4fb687 commit 27253f2

File tree

2 files changed

+80
-10
lines changed

2 files changed

+80
-10
lines changed

src/poetry/puzzle/solver.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@
4040
from poetry.repositories import RepositoryPool
4141
from poetry.utils.env import Env
4242

43-
MarkerOriginDict = defaultdict[Package, defaultdict[Package, BaseMarker]]
43+
# markers[child_package][parent_package][groups] -> BaseMarker
44+
MarkerOriginDict = defaultdict[
45+
Package,
46+
defaultdict[Package, defaultdict[frozenset[NormalizedName], BaseMarker]],
47+
]
4448

4549

4650
class Solver:
@@ -238,7 +242,9 @@ def depth_first_search(
238242
source: PackageNode,
239243
) -> tuple[list[list[PackageNode]], MarkerOriginDict]:
240244
back_edges: dict[DFSNodeID, list[PackageNode]] = defaultdict(list)
241-
markers: MarkerOriginDict = defaultdict(lambda: defaultdict(EmptyMarker))
245+
markers: MarkerOriginDict = defaultdict(
246+
lambda: defaultdict(lambda: defaultdict(EmptyMarker))
247+
)
242248
visited: set[DFSNodeID] = set()
243249
topo_sorted_nodes: list[PackageNode] = []
244250

@@ -272,12 +278,16 @@ def dfs_visit(
272278

273279
for out_neighbor in node.reachable():
274280
back_edges[out_neighbor.id].append(node)
275-
marker = markers[out_neighbor.package][node.package]
276-
markers[out_neighbor.package][node.package] = marker.union(
281+
groups = out_neighbor.groups
282+
prev_marker = markers[out_neighbor.package][node.package][groups]
283+
new_marker = (
277284
out_neighbor.marker
278285
if node.package.is_root()
279286
else out_neighbor.marker.without_extras()
280287
)
288+
markers[out_neighbor.package][node.package][groups] = prev_marker.union(
289+
new_marker
290+
)
281291
dfs_visit(out_neighbor, back_edges, visited, sorted_nodes, markers)
282292
sorted_nodes.insert(0, node)
283293

@@ -398,20 +408,36 @@ def calculate_markers(
398408
transitive_marker: dict[NormalizedName, BaseMarker] = {
399409
group: EmptyMarker() for group in transitive_info.groups
400410
}
401-
for parent, m in markers[package].items():
411+
for parent, group_markers in markers[package].items():
402412
parent_info = packages[parent]
403413
if parent_info.groups:
414+
# If parent has groups, we need to intersect its per-group
415+
# markers with each edge marker and union into child's groups.
404416
if parent_info.groups != set(parent_info.markers):
405417
# there is a cycle -> we need one more iteration
406418
has_incomplete_markers = True
407419
continue
408420
for group in parent_info.groups:
409-
transitive_marker[group] = transitive_marker[group].union(
410-
parent_info.markers[group].intersect(m)
411-
)
421+
for edge_marker in group_markers.values():
422+
transitive_marker[group] = transitive_marker[
423+
group
424+
].union(
425+
parent_info.markers[group].intersect(edge_marker)
426+
)
412427
else:
413-
for group in transitive_info.groups:
414-
transitive_marker[group] = transitive_marker[group].union(m)
428+
# Parent is the root (no groups). Edge markers specify which
429+
# dependency groups the edge belongs to. We should only add
430+
# the edge marker to the corresponding child groups.
431+
for groups, edge_marker in group_markers.items():
432+
assert groups, (
433+
f"Package {package.name} at depth {depth} has no groups."
434+
f" All dependencies except for the root package at depth -1 must have groups"
435+
)
436+
for group in transitive_info.groups:
437+
if group in groups:
438+
transitive_marker[group] = transitive_marker[
439+
group
440+
].union(edge_marker)
415441
transitive_info.markers = transitive_marker
416442

417443

tests/puzzle/test_solver_internals.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,50 @@ def test_propagate_markers_for_groups2(package: ProjectPackage, solver: Solver)
336336
}
337337

338338

339+
def test_propagate_markers_for_groups_same_dep(
340+
package: ProjectPackage, solver: Solver
341+
) -> None:
342+
a = Package("a", "1")
343+
b = Package("b", "1")
344+
package.add_dependency(dep("a", 'sys_platform == "win32"', groups=["main"]))
345+
package.add_dependency(dep("a", 'sys_platform == "linux"', groups=["dev"]))
346+
a.add_dependency(dep("b", 'python_version == "3.8"'))
347+
348+
packages = [package, a, b]
349+
result = solver._aggregate_solved_packages(packages)
350+
351+
assert len(result) == len(packages)
352+
assert result[package].groups == set()
353+
assert result[a].groups == {"main", "dev"}
354+
assert result[b].groups == {"main", "dev"}
355+
assert tm(result[package]) == {}
356+
assert tm(result[a]) == {
357+
"main": 'sys_platform == "win32"',
358+
"dev": 'sys_platform == "linux"',
359+
}
360+
assert tm(result[b]) == {
361+
"main": 'sys_platform == "win32" and python_version == "3.8"',
362+
"dev": 'sys_platform == "linux" and python_version == "3.8"',
363+
}
364+
365+
366+
def test_propagate_markers_for_groups_with_extra(
367+
package: ProjectPackage, solver: Solver
368+
) -> None:
369+
a = Package("a", "1")
370+
package.add_dependency(dep("a", groups=["main"], in_extras=["foo"]))
371+
package.add_dependency(dep("a", groups=["dev"]))
372+
373+
packages = [package, a]
374+
result = solver._aggregate_solved_packages(packages)
375+
376+
assert len(result) == len(packages)
377+
assert result[package].groups == set()
378+
assert result[a].groups == {"main", "dev"}
379+
assert tm(result[package]) == {}
380+
assert tm(result[a]) == {"main": 'extra == "foo"', "dev": ""}
381+
382+
339383
def test_propagate_markers_with_cycle(package: ProjectPackage, solver: Solver) -> None:
340384
a = Package("a", "1")
341385
b = Package("b", "1")

0 commit comments

Comments
 (0)