Added support for virtual dependencies ("provides")
This commit is contained in:
		@@ -19,7 +19,3 @@ def spec(parser, args):
 | 
			
		||||
 | 
			
		||||
        spec.concretize()
 | 
			
		||||
        print spec.tree(color=True)
 | 
			
		||||
 | 
			
		||||
        pkg = spec.package
 | 
			
		||||
        wc = url.wildcard_version(pkg.url)
 | 
			
		||||
        print wc
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@
 | 
			
		||||
"""
 | 
			
		||||
import spack.arch
 | 
			
		||||
import spack.compilers
 | 
			
		||||
import spack.packages
 | 
			
		||||
from spack.version import *
 | 
			
		||||
from spack.spec import *
 | 
			
		||||
 | 
			
		||||
@@ -84,3 +85,15 @@ def concretize_compiler(self, spec):
 | 
			
		||||
                raise spack.spec.UnknownCompilerError(str(spec.compiler))
 | 
			
		||||
        else:
 | 
			
		||||
            spec.compiler = spack.compilers.default_compiler()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def choose_provider(self, spec, providers):
 | 
			
		||||
        """This is invoked for virtual specs.  Given a spec with a virtual name,
 | 
			
		||||
           say "mpi", and a list of specs of possible providers of that spec,
 | 
			
		||||
           select a provider and return it.
 | 
			
		||||
 | 
			
		||||
           Default implementation just chooses the last provider in sorted order.
 | 
			
		||||
        """
 | 
			
		||||
        assert(spec.virtual)
 | 
			
		||||
        assert(providers)
 | 
			
		||||
        return sorted(providers)[-1]
 | 
			
		||||
 
 | 
			
		||||
@@ -391,8 +391,10 @@ def dependents(self):
 | 
			
		||||
        return tuple(self._dependents)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def preorder_traversal(self, visited=None):
 | 
			
		||||
    def preorder_traversal(self, visited=None, **kwargs):
 | 
			
		||||
        """This does a preorder traversal of the package's dependence DAG."""
 | 
			
		||||
        virtual = kwargs.get("virtual", False)
 | 
			
		||||
 | 
			
		||||
        if visited is None:
 | 
			
		||||
            visited = set()
 | 
			
		||||
 | 
			
		||||
@@ -400,16 +402,41 @@ def preorder_traversal(self, visited=None):
 | 
			
		||||
            return
 | 
			
		||||
        visited.add(self.name)
 | 
			
		||||
 | 
			
		||||
        yield self
 | 
			
		||||
        if not virtual:
 | 
			
		||||
            yield self
 | 
			
		||||
 | 
			
		||||
        for name in sorted(self.dependencies.keys()):
 | 
			
		||||
            spec = self.dependencies[name]
 | 
			
		||||
            for pkg in packages.get(name).preorder_traversal(visited):
 | 
			
		||||
 | 
			
		||||
            # currently, we do not descend into virtual dependencies, as this
 | 
			
		||||
            # makes doing a sensible traversal much harder.  We just assume that
 | 
			
		||||
            # ANY of the virtual deps will work, which might not be true (due to
 | 
			
		||||
            # conflicts or unsatisfiable specs).  For now this is ok but we might
 | 
			
		||||
            # want to reinvestigate if we start using a lot of complicated virtual
 | 
			
		||||
            # dependencies
 | 
			
		||||
            # TODO: reinvestigate this.
 | 
			
		||||
            if spec.virtual:
 | 
			
		||||
                if virtual:
 | 
			
		||||
                    yield spec
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            for pkg in packages.get(name).preorder_traversal(visited, **kwargs):
 | 
			
		||||
                yield pkg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def validate_dependencies(self):
 | 
			
		||||
        """Ensure that this package and its dependencies all have consistent
 | 
			
		||||
           constraints on them.
 | 
			
		||||
 | 
			
		||||
           NOTE that this will NOT find sanity problems through a virtual
 | 
			
		||||
           dependency.  Virtual deps complicate the problem because we
 | 
			
		||||
           don't know in advance which ones conflict with others in the
 | 
			
		||||
           dependency DAG. If there's more than one virtual dependency,
 | 
			
		||||
           it's a full-on SAT problem, so hold off on this for now.
 | 
			
		||||
           The vdeps are actually skipped in preorder_traversal, so see
 | 
			
		||||
           that for details.
 | 
			
		||||
 | 
			
		||||
           TODO: investigate validating virtual dependencies.
 | 
			
		||||
        """
 | 
			
		||||
        # This algorithm just attempts to merge all the constraints on the same
 | 
			
		||||
        # package together, loses information about the source of the conflict.
 | 
			
		||||
@@ -432,13 +459,14 @@ def validate_dependencies(self):
 | 
			
		||||
                % (self.name, e.message))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    @memoized
 | 
			
		||||
    def all_dependencies(self):
 | 
			
		||||
        """Dict(str -> Package) of all transitive dependencies of this package."""
 | 
			
		||||
        all_deps = {name : dep for dep in self.preorder_traversal}
 | 
			
		||||
        del all_deps[self.name]
 | 
			
		||||
        return all_deps
 | 
			
		||||
    def provides(self, vpkg_name):
 | 
			
		||||
        """True if this package provides a virtual package with the specified name."""
 | 
			
		||||
        return vpkg_name in self.provided
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def virtual_dependencies(self, visited=None):
 | 
			
		||||
        for spec in sorted(set(self.preorder_traversal(virtual=True))):
 | 
			
		||||
            yield spec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
import spack
 | 
			
		||||
import spack.error
 | 
			
		||||
import spack.spec
 | 
			
		||||
import spack.tty as tty
 | 
			
		||||
from spack.util.filesystem import new_path
 | 
			
		||||
from spack.util.lang import list_modules
 | 
			
		||||
import spack.arch as arch
 | 
			
		||||
@@ -19,7 +20,60 @@
 | 
			
		||||
invalid_package_re = r'[_-][_-]+'
 | 
			
		||||
 | 
			
		||||
instances = {}
 | 
			
		||||
