in lib/ramble/spack/spec.py [0:0]
def splice(self, other, transitive):
"""Splices dependency "other" into this ("target") Spec, and return the
result as a concrete Spec.
If transitive, then other and its dependencies will be extrapolated to
a list of Specs and spliced in accordingly.
For example, let there exist a dependency graph as follows:
T
| \
Z<-H
In this example, Spec T depends on H and Z, and H also depends on Z.
Suppose, however, that we wish to use a different H, known as H'. This
function will splice in the new H' in one of two ways:
1. transitively, where H' depends on the Z' it was built with, and the
new T* also directly depends on this new Z', or
2. intransitively, where the new T* and H' both depend on the original
Z.
Since the Spec returned by this splicing function is no longer deployed
the same way it was built, any such changes are tracked by setting the
build_spec to point to the corresponding dependency from the original
Spec.
TODO: Extend this for non-concrete Specs.
"""
assert self.concrete
assert other.concrete
virtuals_to_replace = [v.name for v in other.package.virtuals_provided
if v in self]
if virtuals_to_replace:
deps_to_replace = dict((self[v], other) for v in virtuals_to_replace)
# deps_to_replace = [self[v] for v in virtuals_to_replace]
else:
# TODO: sanity check and error raise here for other.name not in self
deps_to_replace = {self[other.name]: other}
# deps_to_replace = [self[other.name]]
for d in deps_to_replace:
if not all(v in other.package.virtuals_provided or v not in self
for v in d.package.virtuals_provided):
# There was something provided by the original that we don't
# get from its replacement.
raise SpliceError(("Splice between {0} and {1} will not provide "
"the same virtuals.").format(self.name, other.name))
for n in d.traverse(root=False):
if not all(any(v in other_n.package.virtuals_provided
for other_n in other.traverse(root=False))
or v not in self for v in n.package.virtuals_provided):
raise SpliceError(("Splice between {0} and {1} will not provide "
"the same virtuals."
).format(self.name, other.name))
# For now, check that we don't have DAG with multiple specs from the
# same package
def multiple_specs(root):
counter = collections.Counter([node.name for node in root.traverse()])
_, max_number = counter.most_common()[0]
return max_number > 1
if multiple_specs(self) or multiple_specs(other):
msg = ('Either "{0}" or "{1}" contain multiple specs from the same '
'package, which cannot be handled by splicing at the moment')
raise ValueError(msg.format(self, other))
# Multiple unique specs with the same name will collide, so the
# _dependents of these specs should not be trusted.
# Variants may also be ignored here for now...
# Keep all cached hashes because we will invalidate the ones that need
# invalidating later, and we don't want to invalidate unnecessarily
def from_self(name, transitive):
if transitive:
if name in other:
return False
if any(v in other for v in self[name].package.virtuals_provided):
return False
return True
else:
if name == other.name:
return False
if any(v in other.package.virtuals_provided
for v in self[name].package.virtuals_provided):
return False
return True
self_nodes = dict((s.name, s.copy(deps=False))
for s in self.traverse(root=True)
if from_self(s.name, transitive))
if transitive:
other_nodes = dict((s.name, s.copy(deps=False))
for s in other.traverse(root=True))
else:
# NOTE: Does not fully validate providers; loader races possible
other_nodes = dict((s.name, s.copy(deps=False))
for s in other.traverse(root=True)
if s is other or s.name not in self)
nodes = other_nodes.copy()
nodes.update(self_nodes)
for name in nodes:
if name in self_nodes:
for edge in self[name].edges_to_dependencies():
dep_name = deps_to_replace.get(edge.spec, edge.spec).name
nodes[name].add_dependency_edge(
nodes[dep_name], edge.deptypes
)
if any(dep not in self_nodes
for dep in self[name]._dependencies):
nodes[name].build_spec = self[name].build_spec
else:
for edge in other[name].edges_to_dependencies():
nodes[name].add_dependency_edge(
nodes[edge.spec.name], edge.deptypes
)
if any(dep not in other_nodes
for dep in other[name]._dependencies):
nodes[name].build_spec = other[name].build_spec
ret = nodes[self.name]
# Clear cached hashes for all affected nodes
# Do not touch unaffected nodes
for dep in ret.traverse(root=True, order='post'):
opposite = other_nodes if dep.name in self_nodes else self_nodes
if any(name in dep for name in opposite.keys()):
# package hash cannot be affected by splice
dep.clear_cached_hashes(ignore=['package_hash'])
dep.dag_hash()
return nodes[self.name]