Spec.dependencies: allow to filter on virtuals (#47284)
Signed-off-by: Massimiliano Culpo <massimiliano.culpo@gmail.com>
This commit is contained in:
parent
9ac261af58
commit
354615d491
@ -871,15 +871,6 @@ class UnhashableArguments(TypeError):
|
||||
"""Raise when an @memoized function receives unhashable arg or kwarg values."""
|
||||
|
||||
|
||||
def enum(**kwargs):
|
||||
"""Return an enum-like class.
|
||||
|
||||
Args:
|
||||
**kwargs: explicit dictionary of enums
|
||||
"""
|
||||
return type("Enum", (object,), kwargs)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
|
@ -963,10 +963,6 @@ def _sort_by_dep_types(dspec: DependencySpec):
|
||||
return dspec.depflag
|
||||
|
||||
|
||||
#: Enum for edge directions
|
||||
EdgeDirection = lang.enum(parent=0, child=1)
|
||||
|
||||
|
||||
@lang.lazy_lexicographic_ordering
|
||||
class _EdgeMap(collections.abc.Mapping):
|
||||
"""Represent a collection of edges (DependencySpec objects) in the DAG.
|
||||
@ -980,26 +976,20 @@ class _EdgeMap(collections.abc.Mapping):
|
||||
|
||||
__slots__ = "edges", "store_by_child"
|
||||
|
||||
def __init__(self, store_by=EdgeDirection.child):
|
||||
# Sanitize input arguments
|
||||
msg = 'unexpected value for "store_by" argument'
|
||||
assert store_by in (EdgeDirection.child, EdgeDirection.parent), msg
|
||||
def __init__(self, store_by_child: bool = True) -> None:
|
||||
self.edges: Dict[str, List[DependencySpec]] = {}
|
||||
self.store_by_child = store_by_child
|
||||
|
||||
#: This dictionary maps a package name to a list of edges
|
||||
#: i.e. to a list of DependencySpec objects
|
||||
self.edges = {}
|
||||
self.store_by_child = store_by == EdgeDirection.child
|
||||
|
||||
def __getitem__(self, key):
|
||||
def __getitem__(self, key: str) -> List[DependencySpec]:
|
||||
return self.edges[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.edges)
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
return len(self.edges)
|
||||
|
||||
def add(self, edge: DependencySpec):
|
||||
def add(self, edge: DependencySpec) -> None:
|
||||
key = edge.spec.name if self.store_by_child else edge.parent.name
|
||||
if key in self.edges:
|
||||
lst = self.edges[key]
|
||||
@ -1008,8 +998,8 @@ def add(self, edge: DependencySpec):
|
||||
else:
|
||||
self.edges[key] = [edge]
|
||||
|
||||
def __str__(self):
|
||||
return "{deps: %s}" % ", ".join(str(d) for d in sorted(self.values()))
|
||||
def __str__(self) -> str:
|
||||
return f"{{deps: {', '.join(str(d) for d in sorted(self.values()))}}}"
|
||||
|
||||
def _cmp_iter(self):
|
||||
for item in sorted(itertools.chain.from_iterable(self.edges.values())):
|
||||
@ -1026,24 +1016,32 @@ def copy(self):
|
||||
|
||||
return clone
|
||||
|
||||
def select(self, parent=None, child=None, depflag: dt.DepFlag = dt.ALL):
|
||||
"""Select a list of edges and return them.
|
||||
def select(
|
||||
self,
|
||||
*,
|
||||
parent: Optional[str] = None,
|
||||
child: Optional[str] = None,
|
||||
depflag: dt.DepFlag = dt.ALL,
|
||||
virtuals: Optional[List[str]] = None,
|
||||
) -> List[DependencySpec]:
|
||||
"""Selects a list of edges and returns them.
|
||||
|
||||
If an edge:
|
||||
|
||||
- Has *any* of the dependency types passed as argument,
|
||||
- Matches the parent and/or child name, if passed
|
||||
- Matches the parent and/or child name
|
||||
- Provides *any* of the virtuals passed as argument
|
||||
|
||||
then it is selected.
|
||||
|
||||
The deptypes argument needs to be a flag, since the method won't
|
||||
convert it for performance reason.
|
||||
|
||||
Args:
|
||||
parent (str): name of the parent package
|
||||
child (str): name of the child package
|
||||
parent: name of the parent package
|
||||
child: name of the child package
|
||||
depflag: allowed dependency types in flag form
|
||||
|
||||
Returns:
|
||||
List of DependencySpec objects
|
||||
virtuals: list of virtuals on the edge
|
||||
"""
|
||||
if not depflag:
|
||||
return []
|
||||
@ -1062,6 +1060,10 @@ def select(self, parent=None, child=None, depflag: dt.DepFlag = dt.ALL):
|
||||
# Filter by allowed dependency types
|
||||
selected = (dep for dep in selected if not dep.depflag or (depflag & dep.depflag))
|
||||
|
||||
# Filter by virtuals
|
||||
if virtuals is not None:
|
||||
selected = (dep for dep in selected if any(v in dep.virtuals for v in virtuals))
|
||||
|
||||
return list(selected)
|
||||
|
||||
def clear(self):
|
||||
@ -1470,8 +1472,8 @@ def __init__(
|
||||
self.architecture = None
|
||||
self.compiler = None
|
||||
self.compiler_flags = FlagMap(self)
|
||||
self._dependents = _EdgeMap(store_by=EdgeDirection.parent)
|
||||
self._dependencies = _EdgeMap(store_by=EdgeDirection.child)
|
||||
self._dependents = _EdgeMap(store_by_child=False)
|
||||
self._dependencies = _EdgeMap(store_by_child=True)
|
||||
self.namespace = None
|
||||
|
||||
# initial values for all spec hash types
|
||||
@ -1591,7 +1593,7 @@ def _get_dependency(self, name):
|
||||
return deps[0]
|
||||
|
||||
def edges_from_dependents(
|
||||
self, name=None, depflag: dt.DepFlag = dt.ALL
|
||||
self, name=None, depflag: dt.DepFlag = dt.ALL, *, virtuals: Optional[List[str]] = None
|
||||
) -> List[DependencySpec]:
|
||||
"""Return a list of edges connecting this node in the DAG
|
||||
to parents.
|
||||
@ -1599,20 +1601,25 @@ def edges_from_dependents(
|
||||
Args:
|
||||
name (str): filter dependents by package name
|
||||
depflag: allowed dependency types
|
||||
virtuals: allowed virtuals
|
||||
"""
|
||||
return [d for d in self._dependents.select(parent=name, depflag=depflag)]
|
||||
return [
|
||||
d for d in self._dependents.select(parent=name, depflag=depflag, virtuals=virtuals)
|
||||
]
|
||||
|
||||
def edges_to_dependencies(
|
||||
self, name=None, depflag: dt.DepFlag = dt.ALL
|
||||
self, name=None, depflag: dt.DepFlag = dt.ALL, *, virtuals: Optional[List[str]] = None
|
||||
) -> List[DependencySpec]:
|
||||
"""Return a list of edges connecting this node in the DAG
|
||||
to children.
|
||||
"""Returns a list of edges connecting this node in the DAG to children.
|
||||
|
||||
Args:
|
||||
name (str): filter dependencies by package name
|
||||
depflag: allowed dependency types
|
||||
virtuals: allowed virtuals
|
||||
"""
|
||||
return [d for d in self._dependencies.select(child=name, depflag=depflag)]
|
||||
return [
|
||||
d for d in self._dependencies.select(child=name, depflag=depflag, virtuals=virtuals)
|
||||
]
|
||||
|
||||
@property
|
||||
def edge_attributes(self) -> str:
|
||||
@ -1635,17 +1642,24 @@ def edge_attributes(self) -> str:
|
||||
return f"[{result}]"
|
||||
|
||||
def dependencies(
|
||||
self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL
|
||||
self,
|
||||
name=None,
|
||||
deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL,
|
||||
*,
|
||||
virtuals: Optional[List[str]] = None,
|
||||
) -> List["Spec"]:
|
||||
"""Return a list of direct dependencies (nodes in the DAG).
|
||||
"""Returns a list of direct dependencies (nodes in the DAG)
|
||||
|
||||
Args:
|
||||
name (str): filter dependencies by package name
|
||||
name: filter dependencies by package name
|
||||
deptype: allowed dependency types
|
||||
virtuals: allowed virtuals
|
||||
"""
|
||||
if not isinstance(deptype, dt.DepFlag):
|
||||
deptype = dt.canonicalize(deptype)
|
||||
return [d.spec for d in self.edges_to_dependencies(name, depflag=deptype)]
|
||||
return [
|
||||
d.spec for d in self.edges_to_dependencies(name, depflag=deptype, virtuals=virtuals)
|
||||
]
|
||||
|
||||
def dependents(
|
||||
self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL
|
||||
@ -3519,8 +3533,8 @@ def _dup(self, other, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, clearde
|
||||
self.architecture = other.architecture.copy() if other.architecture else None
|
||||
self.compiler = other.compiler.copy() if other.compiler else None
|
||||
if cleardeps:
|
||||
self._dependents = _EdgeMap(store_by=EdgeDirection.parent)
|
||||
self._dependencies = _EdgeMap(store_by=EdgeDirection.child)
|
||||
self._dependents = _EdgeMap(store_by_child=False)
|
||||
self._dependencies = _EdgeMap(store_by_child=True)
|
||||
self.compiler_flags = other.compiler_flags.copy()
|
||||
self.compiler_flags.spec = self
|
||||
self.variants = other.variants.copy()
|
||||
|
@ -756,6 +756,48 @@ def test_spec_tree_respect_deptypes(self):
|
||||
out = s.tree(deptypes=("link", "run"))
|
||||
assert "version-test-pkg" not in out
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"query,expected_length,expected_satisfies",
|
||||
[
|
||||
({"virtuals": ["mpi"]}, 1, ["mpich", "mpi"]),
|
||||
({"depflag": dt.BUILD}, 2, ["mpich", "mpi", "callpath"]),
|
||||
({"depflag": dt.BUILD, "virtuals": ["mpi"]}, 1, ["mpich", "mpi"]),
|
||||
({"depflag": dt.LINK}, 2, ["mpich", "mpi", "callpath"]),
|
||||
({"depflag": dt.BUILD | dt.LINK}, 2, ["mpich", "mpi", "callpath"]),
|
||||
({"virtuals": ["lapack"]}, 0, []),
|
||||
],
|
||||
)
|
||||
def test_query_dependency_edges(
|
||||
self, default_mock_concretization, query, expected_length, expected_satisfies
|
||||
):
|
||||
"""Tests querying edges to dependencies on the following DAG:
|
||||
|
||||
[ ] mpileaks@=2.3
|
||||
[bl ] ^callpath@=1.0
|
||||
[bl ] ^dyninst@=8.2
|
||||
[bl ] ^libdwarf@=20130729
|
||||
[bl ] ^libelf@=0.8.13
|
||||
[bl ] ^mpich@=3.0.4
|
||||
"""
|
||||
mpileaks = default_mock_concretization("mpileaks")
|
||||
edges = mpileaks.edges_to_dependencies(**query)
|
||||
assert len(edges) == expected_length
|
||||
for constraint in expected_satisfies:
|
||||
assert any(x.spec.satisfies(constraint) for x in edges)
|
||||
|
||||
def test_query_dependents_edges(self, default_mock_concretization):
|
||||
"""Tests querying edges from dependents"""
|
||||
mpileaks = default_mock_concretization("mpileaks")
|
||||
mpich = mpileaks["mpich"]
|
||||
|
||||
# Recover the root with 2 different queries
|
||||
edges_of_link_type = mpich.edges_from_dependents(depflag=dt.LINK)
|
||||
edges_with_mpi = mpich.edges_from_dependents(virtuals=["mpi"])
|
||||
assert edges_with_mpi == edges_of_link_type
|
||||
|
||||
# Check a node dependend upon by 2 parents
|
||||
assert len(mpileaks["libelf"].edges_from_dependents(depflag=dt.LINK)) == 2
|
||||
|
||||
|
||||
def test_tree_cover_nodes_reduce_deptype():
|
||||
"""Test that tree output with deptypes sticks to the sub-dag of interest, instead of looking
|
||||
|
Loading…
Reference in New Issue
Block a user