providers = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProviderIndex(object):
 | 
			
		||||
    """This is a dict of dicts used for finding providers of particular
 | 
			
		||||
       virtual dependencies. The dict of dicts looks like:
 | 
			
		||||
 | 
			
		||||
       { vpkg name :
 | 
			
		||||
           { full vpkg spec : package providing spec } }
 | 
			
		||||
 | 
			
		||||
       Callers can use this to first find which packages provide a vpkg,
 | 
			
		||||
       then find a matching full spec.  e.g., in this scenario:
 | 
			
		||||
 | 
			
		||||
       { 'mpi' :
 | 
			
		||||
           { mpi@:1.1 : mpich,
 | 
			
		||||
             mpi@:2.3 : mpich2@1.9: } }
 | 
			
		||||
 | 
			
		||||
       Calling find_provider(spec) will find a package that provides a
 | 
			
		||||
       matching implementation of MPI.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, providers):
 | 
			
		||||
        """Takes a list of provider packagse and build an index of the virtual
 | 
			
		||||
           packages they provide."""
 | 
			
		||||
        self.providers = {}
 | 
			
		||||
        self.add(*providers)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add(self, *providers):
 | 
			
		||||
        """Look at the provided map on the provider packages, invert it and
 | 
			
		||||
           add it to this provider index."""
 | 
			
		||||
        for pkg in providers:
 | 
			
		||||
            for provided_spec, provider_spec in pkg.provided.iteritems():
 | 
			
		||||
                provided_name = provided_spec.name
 | 
			
		||||
                if provided_name not in self.providers:
 | 
			
		||||
                    self.providers[provided_name] = {}
 | 
			
		||||
                self.providers[provided_name][provided_spec] = provider_spec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def providers_for(self, *vpkg_specs):
 | 
			
		||||
        """Gives names of all packages that provide virtual packages
 | 
			
		||||
           with the supplied names."""
 | 
			
		||||
        packages = set()
 | 
			
		||||
        for vspec in vpkg_specs:
 | 
			
		||||
            # Allow string names to be passed as input, as well as specs
 | 
			
		||||
            if type(vspec) == str:
 | 
			
		||||
                vspec = spack.spec.Spec(vspec)
 | 
			
		||||
 | 
			
		||||
            # Add all the packages that satisfy the vpkg spec.
 | 
			
		||||
            if vspec.name in self.providers:
 | 
			
		||||
                for provider_spec, pkg in self.providers[vspec.name].items():
 | 
			
		||||
                    if provider_spec.satisfies(vspec):
 | 
			
		||||
                        packages.add(pkg)
 | 
			
		||||
 | 
			
		||||
        # Return packages in order
 | 
			
		||||
        return sorted(packages)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get(pkg_name):
 | 
			
		||||
@@ -30,22 +84,15 @@ def get(pkg_name):
 | 
			
		||||
    return instances[pkg_name]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_providers(vpkg_name):
 | 
			
		||||
def providers_for(vpkg_spec):
 | 
			
		||||
    if providers_for.index is None:
 | 
			
		||||
        providers_for.index = ProviderIndex(all_packages())
 | 
			
		||||
 | 
			
		||||
    providers = providers_for.index.providers_for(vpkg_spec)
 | 
			
		||||
    if not providers:
 | 
			
		||||
        compute_providers()
 | 
			
		||||
 | 
			
		||||
    if not vpkg_name in providers:
 | 
			
		||||
        raise UnknownPackageError("No such virtual package: %s" % vpkg_name)
 | 
			
		||||
 | 
			
		||||
    return providers[vpkg_name]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compute_providers():
 | 
			
		||||
    for pkg in all_packages():
 | 
			
		||||
        for vpkg in pkg.provided:
 | 
			
		||||
            if vpkg not in providers:
 | 
			
		||||
                providers[vpkg] = []
 | 
			
		||||
            providers[vpkg].append(pkg)
 | 
			
		||||
        raise UnknownPackageError("No such virtual package: %s" % vpkg_spec)
 | 
			
		||||
    return providers
 | 
			
		||||
providers_for.index = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def valid_package_name(pkg_name):
 | 
			
		||||
@@ -99,6 +146,13 @@ def exists(pkg_name):
 | 
			
		||||
    return os.path.exists(filename_for_package_name(pkg_name))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def packages_module():
 | 
			
		||||
    # TODO: replace this with a proper package DB class, instead of this hackiness.
 | 
			
		||||
    packages_path = re.sub(spack.module_path + '\/+', 'spack.', spack.packages_path)
 | 
			
		||||
    packages_module = re.sub(r'\/', '.', packages_path)
 | 
			
		||||
    return packages_module
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_class_for_package_name(pkg_name):
 | 
			
		||||
    file_name = filename_for_package_name(pkg_name)
 | 
			
		||||
 | 
			
		||||
@@ -115,22 +169,18 @@ def get_class_for_package_name(pkg_name):
 | 
			
		||||
    if not re.match(r'%s' % spack.module_path, spack.packages_path):
 | 
			
		||||
        raise RuntimeError("Packages path is not a submodule of spack.")
 | 
			
		||||
 | 
			
		||||
    # TODO: replace this with a proper package DB class, instead of this hackiness.
 | 
			
		||||
    packages_path = re.sub(spack.module_path + '\/+', 'spack.', spack.packages_path)
 | 
			
		||||
    packages_module = re.sub(r'\/', '.', packages_path)
 | 
			
		||||
 | 
			
		||||
    class_name = pkg_name.capitalize()
 | 
			
		||||
    try:
 | 
			
		||||
        module_name = "%s.%s" % (packages_module, pkg_name)
 | 
			
		||||
        module_name = "%s.%s" % (packages_module(), pkg_name)
 | 
			
		||||
        module = __import__(module_name, fromlist=[class_name])
 | 
			
		||||
    except ImportError, e:
 | 
			
		||||
        tty.die("Error while importing %s.%s:\n%s" % (pkg_name, class_name, e.message))
 | 
			
		||||
 | 
			
		||||
    klass = getattr(module, class_name)
 | 
			
		||||
    if not inspect.isclass(klass):
 | 
			
		||||
    cls = getattr(module, class_name)
 | 
			
		||||
    if not inspect.isclass(cls):
 | 
			
		||||
        tty.die("%s.%s is not a class" % (pkg_name, class_name))
 | 
			
		||||
 | 
			
		||||
    return klass
 | 
			
		||||
    return cls
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compute_dependents():
 | 
			
		||||
 
 | 
			
		||||
@@ -44,9 +44,15 @@ class Mpileaks(Package):
 | 
			
		||||
        spack install mpileaks ^mvapich
 | 
			
		||||
        spack install mpileaks ^mpich
 | 
			
		||||
"""
 | 
			
		||||
import sys
 | 
			
		||||
import re
 | 
			
		||||
import inspect
 | 
			
		||||
import importlib
 | 
			
		||||
 | 
			
		||||
import spack
 | 
			
		||||
import spack.spec
 | 
			
		||||
from spack.spec import Spec
 | 
			
		||||
import spack.error
 | 
			
		||||
from spack.packages import packages_module
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _caller_locals():
 | 
			
		||||
