def splice()

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]