summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2024-10-30 12:15:01 +0100
committerGitHub <noreply@github.com>2024-10-30 12:15:01 +0100
commit354615d4918d5ceb42b9477b068ce525c54f9ad9 (patch)
tree173294954cced5a6041436f1724628217bfc4f5e /lib
parent9ac261af584c1ad6bdbcbad8b5ab1aec9283fa11 (diff)
downloadspack-354615d4918d5ceb42b9477b068ce525c54f9ad9.tar.gz
spack-354615d4918d5ceb42b9477b068ce525c54f9ad9.tar.bz2
spack-354615d4918d5ceb42b9477b068ce525c54f9ad9.tar.xz
spack-354615d4918d5ceb42b9477b068ce525c54f9ad9.zip
Spec.dependencies: allow to filter on virtuals (#47284)
Signed-off-by: Massimiliano Culpo <massimiliano.culpo@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/llnl/util/lang.py9
-rw-r--r--lib/spack/spack/spec.py94
-rw-r--r--lib/spack/spack/test/spec_dag.py42
3 files changed, 96 insertions, 49 deletions
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index 326d25e79b..f43773346a 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -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")
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index d64507a9a1..fc65dcb64b 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -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
-
- #: 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 __init__(self, store_by_child: bool = True) -> None:
+ self.edges: Dict[str, List[DependencySpec]] = {}
+ self.store_by_child = store_by_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 @@ class _EdgeMap(collections.abc.Mapping):
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 @@ class _EdgeMap(collections.abc.Mapping):
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 @@ class _EdgeMap(collections.abc.Mapping):
# 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 @@ class Spec:
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 @@ class Spec:
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 @@ class Spec:
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 @@ class Spec:
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 @@ class Spec:
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()
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index 0d1fe4abcf..05cc15f3da 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -756,6 +756,48 @@ class TestSpecDag:
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