@@ -62,8 +68,61 @@ def _caller_locals():
 | 
			
		||||
        del stack
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _ensure_caller_is_spack_package():
 | 
			
		||||
    """Make sure that the caller is a spack package.  If it's not,
 | 
			
		||||
       raise ScopeError.  if it is, return its name."""
 | 
			
		||||
    stack = inspect.stack()
 | 
			
		||||
    try:
 | 
			
		||||
        # get calling function name (the relation)
 | 
			
		||||
        relation = stack[1][3]
 | 
			
		||||
 | 
			
		||||
        # Make sure locals contain __module__
 | 
			
		||||
        caller_locals = stack[2][0].f_locals
 | 
			
		||||
    finally:
 | 
			
		||||
        del stack
 | 
			
		||||
 | 
			
		||||
    if not '__module__' in caller_locals:
 | 
			
		||||
        raise ScopeError(relation)
 | 
			
		||||
 | 
			
		||||
    module_name = caller_locals['__module__']
 | 
			
		||||
    if not module_name.startswith(packages_module()):
 | 
			
		||||
        raise ScopeError(relation)
 | 
			
		||||
 | 
			
		||||
    base_name = module_name.split('.')[-1]
 | 
			
		||||
    return base_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _parse_local_spec(spec_like, pkg_name):
 | 
			
		||||
    """Allow the user to omit the package name part of a spec in relations.
 | 
			
		||||
       e.g., provides('mpi@2.1', when='@1.9:') says that this package provides
 | 
			
		||||
       MPI 2.1 when its version is higher than 1.9.
 | 
			
		||||
    """
 | 
			
		||||
    if type(spec_like) not in (str, Spec):
 | 
			
		||||
        raise TypeError('spec must be Spec or spec string.  Found %s'
 | 
			
		||||
                        % type(spec_like))
 | 
			
		||||
 | 
			
		||||
    if type(spec_like) == str:
 | 
			
		||||
        try:
 | 
			
		||||
            local_spec = Spec(spec_like)
 | 
			
		||||
        except ParseError:
 | 
			
		||||
            local_spec = Spec(pkg_name + spec_like)
 | 
			
		||||
            if local_spec.name != pkg_name: raise ValueError(
 | 
			
		||||
                    "Invalid spec for package %s: %s" % (pkg_name, spec_like))
 | 
			
		||||
    else:
 | 
			
		||||
        local_spec = spec_like
 | 
			
		||||
 | 
			
		||||
    if local_spec.name != pkg_name:
 | 
			
		||||
        raise ValueError("Spec name '%s' must match package name '%s'"
 | 
			
		||||
                         % (spec_like.name, pkg_name))
 | 
			
		||||
 | 
			
		||||
    return local_spec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_relation(map_name):
 | 
			
		||||
    def relation_fun(*specs):
 | 
			
		||||
        _ensure_caller_is_spack_package()
 | 
			
		||||
        package_map = _caller_locals().setdefault(map_name, {})
 | 
			
		||||
        for string in specs:
 | 
			
		||||
            for spec in spack.spec.parse(string):
 | 
			
		||||
@@ -76,14 +135,31 @@ def relation_fun(*specs):
 | 
			
		||||
depends_on = _make_relation("dependencies")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""Allows packages to provide a virtual dependency.  If a package provides
 | 
			
		||||
   'mpi', other packages can declare that they depend on "mpi", and spack
 | 
			
		||||
   can use the providing package to satisfy the dependency.
 | 
			
		||||
"""
 | 
			
		||||
provides   = _make_relation("provided")
 | 
			
		||||
def provides(*specs, **kwargs):
 | 
			
		||||
    """Allows packages to provide a virtual dependency.  If a package provides
 | 
			
		||||
       'mpi', other packages can declare that they depend on "mpi", and spack
 | 
			
		||||
       can use the providing package to satisfy the dependency.
 | 
			
		||||
    """
 | 
			
		||||
    pkg = _ensure_caller_is_spack_package()
 | 
			
		||||
    spec_string = kwargs.get('when', pkg)
 | 
			
		||||
    provider_spec = _parse_local_spec(spec_string, pkg)
 | 
			
		||||
 | 
			
		||||
    provided = _caller_locals().setdefault("provided", {})
 | 
			
		||||
    for string in specs:
 | 
			
		||||
        for provided_spec in spack.spec.parse(string):
 | 
			
		||||
            provided[provided_spec] = provider_spec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""Packages can declare conflicts with other packages.
 | 
			
		||||
   This can be as specific as you like: use regular spec syntax.
 | 
			
		||||
"""
 | 
			
		||||
conflicts  = _make_relation("conflicted")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ScopeError(spack.error.SpackError):
 | 
			
		||||
    """This is raised when a relation is called from outside a spack package."""
 | 
			
		||||
    def __init__(self, relation):
 | 
			
		||||
        super(ScopeError, self).__init__(
 | 
			
		||||
            "Cannot inovke '%s' from outside of a Spack package!" % relation)
 | 
			
		||||
        self.relation = relation
 | 
			
		||||
 
 | 
			
		||||
@@ -247,12 +247,13 @@ def __init__(self, spec_like, *dep_like):
 | 
			
		||||
        if len(spec_list) < 1:
 | 
			
		||||
            raise ValueError("String contains no specs: " + spec_like)
 | 
			
		||||
 | 
			
		||||
        # Take all the attributes from the first parsed spec without copying
 | 
			
		||||
        # This is a little bit nasty, but it's nastier to make the parser
 | 
			
		||||
        # write directly into this Spec object.
 | 
			
		||||
        # Take all the attributes from the first parsed spec without copying.
 | 
			
		||||
        # This is safe b/c we throw out the parsed spec.  It's a bit nasty,
 | 
			
		||||
        # but it's nastier to implement the constructor so that the parser
 | 
			
		||||
        # writes directly into this Spec object.
 | 
			
		||||
        other = spec_list[0]
 | 
			
		||||
        self.name = other.name
 | 
			
		||||
        self.parent = other.parent
 | 
			
		||||
        self.dependents = other.dependents
 | 
			
		||||
        self.versions = other.versions
 | 
			
		||||
        self.variants = other.variants
 | 
			
		||||
        self.architecture = other.architecture
 | 
			
		||||
@@ -263,11 +264,8 @@ def __init__(self, spec_like, *dep_like):
 | 
			
		||||
        # Note that given two specs a and b, Spec(a) copies a, but
 | 
			
		||||
        # Spec(a, b) will copy a but just add b as a dep.
 | 
			
		||||
        for dep in dep_like:
 | 
			
		||||
            if type(dep) == str:
 | 
			
		||||
                dep_spec = Spec(dep)
 | 
			
		||||
                self.dependencies[dep_spec.name] = dep_spec
 | 
			
		||||
            elif type(dep) == Spec:
 | 
			
		||||
                self.dependencies[dep.name] = dep
 | 
			
		||||
            spec = dep if type(dep) == Spec else Spec(dep)
 | 
			
		||||
            self._add_dependency(spec)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
