summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/docs/basic_usage.rst38
-rw-r--r--lib/spack/spack/cmd/__init__.py3
-rw-r--r--lib/spack/spack/cmd/deactivate.py2
-rw-r--r--lib/spack/spack/cmd/extensions.py2
-rw-r--r--lib/spack/spack/compiler.py20
-rw-r--r--lib/spack/spack/provider_index.py3
-rw-r--r--lib/spack/spack/solver/asp.py69
-rw-r--r--lib/spack/spack/solver/concretize.lp64
-rw-r--r--lib/spack/spack/solver/display.lp2
-rw-r--r--lib/spack/spack/spec.py217
-rw-r--r--lib/spack/spack/test/cmd/common/arguments.py18
-rw-r--r--lib/spack/spack/test/concretize.py47
-rw-r--r--lib/spack/spack/test/spec_semantics.py45
-rw-r--r--lib/spack/spack/variant.py14
-rw-r--r--var/spack/repos/builtin.mock/packages/hypre/package.py7
-rw-r--r--var/spack/repos/builtin.mock/packages/openblas/package.py2
16 files changed, 435 insertions, 118 deletions
diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst
index 95c37dd8bf..73895449b0 100644
--- a/lib/spack/docs/basic_usage.rst
+++ b/lib/spack/docs/basic_usage.rst
@@ -998,11 +998,15 @@ More formally, a spec consists of the following pieces:
* ``%`` Optional compiler specifier, with an optional compiler version
(``gcc`` or ``gcc@4.7.3``)
* ``+`` or ``-`` or ``~`` Optional variant specifiers (``+debug``,
- ``-qt``, or ``~qt``) for boolean variants
+ ``-qt``, or ``~qt``) for boolean variants. Use ``++`` or ``--`` or
+ ``~~`` to propagate variants through the dependencies (``++debug``,
+ ``--qt``, or ``~~qt``).
* ``name=<value>`` Optional variant specifiers that are not restricted to
- boolean variants
+ boolean variants. Use ``name==<value>`` to propagate variant through the
+ dependencies.
* ``name=<value>`` Optional compiler flag specifiers. Valid flag names are
``cflags``, ``cxxflags``, ``fflags``, ``cppflags``, ``ldflags``, and ``ldlibs``.
+ Use ``name==<value>`` to propagate compiler flags through the dependencies.
* ``target=<value> os=<value>`` Optional architecture specifier
(``target=haswell os=CNL10``)
* ``^`` Dependency specs (``^callpath@1.1``)
@@ -1226,6 +1230,23 @@ variants using the backwards compatibility syntax and uses only ``~``
for disabled boolean variants. The ``-`` and spaces on the command
line are provided for convenience and legibility.
+Spack allows variants to propagate their value to the package's
+dependency by using ``++``, ``--``, and ``~~`` for boolean variants.
+For example, for a ``debug`` variant:
+
+.. code-block:: sh
+
+ mpileaks ++debug # enabled debug will be propagated to dependencies
+ mpileaks +debug # only mpileaks will have debug enabled
+
+To propagate the value of non-boolean variants Spack uses ``name==value``.
+For example, for the ``stackstart`` variant:
+
+.. code-block:: sh
+
+ mpileaks stackstart=4 # variant will be propagated to dependencies
+ mpileaks stackstart==4 # only mpileaks will have this variant value
+
^^^^^^^^^^^^^^
Compiler Flags
^^^^^^^^^^^^^^
@@ -1233,10 +1254,15 @@ Compiler Flags
Compiler flags are specified using the same syntax as non-boolean variants,
but fulfill a different purpose. While the function of a variant is set by
the package, compiler flags are used by the compiler wrappers to inject
-flags into the compile line of the build. Additionally, compiler flags are
-inherited by dependencies. ``spack install libdwarf cppflags="-g"`` will
-install both libdwarf and libelf with the ``-g`` flag injected into their
-compile line.
+flags into the compile line of the build. Additionally, compiler flags can
+be inherited by dependencies by using ``==``.
+``spack install libdwarf cppflags=="-g"`` will install both libdwarf and
+libelf with the ``-g`` flag injected into their compile line.
+
+.. note::
+
+ versions of spack prior to 0.19.0 will propagate compiler flags using
+ the ``=`` syntax.
Notice that the value of the compiler flags must be quoted if it
contains any spaces. Any of ``cppflags=-O3``, ``cppflags="-O3"``,
diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py
index e829166d39..ee1297e51c 100644
--- a/lib/spack/spack/cmd/__init__.py
+++ b/lib/spack/spack/cmd/__init__.py
@@ -234,7 +234,8 @@ def parse_specs(args, **kwargs):
msg = e.message
if e.long_message:
msg += e.long_message
- if unquoted_flags:
+ # Unquoted flags will be read as a variant or hash
+ if unquoted_flags and ("variant" in msg or "hash" in msg):
msg += "\n\n"
msg += unquoted_flags.report()
diff --git a/lib/spack/spack/cmd/deactivate.py b/lib/spack/spack/cmd/deactivate.py
index d68341037f..2fa18fc2b1 100644
--- a/lib/spack/spack/cmd/deactivate.py
+++ b/lib/spack/spack/cmd/deactivate.py
@@ -91,6 +91,6 @@ def deactivate(parser, args):
)
if not args.force and not spec.package.is_activated(view):
- tty.die("Package %s is not activated." % specs[0].short_spec)
+ tty.die("Package %s is not activated." % spec.short_spec)
spec.package.do_deactivate(view, force=args.force)
diff --git a/lib/spack/spack/cmd/extensions.py b/lib/spack/spack/cmd/extensions.py
index 50f41529b2..fa79f8df2d 100644
--- a/lib/spack/spack/cmd/extensions.py
+++ b/lib/spack/spack/cmd/extensions.py
@@ -76,7 +76,7 @@ def extensions(parser, args):
spec = cmd.disambiguate_spec(spec[0], env)
if not spec.package.extendable:
- tty.die("%s is not an extendable package." % spec[0].name)
+ tty.die("%s is not an extendable package." % spec.name)
if not spec.package.extendable:
tty.die("%s does not have extensions." % spec.short_spec)
diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py
index 2e86309699..37bc250de8 100644
--- a/lib/spack/spack/compiler.py
+++ b/lib/spack/spack/compiler.py
@@ -56,25 +56,25 @@ def get_compiler_version_output(compiler_path, *args, **kwargs):
return _get_compiler_version_output(compiler_path, *args, **kwargs)
-def tokenize_flags(flags_str):
+def tokenize_flags(flags_values, propagate=False):
"""Given a compiler flag specification as a string, this returns a list
where the entries are the flags. For compiler options which set values
using the syntax "-flag value", this function groups flags and their
values together. Any token not preceded by a "-" is considered the
value of a prior flag."""
- tokens = flags_str.split()
+ tokens = flags_values.split()
if not tokens:
return []
flag = tokens[0]
- flags = []
+ flags_with_propagation = []
for token in tokens[1:]:
if not token.startswith("-"):
flag += " " + token
else:
- flags.append(flag)
+ flags_with_propagation.append((flag, propagate))
flag = token
- flags.append(flag)
- return flags
+ flags_with_propagation.append((flag, propagate))
+ return flags_with_propagation
#: regex for parsing linker lines
@@ -311,11 +311,13 @@ class Compiler(object):
# Unfortunately have to make sure these params are accepted
# in the same order they are returned by sorted(flags)
# in compilers/__init__.py
- self.flags = {}
- for flag in spack.spec.FlagMap.valid_compiler_flags():
+ self.flags = spack.spec.FlagMap(self.spec)
+ for flag in self.flags.valid_compiler_flags():
value = kwargs.get(flag, None)
if value is not None:
- self.flags[flag] = tokenize_flags(value)
+ values_with_propagation = tokenize_flags(value, False)
+ for value, propagation in values_with_propagation:
+ self.flags.add_flag(flag, value, propagation)
# caching value for compiler reported version
# used for version checks for API, e.g. C++11 flag
diff --git a/lib/spack/spack/provider_index.py b/lib/spack/spack/provider_index.py
index 2da7d05e94..254492fc21 100644
--- a/lib/spack/spack/provider_index.py
+++ b/lib/spack/spack/provider_index.py
@@ -175,9 +175,10 @@ class ProviderIndex(_IndexBase):
pkg_provided = self.repository.get_pkg_class(spec.name).provided
for provided_spec, provider_specs in six.iteritems(pkg_provided):
- for provider_spec in provider_specs:
+ for provider_spec_readonly in provider_specs:
# TODO: fix this comment.
# We want satisfaction other than flags
+ provider_spec = provider_spec_readonly.copy()
provider_spec.compiler_flags = spec.compiler_flags.copy()
if spec.satisfies(provider_spec, deps=False):
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index af55b757be..fffa58f337 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -302,18 +302,6 @@ def extend_flag_list(flag_list, new_flags):
flag_list.append(flag)
-def check_same_flags(flag_dict_1, flag_dict_2):
- """Return True if flag dicts contain the same flags regardless of order."""
- types = set(flag_dict_1.keys()).union(set(flag_dict_2.keys()))
- for t in types:
- values1 = set(flag_dict_1.get(t, []))
- values2 = set(flag_dict_2.get(t, []))
- error_msg = "Internal Error: A mismatch in flags has occurred:"
- error_msg += "\n\tvalues1: {v1}\n\tvalues2: {v2}".format(v1=values1, v2=values2)
- error_msg += "\n Please report this as an issue to the spack maintainers"
- assert values1 == values2, error_msg
-
-
def check_packages_exist(specs):
"""Ensure all packages mentioned in specs exist."""
repo = spack.repo.path
@@ -723,6 +711,7 @@ class PyclingoDriver(object):
if output.timers:
timer.write_tty()
print()
+
if output.stats:
print("Statistics:")
pprint.pprint(self.control.statistics)
@@ -1359,6 +1348,8 @@ class SpackSolverSetup(object):
node_compiler = fn.node_compiler_set
node_compiler_version = fn.node_compiler_version_set
node_flag = fn.node_flag_set
+ node_flag_propagate = fn.node_flag_propagate
+ variant_propagate = fn.variant_propagate
class Body(object):
node = fn.node
@@ -1370,6 +1361,8 @@ class SpackSolverSetup(object):
node_compiler = fn.node_compiler
node_compiler_version = fn.node_compiler_version
node_flag = fn.node_flag
+ node_flag_propagate = fn.node_flag_propagate
+ variant_propagate = fn.variant_propagate
f = Body if body else Head
@@ -1417,6 +1410,9 @@ class SpackSolverSetup(object):
clauses.append(f.variant_value(spec.name, vname, value))
+ if variant.propagate:
+ clauses.append(f.variant_propagate(spec.name, vname))
+
# Tell the concretizer that this is a possible value for the
# variant, to account for things like int/str values where we
# can't enumerate the valid values
@@ -1443,6 +1439,8 @@ class SpackSolverSetup(object):
for flag_type, flags in spec.compiler_flags.items():
for flag in flags:
clauses.append(f.node_flag(spec.name, flag_type, flag))
+ if not spec.concrete and flag.propagate is True:
+ clauses.append(f.node_flag_propagate(spec.name, flag_type))
# dependencies
if spec.concrete:
@@ -2076,13 +2074,15 @@ class SpecBuilder(object):
# FIXME: is there a way not to special case 'dev_path' everywhere?
if name == "dev_path":
self._specs[pkg].variants.setdefault(
- name, spack.variant.SingleValuedVariant(name, value)
+ name,
+ spack.variant.SingleValuedVariant(name, value)
)
return
if name == "patches":
self._specs[pkg].variants.setdefault(
- name, spack.variant.MultiValuedVariant(name, value)
+ name,
+ spack.variant.MultiValuedVariant(name, value)
)
return
@@ -2101,10 +2101,10 @@ class SpecBuilder(object):
self._flag_compiler_defaults.add(pkg)
def node_flag(self, pkg, flag_type, flag):
- self._specs[pkg].compiler_flags.setdefault(flag_type, []).append(flag)
+ self._specs[pkg].compiler_flags.add_flag(flag_type, flag, False)
- def node_flag_source(self, pkg, source):
- self._flag_sources[pkg].add(source)
+ def node_flag_source(self, pkg, flag_type, source):
+ self._flag_sources[(pkg, flag_type)].add(source)
def no_flags(self, pkg, flag_type):
self._specs[pkg].compiler_flags[flag_type] = []
@@ -2151,15 +2151,24 @@ class SpecBuilder(object):
for pkg in self._flag_compiler_defaults:
spec = self._specs[pkg]
compiler_flags = compilers[spec.compiler].flags
- check_same_flags(spec.compiler_flags, compiler_flags)
- spec.compiler_flags.update(compiler_flags)
+ for key in spec.compiler_flags:
+ spec_compiler_flags_set = set(spec.compiler_flags.get(key, []))
+ compiler_flags_set = set(compiler_flags.get(key, []))
+
+ assert spec_compiler_flags_set == compiler_flags_set, "%s does not equal %s" % (
+ spec_compiler_flags_set,
+ compiler_flags_set,
+ )
+ spec.compiler_flags[key] = compiler_flags.get(key, [])
# index of all specs (and deps) from the command line by name
cmd_specs = dict((s.name, s) for spec in self._command_line_specs for s in spec.traverse())
# iterate through specs with specified flags
- for pkg, sources in self._flag_sources.items():
+ for key, sources in self._flag_sources.items():
+ pkg, flag_type = key
spec = self._specs[pkg]
+ compiler_flags = spec.compiler_flags.get(flag_type, [])
# order is determined by the DAG. A spec's flags come after
# any from its ancestors on the compile line.
@@ -2169,14 +2178,16 @@ class SpecBuilder(object):
sorted_sources = sorted(sources, key=lambda s: order.index(s))
# add flags from each source, lowest to highest precedence
- flags = collections.defaultdict(lambda: [])
+ flags = []
for source_name in sorted_sources:
source = cmd_specs[source_name]
- for name, flag_list in source.compiler_flags.items():
- extend_flag_list(flags[name], flag_list)
+ extend_flag_list(flags, source.compiler_flags.get(flag_type, []))
- check_same_flags(spec.compiler_flags, flags)
- spec.compiler_flags.update(flags)
+ assert set(compiler_flags) == set(flags), "%s does not equal %s" % (
+ set(compiler_flags),
+ set(flags),
+ )
+ spec.compiler_flags.update({flag_type: source.compiler_flags[flag_type]})
def deprecated(self, pkg, version):
msg = 'using "{0}@{1}" which is a deprecated version'
@@ -2187,12 +2198,14 @@ class SpecBuilder(object):
name = function_tuple[0]
if name == "error":
priority = function_tuple[1][0]
- return (-4, priority)
+ return (-5, priority)
elif name == "hash":
- return (-3, 0)
+ return (-4, 0)
elif name == "node":
- return (-2, 0)
+ return (-3, 0)
elif name == "node_compiler":
+ return (-2, 0)
+ elif name == "node_flag":
return (-1, 0)
else:
return (0, 0)
diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp
index 8bef7f2c1e..a176803074 100644
--- a/lib/spack/spack/solver/concretize.lp
+++ b/lib/spack/spack/solver/concretize.lp
@@ -400,6 +400,7 @@ node_target(Package, Target) :- attr("node_target", Package, Target).
node_target_satisfies(Package, Target) :- attr("node_target_satisfies", Package, Target).
variant_value(Package, Variant, Value) :- attr("variant_value", Package, Variant, Value).
variant_set(Package, Variant, Value) :- attr("variant_set", Package, Variant, Value).
+variant_propagate(Package, Variant) :- attr("variant_propagate", Package, Variant).
node_flag(Package, FlagType, Flag) :- attr("node_flag", Package, FlagType, Flag).
node_compiler(Package, Compiler) :- attr("node_compiler", Package, Compiler).
depends_on(Package, Dependency, Type) :- attr("depends_on", Package, Dependency, Type).
@@ -407,6 +408,8 @@ node_compiler_version(Package, Compiler, Version)
:- attr("node_compiler_version", Package, Compiler, Version).
node_compiler_version_satisfies(Package, Compiler, Version)
:- attr("node_compiler_version_satisfies", Package, Compiler, Version).
+node_flag_propagate(Package, FlagType)
+ :- attr("node_flag_propagate", Package, FlagType).
attr("node", Package) :- node(Package).
attr("virtual_node", Virtual) :- virtual_node(Virtual).
@@ -419,6 +422,7 @@ attr("node_target", Package, Target) :- node_target(Package, Target).
attr("node_target_satisfies", Package, Target) :- node_target_satisfies(Package, Target).
attr("variant_value", Package, Variant, Value) :- variant_value(Package, Variant, Value).
attr("variant_set", Package, Variant, Value) :- variant_set(Package, Variant, Value).
+attr("variant_propagate", Package, Variant) :- variant_propagate(Package, Variant).
attr("node_flag", Package, FlagType, Flag) :- node_flag(Package, FlagType, Flag).
attr("node_compiler", Package, Compiler) :- node_compiler(Package, Compiler).
attr("depends_on", Package, Dependency, Type) :- depends_on(Package, Dependency, Type).
@@ -426,6 +430,8 @@ attr("node_compiler_version", Package, Compiler, Version)
:- node_compiler_version(Package, Compiler, Version).
attr("node_compiler_version_satisfies", Package, Compiler, Version)
:- node_compiler_version_satisfies(Package, Compiler, Version).
+attr("node_flag_propagate", Package, FlagType)
+ :- node_flag_propagate(Package, FlagType).
% do not warn if generated program contains none of these.
#defined depends_on/3.
@@ -559,6 +565,24 @@ error(2, "Cannot satisfy requirement group for package '{0}'", Package) :-
variant(Package, Variant) :- variant_condition(ID, Package, Variant),
condition_holds(ID).
+% propagate the variant
+variant_value(Descendant, Variant, Value) :-
+ node(Package), path(Package, Descendant),
+ variant(Package, Variant),
+ variant(Descendant, Variant),
+ variant_value(Package, Variant, Value),
+ variant_propagate(Package, Variant),
+ not variant_set(Descendant, Variant),
+ variant_possible_value(Descendant, Variant, Value).
+
+error(2, "{0} and dependency {1} cannot both propagate variant '{2}'", Package1, Package2, Variant) :-
+ Package1 != Package2,
+ variant_propagate(Package1, Variant),
+ variant_propagate(Package2, Variant),
+ path(Package1, Descendent),
+ path(Package2, Descendent),
+ build(Package).
+
% a variant cannot be set if it is not a variant on the package
error(2, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package)
:- variant_set(Package, Variant),
@@ -703,6 +727,7 @@ variant_single_value(Package, "dev_path")
% warnings like 'info: atom does not occur in any rule head'.
#defined variant/2.
#defined variant_sticky/2.
+#defined variant_propagate/2.
#defined variant_set/3.
#defined variant_condition/3.
#defined variant_single_value/2.
@@ -1005,29 +1030,37 @@ compiler_weight(Package, 100)
%-----------------------------------------------------------------------------
% Compiler flags
%-----------------------------------------------------------------------------
-% propagate flags when compilers match
-inherit_flags(Package, Dependency)
- :- depends_on(Package, Dependency),
+
+% propagate flags when compiler match
+can_inherit_flags(Package, Dependency, FlagType)
+ :- path(Package, Dependency),
node_compiler(Package, Compiler),
node_compiler(Dependency, Compiler),
+ not node_flag_set(Dependency, FlagType, _),
compiler(Compiler), flag_type(FlagType).
node_flag_inherited(Dependency, FlagType, Flag)
- :- node_flag_set(Package, FlagType, Flag), inherit_flags(Package, Dependency).
-node_flag_inherited(Dependency, FlagType, Flag)
- :- node_flag_inherited(Package, FlagType, Flag),
- inherit_flags(Package, Dependency).
-
-% node with flags set to anythingg is "set"
-node_flag_set(Package) :- node_flag_set(Package, _, _).
+ :- node_flag_set(Package, FlagType, Flag), can_inherit_flags(Package, Dependency, FlagType),
+ node_flag_propagate(Package, FlagType).
+% Insure propagation
+:- node_flag_inherited(Package, FlagType, Flag),
+ can_inherit_flags(Package, Dependency, FlagType),
+ node_flag_propagate(Package, FlagType).
+
+error(0, "{0} and dependency {1} cannot both propagate compiler flags '{2}'", Package, Dependency, FlagType) :-
+ node(Dependency),
+ node_flag_propagate(Package, FlagType),
+ node_flag_propagate(Dependency, FlagType),
+ path(Package, Dependency).
% remember where flags came from
-node_flag_source(Package, Package) :- node_flag_set(Package).
-node_flag_source(Dependency, Q)
- :- node_flag_source(Package, Q), inherit_flags(Package, Dependency).
+node_flag_source(Package, FlagType, Package) :- node_flag_set(Package, FlagType, _).
+node_flag_source(Dependency, FlagType, Q)
+ :- node_flag_source(Package, FlagType, Q), node_flag_inherited(Dependency, FlagType, _),
+ node_flag_propagate(Package, FlagType).
% compiler flags from compilers.yaml are put on nodes if compiler matches
node_flag(Package, FlagType, Flag)
- :- not node_flag_set(Package),
+ :- not node_flag_set(Package, FlagType, _),
compiler_version_flag(Compiler, Version, FlagType, Flag),
node_compiler_version(Package, Compiler, Version),
flag_type(FlagType),
@@ -1035,7 +1068,7 @@ node_flag(Package, FlagType, Flag)
compiler_version(Compiler, Version).
node_flag_compiler_default(Package)
- :- not node_flag_set(Package),
+ :- not node_flag_set(Package, FlagType, _),
compiler_version_flag(Compiler, Version, FlagType, Flag),
node_compiler_version(Package, Compiler, Version),
flag_type(FlagType),
@@ -1054,6 +1087,7 @@ no_flags(Package, FlagType)
#defined compiler_version_flag/4.
#defined node_flag/3.
#defined node_flag_set/3.
+#defined node_flag_propagate/2.
%-----------------------------------------------------------------------------
diff --git a/lib/spack/spack/solver/display.lp b/lib/spack/spack/solver/display.lp
index e8f5eeba16..4ba8e9e2cb 100644
--- a/lib/spack/spack/solver/display.lp
+++ b/lib/spack/spack/solver/display.lp
@@ -23,7 +23,7 @@
#show node_compiler_version/3.
#show node_flag/3.
#show node_flag_compiler_default/1.
-#show node_flag_source/2.
+#show node_flag_source/3.
#show no_flags/2.
#show external_spec_selected/2.
#show version_equivalent/3.
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."""
diff --git a/lib/spack/spack/test/cmd/common/arguments.py b/lib/spack/spack/test/cmd/common/arguments.py
index 772cc0297e..d56ad59c85 100644
--- a/lib/spack/spack/test/cmd/common/arguments.py
+++ b/lib/spack/spack/test/cmd/common/arguments.py
@@ -46,21 +46,27 @@ def test_negative_integers_not_allowed_for_parallel_jobs(job_parser):
@pytest.mark.parametrize(
- "specs,cflags,negated_variants",
+ "specs,cflags,propagation,negated_variants",
[
- (['coreutils cflags="-O3 -g"'], ["-O3", "-g"], []),
- (["coreutils", "cflags=-O3 -g"], ["-O3"], ["g"]),
- (["coreutils", "cflags=-O3", "-g"], ["-O3"], ["g"]),
+ (['coreutils cflags="-O3 -g"'], ["-O3", "-g"], [False, False], []),
+ (['coreutils cflags=="-O3 -g"'], ["-O3", "-g"], [True, True], []),
+ (["coreutils", "cflags=-O3 -g"], ["-O3"], [False], ["g"]),
+ (["coreutils", "cflags==-O3 -g"], ["-O3"], [True], ["g"]),
+ (["coreutils", "cflags=-O3", "-g"], ["-O3"], [False], ["g"]),
],
)
@pytest.mark.regression("12951")
-def test_parse_spec_flags_with_spaces(specs, cflags, negated_variants):
+def test_parse_spec_flags_with_spaces(specs, cflags, propagation, negated_variants):
spec_list = spack.cmd.parse_specs(specs)
assert len(spec_list) == 1
s = spec_list.pop()
- assert s.compiler_flags["cflags"] == cflags
+ compiler_flags = [flag for flag in s.compiler_flags["cflags"]]
+ flag_propagation = [flag.propagate for flag in s.compiler_flags["cflags"]]
+
+ assert compiler_flags == cflags
+ assert flag_propagation == propagation
assert list(s.variants.keys()) == negated_variants
for v in negated_variants:
assert "~{0}".format(v) in s
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index bf0110f3e6..43f47259bf 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -2,6 +2,7 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import posixpath
import sys
@@ -81,6 +82,7 @@ def check_concretize(abstract_spec):
"mpich",
# compiler flags
'mpich cppflags="-O3"',
+ 'mpich cppflags=="-O3"',
# with virtual
"mpileaks ^mpi",
"mpileaks ^mpi@:1.1",
@@ -323,6 +325,33 @@ class TestConcretize(object):
assert set(client.compiler_flags["fflags"]) == set(["-O0", "-g"])
assert not set(cmake.compiler_flags["fflags"])
+ def test_concretize_compiler_flag_propagate(self):
+ spec = Spec("hypre cflags=='-g' ^openblas")
+ spec.concretize()
+
+ assert spec.satisfies("^openblas cflags='-g'")
+
+ @pytest.mark.skipif(
+ os.environ.get("SPACK_TEST_SOLVER") == "original" or sys.platform == "win32",
+ reason="Optional compiler propagation isn't deprecated for original concretizer",
+ )
+ def test_concretize_compiler_flag_does_not_propagate(self):
+ spec = Spec("hypre cflags='-g' ^openblas")
+ spec.concretize()
+
+ assert not spec.satisfies("^openblas cflags='-g'")
+
+ @pytest.mark.skipif(
+ os.environ.get("SPACK_TEST_SOLVER") == "original" or sys.platform == "win32",
+ reason="Optional compiler propagation isn't deprecated for original concretizer",
+ )
+ def test_concretize_propagate_compiler_flag_not_passed_to_dependent(self):
+ spec = Spec("hypre cflags=='-g' ^openblas cflags='-O3'")
+ spec.concretize()
+
+ assert set(spec.compiler_flags["cflags"]) == set(["-g"])
+ assert spec.satisfies("^openblas cflags='-O3'")
+
def test_architecture_inheritance(self):
"""test_architecture_inheritance is likely to fail with an
UnavailableCompilerVersionError if the architecture is concretized
@@ -401,6 +430,24 @@ class TestConcretize(object):
with pytest.raises(spack.error.SpackError):
s.concretize()
+ @pytest.mark.skipif(
+ os.environ.get("SPACK_TEST_SOLVER") == "original" or sys.platform == "win32",
+ reason="Optional compiler propagation isn't deprecated for original concretizer",
+ )
+ def test_concretize_propagate_disabled_variant(self):
+ """Test a package variant value was passed from its parent."""
+ spec = Spec("hypre~~shared ^openblas")
+ spec.concretize()
+
+ assert spec.satisfies("^openblas~shared")
+
+ def test_concretize_propagated_variant_is_not_passed_to_dependent(self):
+ """Test a package variant value was passed from its parent."""
+ spec = Spec("hypre~~shared ^openblas+shared")
+ spec.concretize()
+
+ assert spec.satisfies("^openblas+shared")
+
@pytest.mark.skipif(sys.platform == "win32", reason="No Compiler for Arch on Win")
def test_no_matching_compiler_specs(self, mock_low_high_config):
# only relevant when not building compilers as needed
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
index 711e901416..04aa82797c 100644
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -247,16 +247,23 @@ class TestSpecSematics(object):
def test_satisfies_matching_variant(self):
check_satisfies("mpich+foo", "mpich+foo")
+ check_satisfies("mpich++foo", "mpich++foo")
check_satisfies("mpich~foo", "mpich~foo")
+ check_satisfies("mpich~~foo", "mpich~~foo")
check_satisfies("mpich foo=1", "mpich foo=1")
+ check_satisfies("mpich foo==1", "mpich foo==1")
# confirm that synonymous syntax works correctly
check_satisfies("mpich+foo", "mpich foo=True")
+ check_satisfies("mpich++foo", "mpich foo=True")
check_satisfies("mpich foo=true", "mpich+foo")
+ check_satisfies("mpich foo==true", "mpich++foo")
check_satisfies("mpich~foo", "mpich foo=FALSE")
+ check_satisfies("mpich~~foo", "mpich foo=FALSE")
check_satisfies("mpich foo=False", "mpich~foo")
+ check_satisfies("mpich foo==False", "mpich~foo")
check_satisfies("mpich foo=*", "mpich~foo")
- check_satisfies("mpich +foo", "mpich foo=*")
+ check_satisfies("mpich+foo", "mpich foo=*")
def test_satisfies_multi_value_variant(self):
# Check quoting
@@ -295,6 +302,7 @@ class TestSpecSematics(object):
# Assert that an autospec generated from a literal
# gives the right result for a single valued variant
assert "foobar=bar" in a
+ assert "foobar==bar" in a
assert "foobar=baz" not in a
assert "foobar=fee" not in a
@@ -415,21 +423,32 @@ class TestSpecSematics(object):
check_satisfies("mpich", "mpich+foo", False)
check_satisfies("mpich", "mpich~foo", False)
check_satisfies("mpich", "mpich foo=1", False)
+ check_satisfies("mpich", "mpich++foo", False)
+ check_satisfies("mpich", "mpich~~foo", False)
+ check_satisfies("mpich", "mpich foo==1", False)
# 'mpich' is concrete:
check_unsatisfiable("mpich", "mpich+foo", True)
check_unsatisfiable("mpich", "mpich~foo", True)
check_unsatisfiable("mpich", "mpich foo=1", True)
+ check_unsatisfiable("mpich", "mpich++foo", True)
+ check_unsatisfiable("mpich", "mpich~~foo", True)
+ check_unsatisfiable("mpich", "mpich foo==1", True)
def test_unsatisfiable_variant_mismatch(self):
# No matchi in specs
check_unsatisfiable("mpich~foo", "mpich+foo")
check_unsatisfiable("mpich+foo", "mpich~foo")
check_unsatisfiable("mpich foo=True", "mpich foo=False")
+ check_unsatisfiable("mpich~~foo", "mpich++foo")
+ check_unsatisfiable("mpich++foo", "mpich~~foo")
+ check_unsatisfiable("mpich foo==True", "mpich foo==False")
def test_satisfies_matching_compiler_flag(self):
check_satisfies('mpich cppflags="-O3"', 'mpich cppflags="-O3"')
check_satisfies('mpich cppflags="-O3 -Wall"', 'mpich cppflags="-O3 -Wall"')
+ check_satisfies('mpich cppflags=="-O3"', 'mpich cppflags=="-O3"')
+ check_satisfies('mpich cppflags=="-O3 -Wall"', 'mpich cppflags=="-O3 -Wall"')
def test_satisfies_unconstrained_compiler_flag(self):
# only asked for mpich, no constraints. Any will do.
@@ -453,8 +472,9 @@ class TestSpecSematics(object):
assert copy[s.name].satisfies(s)
def test_unsatisfiable_compiler_flag_mismatch(self):
- # No matchi in specs
+ # No match in specs
check_unsatisfiable('mpich cppflags="-O3"', 'mpich cppflags="-O2"')
+ check_unsatisfiable('mpich cppflags="-O3"', 'mpich cppflags=="-O3"')
def test_satisfies_virtual(self):
# Don't use check_satisfies: it checks constrain() too, and
@@ -554,6 +574,12 @@ class TestSpecSematics(object):
check_constrain("libelf+debug~foo", "libelf+debug", "libelf~foo")
check_constrain("libelf+debug~foo", "libelf+debug", "libelf+debug~foo")
+ check_constrain("libelf++debug++foo", "libelf++debug", "libelf+debug+foo")
+ check_constrain("libelf debug==2 foo==1", "libelf debug==2", "libelf foo=1")
+ check_constrain("libelf debug==2 foo==1", "libelf debug==2", "libelf debug=2 foo=1")
+
+ check_constrain("libelf++debug~~foo", "libelf++debug", "libelf++debug~foo")
+
def test_constrain_multi_value_variant(self):
check_constrain(
'multivalue-variant foo="bar,baz"',
@@ -582,6 +608,17 @@ class TestSpecSematics(object):
'libelf cflags="-O3" cppflags="-Wall"',
)
+ check_constrain(
+ 'libelf cflags="-O3" cppflags=="-Wall"',
+ 'libelf cppflags=="-Wall"',
+ 'libelf cflags="-O3"',
+ )
+ check_constrain(
+ 'libelf cflags=="-O3" cppflags=="-Wall"',
+ 'libelf cflags=="-O3"',
+ 'libelf cflags=="-O3" cppflags=="-Wall"',
+ )
+
def test_constrain_architecture(self):
check_constrain(
"libelf target=default_target os=default_os",
@@ -620,6 +657,7 @@ class TestSpecSematics(object):
check_constrain_changed("libelf", "~debug")
check_constrain_changed("libelf", "debug=2")
check_constrain_changed("libelf", 'cppflags="-O3"')
+ check_constrain_changed("libelf", 'cppflags=="-O3"')
platform = spack.platforms.host()
check_constrain_changed("libelf", "target=" + platform.target("default_target").name)
@@ -636,6 +674,7 @@ class TestSpecSematics(object):
check_constrain_not_changed("libelf debug=2", "debug=2")
check_constrain_not_changed("libelf debug=2", "debug=*")
check_constrain_not_changed('libelf cppflags="-O3"', 'cppflags="-O3"')
+ check_constrain_not_changed('libelf cppflags=="-O3"', 'cppflags=="-O3"')
platform = spack.platforms.host()
default_target = platform.target("default_target").name
@@ -791,7 +830,7 @@ class TestSpecSematics(object):
spec.format(fmt_str)
def test_spec_deprecated_formatting(self):
- spec = Spec("libelf cflags=-O2")
+ spec = Spec("libelf cflags==-O2")
spec.concretize()
# Since the default is the full spec see if the string rep of
diff --git a/lib/spack/spack/variant.py b/lib/spack/spack/variant.py
index 92e512219b..cfb43cfc70 100644
--- a/lib/spack/spack/variant.py
+++ b/lib/spack/spack/variant.py
@@ -245,8 +245,9 @@ class AbstractVariant(object):
values.
"""
- def __init__(self, name, value):
+ def __init__(self, name, value, propagate=False):
self.name = name
+ self.propagate = propagate
# Stores 'value' after a bit of massaging
# done by the property setter
@@ -334,7 +335,7 @@ class AbstractVariant(object):
>>> assert a == b
>>> assert a is not b
"""
- return type(self)(self.name, self._original_value)
+ return type(self)(self.name, self._original_value, self.propagate)
@implicit_variant_conversion
def satisfies(self, other):
@@ -401,6 +402,8 @@ class AbstractVariant(object):
return "{0.__name__}({1}, {2})".format(cls, repr(self.name), repr(self._original_value))
def __str__(self):
+ if self.propagate:
+ return "{0}=={1}".format(self.name, ",".join(str(x) for x in self.value))
return "{0}={1}".format(self.name, ",".join(str(x) for x in self.value))
@@ -444,6 +447,9 @@ class MultiValuedVariant(AbstractVariant):
values_str = ",".join(x[:7] for x in self.value)
else:
values_str = ",".join(str(x) for x in self.value)
+
+ if self.propagate:
+ return "{0}=={1}".format(self.name, values_str)
return "{0}={1}".format(self.name, values_str)
@@ -460,6 +466,8 @@ class SingleValuedVariant(AbstractVariant):
self._value = str(self._value[0])
def __str__(self):
+ if self.propagate:
+ return "{0}=={1}".format(self.name, self.value)
return "{0}={1}".format(self.name, self.value)
@implicit_variant_conversion
@@ -523,6 +531,8 @@ class BoolValuedVariant(SingleValuedVariant):
return item is self.value
def __str__(self):
+ if self.propagate:
+ return "{0}{1}".format("++" if self.value else "~~", self.name)
return "{0}{1}".format("+" if self.value else "~", self.name)
diff --git a/var/spack/repos/builtin.mock/packages/hypre/package.py b/var/spack/repos/builtin.mock/packages/hypre/package.py
index fe077f067d..7182cf628a 100644
--- a/var/spack/repos/builtin.mock/packages/hypre/package.py
+++ b/var/spack/repos/builtin.mock/packages/hypre/package.py
@@ -2,6 +2,7 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import sys
from spack.package import *
@@ -17,3 +18,9 @@ class Hypre(Package):
depends_on("lapack")
depends_on("blas")
+
+ variant(
+ "shared",
+ default=(sys.platform != "darwin"),
+ description="Build shared library (disables static library)",
+ )
diff --git a/var/spack/repos/builtin.mock/packages/openblas/package.py b/var/spack/repos/builtin.mock/packages/openblas/package.py
index 1e948da8f2..8c2f26de57 100644
--- a/var/spack/repos/builtin.mock/packages/openblas/package.py
+++ b/var/spack/repos/builtin.mock/packages/openblas/package.py
@@ -17,6 +17,8 @@ class Openblas(Package):
version("0.2.14", "b1190f3d3471685f17cfd1ec1d252ac9")
version("0.2.13", "b1190f3d3471685f17cfd1ec1d252ac9")
+ variant("shared", default=True, description="Build shared libraries")
+
# See #20019 for this conflict
conflicts("%gcc@:4.4", when="@0.2.14:")