summaryrefslogtreecommitdiff
path: root/lib/spack/spack/spec.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r--lib/spack/spack/spec.py167
1 files changed, 114 insertions, 53 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 6416ff9487..1e2da10dcc 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -102,8 +102,7 @@ from llnl.util.tty.color import *
import spack
import spack.parse
import spack.error
-import spack.compilers
-import spack.compilers.gcc
+import spack.compilers as compilers
from spack.version import *
from spack.util.string import *
@@ -169,36 +168,71 @@ def colorize_spec(spec):
@key_ordering
-class Compiler(object):
- """The Compiler field represents the compiler or range of compiler
- versions that a package should be built with. Compilers have a
+class CompilerSpec(object):
+ """The CompilerSpec field represents the compiler or range of compiler
+ versions that a package should be built with. CompilerSpecs have a
name and a version list. """
- def __init__(self, name, version=None):
- self.name = name
- self.versions = VersionList()
- if version:
- self.versions.add(version)
+ def __init__(self, *args):
+ nargs = len(args)
+ if nargs == 1:
+ arg = args[0]
+ # If there is one argument, it's either another CompilerSpec
+ # to copy or a string to parse
+ if isinstance(arg, basestring):
+ c = SpecParser().parse_compiler(arg)
+ self.name = c.name
+ self.versions = c.versions
+
+ elif isinstance(arg, CompilerSpec):
+ self.name = arg.name
+ self.versions = arg.versions.copy()
+
+ else:
+ raise TypeError(
+ "Can only build CompilerSpec from string or CompilerSpec." +
+ " Found %s" % type(arg))
+
+ elif nargs == 2:
+ name, version = args
+ self.name = name
+ self.versions = VersionList()
+ self.versions.add(ver(version))
+
+ else:
+ raise TypeError(
+ "__init__ takes 1 or 2 arguments. (%d given)" % nargs)
def _add_version(self, version):
self.versions.add(version)
+ def _autospec(self, compiler_spec_like):
+ if isinstance(compiler_spec_like, CompilerSpec):
+ return compiler_spec_like
+ return CompilerSpec(compiler_spec_like)
+
+
def satisfies(self, other):
+ other = self._autospec(other)
return (self.name == other.name and
- self.versions.overlaps(other.versions))
+ self.versions.satisfies(other.versions))
def constrain(self, other):
- if not self.satisfies(other):
- raise UnsatisfiableCompilerSpecError(self, other)
+ other = self._autospec(other)
+
+ # ensure that other will actually constrain this spec.
+ if not other.satisfies(self):
+ raise UnsatisfiableCompilerSpecError(other, self)
self.versions.intersect(other.versions)
@property
def concrete(self):
- """A Compiler spec is concrete if its versions are concrete."""
+ """A CompilerSpec is concrete if its versions are concrete and there
+ is an available compiler with the right version."""
return self.versions.concrete
@@ -210,7 +244,8 @@ class Compiler(object):
def copy(self):
- clone = Compiler(self.name)
+ clone = CompilerSpec.__new__(CompilerSpec)
+ clone.name = self.name
clone.versions = self.versions.copy()
return clone
@@ -226,6 +261,9 @@ class Compiler(object):
out += "@%s" % vlist
return out
+ def __repr__(self):
+ return str(self)
+
@key_ordering
class Variant(object):
@@ -332,7 +370,7 @@ class Spec(object):
def _set_compiler(self, compiler):
"""Called by the parser to set the compiler."""
- if self.compiler: raise DuplicateCompilerError(
+ if self.compiler: raise DuplicateCompilerSpecError(
"Spec for '%s' cannot have two compilers." % self.name)
self.compiler = compiler
@@ -361,14 +399,14 @@ class Spec(object):
"""
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
+
+ # 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
@@ -428,17 +466,28 @@ class Spec(object):
root [=True]
If false, this won't yield the root node, just its descendents.
+
+ direction [=children|parents]
+ If 'children', does a traversal of this spec's children. If
+ 'parents', traverses upwards in the DAG towards the root.
+
"""
depth = kwargs.get('depth', False)
key_fun = kwargs.get('key', id)
yield_root = kwargs.get('root', True)
cover = kwargs.get('cover', 'nodes')
+ direction = kwargs.get('direction', 'children')
cover_values = ('nodes', 'edges', 'paths')
if cover not in cover_values:
raise ValueError("Invalid value for cover: %s. Choices are %s"
% (cover, ",".join(cover_values)))
+ direction_values = ('children', 'parents')
+ if direction not in direction_values:
+ raise ValueError("Invalid value for direction: %s. Choices are %s"
+ % (direction, ",".join(direction_values)))
+
if visited is None:
visited = set()
@@ -452,9 +501,13 @@ class Spec(object):
else:
if yield_root or d > 0: yield result
+ successors = self.dependencies
+ if direction == 'parents':
+ successors = self.dependents
+
visited.add(key)
- for name in sorted(self.dependencies):
- child = self.dependencies[name]
+ for name in sorted(successors):
+ child = successors[name]
for elt in child.preorder_traversal(visited, d+1, **kwargs):
yield elt
@@ -763,22 +816,22 @@ class Spec(object):
def validate_names(self):
"""This checks that names of packages and compilers in this spec are real.
If they're not, it will raise either UnknownPackageError or
- UnknownCompilerError.
+ UnsupportedCompilerError.
"""
for spec in self.preorder_traversal():
# Don't get a package for a virtual name.
if not spec.virtual:
spack.db.get(spec.name)
- # validate compiler name in addition to the package name.
+ # validate compiler in addition to the package name.
if spec.compiler:
- compiler_name = spec.compiler.name
- if not spack.compilers.supported(compiler_name):
- raise UnknownCompilerError(compiler_name)
+ if not compilers.supported(spec.compiler):
+ raise UnsupportedCompilerError(spec.compiler.name)
def constrain(self, other, **kwargs):
other = self._autospec(other)
+ constrain_deps = kwargs.get('deps', True)
if not self.name == other.name:
raise UnsatisfiableSpecNameError(self.name, other.name)
@@ -806,7 +859,7 @@ class Spec(object):
self.variants.update(other.variants)
self.architecture = self.architecture or other.architecture
- if kwargs.get('deps', True):
+ if constrain_deps:
self._constrain_dependencies(other)
@@ -818,8 +871,8 @@ class Spec(object):
# TODO: might want more detail than this, e.g. specific deps
# in violation. if this becomes a priority get rid of this
# check and be more specici about what's wrong.
- if not self.satisfies_dependencies(other):
- raise UnsatisfiableDependencySpecError(self, other)
+ if not other.satisfies_dependencies(self):
+ raise UnsatisfiableDependencySpecError(other, self)
# Handle common first-order constraints directly
for name in self.common_dependencies(other):
@@ -863,28 +916,28 @@ class Spec(object):
def satisfies(self, other, **kwargs):
other = self._autospec(other)
+ satisfy_deps = kwargs.get('deps', True)
# First thing we care about is whether the name matches
if self.name != other.name:
return False
- # This function simplifies null checking below
- def check(attribute, op):
- s = getattr(self, attribute)
- o = getattr(other, attribute)
- return not s or not o or op(s,o)
-
- # All these attrs have satisfies criteria of their own
- for attr in ('versions', 'variants', 'compiler'):
- if not check(attr, lambda s, o: s.satisfies(o)):
+ # All these attrs have satisfies criteria of their own,
+ # but can be None to indicate no constraints.
+ for s, o in ((self.versions, other.versions),
+ (self.variants, other.variants),
+ (self.compiler, other.compiler)):
+ if s and o and not s.satisfies(o):
return False
- # Architecture is just a string
- # TODO: inviestigate making an Architecture class for symmetry
- if not check('architecture', lambda s,o: s == o):
+ # Architecture satisfaction is currently just string equality.
+ # Can be None for unconstrained, though.
+ if (self.architecture and other.architecture and
+ self.architecture != other.architecture):
return False
- if kwargs.get('deps', True):
+ # If we need to descend into dependencies, do it, otherwise we're done.
+ if satisfy_deps:
return self.satisfies_dependencies(other)
else:
return True
@@ -1188,6 +1241,11 @@ class SpecParser(spack.parse.Parser):
return specs
+ def parse_compiler(self, text):
+ self.setup(text)
+ return self.compiler()
+
+
def spec(self):
"""Parse a spec out of the input. If a spec is supplied, then initialize
and return it instead of creating a new one."""
@@ -1279,7 +1337,10 @@ class SpecParser(spack.parse.Parser):
def compiler(self):
self.expect(ID)
self.check_identifier()
- compiler = Compiler(self.token.value)
+
+ compiler = CompilerSpec.__new__(CompilerSpec)
+ compiler.name = self.token.value
+ compiler.versions = VersionList()
if self.accept(AT):
vlist = self.version_list()
for version in vlist:
@@ -1359,17 +1420,17 @@ class DuplicateVariantError(SpecError):
super(DuplicateVariantError, self).__init__(message)
-class DuplicateCompilerError(SpecError):
+class DuplicateCompilerSpecError(SpecError):
"""Raised when the same compiler occurs in a spec twice."""
def __init__(self, message):
- super(DuplicateCompilerError, self).__init__(message)
+ super(DuplicateCompilerSpecError, self).__init__(message)
-class UnknownCompilerError(SpecError):
+class UnsupportedCompilerError(SpecError):
"""Raised when the user asks for a compiler spack doesn't know about."""
def __init__(self, compiler_name):
- super(UnknownCompilerError, self).__init__(
- "Unknown compiler: %s" % compiler_name)
+ super(UnsupportedCompilerError, self).__init__(
+ "The '%s' compiler is not yet supported." % compiler_name)
class DuplicateArchitectureError(SpecError):