@@ -299,21 +297,31 @@ def _set_architecture(self, architecture):
 | 
			
		||||
        self.architecture = architecture
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _add_dependency(self, dep):
 | 
			
		||||
    def _add_dependency(self, spec):
 | 
			
		||||
        """Called by the parser to add another spec as a dependency."""
 | 
			
		||||
        if dep.name in self.dependencies:
 | 
			
		||||
            raise DuplicateDependencyError("Cannot depend on '%s' twice" % dep)
 | 
			
		||||
        self.dependencies[dep.name] = dep
 | 
			
		||||
        dep.parent = self
 | 
			
		||||
        if spec.name in self.dependencies:
 | 
			
		||||
            raise DuplicateDependencyError("Cannot depend on '%s' twice" % spec)
 | 
			
		||||
        self.dependencies[spec.name] = spec
 | 
			
		||||
        spec.dependents[self.name] = self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def root(self):
 | 
			
		||||
        """Follow parent links and find the root of this spec's DAG."""
 | 
			
		||||
        root = self
 | 
			
		||||
        while root.parent is not None:
 | 
			
		||||
            root = root.parent
 | 
			
		||||
        return root
 | 
			
		||||
        """Follow dependent links and find the root of this spec's DAG.
 | 
			
		||||
           In spack specs, there should be a single root (the package being
 | 
			
		||||
           installed).  This will throw an assertion error if that is not
 | 
			
		||||
           the case.
 | 
			
		||||
        """
 | 
			
		||||
        if not self.dependents:
 | 
			
		||||
            return self
 | 
			
		||||
        else:
 | 
			
		||||
            # If the spec has multiple dependents, ensure that they all
 | 
			
		||||
            # lead to the same place.  Spack shouldn't deal with any DAGs
 | 
			
		||||
            # with multiple roots, so something's wrong if we find one.
 | 
			
		||||
            depiter = iter(self.dependents.values())
 | 
			
		||||
            first_root = next(depiter).root
 | 
			
		||||
            assert(all(first_root is d.root for d in depiter))
 | 
			
		||||
            return first_root
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@@ -353,10 +361,10 @@ def preorder_traversal(self, visited=None, d=0, **kwargs):
 | 
			
		||||
           This will yield each node in the spec.  Options:
 | 
			
		||||
 | 
			
		||||
           unique   [=True]
 | 
			
		||||
               When True (default) every node in the DAG is yielded only once.
 | 
			
		||||
               When False, the traversal will yield already visited nodes but
 | 
			
		||||
               not their children.  This lets you see that a node ponts to
 | 
			
		||||
               an already-visited subgraph without descending into it.
 | 
			
		||||
               When True (default), every node in the DAG is yielded only once.
 | 
			
		||||
               When False, the traversal will yield already visited
 | 
			
		||||
               nodes but not their children.  This lets you see that a node
 | 
			
		||||
               points to an already-visited subgraph without descending into it.
 | 
			
		||||
 | 
			
		||||
           depth    [=False]
 | 
			
		||||
               Defaults to False.  When True, yields not just nodes in the
 | 
			
		||||
