dfs traversal: simplify edges in reverse mode (#33481)

In the dfs code, flip edges so that `parent` means `from` and
`spec` means `to` in the direction of traversal. This makes it slightly
easier to write generic/composable code. For example when using visitors
where one visitor reverses direction, and another only cares about
accepting particular edges or not depending on whether the target node
is seen before, it would be good if the second visitor didn't have to
know whether the order was changed or not.
This commit is contained in:
Harmen Stoppels 2022-10-26 07:55:05 +02:00 committed by GitHub
parent 57c1d6c410
commit d039744a5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -734,6 +734,9 @@ def __str__(self):
def canonical(self):
return self.parent.dag_hash(), self.spec.dag_hash(), self.deptypes
def flip(self):
return DependencySpec(parent=self.spec, spec=self.parent, deptypes=self.deptypes)
_valid_compiler_flags = ["cflags", "cxxflags", "fflags", "ldflags", "ldlibs", "cppflags"]
@ -812,6 +815,10 @@ def _sort_by_dep_types(dspec):
return tuple(t not in dspec.deptypes for t in ("link", "run", "build", "test"))
def _sort_edges_by_pkg_name(edges):
edges.sort(key=lambda edge: edge.spec.name)
#: Enum for edge directions
EdgeDirection = lang.enum(parent=0, child=1)
@ -1604,29 +1611,28 @@ def installed_upstream(self):
return upstream
def traverse(self, **kwargs):
direction = kwargs.get("direction", "children")
depth = kwargs.get("depth", False)
get_spec = lambda s: s.spec
if direction == "parents":
get_spec = lambda s: s.parent
if depth:
for d, dspec in self.traverse_edges(**kwargs):
yield d, get_spec(dspec)
for d, edge in self.traverse_edges(**kwargs):
yield d, edge.spec
else:
for dspec in self.traverse_edges(**kwargs):
yield get_spec(dspec)
for edge in self.traverse_edges(**kwargs):
yield edge.spec
def traverse_edges(self, visited=None, d=0, deptype="all", dep_spec=None, **kwargs):
"""Generic traversal of the DAG represented by this spec.
This yields ``DependencySpec`` objects as they are traversed.
When traversing top-down, an imaginary incoming edge to the root
is yielded first as ``DependencySpec(None, root, ())``. When
traversing bottom-up, imaginary edges to leaves are yielded first
as ``DependencySpec(left, None, ())`` objects.
An imaginary incoming edge to the root is yielded first as
``DependencySpec(None, root, ())``.
Note: the edges are traversed from ``edge.parent`` to ``edge.spec``,
even if the direction is reversed. When ``direction="children"`` the
parent points to the dependent, and spec to the dependency. Conversely
when ``direction="parents"`` parent points to the dependency, and spec
to the dependent.
Options:
@ -1711,10 +1717,7 @@ def validate(name, val, allowed_values):
def return_val(dspec):
if not dspec:
# make a fake dspec for the root.
if direction == "parents":
dspec = DependencySpec(self, None, ())
else:
dspec = DependencySpec(None, self, ())
dspec = DependencySpec(None, self, ())
return (d, dspec) if depth else dspec
yield_me = yield_root or d > 0
@ -1729,22 +1732,18 @@ def return_val(dspec):
# This code determines direction and yields the children/parents
if direction == "children":
edges = self.edges_to_dependencies
key_fn = lambda dspec: dspec.spec.name
succ = lambda dspec: dspec.spec
elif direction == "parents":
edges = self.edges_from_dependents
key_fn = lambda dspec: dspec.parent.name
succ = lambda dspec: dspec.parent
edges = self.edges_to_dependencies()
else:
raise ValueError("Invalid traversal direction: %s" % direction)
edges = [edge.flip() for edge in self.edges_from_dependents()]
for dspec in sorted(edges(), key=key_fn):
_sort_edges_by_pkg_name(edges)
for dspec in edges:
dt = dspec.deptypes
if dt and not any(d in deptype for d in dt):
continue
for child in succ(dspec).traverse_edges(visited, d + 1, deptype, dspec, **kwargs):
for child in dspec.spec.traverse_edges(visited, d + 1, deptype, dspec, **kwargs):
yield child
# Postorder traversal yields after successors