diff options
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r-- | lib/spack/spack/spec.py | 87 |
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): |