@@ -388,31 +396,67 @@ def preorder_traversal(self, visited=None, d=0, **kwargs):
 | 
			
		||||
            yield (d, self) if depth else self
 | 
			
		||||
 | 
			
		||||
        for key in sorted(self.dependencies.keys()):
 | 
			
		||||
            for spec in self.dependencies[key].preorder_traversal(
 | 
			
		||||
            for result in self.dependencies[key].preorder_traversal(
 | 
			
		||||
                    visited, d+1, **kwargs):
 | 
			
		||||
                yield spec
 | 
			
		||||
                yield result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _concretize_helper(self, presets):
 | 
			
		||||
    def _concretize_helper(self, presets=None, visited=None):
 | 
			
		||||
        """Recursive helper function for concretize().
 | 
			
		||||
           This concretizes everything bottom-up.  As things are
 | 
			
		||||
           concretized, they're added to the presets, and ancestors
 | 
			
		||||
           will prefer the settings of their children.
 | 
			
		||||
        """
 | 
			
		||||
        if presets is None: presets = {}
 | 
			
		||||
        if visited is None: visited = set()
 | 
			
		||||
 | 
			
		||||
        if self.name in visited:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Concretize deps first -- this is a bottom-up process.
 | 
			
		||||
        for name in sorted(self.dependencies.keys()):
 | 
			
		||||
            self.dependencies[name]._concretize_helper(presets)
 | 
			
		||||
            self.dependencies[name]._concretize_helper(presets, visited)
 | 
			
		||||
 | 
			
		||||
        if self.name in presets:
 | 
			
		||||
            self.constrain(presets[self.name])
 | 
			
		||||
        else:
 | 
			
		||||
            spack.concretizer.concretize_architecture(self)
 | 
			
		||||
            spack.concretizer.concretize_compiler(self)
 | 
			
		||||
            spack.concretizer.concretize_version(self)
 | 
			
		||||
            # Concretize virtual dependencies last.  Because they're added
 | 
			
		||||
            # to presets below, their constraints will all be merged, but we'll
 | 
			
		||||
            # still need to select a concrete package later.
 | 
			
		||||
            if not self.virtual:
 | 
			
		||||
                spack.concretizer.concretize_architecture(self)
 | 
			
		||||
                spack.concretizer.concretize_compiler(self)
 | 
			
		||||
                spack.concretizer.concretize_version(self)
 | 
			
		||||
            presets[self.name] = self
 | 
			
		||||
 | 
			
		||||
        visited.add(self.name)
 | 
			
		||||
 | 
			
		||||
    def concretize(self, *presets):
 | 
			
		||||
 | 
			
		||||
    def _expand_virtual_packages(self):
 | 
			
		||||
        """Find virtual packages in this spec, replace them with providers,
 | 
			
		||||
           and normalize again to include the provider's (potentially virtual)
 | 
			
		||||
           dependencies.  Repeat until there are no virtual deps.
 | 
			
		||||
        """
 | 
			
		||||
        while True:
 | 
			
		||||
            virtuals =[v for v in self.preorder_traversal() if v.virtual]
 | 
			
		||||
            if not virtuals:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            for spec in virtuals:
 | 
			
		||||
                providers = packages.providers_for(spec)
 | 
			
		||||
                concrete = spack.concretizer.choose_provider(spec, providers)
 | 
			
		||||
                concrete = concrete.copy()
 | 
			
		||||
 | 
			
		||||
                for name, dependent in spec.dependents.items():
 | 
			
		||||
                    del dependent.dependencies[spec.name]
 | 
			
		||||
                    dependent._add_dependency(concrete)
 | 
			
		||||
 | 
			
		||||
            # If there are duplicate providers or duplicate provider deps, this
 | 
			
		||||
            # consolidates them and merges constraints.
 | 
			
		||||
            self.normalize()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def concretize(self):
 | 
			
		||||
        """A spec is concrete if it describes one build of a package uniquely.
 | 
			
		||||
           This will ensure that this spec is concrete.
 | 
			
		||||
 | 
			
		||||
@@ -424,48 +468,32 @@ def concretize(self, *presets):
 | 
			
		||||
           with requirements of its pacakges.  See flatten() and normalize() for
 | 
			
		||||
           more details on this.
 | 
			
		||||
        """
 | 
			
		||||
        # Build specs out of user-provided presets
 | 
			
		||||
        specs = [Spec(p) for p in presets]
 | 
			
		||||
 | 
			
		||||
        # Concretize the presets first.  They could be partial specs, like just
 | 
			
		||||
        # a particular version that the caller wants.
 | 
			
		||||
        for spec in specs:
 | 
			
		||||
            if not spec.concrete:
 | 
			
		||||
                try:
 | 
			
		||||
                    spec.concretize()
 | 
			
		||||
                except UnsatisfiableSpecError, e:
 | 
			
		||||
                    e.message = ("Unsatisfiable preset in concretize: %s."
 | 
			
		||||
                                 % e.message)
 | 
			
		||||
                    raise e
 | 
			
		||||
 | 
			
		||||
        # build preset specs into a map
 | 
			
		||||
        preset_dict = {spec.name : spec for spec in specs}
 | 
			
		||||
 | 
			
		||||
        # Concretize bottom up, passing in presets to force concretization
 | 
			
		||||
        # for certain specs.
 | 
			
		||||
        self.normalize()
 | 
			
		||||
        self._concretize_helper(preset_dict)
 | 
			
		||||
        self._expand_virtual_packages()
 | 
			
		||||
        self._concretize_helper()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def concretized(self, *presets):
 | 
			
		||||
    def concretized(self):
 | 
			
		||||
        """This is a non-destructive version of concretize().  First clones,
 | 
			
		||||
           then returns a concrete version of this package without modifying
 | 
			
		||||
           this package. """
 | 
			
		||||
        clone = self.copy()
 | 
			
		||||
        clone.concretize(*presets)
 | 
			
		||||
        clone.concretize()
 | 
			
		||||
        return clone
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def flat_dependencies(self):
 | 
			
		||||
        """Return a DependencyMap containing all dependencies with their
 | 
			
		||||
           constraints merged.  If there are any conflicts, throw an exception.
 | 
			
		||||
        """Return a DependencyMap containing all of this spec's dependencies
 | 
			
		||||
           with their constraints merged.  If there are any conflicts, throw
 | 
			
		||||
           an exception.
 | 
			
		||||
 | 
			
		||||
           This will work even on specs that are not normalized; i.e. specs
 | 
			
		||||
           that have two instances of the same dependency in the DAG.
 | 
			
		||||
           This is used as the first step of normalization.
 | 
			
		||||
        """
 | 
			
		||||
        # This ensures that the package descriptions themselves are consistent
 | 
			
		||||
        self.package.validate_dependencies()
 | 
			
		||||
        if not self.virtual:
 | 
			
		||||
            self.package.validate_dependencies()
 | 
			
		||||
 | 
			
		||||
        # Once that is guaranteed, we know any constraint violations are due
 | 
			
		||||
        # to the spec -- so they're the user's fault, not Spack's.
 | 
			
		||||
@@ -497,22 +525,54 @@ def flatten(self):
 | 
			
		||||
        self.dependencies = self.flat_dependencies()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _normalize_helper(self, visited, spec_deps):
 | 
			
		||||
    def _normalize_helper(self, visited, spec_deps, provider_index):
 | 
			
		||||
        """Recursive helper function for _normalize."""
 | 
			
		||||
        if self.name in visited:
 | 
			
		||||
            return
 | 
			
		||||
        visited.add(self.name)
 | 
			
		||||
 | 
			
		||||
        # if we descend into a virtual spec, there's nothing more
 | 
			
		||||
        # to normalize.  Concretize will finish resolving it later.
 | 
			
		||||
        if self.virtual:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Combine constraints from package dependencies with
 | 
			
		||||
        # information in this spec's dependencies.
 | 
			
		||||
        # constraints on the spec's dependencies.
 | 
			
		||||
        pkg = packages.get(self.name)
 | 
			
		||||
        for name, pkg_dep in self.package.dependencies.iteritems():
 | 
			
		||||
        for name, pkg_dep in self.package.dependencies.items():
 | 
			
		||||
            # If it's a virtual dependency, try to find a provider
 | 
			
		||||
            if pkg_dep.virtual:
 | 
			
		||||
                providers = provider_index.providers_for(pkg_dep)
 | 
			
		||||
 | 
			
		||||
                # If there is a provider for the vpkg, then use that instead of
 | 
			
		||||
                # the virtual package.  If there isn't a provider, just merge
 | 
			
		||||
                # constraints on the virtual package.
 | 
			
		||||
                if providers:
 | 
			
		||||
                    # Can't have multiple providers for the same thing in one spec.
 | 
			
		||||
                    if len(providers) > 1:
 | 
			
		||||
                        raise MultipleProviderError(pkg_dep, providers)
 | 
			
		||||
 | 
			
		||||
                    pkg_dep = providers[0]
 | 
			
		||||
                    name    = pkg_dep.name
 | 
			
		||||
 | 
			
		||||
                else:
 | 
			
		||||
                    # The user might have required something insufficient for
 | 
			
		||||
                    # pkg_dep -- so we'll get a conflict.  e.g., user asked for
 | 
			
		||||
                    # mpi@:1.1 but some package required mpi@2.1:.
 | 
			
		||||
                    providers = provider_index.providers_for(name)
 | 
			
		||||
                    if len(providers) > 1:
 | 
			
		||||
                        raise MultipleProviderError(pkg_dep, providers)
 | 
			
		||||
                    if providers:
 | 
			
		||||
                        raise UnsatisfiableProviderSpecError(providers[0], pkg_dep)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            if name not in spec_deps:
 | 
			
		||||
                # Clone the spec from the package
 | 
			
		||||
                # If the spec doesn't reference a dependency that this package
 | 
			
		||||
                # needs, then clone it from the package description.
 | 
			
		||||
                spec_deps[name] = pkg_dep.copy()
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                # intersect package information with spec info
 | 
			
		||||
                # Constrain package information with spec info
 | 
			
		||||
                spec_deps[name].constrain(pkg_dep)
 | 
			
		||||
 | 
			
		||||
            except UnsatisfiableSpecError, e:
 | 
			
		||||
@@ -523,35 +583,61 @@ def _normalize_helper(self, visited, spec_deps):
 | 
			
		||||
                raise e
 | 
			
		||||
 | 
			
		||||
            # Add merged spec to my deps and recurse
 | 
			
		||||
            self._add_dependency(spec_deps[name])
 | 
			
		||||
            self.dependencies[name]._normalize_helper(visited, spec_deps)
 | 
			
		||||
            dependency = spec_deps[name]
 | 
			
		||||
            self._add_dependency(dependency)
 | 
			
		||||
            dependency._normalize_helper(visited, spec_deps, provider_index)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def normalize(self):
 | 
			
		||||
        # Ensure first that all packages exist.
 | 
			
		||||
        """When specs are parsed, any dependencies specified are hanging off
 | 
			
		||||
           the root, and ONLY the ones that were explicitly provided are there.
 | 
			
		||||
           Normalization turns a partial flat spec into a DAG, where:
 | 
			
		||||
             1) ALL dependencies of the root package are in the DAG.
 | 
			
		||||
             2) Each node's dependencies dict only contains its direct deps.
 | 
			
		||||
             3) There is only ONE unique spec for each package in the DAG.
 | 
			
		||||
                - This includes virtual packages.  If there a non-virtual
 | 
			
		||||
                  package that provides a virtual package that is in the spec,
 | 
			
		||||
                  then we replace the virtual package with the non-virtual one.
 | 
			
		||||
             4) The spec DAG matches package DAG.
 | 
			
		||||
        """
 | 
			
		||||
        # Ensure first that all packages in the DAG exist.
 | 
			
		||||
        self.validate_package_names()
 | 
			
		||||
 | 
			
		||||
        # Then ensure that the packages mentioned are sane, that the
 | 
			
		||||
        # Then ensure that the packages referenced are sane, that the
 | 
			
		||||
        # provided spec is sane, and that all dependency specs are in the
 | 
			
		||||
        # root node of the spec.  flat_dependencies will do this for us.
 | 
			
		||||
        spec_deps = self.flat_dependencies()
 | 
			
		||||
        self.dependencies.clear()
 | 
			
		||||
 | 
			
		||||
        # Figure out which of the user-provided deps provide virtual deps.
 | 
			
		||||
        # Remove virtual deps that are already provided by something in the spec
 | 
			
		||||
        spec_packages = [d.package for d in spec_deps.values() if not d.virtual]
 | 
			
		||||
 | 
			
		||||
        index = packages.ProviderIndex(spec_packages)
 | 
			
		||||
        visited = set()
 | 
			
		||||
        self._normalize_helper(visited, spec_deps)
 | 
			
		||||
        self._normalize_helper(visited, spec_deps, index)
 | 
			
		||||
 | 
			
		||||
        # If there are deps specified but not visited, they're not
 | 
			
		||||
        # actually deps of this package.  Raise an error.
 | 
			
		||||
        extra = set(spec_deps.viewkeys()).difference(visited)
 | 
			
		||||
 | 
			
		||||
        # Also subtract out all the packags that provide a needed vpkg
 | 
			
		||||
        vdeps = [v for v in self.package.virtual_dependencies()]
 | 
			
		||||
 | 
			
		||||
        vpkg_providers = index.providers_for(*vdeps)
 | 
			
		||||
        extra.difference_update(p.name for p in vpkg_providers)
 | 
			
		||||
 | 
			
		||||
        # Anything left over is not a valid part of the spec.
 | 
			
		||||
        if extra:
 | 
			
		||||
            raise InvalidDependencyException(
 | 
			
		||||
                self.name + " does not depend on " + comma_or(extra))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def validate_package_names(self):
 | 
			
		||||
        packages.get(self.name)
 | 
			
		||||
        for name, dep in self.dependencies.iteritems():
 | 
			
		||||
            dep.validate_package_names()
 | 
			
		||||
        for spec in self.preorder_traversal():
 | 
			
		||||
            # Don't get a package for a virtual name.
 | 
			
		||||
            if not spec.virtual:
 | 
			
		||||
                packages.get(spec.name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def constrain(self, other):
 | 
			
		||||
@@ -593,14 +679,19 @@ def sat(attribute):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _dup(self, other, **kwargs):
 | 
			
		||||
        """Copy the spec other into self.  This is a
 | 
			
		||||
           first-party, overwriting copy.  This does not copy
 | 
			
		||||
           parent; if the other spec has a parent, this one will not.
 | 
			
		||||
           To duplicate an entire DAG, Duplicate the root of the DAG.
 | 
			
		||||
        """Copy the spec other into self.  This is an overwriting
 | 
			
		||||
           copy.  It does not copy any dependents (parents), but by default
 | 
			
		||||
           copies dependencies.
 | 
			
		||||
 | 
			
		||||
           To duplicate an entire DAG, call _dup() on the root of the DAG.
 | 
			
		||||
 | 
			
		||||
           Options:
 | 
			
		||||
           dependencies[=True]
 | 
			
		||||
               Whether deps should be copied too.  Set to false to copy a
 | 
			
		||||
               spec but not its dependencies.
 | 
			
		||||
        """
 | 
			
		||||
        # TODO: this needs to handle DAGs.
 | 
			
		||||
        self.name = other.name
 | 
			
		||||
        self.parent = None
 | 
			
		||||
        self.versions = other.versions.copy()
 | 
			
		||||
        self.variants = other.variants.copy()
 | 
			
		||||
        self.architecture = other.architecture
 | 
			
		||||
