diff options
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r-- | lib/spack/spack/spec.py | 233 |
1 files changed, 92 insertions, 141 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 0d8fb2893b..0cf392a7ce 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -121,12 +121,14 @@ from llnl.util.filesystem import find_headers, find_libraries, is_exe from llnl.util.lang import * from llnl.util.tty.color import * from spack.build_environment import get_path_from_module, load_module +from spack.error import SpecError, UnsatisfiableSpecError from spack.provider_index import ProviderIndex from spack.util.crypto import prefix_bits from spack.util.executable import Executable from spack.util.prefix import Prefix from spack.util.spack_yaml import syaml_dict from spack.util.string import * +from spack.variant import * from spack.version import * from yaml.error import MarkedYAMLError @@ -606,81 +608,6 @@ class DependencySpec(object): self.spec.name if self.spec else None) -@key_ordering -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. - """ - - def __init__(self, name, value): - self.name = name - self.value = value - - def _cmp_key(self): - return (self.name, self.value) - - def copy(self): - return VariantSpec(self.name, self.value) - - def __str__(self): - if type(self.value) == bool: - return '{0}{1}'.format('+' if self.value else '~', self.name) - else: - return ' {0}={1} '.format(self.name, self.value) - - -class VariantMap(HashableMap): - - def __init__(self, spec): - super(VariantMap, self).__init__() - self.spec = spec - - def satisfies(self, other, strict=False): - if strict or self.spec._concrete: - return all(k in self and self[k].value == other[k].value - for k in other) - else: - return all(self[k].value == other[k].value - for k in other if k in self) - - def constrain(self, other): - """Add all variants in other that aren't in self to self. - - Raises an error if any common variants don't match. - Return whether the spec changed. - """ - if other.spec._concrete: - for k in self: - if k not in other: - raise UnsatisfiableVariantSpecError(self[k], '<absent>') - - changed = False - for k in other: - if k in self: - if self[k].value != other[k].value: - raise UnsatisfiableVariantSpecError(self[k], other[k]) - else: - self[k] = other[k].copy() - changed = True - return changed - - @property - def concrete(self): - return self.spec._concrete or all( - v in self for v in self.spec.package_class.variants) - - def copy(self): - clone = VariantMap(None) - for name, variant in self.items(): - clone[name] = variant.copy() - return clone - - def __str__(self): - sorted_keys = sorted(self.keys()) - return ''.join(str(self[key]) for key in sorted_keys) - - _valid_compiler_flags = [ 'cflags', 'cxxflags', 'fflags', 'ldflags', 'ldlibs', 'cppflags'] @@ -1094,17 +1021,6 @@ class Spec(object): """Called by the parser to add an allowable version.""" self.versions.add(version) - def _add_variant(self, name, value): - """Called by the parser to add a variant.""" - if name in self.variants: - raise DuplicateVariantError( - "Cannot specify variant '%s' twice" % name) - if isinstance(value, string_types) and value.upper() == 'TRUE': - value = True - elif isinstance(value, string_types) and value.upper() == 'FALSE': - value = False - self.variants[name] = VariantSpec(name, value) - def _add_flag(self, name, value): """Called by the parser to add a known flag. Known flags currently include "arch" @@ -1124,7 +1040,13 @@ class Spec(object): assert(self.compiler_flags is not None) self.compiler_flags[name] = value.split() else: - self._add_variant(name, value) + # All other flags represent variants. 'foo=true' and 'foo=false' + # map to '+foo' and '~foo' respectively. As such they need a + # BoolValuedVariant instance. + if str(value).upper() == 'TRUE' or str(value).upper() == 'FALSE': + self.variants[name] = BoolValuedVariant(name, value) + else: + self.variants[name] = MultiValuedVariant(name, value) def _set_architecture(self, **kwargs): """Called by the parser to set the architecture.""" @@ -1424,8 +1346,11 @@ class Spec(object): if self.namespace: d['namespace'] = self.namespace - params = syaml_dict(sorted( - (name, v.value) for name, v in self.variants.items())) + params = syaml_dict( + sorted( + v.yaml_entry() for _, v in self.variants.items() + ) + ) params.update(sorted(self.compiler_flags.items())) if params: d['parameters'] = params @@ -1491,11 +1416,14 @@ class Spec(object): if name in _valid_compiler_flags: spec.compiler_flags[name] = value else: - spec.variants[name] = VariantSpec(name, value) - + spec.variants[name] = MultiValuedVariant.from_node_dict( + name, value + ) elif 'variants' in node: for name, value in node['variants'].items(): - spec.variants[name] = VariantSpec(name, value) + spec.variants[name] = MultiValuedVariant.from_node_dict( + name, value + ) for name in FlagMap.valid_compiler_flags(): spec.compiler_flags[name] = [] @@ -2076,7 +2004,7 @@ class Spec(object): self._mark_concrete(False) # Ensure first that all packages & compilers in the DAG exist. - self.validate_names() + self.validate_or_raise() # Get all the dependencies into one DependencyMap spec_deps = self.flat_dependencies(copy=False, deptype_query=alldeps) @@ -2110,11 +2038,13 @@ class Spec(object): clone.normalize() return clone - 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 - UnsupportedCompilerError. + def validate_or_raise(self): + """Checks that names and values in this spec are real. If they're not, + it will raise an appropriate exception. """ + # FIXME: this function should be lazy, and collect all the errors + # FIXME: before raising the exceptions, instead of being greedy and + # FIXME: raise just the first one encountered for spec in self.traverse(): # raise an UnknownPackageError if the spec's package isn't real. if (not spec.virtual) and spec.name: @@ -2125,16 +2055,44 @@ 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_class.variants: - raise UnknownVariantError(spec.name, vname) + # FIXME: Move the logic below into the variant.py module + # Ensure correctness of variants (if the spec is not virtual) + if not spec.virtual: + pkg_cls = spec.package_class + pkg_variants = pkg_cls.variants + not_existing = set(spec.variants) - set(pkg_variants) + if not_existing: + raise UnknownVariantError(spec.name, not_existing) + + for name, v in [(x, y) for (x, y) in spec.variants.items()]: + # When parsing a spec every variant of the form + # 'foo=value' will be interpreted by default as a + # multi-valued variant. During validation of the + # variants we use the information in the package + # to turn any variant that needs it to a single-valued + # variant. + pkg_variant = pkg_variants[name] + pkg_variant.validate_or_raise(v, pkg_cls) + spec.variants.substitute( + pkg_variant.make_variant(v._original_value) + ) def constrain(self, other, deps=True): """Merge the constraints of other with self. Returns True if the spec changed as a result, False if not. """ + # If we are trying to constrain a concrete spec, either the spec + # already satisfies the constraint (and the method returns False) + # or it raises an exception + if self.concrete: + if self.satisfies(other): + return False + else: + raise UnsatisfiableSpecError( + self, other, 'constrain a concrete spec' + ) + other = self._autospec(other) if not (self.name == other.name or @@ -2150,11 +2108,11 @@ class Spec(object): if not self.versions.overlaps(other.versions): raise UnsatisfiableVersionSpecError(self.versions, other.versions) - for v in other.variants: - if (v in self.variants and - self.variants[v].value != other.variants[v].value): - raise UnsatisfiableVariantSpecError(self.variants[v], - other.variants[v]) + for v in [x for x in other.variants if x in self.variants]: + if not self.variants[v].compatible(other.variants[v]): + raise UnsatisfiableVariantSpecError( + self.variants[v], other.variants[v] + ) # TODO: Check out the logic here sarch, oarch = self.architecture, other.architecture @@ -2328,6 +2286,30 @@ class Spec(object): elif strict and (other.compiler and not self.compiler): return False + # If self is a concrete spec, and other is not virtual, then we need + # to substitute every multi-valued variant that needs it with a + # single-valued variant. + if self.concrete: + for name, v in [(x, y) for (x, y) in other.variants.items()]: + # When parsing a spec every variant of the form + # 'foo=value' will be interpreted by default as a + # multi-valued variant. During validation of the + # variants we use the information in the package + # to turn any variant that needs it to a single-valued + # variant. + pkg_cls = type(other.package) + try: + pkg_variant = other.package.variants[name] + pkg_variant.validate_or_raise(v, pkg_cls) + except (SpecError, KeyError): + # Catch the two things that could go wrong above: + # 1. name is not a valid variant (KeyError) + # 2. the variant is not validated (SpecError) + return False + other.variants.substitute( + pkg_variant.make_variant(v._original_value) + ) + var_strict = strict if (not self.name) or (not other.name): var_strict = True @@ -3118,10 +3100,12 @@ class SpecParser(spack.parse.Parser): added_version = True elif self.accept(ON): - spec._add_variant(self.variant(), True) + name = self.variant() + spec.variants[name] = BoolValuedVariant(name, True) elif self.accept(OFF): - spec._add_variant(self.variant(), False) + name = self.variant() + spec.variants[name] = BoolValuedVariant(name, False) elif self.accept(PCT): spec._set_compiler(self.compiler()) @@ -3275,10 +3259,6 @@ def base32_prefix_bits(hash_string, bits): return prefix_bits(hash_bytes, bits) -class SpecError(spack.error.SpackError): - """Superclass for all errors that occur while constructing specs.""" - - class SpecParseError(SpecError): """Wrapper for ParseError for when we're parsing specs.""" def __init__(self, parse_error): @@ -3291,10 +3271,6 @@ class DuplicateDependencyError(SpecError): """Raised when the same dependency occurs in a spec twice.""" -class DuplicateVariantError(SpecError): - """Raised when the same variant occurs in a spec twice.""" - - class DuplicateCompilerSpecError(SpecError): """Raised when the same compiler occurs in a spec twice.""" @@ -3306,13 +3282,6 @@ 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.""" @@ -3354,17 +3323,6 @@ class MultipleProviderError(SpecError): self.providers = providers -class UnsatisfiableSpecError(SpecError): - """Raised when a spec conflicts with package constraints. - Provide the requirement that was violated when raising.""" - def __init__(self, provided, required, constraint_type): - super(UnsatisfiableSpecError, self).__init__( - "%s does not satisfy %s" % (provided, required)) - self.provided = provided - self.required = required - self.constraint_type = constraint_type - - class UnsatisfiableSpecNameError(UnsatisfiableSpecError): """Raised when two specs aren't even for the same package.""" def __init__(self, provided, required): @@ -3386,13 +3344,6 @@ class UnsatisfiableCompilerSpecError(UnsatisfiableSpecError): provided, required, "compiler") -class UnsatisfiableVariantSpecError(UnsatisfiableSpecError): - """Raised when a spec variant conflicts with package constraints.""" - def __init__(self, provided, required): - super(UnsatisfiableVariantSpecError, self).__init__( - provided, required, "variant") - - class UnsatisfiableCompilerFlagSpecError(UnsatisfiableSpecError): """Raised when a spec variant conflicts with package constraints.""" def __init__(self, provided, required): |