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.py217
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."""