@@ -608,6 +699,7 @@ def _dup(self, other, **kwargs):
 | 
			
		||||
        if other.compiler:
 | 
			
		||||
            self.compiler = other.compiler.copy()
 | 
			
		||||
 | 
			
		||||
        self.dependents = DependencyMap()
 | 
			
		||||
        copy_deps = kwargs.get('dependencies', True)
 | 
			
		||||
        if copy_deps:
 | 
			
		||||
            self.dependencies = other.dependencies.copy()
 | 
			
		||||
@@ -744,11 +836,11 @@ def spec(self):
 | 
			
		||||
        # This will init the spec without calling __init__.
 | 
			
		||||
        spec = Spec.__new__(Spec)
 | 
			
		||||
        spec.name = self.token.value
 | 
			
		||||
        spec.parent = None
 | 
			
		||||
        spec.versions = VersionList()
 | 
			
		||||
        spec.variants = VariantMap()
 | 
			
		||||
        spec.architecture = None
 | 
			
		||||
        spec.compiler = None
 | 
			
		||||
        spec.dependents   = DependencyMap()
 | 
			
		||||
        spec.dependencies = DependencyMap()
 | 
			
		||||
 | 
			
		||||
        # record this so that we know whether version is
 | 
			
		||||
@@ -903,6 +995,29 @@ def __init__(self, message):
 | 
			
		||||
        super(InvalidDependencyException, self).__init__(message)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NoProviderError(SpecError):
 | 
			
		||||
    """Raised when there is no package that provides a particular
 | 
			
		||||
       virtual dependency.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, vpkg):
 | 
			
		||||
        super(NoProviderError, self).__init__(
 | 
			
		||||
            "No providers found for virtual package: '%s'" % vpkg)
 | 
			
		||||
        self.vpkg = vpkg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MultipleProviderError(SpecError):
 | 
			
		||||
    """Raised when there is no package that provides a particular
 | 
			
		||||
       virtual dependency.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, vpkg, providers):
 | 
			
		||||
        """Takes the name of the vpkg"""
 | 
			
		||||
        super(NoProviderError, self).__init__(
 | 
			
		||||
            "Multiple providers found for vpkg '%s': %s"
 | 
			
		||||
            % (vpkg, [str(s) for s in providers]))
 | 
			
		||||
        self.vpkg = vpkg
 | 
			
		||||
        self.providers = providers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsatisfiableSpecError(SpecError):
 | 
			
		||||
    """Raised when a spec conflicts with package constraints.
 | 
			
		||||
       Provide the requirement that was violated when raising."""
 | 
			
		||||
