Skip to content

Commit

Permalink
temp: get transitive dependencies alternative
Browse files Browse the repository at this point in the history
  • Loading branch information
radoering committed Apr 13, 2024
1 parent dd925d1 commit 90f9660
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 17 deletions.
58 changes: 41 additions & 17 deletions src/poetry/puzzle/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import TypeVar

from poetry.core.version.markers import AnyMarker
from poetry.core.version.markers import EmptyMarker

from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure
Expand Down Expand Up @@ -175,11 +176,11 @@ def _solve(self) -> dict[Package, SolverPackageInfo]:
except SolveFailure as e:
raise SolverProblemError(e)

combined_nodes = depth_first_search(PackageNode(self._package, packages))
results = dict(
aggregate_package_nodes(nodes, result.transitive_markers)
for nodes in combined_nodes
combined_nodes, markers = depth_first_search(
PackageNode(self._package, packages)
)
results = dict(aggregate_package_nodes(nodes) for nodes in combined_nodes)
calculate_markers(results, markers)

# Merging feature packages with base packages
solved_packages = {}
Expand Down Expand Up @@ -233,12 +234,15 @@ def __str__(self) -> str:
return str(self.id)


def depth_first_search(source: PackageNode) -> list[list[PackageNode]]:
def depth_first_search(
source: PackageNode,
) -> tuple[list[list[PackageNode]], dict[Package, dict[Package, BaseMarker]]]:
back_edges: dict[DFSNodeID, list[PackageNode]] = defaultdict(list)
markers: dict[Package, dict[Package, BaseMarker]] = defaultdict(dict)
visited: set[DFSNodeID] = set()
topo_sorted_nodes: list[PackageNode] = []

dfs_visit(source, back_edges, visited, topo_sorted_nodes)
dfs_visit(source, back_edges, visited, topo_sorted_nodes, markers)

# Combine the nodes by name
combined_nodes: dict[str, list[PackageNode]] = defaultdict(list)
Expand All @@ -252,22 +256,24 @@ def depth_first_search(source: PackageNode) -> list[list[PackageNode]]:
if node.name in combined_nodes
]

return combined_topo_sorted_nodes
return combined_topo_sorted_nodes, markers


def dfs_visit(
node: PackageNode,
back_edges: dict[DFSNodeID, list[PackageNode]],
visited: set[DFSNodeID],
sorted_nodes: list[PackageNode],
markers: dict[Package, dict[Package, BaseMarker]],
) -> None:
if node.id in visited:
return
visited.add(node.id)

for neighbor in node.reachable():
back_edges[neighbor.id].append(node)
dfs_visit(neighbor, back_edges, visited, sorted_nodes)
for out_neighbor in node.reachable():
back_edges[out_neighbor.id].append(node)
markers[out_neighbor.package][node.package] = out_neighbor.marker
dfs_visit(out_neighbor, back_edges, visited, sorted_nodes, markers)
sorted_nodes.insert(0, node)


Expand All @@ -278,11 +284,13 @@ def __init__(
packages: list[Package],
previous: PackageNode | None = None,
dep: Dependency | None = None,
marker: BaseMarker | None = None,
) -> None:
self.package = package
self.packages = packages

self.dep = dep
self.marker = marker or AnyMarker()
self.depth = -1

if not previous:
Expand Down Expand Up @@ -314,6 +322,7 @@ def reachable(self) -> Sequence[PackageNode]:
self.packages,
self,
self.dep or dependency,
dependency.marker,
)
)

Expand All @@ -332,7 +341,7 @@ def visit(self, parents: list[PackageNode]) -> None:


def aggregate_package_nodes(
nodes: list[PackageNode], transitve_markers: dict[str, BaseMarker]
nodes: list[PackageNode],
) -> tuple[Package, SolverPackageInfo]:
package = nodes[0].package
depth = max(node.depth for node in nodes)
Expand All @@ -347,10 +356,25 @@ def aggregate_package_nodes(

package.optional = optional

try:
marker = transitve_markers[package.name]
except KeyError:
assert package.is_root()
marker = AnyMarker()
# SolverPackageInfo.transitive_marker is updated later,
# because the nodes of all packages have to be aggregated first.
return package, SolverPackageInfo(depth, groups, AnyMarker())

return package, SolverPackageInfo(depth, groups, marker)

def calculate_markers(
packages: dict[Package, SolverPackageInfo],
markers: dict[Package, dict[Package, BaseMarker]],
) -> None:
# group packages by depth
packages_by_depth: dict[int, list[Package]] = defaultdict(list)
for package, info in packages.items():
packages_by_depth[info.depth].append(package)

# calculate markers from lowest to highest depth
for depth in sorted(packages_by_depth):
for package in packages_by_depth[depth]:
if depth != -1:
marker: BaseMarker = EmptyMarker()
for pkg, m in markers[package].items():
marker = marker.union(packages[pkg].transitive_marker.intersect(m))
packages[package].transitive_marker = marker
72 changes: 72 additions & 0 deletions tests/puzzle/test_solver_dfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from __future__ import annotations

from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.project_package import ProjectPackage

from poetry.puzzle.solver import PackageNode
from poetry.puzzle.solver import aggregate_package_nodes
from poetry.puzzle.solver import calculate_markers
from poetry.puzzle.solver import depth_first_search


def dep(name: str, marker: str) -> Dependency:
d = Dependency(name, "1")
d.marker = marker # type: ignore[assignment]
return d


def test_dfs() -> None:
root = ProjectPackage("root", "1")
a = Package("a", "1")
b = Package("b", "1")
c = Package("c", "1")
packages = [root, a, b, c]
root.add_dependency(Dependency("a", "1"))
root.add_dependency(Dependency("b", "1"))
a.add_dependency(Dependency("b", "1"))
b.add_dependency(Dependency("a", "1"))
a.add_dependency(Dependency("c", "1"))
result, __ = depth_first_search(PackageNode(root, packages))
depths = {nodes[0].name: [node.depth for node in nodes] for nodes in result}
assert not depths

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / Ubuntu (Python 3.11) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / Ubuntu (Python 3.8) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / Ubuntu (Python 3.10) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / Ubuntu (Python 3.12) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / macOS (Python 3.10) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / macOS (Python 3.11) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / Ubuntu (Python 3.9) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / macOS (Python 3.12) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / macOS (Python 3.8) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}

Check failure on line 32 in tests/puzzle/test_solver_dfs.py

View workflow job for this annotation

GitHub Actions / macOS (Python 3.9) / pytest

test_dfs AssertionError: assert not {'a': [0], 'b': [1], 'c': [1], 'root': [-1]}


def test_dfs_propagate() -> None:
root = ProjectPackage("root", "1")
a = Package("a", "1")
b = Package("b", "1")
c = Package("c", "1")
d = Package("d", "1")
e = Package("e", "1")
packages = [root, a, b, c, d, e]
root.add_dependency(dep("a", 'sys_platform == "win32"'))
root.add_dependency(dep("b", 'sys_platform == "linux"'))
a.add_dependency(dep("c", 'python_version == "3.8"'))
b.add_dependency(dep("d", 'python_version == "3.9"'))
a.add_dependency(dep("e", 'python_version == "3.10"'))
b.add_dependency(dep("e", 'python_version == "3.11"'))
combined_nodes, markers = depth_first_search(PackageNode(root, packages))
results = dict(aggregate_package_nodes(nodes) for nodes in combined_nodes)
calculate_markers(results, markers)
assert str(results[root].transitive_marker) == ""
assert str(results[a].transitive_marker) == 'sys_platform == "win32"'
assert str(results[b].transitive_marker) == 'sys_platform == "linux"'
assert (
str(results[c].transitive_marker)
== 'sys_platform == "win32" and python_version == "3.8"'
)
assert (
str(results[d].transitive_marker)
== 'sys_platform == "linux" and python_version == "3.9"'
)
assert str(results[e].transitive_marker) == (
'sys_platform == "win32" and python_version == "3.10"'
' or sys_platform == "linux" and python_version == "3.11"'
)


# TODO: root extras
# TODO: dep with extras
# TODO: loops
# TODO: overrides

0 comments on commit 90f9660

Please sign in to comment.