diff options
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r-- | lib/spack/spack/spec.py | 108 |
1 files changed, 92 insertions, 16 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 6030ff2681..20e5c3ffa3 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -59,7 +59,7 @@ import platform import re import socket import warnings -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union import llnl.path import llnl.string @@ -1464,6 +1464,26 @@ class Spec: """ return [d for d in self._dependencies.select(child=name, depflag=depflag)] + @property + def edge_attributes(self) -> str: + """Helper method to print edge attributes in spec literals""" + edges = self.edges_from_dependents() + if not edges: + return "" + + union = DependencySpec(parent=Spec(), spec=self, depflag=0, virtuals=()) + for edge in edges: + union.update_deptypes(edge.depflag) + union.update_virtuals(edge.virtuals) + deptypes_str = ( + f"deptypes={','.join(dt.flag_to_tuple(union.depflag))}" if union.depflag else "" + ) + virtuals_str = f"virtuals={','.join(union.virtuals)}" if union.virtuals else "" + if not deptypes_str and not virtuals_str: + return "" + result = f"{deptypes_str} {virtuals_str}".strip() + return f"[{result}]" + def dependencies(self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL): """Return a list of direct dependencies (nodes in the DAG). @@ -3688,8 +3708,15 @@ class Spec: if other.concrete and self.concrete: return self.dag_hash() == other.dag_hash() - self_hash = self.dag_hash() if self.concrete else self.abstract_hash - other_hash = other.dag_hash() if other.concrete else other.abstract_hash + elif self.concrete: + return self.satisfies(other) + + elif other.concrete: + return other.satisfies(self) + + # From here we know both self and other are not concrete + self_hash = self.abstract_hash + other_hash = other.abstract_hash if ( self_hash @@ -3778,10 +3805,6 @@ class Spec: repository=spack.repo.PATH, specs=other.traverse(), restrict=True ) - # This handles cases where there are already providers for both vpkgs - if not self_index.satisfies(other_index): - return False - # These two loops handle cases where there is an overly restrictive # vpkg in one spec for a provider in the other (e.g., mpi@3: is not # compatible with mpich2) @@ -3879,7 +3902,46 @@ class Spec: return False # If we arrived here, then rhs is abstract. At the moment we don't care about the edge - # structure of an abstract DAG - hence the deps=False parameter. + # structure of an abstract DAG, so we check if any edge could satisfy the properties + # we ask for. + lhs_edges: Dict[str, Set[DependencySpec]] = collections.defaultdict(set) + for rhs_edge in other.traverse_edges(root=False, cover="edges"): + # If we are checking for ^mpi we need to verify if there is any edge + if rhs_edge.spec.virtual: + rhs_edge.update_virtuals(virtuals=(rhs_edge.spec.name,)) + + if not rhs_edge.virtuals: + continue + + if not lhs_edges: + # Construct a map of the link/run subDAG + direct "build" edges, + # keyed by dependency name + for lhs_edge in self.traverse_edges( + root=False, cover="edges", deptype=("link", "run") + ): + lhs_edges[lhs_edge.spec.name].add(lhs_edge) + for virtual_name in lhs_edge.virtuals: + lhs_edges[virtual_name].add(lhs_edge) + + build_edges = self.edges_to_dependencies(depflag=dt.BUILD) + for lhs_edge in build_edges: + lhs_edges[lhs_edge.spec.name].add(lhs_edge) + for virtual_name in lhs_edge.virtuals: + lhs_edges[virtual_name].add(lhs_edge) + + # We don't have edges to this dependency + current_dependency_name = rhs_edge.spec.name + if current_dependency_name not in lhs_edges: + return False + + for virtual in rhs_edge.virtuals: + has_virtual = any( + virtual in edge.virtuals for edge in lhs_edges[current_dependency_name] + ) + if not has_virtual: + return False + + # Edges have been checked above already, hence deps=False return all( any(lhs.satisfies(rhs, deps=False) for lhs in self.traverse(root=False)) for rhs in other.traverse(root=False) @@ -4081,9 +4143,7 @@ class Spec: """ query_parameters = name.split(":") if len(query_parameters) > 2: - msg = "key has more than one ':' symbol." - msg += " At most one is admitted." - raise KeyError(msg) + raise KeyError("key has more than one ':' symbol. At most one is admitted.") name, query_parameters = query_parameters[0], query_parameters[1:] if query_parameters: @@ -4108,11 +4168,17 @@ class Spec: itertools.chain( # Regular specs (x for x in order() if x.name == name), + ( + x + for x in order() + if (not x.virtual) + and any(name in edge.virtuals for edge in x.edges_from_dependents()) + ), (x for x in order() if (not x.virtual) and x.package.provides(name)), ) ) except StopIteration: - raise KeyError("No spec with name %s in %s" % (name, self)) + raise KeyError(f"No spec with name {name} in {self}") if self._concrete: return SpecBuildInterface(value, name, query_parameters) @@ -4490,17 +4556,27 @@ class Spec: return str(path_ctor(*output_path_components)) def __str__(self): - sorted_nodes = [self] + sorted( + root_str = [self.format()] + sorted_dependencies = sorted( self.traverse(root=False), key=lambda x: (x.name, x.abstract_hash) ) - return " ^".join(d.format() for d in sorted_nodes).strip() + sorted_dependencies = [ + d.format("{edge_attributes} " + DEFAULT_FORMAT) for d in sorted_dependencies + ] + spec_str = " ^".join(root_str + sorted_dependencies) + return spec_str.strip() @property def colored_str(self): - sorted_nodes = [self] + sorted( + root_str = [self.cformat()] + sorted_dependencies = sorted( self.traverse(root=False), key=lambda x: (x.name, x.abstract_hash) ) - return " ^".join(d.cformat() for d in sorted_nodes).strip() + sorted_dependencies = [ + d.cformat("{edge_attributes} " + DISPLAY_FORMAT) for d in sorted_dependencies + ] + spec_str = " ^".join(root_str + sorted_dependencies) + return spec_str.strip() def install_status(self): """Helper for tree to print DB install status.""" |