@@ -940,3 +1055,11 @@ class UnsatisfiableArchitectureSpecError(UnsatisfiableSpecError):
 | 
			
		||||
    def __init__(self, provided, required):
 | 
			
		||||
        super(UnsatisfiableArchitectureSpecError, self).__init__(
 | 
			
		||||
            provided, required, "architecture")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsatisfiableProviderSpecError(UnsatisfiableSpecError):
 | 
			
		||||
    """Raised when a provider is supplied but constraints don't match
 | 
			
		||||
       a vpkg requirement"""
 | 
			
		||||
    def __init__(self, provided, required):
 | 
			
		||||
        super(UnsatisfiableProviderSpecError, self).__init__(
 | 
			
		||||
            provided, required, "provider")
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@ def check_spec(self, abstract, concrete):
 | 
			
		||||
            self.assertEqual(abstract.architecture, concrete.architecture)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def check_concretize(self, abstract_spec, *presets):
 | 
			
		||||
    def check_concretize(self, abstract_spec):
 | 
			
		||||
        abstract = Spec(abstract_spec)
 | 
			
		||||
        concrete = abstract.concretized(*presets)
 | 
			
		||||
        concrete = abstract.concretized()
 | 
			
		||||
 | 
			
		||||
        self.assertFalse(abstract.concrete)
 | 
			
		||||
        self.assertTrue(concrete.concrete)
 | 
			
		||||
@@ -29,32 +29,14 @@ def check_concretize(self, abstract_spec, *presets):
 | 
			
		||||
        return concrete
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def check_presets(self, abstract, *presets):
 | 
			
		||||
        abstract = Spec(abstract)
 | 
			
		||||
        concrete = self.check_concretize(abstract, *presets)
 | 
			
		||||
 | 
			
		||||
        flat_deps = concrete.flat_dependencies()
 | 
			
		||||
        for preset in presets:
 | 
			
		||||
            preset_spec = Spec(preset)
 | 
			
		||||
            name = preset_spec.name
 | 
			
		||||
 | 
			
		||||
            self.assertTrue(name in flat_deps)
 | 
			
		||||
            self.check_spec(preset_spec, flat_deps[name])
 | 
			
		||||
 | 
			
		||||
        return concrete
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_concretize_no_deps(self):
 | 
			
		||||
        self.check_concretize('libelf')
 | 
			
		||||
        self.check_concretize('libelf@0.8.13')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_concretize_dag(self):
 | 
			
		||||
        self.check_concretize('mpileaks')
 | 
			
		||||
        spec = Spec('mpileaks')
 | 
			
		||||
        spec.normalize()
 | 
			
		||||
 | 
			
		||||
        self.check_concretize('callpath')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_concretize_with_presets(self):
 | 
			
		||||
        self.check_presets('mpileaks', 'callpath@0.8')
 | 
			
		||||
        self.check_presets('mpileaks', 'callpath@0.9', 'dyninst@8.0+debug')
 | 
			
		||||
        self.check_concretize('callpath', 'libelf@0.8.13+debug~foo', 'mpich@1.0')
 | 
			
		||||
        self.check_concretize('mpileaks')
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ class Callpath(Package):
 | 
			
		||||
                 1.0 : 'bf03b33375afa66fe0efa46ce3f4b17a' }
 | 
			
		||||
 | 
			
		||||
    depends_on("dyninst")
 | 
			
		||||
    depends_on("mpich")
 | 
			
		||||
    depends_on("mpi")
 | 
			
		||||
 | 
			
		||||
    def install(self, prefix):
 | 
			
		||||
        configure("--prefix=%s" % prefix)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ class Mpileaks(Package):
 | 
			
		||||
                 2.2 : None,
 | 
			
		||||
                 2.3 : None }
 | 
			
		||||
 | 
			
		||||
    depends_on("mpich")
 | 
			
		||||
    depends_on("mpi")
 | 
			
		||||
    depends_on("callpath")
 | 
			
		||||
 | 
			
		||||
    def install(self, prefix):
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,44 @@ def test_conflicting_package_constraints(self):
 | 
			
		||||
                          spec.package.validate_dependencies)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_preorder_traversal(self):
 | 
			
		||||
        dag = Spec('mpileaks',
 | 
			
		||||
                   Spec('callpath',
 | 
			
		||||
                        Spec('dyninst',
 | 
			
		||||
                             Spec('libdwarf',
 | 
			
		||||
                                  Spec('libelf')),
 | 
			
		||||
                             Spec('libelf')),
 | 
			
		||||
                        Spec('mpich')),
 | 
			
		||||
                   Spec('mpich'))
 | 
			
		||||
        dag.normalize()
 | 
			
		||||
 | 
			
		||||
        unique_names = [
 | 
			
		||||
            'mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf', 'mpich']
 | 
			
		||||
        unique_depths = [0,1,2,3,4,2]
 | 
			
		||||
 | 
			
		||||
        non_unique_names = [
 | 
			
		||||
            'mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf', 'libelf',
 | 
			
		||||
            'mpich', 'mpich']
 | 
			
		||||
        non_unique_depths = [0,1,2,3,4,3,2,1]
 | 
			
		||||
 | 
			
		||||
        self.assertListEqual(
 | 
			
		||||
            [x.name for x in dag.preorder_traversal()],
 | 
			
		||||
            unique_names)
 | 
			
		||||
 | 
			
		||||
        self.assertListEqual(
 | 
			
		||||
            [(x, y.name) for x,y in dag.preorder_traversal(depth=True)],
 | 
			
		||||
            zip(unique_depths, unique_names))
 | 
			
		||||
 | 
			
		||||
        self.assertListEqual(
 | 
			
		||||
            [x.name for x in dag.preorder_traversal(unique=False)],
 | 
			
		||||
            non_unique_names)
 | 
			
		||||
 | 
			
		||||
        self.assertListEqual(
 | 
			
		||||
            [(x, y.name) for x,y in dag.preorder_traversal(unique=False, depth=True)],
 | 
			
		||||
            zip(non_unique_depths, non_unique_names))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_conflicting_spec_constraints(self):
 | 
			
		||||
        mpileaks = Spec('mpileaks ^mpich ^callpath ^dyninst ^libelf ^libdwarf')
 | 
			
		||||
        try:
 | 
			
		||||
