diff options
-rw-r--r-- | lib/spack/docs/basic_usage.rst | 38 | ||||
-rw-r--r-- | lib/spack/spack/cmd/__init__.py | 3 | ||||
-rw-r--r-- | lib/spack/spack/cmd/deactivate.py | 2 | ||||
-rw-r--r-- | lib/spack/spack/cmd/extensions.py | 2 | ||||
-rw-r--r-- | lib/spack/spack/compiler.py | 20 | ||||
-rw-r--r-- | lib/spack/spack/provider_index.py | 3 | ||||
-rw-r--r-- | lib/spack/spack/solver/asp.py | 69 | ||||
-rw-r--r-- | lib/spack/spack/solver/concretize.lp | 64 | ||||
-rw-r--r-- | lib/spack/spack/solver/display.lp | 2 | ||||
-rw-r--r-- | lib/spack/spack/spec.py | 217 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/common/arguments.py | 18 | ||||
-rw-r--r-- | lib/spack/spack/test/concretize.py | 47 | ||||
-rw-r--r-- | lib/spack/spack/test/spec_semantics.py | 45 | ||||
-rw-r--r-- | lib/spack/spack/variant.py | 14 | ||||
-rw-r--r-- | var/spack/repos/builtin.mock/packages/hypre/package.py | 7 | ||||
-rw-r--r-- | var/spack/repos/builtin.mock/packages/openblas/package.py | 2 |
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:") |