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.py87
1 files changed, 72 insertions, 15 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 5f34a33ac1..f2625ae596 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -113,6 +113,9 @@ from spack.util.string import *
from spack.util.prefix import Prefix
from spack.virtual import ProviderIndex
+# Valid pattern for an identifier in Spack
+identifier_re = r'\w[\w-]*'
+
# Convenient names for color formats so that other things can use them
compiler_color = '@g'
version_color = '@c'
@@ -284,7 +287,7 @@ class CompilerSpec(object):
@key_ordering
-class Variant(object):
+class VariantSpec(object):
"""Variants are named, build-time options for a package. Names depend
on the particular package being built, and each named variant can
be enabled or disabled.
@@ -299,7 +302,7 @@ class Variant(object):
def copy(self):
- return Variant(self.name, self.enabled)
+ return VariantSpec(self.name, self.enabled)
def __str__(self):
@@ -308,9 +311,44 @@ class Variant(object):
class VariantMap(HashableMap):
+ def __init__(self, spec):
+ super(VariantMap, self).__init__()
+ self.spec = spec
+
+
def satisfies(self, other):
- return all(self[key].enabled == other[key].enabled
- for key in other if key in self)
+ if self.spec._concrete:
+ return all(k in self and self[k].enabled == other[k].enabled
+ for k in other)
+ else:
+ return all(self[k].enabled == other[k].enabled
+ for k in other if k in self)
+
+
+ def constrain(self, other):
+ if other.spec._concrete:
+ for k in self:
+ if k not in other:
+ raise UnsatisfiableVariantSpecError(self[k], '<absent>')
+
+ for k in other:
+ if k in self:
+ if self[k].enabled != other[k].enabled:
+ raise UnsatisfiableVariantSpecError(self[k], other[k])
+ else:
+ self[k] = other[k].copy()
+
+ @property
+ def concrete(self):
+ return self.spec._concrete or all(
+ v in self for v in self.spec.package.variants)
+
+
+ def copy(self):
+ clone = VariantMap(None)
+ for name, variant in self.items():
+ clone[name] = variant.copy()
+ return clone
def __str__(self):
@@ -357,10 +395,11 @@ class Spec(object):
self.name = other.name
self.dependents = other.dependents
self.versions = other.versions
- self.variants = other.variants
self.architecture = other.architecture
self.compiler = other.compiler
self.dependencies = other.dependencies
+ self.variants = other.variants
+ self.variants.spec = self
# Specs are by default not assumed to be normal, but in some
# cases we've read them from a file want to assume normal.
@@ -389,7 +428,7 @@ class Spec(object):
"""Called by the parser to add a variant."""
if name in self.variants: raise DuplicateVariantError(
"Cannot specify variant '%s' twice" % name)
- self.variants[name] = Variant(name, enabled)
+ self.variants[name] = VariantSpec(name, enabled)
def _set_compiler(self, compiler):
@@ -453,14 +492,15 @@ class Spec(object):
@property
def concrete(self):
"""A spec is concrete if it can describe only ONE build of a package.
- If any of the name, version, architecture, compiler, or depdenencies
- are ambiguous,then it is not concrete.
+ If any of the name, version, architecture, compiler,
+ variants, or depdenencies are ambiguous,then it is not concrete.
"""
if self._concrete:
return True
self._concrete = bool(not self.virtual
and self.versions.concrete
+ and self.variants.concrete
and self.architecture
and self.compiler and self.compiler.concrete
and self.dependencies.concrete)
@@ -630,7 +670,7 @@ class Spec(object):
spec.compiler = CompilerSpec.from_dict(node)
for name, enabled in node['variants'].items():
- spec.variants[name] = Variant(name, enabled)
+ spec.variants[name] = VariantSpec(name, enabled)
return spec
@@ -694,6 +734,7 @@ class Spec(object):
spack.concretizer.concretize_architecture(self)
spack.concretizer.concretize_compiler(self)
spack.concretizer.concretize_version(self)
+ spack.concretizer.concretize_variants(self)
presets[self.name] = self
visited.add(self.name)
@@ -876,8 +917,7 @@ class Spec(object):
else:
required = index.providers_for(vspec.name)
if required:
- raise UnsatisfiableProviderSpecError(
- required[0], pkg_dep)
+ raise UnsatisfiableProviderSpecError(required[0], pkg_dep)
provider_index.update(pkg_dep)
if name not in spec_deps:
@@ -983,6 +1023,11 @@ class Spec(object):
if not compilers.supported(spec.compiler):
raise UnsupportedCompilerError(spec.compiler.name)
+ # Ensure that variants all exist.
+ for vname, variant in spec.variants.items():
+ if vname not in spec.package.variants:
+ raise UnknownVariantError(spec.name, vname)
+
def constrain(self, other, **kwargs):
other = self._autospec(other)
@@ -1011,7 +1056,7 @@ class Spec(object):
self.compiler = other.compiler
self.versions.intersect(other.versions)
- self.variants.update(other.variants)
+ self.variants.constrain(other.variants)
self.architecture = self.architecture or other.architecture
if constrain_deps:
@@ -1080,11 +1125,13 @@ class Spec(object):
# 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
+ if not self.variants.satisfies(other.variants):
+ return False
+
# Architecture satisfaction is currently just string equality.
# Can be None for unconstrained, though.
if (self.architecture and other.architecture and
@@ -1151,11 +1198,12 @@ class Spec(object):
# Local node attributes get copied first.
self.name = other.name
self.versions = other.versions.copy()
- self.variants = other.variants.copy()
self.architecture = other.architecture
self.compiler = other.compiler.copy() if other.compiler else None
self.dependents = DependencyMap()
self.dependencies = DependencyMap()
+ self.variants = other.variants.copy()
+ self.variants.spec = self
# If we copy dependencies, preserve DAG structure in the new spec
if kwargs.get('deps', True):
@@ -1446,6 +1494,8 @@ class SpecLexer(spack.parse.Lexer):
(r'\~', lambda scanner, val: self.token(OFF, val)),
(r'\%', lambda scanner, val: self.token(PCT, val)),
(r'\=', lambda scanner, val: self.token(EQ, val)),
+ # This is more liberal than identifier_re (see above).
+ # Checked by check_identifier() for better error messages.
(r'\w[\w.-]*', lambda scanner, val: self.token(ID, val)),
(r'\s+', lambda scanner, val: None)])
@@ -1491,7 +1541,7 @@ class SpecParser(spack.parse.Parser):
spec = Spec.__new__(Spec)
spec.name = self.token.value
spec.versions = VersionList()
- spec.variants = VariantMap()
+ spec.variants = VariantMap(spec)
spec.architecture = None
spec.compiler = None
spec.dependents = DependencyMap()
@@ -1672,6 +1722,13 @@ class UnsupportedCompilerError(SpecError):
"The '%s' compiler is not yet supported." % compiler_name)
+class UnknownVariantError(SpecError):
+ """Raised when the same variant occurs in a spec twice."""
+ def __init__(self, pkg, variant):
+ super(UnknownVariantError, self).__init__(
+ "Package %s has no variant %s!" % (pkg, variant))
+
+
class DuplicateArchitectureError(SpecError):
"""Raised when the same architecture occurs in a spec twice."""
def __init__(self, message):