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."""
|
"""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")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
@ -963,10 +963,6 @@ def _sort_by_dep_types(dspec: DependencySpec):
|
|||||||
return dspec.depflag
|
return dspec.depflag
|
||||||
|
|
||||||
|
|
||||||
#: Enum for edge directions
|
|
||||||
EdgeDirection = lang.enum(parent=0, child=1)
|
|
||||||
|
|
||||||
|
|
||||||
@lang.lazy_lexicographic_ordering
|
@lang.lazy_lexicographic_ordering
|
||||||
class _EdgeMap(collections.abc.Mapping):
|
class _EdgeMap(collections.abc.Mapping):
|
||||||
"""Represent a collection of edges (DependencySpec objects) in the DAG.
|
"""Represent a collection of edges (DependencySpec objects) in the DAG.
|
||||||
@ -980,26 +976,20 @@ class _EdgeMap(collections.abc.Mapping):
|
|||||||
|
|
||||||
__slots__ = "edges", "store_by_child"
|
__slots__ = "edges", "store_by_child"
|
||||||
|
|
||||||
def __init__(self, store_by=EdgeDirection.child):
|
def __init__(self, store_by_child: bool = True) -> None:
|
||||||
# Sanitize input arguments
|
self.edges: Dict[str, List[DependencySpec]] = {}
|
||||||
msg = 'unexpected value for "store_by" argument'
|
self.store_by_child = store_by_child
|
||||||
assert store_by in (EdgeDirection.child, EdgeDirection.parent), msg
|
|
||||||
|
|
||||||
#: This dictionary maps a package name to a list of edges
|
def __getitem__(self, key: str) -> List[DependencySpec]:
|
||||||
#: i.e. to a list of DependencySpec objects
|
|
||||||
self.edges = {}
|
|
||||||
self.store_by_child = store_by == EdgeDirection.child
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self.edges[key]
|
return self.edges[key]
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self.edges)
|
return iter(self.edges)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
return len(self.edges)
|
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
|
key = edge.spec.name if self.store_by_child else edge.parent.name
|
||||||
if key in self.edges:
|
if key in self.edges:
|
||||||
lst = self.edges[key]
|
lst = self.edges[key]
|
||||||
@ -1008,8 +998,8 @@ def add(self, edge: DependencySpec):
|
|||||||
else:
|
else:
|
||||||
self.edges[key] = [edge]
|
self.edges[key] = [edge]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return "{deps: %s}" % ", ".join(str(d) for d in sorted(self.values()))
|
return f"{{deps: {', '.join(str(d) for d in sorted(self.values()))}}}"
|
||||||
|
|
||||||
def _cmp_iter(self):
|
def _cmp_iter(self):
|
||||||
for item in sorted(itertools.chain.from_iterable(self.edges.values())):
|
for item in sorted(itertools.chain.from_iterable(self.edges.values())):
|
||||||
@ -1026,24 +1016,32 @@ def copy(self):
|
|||||||
|
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
def select(self, parent=None, child=None, depflag: dt.DepFlag = dt.ALL):
|
def select(
|
||||||
"""Select a list of edges and return them.
|
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:
|
If an edge:
|
||||||
|
|
||||||
- Has *any* of the dependency types passed as argument,
|
- 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.
|
then it is selected.
|
||||||
|
|
||||||
The deptypes argument needs to be a flag, since the method won't
|
The deptypes argument needs to be a flag, since the method won't
|
||||||
convert it for performance reason.
|
convert it for performance reason.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent (str): name of the parent package
|
parent: name of the parent package
|
||||||
child (str): name of the child package
|
child: name of the child package
|
||||||
depflag: allowed dependency types in flag form
|
depflag: allowed dependency types in flag form
|
||||||
|
virtuals: list of virtuals on the edge
|
||||||
Returns:
|
|
||||||
List of DependencySpec objects
|
|
||||||
"""
|
"""
|
||||||
if not depflag:
|
if not depflag:
|
||||||
return []
|
return []
|
||||||
@ -1062,6 +1060,10 @@ def select(self, parent=None, child=None, depflag: dt.DepFlag = dt.ALL):
|
|||||||
# Filter by allowed dependency types
|
# Filter by allowed dependency types
|
||||||
selected = (dep for dep in selected if not dep.depflag or (depflag & dep.depflag))
|
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)
|
return list(selected)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
@ -1470,8 +1472,8 @@ def __init__(
|
|||||||
self.architecture = None
|
self.architecture = None
|
||||||
self.compiler = None
|
self.compiler = None
|
||||||
self.compiler_flags = FlagMap(self)
|
self.compiler_flags = FlagMap(self)
|
||||||
self._dependents = _EdgeMap(store_by=EdgeDirection.parent)
|
self._dependents = _EdgeMap(store_by_child=False)
|
||||||
self._dependencies = _EdgeMap(store_by=EdgeDirection.child)
|
self._dependencies = _EdgeMap(store_by_child=True)
|
||||||
self.namespace = None
|
self.namespace = None
|
||||||
|
|
||||||
# initial values for all spec hash types
|
# initial values for all spec hash types
|
||||||
@ -1591,7 +1593,7 @@ def _get_dependency(self, name):
|
|||||||
return deps[0]
|
return deps[0]
|
||||||
|
|
||||||
def edges_from_dependents(
|
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]:
|
) -> List[DependencySpec]:
|
||||||
"""Return a list of edges connecting this node in the DAG
|
"""Return a list of edges connecting this node in the DAG
|
||||||
to parents.
|
to parents.
|
||||||
@ -1599,20 +1601,25 @@ def edges_from_dependents(
|
|||||||
Args:
|
Args:
|
||||||
name (str): filter dependents by package name
|
name (str): filter dependents by package name
|
||||||
depflag: allowed dependency types
|
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(
|
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]:
|
) -> List[DependencySpec]:
|
||||||
"""Return a list of edges connecting this node in the DAG
|
"""Returns a list of edges connecting this node in the DAG to children.
|
||||||
to children.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str): filter dependencies by package name
|
name (str): filter dependencies by package name
|
||||||
depflag: allowed dependency types
|
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
|
@property
|
||||||
def edge_attributes(self) -> str:
|
def edge_attributes(self) -> str:
|
||||||
@ -1635,17 +1642,24 @@ def edge_attributes(self) -> str:
|
|||||||
return f"[{result}]"
|
return f"[{result}]"
|
||||||
|
|
||||||
def dependencies(
|
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"]:
|
) -> List["Spec"]:
|
||||||
"""Return a list of direct dependencies (nodes in the DAG).
|
"""Returns a list of direct dependencies (nodes in the DAG)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str): filter dependencies by package name
|
name: filter dependencies by package name
|
||||||
deptype: allowed dependency types
|
deptype: allowed dependency types
|
||||||
|
virtuals: allowed virtuals
|
||||||
"""
|
"""
|
||||||
if not isinstance(deptype, dt.DepFlag):
|
if not isinstance(deptype, dt.DepFlag):
|
||||||
deptype = dt.canonicalize(deptype)
|
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(
|
def dependents(
|
||||||
self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL
|
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.architecture = other.architecture.copy() if other.architecture else None
|
||||||
self.compiler = other.compiler.copy() if other.compiler else None
|
self.compiler = other.compiler.copy() if other.compiler else None
|
||||||
if cleardeps:
|
if cleardeps:
|
||||||
self._dependents = _EdgeMap(store_by=EdgeDirection.parent)
|
self._dependents = _EdgeMap(store_by_child=False)
|
||||||
self._dependencies = _EdgeMap(store_by=EdgeDirection.child)
|
self._dependencies = _EdgeMap(store_by_child=True)
|
||||||
self.compiler_flags = other.compiler_flags.copy()
|
self.compiler_flags = other.compiler_flags.copy()
|
||||||
self.compiler_flags.spec = self
|
self.compiler_flags.spec = self
|
||||||
self.variants = other.variants.copy()
|
self.variants = other.variants.copy()
|
||||||
|
@ -756,6 +756,48 @@ def test_spec_tree_respect_deptypes(self):
|
|||||||
out = s.tree(deptypes=("link", "run"))
|
out = s.tree(deptypes=("link", "run"))
|
||||||
assert "version-test-pkg" not in out
|
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():
|
def test_tree_cover_nodes_reduce_deptype():
|
||||||
"""Test that tree output with deptypes sticks to the sub-dag of interest, instead of looking
|
"""Test that tree output with deptypes sticks to the sub-dag of interest, instead of looking
|
||||||
|
Loading…
Reference in New Issue
Block a user