@@ -63,6 +101,56 @@ def test_normalize_a_lot(self):
 | 
			
		||||
        spec.normalize()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_normalize_with_virtual_spec(self):
 | 
			
		||||
        dag = Spec('mpileaks',
 | 
			
		||||
                   Spec('callpath',
 | 
			
		||||
                        Spec('dyninst',
 | 
			
		||||
                             Spec('libdwarf',
 | 
			
		||||
                                  Spec('libelf')),
 | 
			
		||||
                             Spec('libelf')),
 | 
			
		||||
                        Spec('mpi')),
 | 
			
		||||
                   Spec('mpi'))
 | 
			
		||||
        dag.normalize()
 | 
			
		||||
 | 
			
		||||
        # make sure nothing with the same name occurs twice
 | 
			
		||||
        counts = {}
 | 
			
		||||
        for spec in dag.preorder_traversal(keyfun=id):
 | 
			
		||||
            if not spec.name in counts:
 | 
			
		||||
                counts[spec.name] = 0
 | 
			
		||||
            counts[spec.name] += 1
 | 
			
		||||
 | 
			
		||||
        for name in counts:
 | 
			
		||||
            self.assertEqual(counts[name], 1, "Count for %s was not 1!" % name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def check_links(self, spec_to_check):
 | 
			
		||||
        for spec in spec_to_check.preorder_traversal():
 | 
			
		||||
            for dependent in spec.dependents.values():
 | 
			
		||||
                self.assertIn(
 | 
			
		||||
                    spec.name, dependent.dependencies,
 | 
			
		||||
                    "%s not in dependencies of %s" % (spec.name, dependent.name))
 | 
			
		||||
 | 
			
		||||
            for dependency in spec.dependencies.values():
 | 
			
		||||
                self.assertIn(
 | 
			
		||||
                    spec.name, dependency.dependents,
 | 
			
		||||
                    "%s not in dependents of %s" % (spec.name, dependency.name))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_dependents_and_dependencies_are_correct(self):
 | 
			
		||||
        spec = Spec('mpileaks',
 | 
			
		||||
                   Spec('callpath',
 | 
			
		||||
                        Spec('dyninst',
 | 
			
		||||
                             Spec('libdwarf',
 | 
			
		||||
                                  Spec('libelf')),
 | 
			
		||||
                             Spec('libelf')),
 | 
			
		||||
                        Spec('mpi')),
 | 
			
		||||
                   Spec('mpi'))
 | 
			
		||||
 | 
			
		||||
        self.check_links(spec)
 | 
			
		||||
        spec.normalize()
 | 
			
		||||
        self.check_links(spec)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_unsatisfiable_version(self):
 | 
			
		||||
        set_pkg_dep('mpileaks', 'mpich@1.0')
 | 
			
		||||
        spec = Spec('mpileaks ^mpich@2.0 ^callpath ^dyninst ^libelf ^libdwarf')
 | 
			
		||||
@@ -134,29 +222,51 @@ def test_normalize_mpileaks(self):
 | 
			
		||||
        expected_normalized = Spec(
 | 
			
		||||
            'mpileaks',
 | 
			
		||||
            Spec('callpath',
 | 
			
		||||
                 Spec('dyninst', Spec('libdwarf', libelf),
 | 
			
		||||
                 Spec('dyninst',
 | 
			
		||||
                      Spec('libdwarf',
 | 
			
		||||
                           libelf),
 | 
			
		||||
                      libelf),
 | 
			
		||||
                 mpich), mpich)
 | 
			
		||||
                 mpich),
 | 
			
		||||
            mpich)
 | 
			
		||||
 | 
			
		||||
        expected_non_dag = Spec(
 | 
			
		||||
        expected_non_unique_nodes = Spec(
 | 
			
		||||
            'mpileaks',
 | 
			
		||||
            Spec('callpath',
 | 
			
		||||
                 Spec('dyninst', Spec('libdwarf', Spec('libelf@1.8.11')),
 | 
			
		||||
                 Spec('dyninst',
 | 
			
		||||
                      Spec('libdwarf',
 | 
			
		||||
                           Spec('libelf@1.8.11')),
 | 
			
		||||
                      Spec('libelf@1.8.11')),
 | 
			
		||||
                 mpich), Spec('mpich'))
 | 
			
		||||
                 mpich),
 | 
			
		||||
            Spec('mpich'))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expected_normalized, expected_non_dag)
 | 
			
		||||
        self.assertEqual(expected_normalized, expected_non_unique_nodes)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(str(expected_normalized), str(expected_non_dag))
 | 
			
		||||
        self.assertEqual(str(spec), str(expected_non_dag))
 | 
			
		||||
        self.assertEqual(str(expected_normalized), str(expected_non_unique_nodes))
 | 
			
		||||
        self.assertEqual(str(spec), str(expected_non_unique_nodes))
 | 
			
		||||
        self.assertEqual(str(expected_normalized), str(spec))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(spec, expected_flat)
 | 
			
		||||
        self.assertNotEqual(spec, expected_normalized)
 | 
			
		||||
        self.assertNotEqual(spec, expected_non_dag)
 | 
			
		||||
        self.assertNotEqual(spec, expected_non_unique_nodes)
 | 
			
		||||
 | 
			
		||||
        spec.normalize()
 | 
			
		||||
 | 
			
		||||
        self.assertNotEqual(spec, expected_flat)
 | 
			
		||||
        self.assertEqual(spec, expected_normalized)
 | 
			
		||||
        self.assertEqual(spec, expected_non_dag)
 | 
			
		||||
        self.assertEqual(spec, expected_non_unique_nodes)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_normalize_with_virtual_package(self):
 | 
			
		||||
        spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
 | 
			
		||||
        spec.normalize()
 | 
			
		||||
 | 
			
		||||
        expected_normalized = Spec(
 | 
			
		||||
            'mpileaks',
 | 
			
		||||
            Spec('callpath',
 | 
			
		||||
                 Spec('dyninst',
 | 
			
		||||
                      Spec('libdwarf',
 | 
			
		||||
                           Spec('libelf@1.8.11')),
 | 
			
		||||
                      Spec('libelf@1.8.11')),
 | 
			
		||||
                 Spec('mpi')), Spec('mpi'))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(str(spec), str(expected_normalized))
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user