diff options
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r-- | lib/spack/spack/spec.py | 217 |
1 files changed, 173 insertions, 44 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index bf0acf58d8..194726baa9 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -34,6 +34,8 @@ line is a spec for a particular installation of the mpileaks package. built in debug mode for your package to work, you can require it by adding +debug to the openmpi spec when you depend on it. If you do NOT want the debug option to be enabled, then replace this with -debug. + If you would like for the variant to be propagated through all your + package's dependencies use "++" for enabling and "--" or "~~" for disabling. 4. The name of the compiler to build with. @@ -51,8 +53,10 @@ Here is the EBNF grammar for a spec:: spec-list = { spec [ dep-list ] } dep_list = { ^ spec } spec = id [ options ] - options = { @version-list | +variant | -variant | ~variant | - %compiler | arch=architecture | [ flag ]=value} + options = { @version-list | ++variant | +variant | + --variant | -variant | ~~variant | ~variant | + variant=value | variant==value | %compiler | + arch=architecture | [ flag ]==value | [ flag ]=value} flag = { cflags | cxxflags | fcflags | fflags | cppflags | ldflags | ldlibs } variant = id @@ -124,6 +128,7 @@ __all__ = [ "SpecParser", "parse", "SpecParseError", + "ArchitecturePropagationError", "DuplicateDependencyError", "DuplicateCompilerSpecError", "UnsupportedCompilerError", @@ -148,15 +153,16 @@ __all__ = [ is_windows = sys.platform == "win32" #: Valid pattern for an identifier in Spack + identifier_re = r"\w[\w-]*" -compiler_color = "@g" #: color for highlighting compilers -version_color = "@c" #: color for highlighting versions -architecture_color = "@m" #: color for highlighting architectures -enabled_variant_color = "@B" #: color for highlighting enabled variants -disabled_variant_color = "@r" #: color for highlighting disabled varaints -dependency_color = "@." #: color for highlighting dependencies -hash_color = "@K" #: color for highlighting package hashes +compiler_color = "@g" #: color for highlighting compilers +version_color = "@c" #: color for highlighting versions +architecture_color ="@m" #: color for highlighting architectures +enabled_variant_color = "@B" #: color for highlighting enabled variants +disabled_variant_color = "r" #: color for highlighting disabled varaints +dependency_color = "@." #: color for highlighting dependencies +hash_color = "@K" #: color for highlighting package hashes #: This map determines the coloring of specs when using color output. #: We make the fields different colors to enhance readability. @@ -737,6 +743,22 @@ class DependencySpec(object): return DependencySpec(parent=self.spec, spec=self.parent, deptypes=self.deptypes) +class CompilerFlag(str): + """Will store a flag value and it's propagation value + + Args: + value (str): the flag's value + propagate (bool): if ``True`` the flag value will + be passed to the package's dependencies. If + ``False`` it will not + """ + + def __new__(cls, value, **kwargs): + obj = str.__new__(cls, value) + obj.propagate = kwargs.pop("propagate", False) + return obj + + _valid_compiler_flags = ["cflags", "cxxflags", "fflags", "ldflags", "ldlibs", "cppflags"] @@ -752,9 +774,20 @@ class FlagMap(lang.HashableMap): if strict or (self.spec and self.spec._concrete): return all(f in self and set(self[f]) == set(other[f]) for f in other) else: - return all( + if not all( set(self[f]) == set(other[f]) for f in other if (other[f] != [] and f in self) - ) + ): + return False + + # Check that the propagation values match + for flag_type in other: + if not all( + other[flag_type][i].propagate == self[flag_type][i].propagate + for i in range(len(other[flag_type])) + if flag_type in self + ): + return False + return True def constrain(self, other): """Add all flags in other that aren't in self to self. @@ -775,6 +808,14 @@ class FlagMap(lang.HashableMap): elif k not in self: self[k] = other[k] changed = True + + # Check that the propagation values match + if self[k] == other[k]: + for i in range(len(other[k])): + if self[k][i].propagate != other[k][i].propagate: + raise UnsatisfiableCompilerFlagSpecError( + self[k][i].propagate, other[k][i].propagate + ) return changed @staticmethod @@ -782,11 +823,41 @@ class FlagMap(lang.HashableMap): return _valid_compiler_flags def copy(self): - clone = FlagMap(None) - for name, value in self.items(): - clone[name] = value + clone = FlagMap(self.spec) + for name, compiler_flag in self.items(): + clone[name] = compiler_flag return clone + def add_flag(self, flag_type, value, propagation): + """Stores the flag's value in CompilerFlag and adds it + to the FlagMap + + Args: + flag_type (str): the type of flag + value (str): the flag's value that will be added to the flag_type's + corresponding list + propagation (bool): if ``True`` the flag value will be passed to + the packages' dependencies. If``False`` it will not be passed + """ + flag = CompilerFlag(value, propagate=propagation) + + if flag_type not in self: + self[flag_type] = [flag] + else: + self[flag_type].append(flag) + + def yaml_entry(self, flag_type): + """Returns the flag type and a list of the flag values since the + propagation values aren't needed when writing to yaml + + Args: + flag_type (str): the type of flag to get values from + + Returns the flag_type and a list of the corresponding flags in + string format + """ + return flag_type, [str(flag) for flag in self[flag_type]] + def _cmp_iter(self): for k, v in sorted(self.items()): yield k @@ -803,7 +874,11 @@ class FlagMap(lang.HashableMap): return ( cond_symbol + " ".join( - str(key) + '="' + " ".join(str(f) for f in self[key]) + '"' for key in sorted_keys + key + + ('=="' if True in [f.propagate for f in self[key]] else '="') + + " ".join(self[key]) + + '"' + for key in sorted_keys ) + cond_symbol ) @@ -1405,10 +1480,26 @@ class Spec(object): for version in version_list: self.versions.add(version) - def _add_flag(self, name, value): + def _add_flag(self, name, value, propagate): """Called by the parser to add a known flag. Known flags currently include "arch" """ + + # If the == syntax is used to propagate the spec architecture + # This is an error + architecture_names = [ + "arch", + "architecture", + "platform", + "os", + "operating_system", + "target", + ] + if propagate and name in architecture_names: + raise ArchitecturePropagationError( + "Unable to propagate the architecture failed." " Use a '=' instead." + ) + valid_flags = FlagMap.valid_compiler_flags() if name == "arch" or name == "architecture": parts = tuple(value.split("-")) @@ -1422,16 +1513,18 @@ class Spec(object): self._set_architecture(target=value) elif name in valid_flags: assert self.compiler_flags is not None - self.compiler_flags[name] = spack.compiler.tokenize_flags(value) + flags_and_propagation = spack.compiler.tokenize_flags(value, propagate) + for flag, propagation in flags_and_propagation: + self.compiler_flags.add_flag(name, flag, propagation) else: # FIXME: # 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] = vt.BoolValuedVariant(name, value) + self.variants[name] = vt.BoolValuedVariant(name, value, propagate) else: - self.variants[name] = vt.AbstractVariant(name, value) + self.variants[name] = vt.AbstractVariant(name, value, propagate) def _set_architecture(self, **kwargs): """Called by the parser to set the architecture.""" @@ -1783,7 +1876,14 @@ class Spec(object): params = syaml.syaml_dict(sorted(v.yaml_entry() for _, v in self.variants.items())) - params.update(sorted(self.compiler_flags.items())) + # Only need the string compiler flag for yaml file + params.update( + sorted( + self.compiler_flags.yaml_entry(flag_type) + for flag_type in self.compiler_flags.keys() + ) + ) + if params: d["parameters"] = params @@ -2008,11 +2108,13 @@ class Spec(object): spec.compiler = None if "parameters" in node: - for name, value in node["parameters"].items(): + for name, values in node["parameters"].items(): if name in _valid_compiler_flags: - spec.compiler_flags[name] = value + spec.compiler_flags[name] = [] + for val in values: + spec.compiler_flags.add_flag(name, val, False) else: - spec.variants[name] = vt.MultiValuedVariant.from_node_dict(name, value) + spec.variants[name] = vt.MultiValuedVariant.from_node_dict(name, values) elif "variants" in node: for name, value in node["variants"].items(): spec.variants[name] = vt.MultiValuedVariant.from_node_dict(name, value) @@ -4122,7 +4224,7 @@ class Spec(object): except AttributeError: parent = ".".join(parts[:idx]) m = "Attempted to format attribute %s." % attribute - m += "Spec.%s has no attribute %s" % (parent, part) + m += "Spec %s has no attribute %s" % (parent, part) raise SpecFormatStringError(m) if isinstance(current, vn.VersionList): if current == _any_version: @@ -4859,7 +4961,7 @@ class LazySpecCache(collections.defaultdict): #: These are possible token types in the spec grammar. -HASH, DEP, VER, COLON, COMMA, ON, OFF, PCT, EQ, ID, VAL, FILE = range(12) +HASH, DEP, VER, COLON, COMMA, ON, D_ON, OFF, D_OFF, PCT, EQ, D_EQ, ID, VAL, FILE = range(15) #: Regex for fully qualified spec names. (e.g., builtin.hdf5) spec_id_re = r"\w[\w.-]*" @@ -4882,15 +4984,19 @@ class SpecLexer(spack.parse.Lexer): ( r"\@([\w.\-]*\s*)*(\s*\=\s*\w[\w.\-]*)?", lambda scanner, val: self.token(VER, val), - ), - (r"\:", lambda scanner, val: self.token(COLON, val)), - (r"\,", lambda scanner, val: self.token(COMMA, val)), - (r"\^", lambda scanner, val: self.token(DEP, val)), - (r"\+", lambda scanner, val: self.token(ON, val)), - (r"\-", lambda scanner, val: self.token(OFF, val)), - (r"\~", lambda scanner, val: self.token(OFF, val)), - (r"\%", lambda scanner, val: self.token(PCT, val)), - (r"\=", lambda scanner, val: self.token(EQ, val)), + ), + (r"\:", lambda scanner, val: self.token(COLON, val)), + (r"\,", lambda scanner, val: self.token(COMMA, val)), + (r"\^", lambda scanner, val: self.token(DEP, val)), + (r"\+\+", lambda scanner, val: self.token(D_ON, val)), + (r"\+", lambda scanner, val: self.token(ON, val)), + (r"\-\-", lambda scanner, val: self.token(D_OFF, val)), + (r"\-", lambda scanner, val: self.token(OFF, val)), + (r"\~\~", lambda scanner, val: self.token(D_OFF, val)), + (r"\~", lambda scanner, val: self.token(OFF, val)), + (r"\%", lambda scanner, val: self.token(PCT, val)), + (r"\=\=", lambda scanner, val: self.token(D_EQ, val)), + (r"\=", lambda scanner, val: self.token(EQ, val)), # Filenames match before identifiers, so no initial filename # component is parsed as a spec (e.g., in subdir/spec.yaml/json) (filename_reg, lambda scanner, v: self.token(FILE, v)), @@ -4901,7 +5007,7 @@ class SpecLexer(spack.parse.Lexer): (spec_id_re, lambda scanner, val: self.token(ID, val)), (r"\s+", lambda scanner, val: None), ], - [EQ], + [D_EQ, EQ], [ (r"[\S].*", lambda scanner, val: self.token(VAL, val)), (r"\s+", lambda scanner, val: None), @@ -4946,7 +5052,7 @@ class SpecParser(spack.parse.Parser): if self.accept(ID): self.previous = self.token - if self.accept(EQ): + if self.accept(EQ) or self.accept(D_EQ): # We're parsing an anonymous spec beginning with a # key-value pair. if not specs: @@ -5023,10 +5129,13 @@ class SpecParser(spack.parse.Parser): else: # If the next token can be part of a valid anonymous spec, # create the anonymous spec - if self.next.type in (VER, ON, OFF, PCT): - # Raise an error if the previous spec is already concrete - if specs and specs[-1].concrete: - raise RedundantSpecError(specs[-1], "compiler, version, " "or variant") + if self.next.type in (VER, ON, D_ON, OFF, D_OFF, PCT): + # Raise an error if the previous spec is already + # concrete (assigned by hash) + if specs and specs[-1]._hash: + raise RedundantSpecError(specs[-1], + 'compiler, version, ' + 'or variant') specs.append(self.spec(None)) else: self.unexpected_token() @@ -5135,22 +5244,36 @@ class SpecParser(spack.parse.Parser): vlist = self.version_list() spec._add_versions(vlist) + elif self.accept(D_ON): + name = self.variant() + spec.variants[name] = vt.BoolValuedVariant(name, True, propagate=True) + elif self.accept(ON): name = self.variant() - spec.variants[name] = vt.BoolValuedVariant(name, True) + spec.variants[name] = vt.BoolValuedVariant(name, True, propagate=False) + + elif self.accept(D_OFF): + name = self.variant() + spec.variants[name] = vt.BoolValuedVariant(name, False, propagate=True) elif self.accept(OFF): name = self.variant() - spec.variants[name] = vt.BoolValuedVariant(name, False) + spec.variants[name] = vt.BoolValuedVariant(name, False, propagate=False) elif self.accept(PCT): spec._set_compiler(self.compiler()) elif self.accept(ID): self.previous = self.token - if self.accept(EQ): + if self.accept(D_EQ): + # We're adding a key-value pair to the spec self.expect(VAL) - spec._add_flag(self.previous.value, self.token.value) + spec._add_flag(self.previous.value, self.token.value, propagate=True) + self.previous = None + elif self.accept(EQ): + # We're adding a key-value pair to the spec + self.expect(VAL) + spec._add_flag(self.previous.value, self.token.value, propagate=False) self.previous = None else: # We've found the start of a new spec. Go back to do_parse @@ -5313,6 +5436,12 @@ class SpecParseError(spack.error.SpecError): ) +class ArchitecturePropagationError(spack.error.SpecError): + """Raised when the double equal symbols are used to assign + the spec's architecture. + """ + + class DuplicateDependencyError(spack.error.SpecError): """Raised when the same dependency occurs in a spec twice.""" |