flags/variants: Add ++/~~/== syntax for propagation to dependencies

Currently, compiler flags and variants are inconsistent: compiler flags set for a
package are inherited by its dependencies, while variants are not. We should have these
be consistent by allowing for inheritance to be enabled or disabled for both variants
and compiler flags.

- [x] Make new (spec language) operators
- [x] Apply operators to variants and compiler flags
- [x] Conflicts currently result in an unsatisfiable spec
      (i.e., you can't propagate two conflicting values)

What I propose is using two of the currently used sigils to symbolized that the variant
or compiler flag will be inherited:

Example syntax:
- `package ++variant`
      enabled variant that will be propagated to dependencies
- `package +variant`
      enabled variant that will NOT be propagated to dependencies
- `package ~~variant`
      disabled variant that will be propagated to dependencies
- `package ~variant`
      disabled variant that will NOT be propagated to dependencies
- `package cflags==True`
      `cflags` will be propagated to dependencies
- `package cflags=True`
      `cflags` will NOT be propagated to dependencies

Syntax for string-valued variants is similar to compiler flags.
This commit is contained in:
Kayla Butler 2022-03-28 14:18:00 -07:00 committed by Todd Gamblin
parent ae99829af4
commit bc209c470d
16 changed files with 434 additions and 117 deletions

View File

@ -998,11 +998,15 @@ More formally, a spec consists of the following pieces:
* ``%`` Optional compiler specifier, with an optional compiler version * ``%`` Optional compiler specifier, with an optional compiler version
(``gcc`` or ``gcc@4.7.3``) (``gcc`` or ``gcc@4.7.3``)
* ``+`` or ``-`` or ``~`` Optional variant specifiers (``+debug``, * ``+`` 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 * ``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 * ``name=<value>`` Optional compiler flag specifiers. Valid flag names are
``cflags``, ``cxxflags``, ``fflags``, ``cppflags``, ``ldflags``, and ``ldlibs``. ``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=<value> os=<value>`` Optional architecture specifier
(``target=haswell os=CNL10``) (``target=haswell os=CNL10``)
* ``^`` Dependency specs (``^callpath@1.1``) * ``^`` 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 for disabled boolean variants. The ``-`` and spaces on the command
line are provided for convenience and legibility. 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 Compiler Flags
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
@ -1233,10 +1254,15 @@ Compiler Flags
Compiler flags are specified using the same syntax as non-boolean variants, 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 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 the package, compiler flags are used by the compiler wrappers to inject
flags into the compile line of the build. Additionally, compiler flags are flags into the compile line of the build. Additionally, compiler flags can
inherited by dependencies. ``spack install libdwarf cppflags="-g"`` will be inherited by dependencies by using ``==``.
install both libdwarf and libelf with the ``-g`` flag injected into their ``spack install libdwarf cppflags=="-g"`` will install both libdwarf and
compile line. 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 Notice that the value of the compiler flags must be quoted if it
contains any spaces. Any of ``cppflags=-O3``, ``cppflags="-O3"``, contains any spaces. Any of ``cppflags=-O3``, ``cppflags="-O3"``,

View File

@ -234,7 +234,8 @@ def parse_specs(args, **kwargs):
msg = e.message msg = e.message
if e.long_message: if e.long_message:
msg += 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 += "\n\n"
msg += unquoted_flags.report() msg += unquoted_flags.report()

View File

@ -91,6 +91,6 @@ def deactivate(parser, args):
) )
if not args.force and not spec.package.is_activated(view): 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) spec.package.do_deactivate(view, force=args.force)

View File

@ -76,7 +76,7 @@ def extensions(parser, args):
spec = cmd.disambiguate_spec(spec[0], env) spec = cmd.disambiguate_spec(spec[0], env)
if not spec.package.extendable: 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: if not spec.package.extendable:
tty.die("%s does not have extensions." % spec.short_spec) tty.die("%s does not have extensions." % spec.short_spec)

View File

@ -56,25 +56,25 @@ def get_compiler_version_output(compiler_path, *args, **kwargs):
return _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 """Given a compiler flag specification as a string, this returns a list
where the entries are the flags. For compiler options which set values where the entries are the flags. For compiler options which set values
using the syntax "-flag value", this function groups flags and their using the syntax "-flag value", this function groups flags and their
values together. Any token not preceded by a "-" is considered the values together. Any token not preceded by a "-" is considered the
value of a prior flag.""" value of a prior flag."""
tokens = flags_str.split() tokens = flags_values.split()
if not tokens: if not tokens:
return [] return []
flag = tokens[0] flag = tokens[0]
flags = [] flags_with_propagation = []
for token in tokens[1:]: for token in tokens[1:]:
if not token.startswith("-"): if not token.startswith("-"):
flag += " " + token flag += " " + token
else: else:
flags.append(flag) flags_with_propagation.append((flag, propagate))
flag = token flag = token
flags.append(flag) flags_with_propagation.append((flag, propagate))
return flags return flags_with_propagation
#: regex for parsing linker lines #: regex for parsing linker lines
@ -311,11 +311,13 @@ def __init__(
# Unfortunately have to make sure these params are accepted # Unfortunately have to make sure these params are accepted
# in the same order they are returned by sorted(flags) # in the same order they are returned by sorted(flags)
# in compilers/__init__.py # in compilers/__init__.py
self.flags = {} self.flags = spack.spec.FlagMap(self.spec)
for flag in spack.spec.FlagMap.valid_compiler_flags(): for flag in self.flags.valid_compiler_flags():
value = kwargs.get(flag, None) value = kwargs.get(flag, None)
if value is not 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 # caching value for compiler reported version
# used for version checks for API, e.g. C++11 flag # used for version checks for API, e.g. C++11 flag

View File

@ -175,9 +175,10 @@ def update(self, spec):
pkg_provided = self.repository.get_pkg_class(spec.name).provided pkg_provided = self.repository.get_pkg_class(spec.name).provided
for provided_spec, provider_specs in six.iteritems(pkg_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. # TODO: fix this comment.
# We want satisfaction other than flags # We want satisfaction other than flags
provider_spec = provider_spec_readonly.copy()
provider_spec.compiler_flags = spec.compiler_flags.copy() provider_spec.compiler_flags = spec.compiler_flags.copy()
if spec.satisfies(provider_spec, deps=False): if spec.satisfies(provider_spec, deps=False):

View File

@ -302,18 +302,6 @@ def extend_flag_list(flag_list, new_flags):
flag_list.append(flag) 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): def check_packages_exist(specs):
"""Ensure all packages mentioned in specs exist.""" """Ensure all packages mentioned in specs exist."""
repo = spack.repo.path repo = spack.repo.path
@ -723,6 +711,7 @@ def stringify(x):
if output.timers: if output.timers:
timer.write_tty() timer.write_tty()
print() print()
if output.stats: if output.stats:
print("Statistics:") print("Statistics:")
pprint.pprint(self.control.statistics) pprint.pprint(self.control.statistics)
@ -1359,6 +1348,8 @@ class Head(object):
node_compiler = fn.node_compiler_set node_compiler = fn.node_compiler_set
node_compiler_version = fn.node_compiler_version_set node_compiler_version = fn.node_compiler_version_set
node_flag = fn.node_flag_set node_flag = fn.node_flag_set
node_flag_propagate = fn.node_flag_propagate
variant_propagate = fn.variant_propagate
class Body(object): class Body(object):
node = fn.node node = fn.node
@ -1370,6 +1361,8 @@ class Body(object):
node_compiler = fn.node_compiler node_compiler = fn.node_compiler
node_compiler_version = fn.node_compiler_version node_compiler_version = fn.node_compiler_version
node_flag = fn.node_flag node_flag = fn.node_flag
node_flag_propagate = fn.node_flag_propagate
variant_propagate = fn.variant_propagate
f = Body if body else Head f = Body if body else Head
@ -1417,6 +1410,9 @@ class Body(object):
clauses.append(f.variant_value(spec.name, vname, value)) 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 # Tell the concretizer that this is a possible value for the
# variant, to account for things like int/str values where we # variant, to account for things like int/str values where we
# can't enumerate the valid values # can't enumerate the valid values
@ -1443,6 +1439,8 @@ class Body(object):
for flag_type, flags in spec.compiler_flags.items(): for flag_type, flags in spec.compiler_flags.items():
for flag in flags: for flag in flags:
clauses.append(f.node_flag(spec.name, flag_type, flag)) 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 # dependencies
if spec.concrete: if spec.concrete:
@ -2076,13 +2074,15 @@ def variant_value(self, pkg, name, value):
# FIXME: is there a way not to special case 'dev_path' everywhere? # FIXME: is there a way not to special case 'dev_path' everywhere?
if name == "dev_path": if name == "dev_path":
self._specs[pkg].variants.setdefault( self._specs[pkg].variants.setdefault(
name, spack.variant.SingleValuedVariant(name, value) name,
spack.variant.SingleValuedVariant(name, value)
) )
return return
if name == "patches": if name == "patches":
self._specs[pkg].variants.setdefault( self._specs[pkg].variants.setdefault(
name, spack.variant.MultiValuedVariant(name, value) name,
spack.variant.MultiValuedVariant(name, value)
) )
return return
@ -2101,10 +2101,10 @@ def node_flag_compiler_default(self, pkg):
self._flag_compiler_defaults.add(pkg) self._flag_compiler_defaults.add(pkg)
def node_flag(self, pkg, flag_type, flag): 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): def node_flag_source(self, pkg, flag_type, source):
self._flag_sources[pkg].add(source) self._flag_sources[(pkg, flag_type)].add(source)
def no_flags(self, pkg, flag_type): def no_flags(self, pkg, flag_type):
self._specs[pkg].compiler_flags[flag_type] = [] self._specs[pkg].compiler_flags[flag_type] = []
@ -2151,15 +2151,24 @@ def reorder_flags(self):
for pkg in self._flag_compiler_defaults: for pkg in self._flag_compiler_defaults:
spec = self._specs[pkg] spec = self._specs[pkg]
compiler_flags = compilers[spec.compiler].flags compiler_flags = compilers[spec.compiler].flags
check_same_flags(spec.compiler_flags, compiler_flags) for key in spec.compiler_flags:
spec.compiler_flags.update(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 # 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()) cmd_specs = dict((s.name, s) for spec in self._command_line_specs for s in spec.traverse())
# iterate through specs with specified flags # 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] spec = self._specs[pkg]
compiler_flags = spec.compiler_flags.get(flag_type, [])
# order is determined by the DAG. A spec's flags come after # order is determined by the DAG. A spec's flags come after
# any from its ancestors on the compile line. # any from its ancestors on the compile line.
@ -2169,14 +2178,16 @@ def reorder_flags(self):
sorted_sources = sorted(sources, key=lambda s: order.index(s)) sorted_sources = sorted(sources, key=lambda s: order.index(s))
# add flags from each source, lowest to highest precedence # add flags from each source, lowest to highest precedence
flags = collections.defaultdict(lambda: []) flags = []
for source_name in sorted_sources: for source_name in sorted_sources:
source = cmd_specs[source_name] source = cmd_specs[source_name]
for name, flag_list in source.compiler_flags.items(): extend_flag_list(flags, source.compiler_flags.get(flag_type, []))
extend_flag_list(flags[name], flag_list)
check_same_flags(spec.compiler_flags, flags) assert set(compiler_flags) == set(flags), "%s does not equal %s" % (
spec.compiler_flags.update(flags) set(compiler_flags),
set(flags),
)
spec.compiler_flags.update({flag_type: source.compiler_flags[flag_type]})
def deprecated(self, pkg, version): def deprecated(self, pkg, version):
msg = 'using "{0}@{1}" which is a deprecated version' msg = 'using "{0}@{1}" which is a deprecated version'
@ -2187,12 +2198,14 @@ def sort_fn(function_tuple):
name = function_tuple[0] name = function_tuple[0]
if name == "error": if name == "error":
priority = function_tuple[1][0] priority = function_tuple[1][0]
return (-4, priority) return (-5, priority)
elif name == "hash": elif name == "hash":
return (-3, 0) return (-4, 0)
elif name == "node": elif name == "node":
return (-2, 0) return (-3, 0)
elif name == "node_compiler": elif name == "node_compiler":
return (-2, 0)
elif name == "node_flag":
return (-1, 0) return (-1, 0)
else: else:
return (0, 0) return (0, 0)

View File

@ -400,6 +400,7 @@ node_target(Package, Target) :- attr("node_target", Package, Target).
node_target_satisfies(Package, Target) :- attr("node_target_satisfies", 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_value(Package, Variant, Value) :- attr("variant_value", Package, Variant, Value).
variant_set(Package, Variant, Value) :- attr("variant_set", 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_flag(Package, FlagType, Flag) :- attr("node_flag", Package, FlagType, Flag).
node_compiler(Package, Compiler) :- attr("node_compiler", Package, Compiler). node_compiler(Package, Compiler) :- attr("node_compiler", Package, Compiler).
depends_on(Package, Dependency, Type) :- attr("depends_on", Package, Dependency, Type). 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). :- attr("node_compiler_version", Package, Compiler, Version).
node_compiler_version_satisfies(Package, Compiler, Version) node_compiler_version_satisfies(Package, Compiler, Version)
:- attr("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("node", Package) :- node(Package).
attr("virtual_node", Virtual) :- virtual_node(Virtual). 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("node_target_satisfies", Package, Target) :- node_target_satisfies(Package, Target).
attr("variant_value", Package, Variant, Value) :- variant_value(Package, Variant, Value). attr("variant_value", Package, Variant, Value) :- variant_value(Package, Variant, Value).
attr("variant_set", Package, Variant, Value) :- variant_set(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_flag", Package, FlagType, Flag) :- node_flag(Package, FlagType, Flag).
attr("node_compiler", Package, Compiler) :- node_compiler(Package, Compiler). attr("node_compiler", Package, Compiler) :- node_compiler(Package, Compiler).
attr("depends_on", Package, Dependency, Type) :- depends_on(Package, Dependency, Type). 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). :- node_compiler_version(Package, Compiler, Version).
attr("node_compiler_version_satisfies", Package, Compiler, Version) attr("node_compiler_version_satisfies", Package, Compiler, Version)
:- 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. % do not warn if generated program contains none of these.
#defined depends_on/3. #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), variant(Package, Variant) :- variant_condition(ID, Package, Variant),
condition_holds(ID). 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 % 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) 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), :- 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'. % warnings like 'info: atom does not occur in any rule head'.
#defined variant/2. #defined variant/2.
#defined variant_sticky/2. #defined variant_sticky/2.
#defined variant_propagate/2.
#defined variant_set/3. #defined variant_set/3.
#defined variant_condition/3. #defined variant_condition/3.
#defined variant_single_value/2. #defined variant_single_value/2.
@ -1005,29 +1030,37 @@ compiler_weight(Package, 100)
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
% Compiler flags % Compiler flags
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
% propagate flags when compilers match
inherit_flags(Package, Dependency) % propagate flags when compiler match
:- depends_on(Package, Dependency), can_inherit_flags(Package, Dependency, FlagType)
:- path(Package, Dependency),
node_compiler(Package, Compiler), node_compiler(Package, Compiler),
node_compiler(Dependency, Compiler), node_compiler(Dependency, Compiler),
not node_flag_set(Dependency, FlagType, _),
compiler(Compiler), flag_type(FlagType). compiler(Compiler), flag_type(FlagType).
node_flag_inherited(Dependency, FlagType, Flag) node_flag_inherited(Dependency, FlagType, Flag)
:- node_flag_set(Package, FlagType, Flag), inherit_flags(Package, Dependency). :- node_flag_set(Package, FlagType, Flag), can_inherit_flags(Package, Dependency, FlagType),
node_flag_inherited(Dependency, FlagType, Flag) node_flag_propagate(Package, FlagType).
:- node_flag_inherited(Package, FlagType, Flag), % Insure propagation
inherit_flags(Package, Dependency). :- node_flag_inherited(Package, FlagType, Flag),
can_inherit_flags(Package, Dependency, FlagType),
node_flag_propagate(Package, FlagType).
% node with flags set to anythingg is "set" error(0, "{0} and dependency {1} cannot both propagate compiler flags '{2}'", Package, Dependency, FlagType) :-
node_flag_set(Package) :- node_flag_set(Package, _, _). node(Dependency),
node_flag_propagate(Package, FlagType),
node_flag_propagate(Dependency, FlagType),
path(Package, Dependency).
% remember where flags came from % remember where flags came from
node_flag_source(Package, Package) :- node_flag_set(Package). node_flag_source(Package, FlagType, Package) :- node_flag_set(Package, FlagType, _).
node_flag_source(Dependency, Q) node_flag_source(Dependency, FlagType, Q)
:- node_flag_source(Package, Q), inherit_flags(Package, Dependency). :- 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 % compiler flags from compilers.yaml are put on nodes if compiler matches
node_flag(Package, FlagType, Flag) node_flag(Package, FlagType, Flag)
:- not node_flag_set(Package), :- not node_flag_set(Package, FlagType, _),
compiler_version_flag(Compiler, Version, FlagType, Flag), compiler_version_flag(Compiler, Version, FlagType, Flag),
node_compiler_version(Package, Compiler, Version), node_compiler_version(Package, Compiler, Version),
flag_type(FlagType), flag_type(FlagType),
@ -1035,7 +1068,7 @@ node_flag(Package, FlagType, Flag)
compiler_version(Compiler, Version). compiler_version(Compiler, Version).
node_flag_compiler_default(Package) node_flag_compiler_default(Package)
:- not node_flag_set(Package), :- not node_flag_set(Package, FlagType, _),
compiler_version_flag(Compiler, Version, FlagType, Flag), compiler_version_flag(Compiler, Version, FlagType, Flag),
node_compiler_version(Package, Compiler, Version), node_compiler_version(Package, Compiler, Version),
flag_type(FlagType), flag_type(FlagType),
@ -1054,6 +1087,7 @@ no_flags(Package, FlagType)
#defined compiler_version_flag/4. #defined compiler_version_flag/4.
#defined node_flag/3. #defined node_flag/3.
#defined node_flag_set/3. #defined node_flag_set/3.
#defined node_flag_propagate/2.
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------

View File

@ -23,7 +23,7 @@
#show node_compiler_version/3. #show node_compiler_version/3.
#show node_flag/3. #show node_flag/3.
#show node_flag_compiler_default/1. #show node_flag_compiler_default/1.
#show node_flag_source/2. #show node_flag_source/3.
#show no_flags/2. #show no_flags/2.
#show external_spec_selected/2. #show external_spec_selected/2.
#show version_equivalent/3. #show version_equivalent/3.

View File

@ -34,6 +34,8 @@
built in debug mode for your package to work, you can require it by 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 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. 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. 4. The name of the compiler to build with.
@ -51,8 +53,10 @@
spec-list = { spec [ dep-list ] } spec-list = { spec [ dep-list ] }
dep_list = { ^ spec } dep_list = { ^ spec }
spec = id [ options ] spec = id [ options ]
options = { @version-list | +variant | -variant | ~variant | options = { @version-list | ++variant | +variant |
%compiler | arch=architecture | [ flag ]=value} --variant | -variant | ~~variant | ~variant |
variant=value | variant==value | %compiler |
arch=architecture | [ flag ]==value | [ flag ]=value}
flag = { cflags | cxxflags | fcflags | fflags | cppflags | flag = { cflags | cxxflags | fcflags | fflags | cppflags |
ldflags | ldlibs } ldflags | ldlibs }
variant = id variant = id
@ -124,6 +128,7 @@
"SpecParser", "SpecParser",
"parse", "parse",
"SpecParseError", "SpecParseError",
"ArchitecturePropagationError",
"DuplicateDependencyError", "DuplicateDependencyError",
"DuplicateCompilerSpecError", "DuplicateCompilerSpecError",
"UnsupportedCompilerError", "UnsupportedCompilerError",
@ -148,15 +153,16 @@
is_windows = sys.platform == "win32" is_windows = sys.platform == "win32"
#: Valid pattern for an identifier in Spack #: Valid pattern for an identifier in Spack
identifier_re = r"\w[\w-]*" identifier_re = r"\w[\w-]*"
compiler_color = "@g" #: color for highlighting compilers compiler_color = "@g" #: color for highlighting compilers
version_color = "@c" #: color for highlighting versions version_color = "@c" #: color for highlighting versions
architecture_color = "@m" #: color for highlighting architectures architecture_color ="@m" #: color for highlighting architectures
enabled_variant_color = "@B" #: color for highlighting enabled variants enabled_variant_color = "@B" #: color for highlighting enabled variants
disabled_variant_color = "@r" #: color for highlighting disabled varaints disabled_variant_color = "r" #: color for highlighting disabled varaints
dependency_color = "@." #: color for highlighting dependencies dependency_color = "@." #: color for highlighting dependencies
hash_color = "@K" #: color for highlighting package hashes hash_color = "@K" #: color for highlighting package hashes
#: This map determines the coloring of specs when using color output. #: This map determines the coloring of specs when using color output.
#: We make the fields different colors to enhance readability. #: We make the fields different colors to enhance readability.
@ -737,6 +743,22 @@ def flip(self):
return DependencySpec(parent=self.spec, spec=self.parent, deptypes=self.deptypes) 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"] _valid_compiler_flags = ["cflags", "cxxflags", "fflags", "ldflags", "ldlibs", "cppflags"]
@ -752,9 +774,20 @@ def satisfies(self, other, strict=False):
if strict or (self.spec and self.spec._concrete): 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) return all(f in self and set(self[f]) == set(other[f]) for f in other)
else: else:
return all( if not all(
set(self[f]) == set(other[f]) for f in other if (other[f] != [] and f in self) 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): def constrain(self, other):
"""Add all flags in other that aren't in self to self. """Add all flags in other that aren't in self to self.
@ -775,6 +808,14 @@ def constrain(self, other):
elif k not in self: elif k not in self:
self[k] = other[k] self[k] = other[k]
changed = True 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 return changed
@staticmethod @staticmethod
@ -782,11 +823,41 @@ def valid_compiler_flags():
return _valid_compiler_flags return _valid_compiler_flags
def copy(self): def copy(self):
clone = FlagMap(None) clone = FlagMap(self.spec)
for name, value in self.items(): for name, compiler_flag in self.items():
clone[name] = value clone[name] = compiler_flag
return clone 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): def _cmp_iter(self):
for k, v in sorted(self.items()): for k, v in sorted(self.items()):
yield k yield k
@ -803,7 +874,11 @@ def __str__(self):
return ( return (
cond_symbol cond_symbol
+ " ".join( + " ".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 + cond_symbol
) )
@ -1405,10 +1480,26 @@ def _add_versions(self, version_list):
for version in version_list: for version in version_list:
self.versions.add(version) 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. """Called by the parser to add a known flag.
Known flags currently include "arch" 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() valid_flags = FlagMap.valid_compiler_flags()
if name == "arch" or name == "architecture": if name == "arch" or name == "architecture":
parts = tuple(value.split("-")) parts = tuple(value.split("-"))
@ -1422,16 +1513,18 @@ def _add_flag(self, name, value):
self._set_architecture(target=value) self._set_architecture(target=value)
elif name in valid_flags: elif name in valid_flags:
assert self.compiler_flags is not None 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: else:
# FIXME: # FIXME:
# All other flags represent variants. 'foo=true' and 'foo=false' # All other flags represent variants. 'foo=true' and 'foo=false'
# map to '+foo' and '~foo' respectively. As such they need a # map to '+foo' and '~foo' respectively. As such they need a
# BoolValuedVariant instance. # BoolValuedVariant instance.
if str(value).upper() == "TRUE" or str(value).upper() == "FALSE": 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: else:
self.variants[name] = vt.AbstractVariant(name, value) self.variants[name] = vt.AbstractVariant(name, value, propagate)
def _set_architecture(self, **kwargs): def _set_architecture(self, **kwargs):
"""Called by the parser to set the architecture.""" """Called by the parser to set the architecture."""
@ -1783,7 +1876,14 @@ def to_node_dict(self, hash=ht.dag_hash):
params = syaml.syaml_dict(sorted(v.yaml_entry() for _, v in self.variants.items())) 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: if params:
d["parameters"] = params d["parameters"] = params
@ -2008,11 +2108,13 @@ def from_node_dict(node):
spec.compiler = None spec.compiler = None
if "parameters" in node: if "parameters" in node:
for name, value in node["parameters"].items(): for name, values in node["parameters"].items():
if name in _valid_compiler_flags: 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: 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: elif "variants" in node:
for name, value in node["variants"].items(): for name, value in node["variants"].items():
spec.variants[name] = vt.MultiValuedVariant.from_node_dict(name, value) spec.variants[name] = vt.MultiValuedVariant.from_node_dict(name, value)
@ -4122,7 +4224,7 @@ def write_attribute(spec, attribute, color):
except AttributeError: except AttributeError:
parent = ".".join(parts[:idx]) parent = ".".join(parts[:idx])
m = "Attempted to format attribute %s." % attribute 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) raise SpecFormatStringError(m)
if isinstance(current, vn.VersionList): if isinstance(current, vn.VersionList):
if current == _any_version: if current == _any_version:
@ -4859,7 +4961,7 @@ def __missing__(self, key):
#: These are possible token types in the spec grammar. #: 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) #: Regex for fully qualified spec names. (e.g., builtin.hdf5)
spec_id_re = r"\w[\w.-]*" spec_id_re = r"\w[\w.-]*"
@ -4882,15 +4984,19 @@ def __init__(self):
( (
r"\@([\w.\-]*\s*)*(\s*\=\s*\w[\w.\-]*)?", r"\@([\w.\-]*\s*)*(\s*\=\s*\w[\w.\-]*)?",
lambda scanner, val: self.token(VER, val), lambda scanner, val: self.token(VER, val),
), ),
(r"\:", lambda scanner, val: self.token(COLON, val)), (r"\:", lambda scanner, val: self.token(COLON, val)),
(r"\,", lambda scanner, val: self.token(COMMA, val)), (r"\,", lambda scanner, val: self.token(COMMA, val)),
(r"\^", lambda scanner, val: self.token(DEP, val)), (r"\^", lambda scanner, val: self.token(DEP, val)),
(r"\+", lambda scanner, val: self.token(ON, val)), (r"\+\+", lambda scanner, val: self.token(D_ON, val)),
(r"\-", lambda scanner, val: self.token(OFF, val)), (r"\+", lambda scanner, val: self.token(ON, val)),
(r"\~", lambda scanner, val: self.token(OFF, val)), (r"\-\-", lambda scanner, val: self.token(D_OFF, val)),
(r"\%", lambda scanner, val: self.token(PCT, val)), (r"\-", lambda scanner, val: self.token(OFF, val)),
(r"\=", lambda scanner, val: self.token(EQ, 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 # Filenames match before identifiers, so no initial filename
# component is parsed as a spec (e.g., in subdir/spec.yaml/json) # component is parsed as a spec (e.g., in subdir/spec.yaml/json)
(filename_reg, lambda scanner, v: self.token(FILE, v)), (filename_reg, lambda scanner, v: self.token(FILE, v)),
@ -4901,7 +5007,7 @@ def __init__(self):
(spec_id_re, lambda scanner, val: self.token(ID, val)), (spec_id_re, lambda scanner, val: self.token(ID, val)),
(r"\s+", lambda scanner, val: None), (r"\s+", lambda scanner, val: None),
], ],
[EQ], [D_EQ, EQ],
[ [
(r"[\S].*", lambda scanner, val: self.token(VAL, val)), (r"[\S].*", lambda scanner, val: self.token(VAL, val)),
(r"\s+", lambda scanner, val: None), (r"\s+", lambda scanner, val: None),
@ -4946,7 +5052,7 @@ def do_parse(self):
if self.accept(ID): if self.accept(ID):
self.previous = self.token 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 # We're parsing an anonymous spec beginning with a
# key-value pair. # key-value pair.
if not specs: if not specs:
@ -5023,10 +5129,13 @@ def do_parse(self):
else: else:
# If the next token can be part of a valid anonymous spec, # If the next token can be part of a valid anonymous spec,
# create the anonymous spec # create the anonymous spec
if self.next.type in (VER, ON, OFF, PCT): if self.next.type in (VER, ON, D_ON, OFF, D_OFF, PCT):
# Raise an error if the previous spec is already concrete # Raise an error if the previous spec is already
if specs and specs[-1].concrete: # concrete (assigned by hash)
raise RedundantSpecError(specs[-1], "compiler, version, " "or variant") if specs and specs[-1]._hash:
raise RedundantSpecError(specs[-1],
'compiler, version, '
'or variant')
specs.append(self.spec(None)) specs.append(self.spec(None))
else: else:
self.unexpected_token() self.unexpected_token()
@ -5135,22 +5244,36 @@ def spec(self, name):
vlist = self.version_list() vlist = self.version_list()
spec._add_versions(vlist) 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): elif self.accept(ON):
name = self.variant() 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): elif self.accept(OFF):
name = self.variant() name = self.variant()
spec.variants[name] = vt.BoolValuedVariant(name, False) spec.variants[name] = vt.BoolValuedVariant(name, False, propagate=False)
elif self.accept(PCT): elif self.accept(PCT):
spec._set_compiler(self.compiler()) spec._set_compiler(self.compiler())
elif self.accept(ID): elif self.accept(ID):
self.previous = self.token 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) 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 self.previous = None
else: else:
# We've found the start of a new spec. Go back to do_parse # We've found the start of a new spec. Go back to do_parse
@ -5313,6 +5436,12 @@ def long_message(self):
) )
class ArchitecturePropagationError(spack.error.SpecError):
"""Raised when the double equal symbols are used to assign
the spec's architecture.
"""
class DuplicateDependencyError(spack.error.SpecError): class DuplicateDependencyError(spack.error.SpecError):
"""Raised when the same dependency occurs in a spec twice.""" """Raised when the same dependency occurs in a spec twice."""

View File

@ -46,21 +46,27 @@ def test_negative_integers_not_allowed_for_parallel_jobs(job_parser):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"specs,cflags,negated_variants", "specs,cflags,propagation,negated_variants",
[ [
(['coreutils cflags="-O3 -g"'], ["-O3", "-g"], []), (['coreutils cflags="-O3 -g"'], ["-O3", "-g"], [False, False], []),
(["coreutils", "cflags=-O3 -g"], ["-O3"], ["g"]), (['coreutils cflags=="-O3 -g"'], ["-O3", "-g"], [True, True], []),
(["coreutils", "cflags=-O3", "-g"], ["-O3"], ["g"]), (["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") @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) spec_list = spack.cmd.parse_specs(specs)
assert len(spec_list) == 1 assert len(spec_list) == 1
s = spec_list.pop() 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 assert list(s.variants.keys()) == negated_variants
for v in negated_variants: for v in negated_variants:
assert "~{0}".format(v) in s assert "~{0}".format(v) in s

View File

@ -2,6 +2,7 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details. # Spack Project Developers. See the top-level COPYRIGHT file for details.
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import posixpath import posixpath
import sys import sys
@ -81,6 +82,7 @@ def check_concretize(abstract_spec):
"mpich", "mpich",
# compiler flags # compiler flags
'mpich cppflags="-O3"', 'mpich cppflags="-O3"',
'mpich cppflags=="-O3"',
# with virtual # with virtual
"mpileaks ^mpi", "mpileaks ^mpi",
"mpileaks ^mpi@:1.1", "mpileaks ^mpi@:1.1",
@ -323,6 +325,33 @@ def test_different_compilers_get_different_flags(self):
assert set(client.compiler_flags["fflags"]) == set(["-O0", "-g"]) assert set(client.compiler_flags["fflags"]) == set(["-O0", "-g"])
assert not set(cmake.compiler_flags["fflags"]) 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): def test_architecture_inheritance(self):
"""test_architecture_inheritance is likely to fail with an """test_architecture_inheritance is likely to fail with an
UnavailableCompilerVersionError if the architecture is concretized UnavailableCompilerVersionError if the architecture is concretized
@ -401,6 +430,24 @@ def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(self):
with pytest.raises(spack.error.SpackError): with pytest.raises(spack.error.SpackError):
s.concretize() 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") @pytest.mark.skipif(sys.platform == "win32", reason="No Compiler for Arch on Win")
def test_no_matching_compiler_specs(self, mock_low_high_config): def test_no_matching_compiler_specs(self, mock_low_high_config):
# only relevant when not building compilers as needed # only relevant when not building compilers as needed

View File

@ -247,16 +247,23 @@ def test_satisfies_virtual_dependency_versions(self):
def test_satisfies_matching_variant(self): 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", "mpich~foo")
check_satisfies("mpich~~foo", "mpich~~foo")
check_satisfies("mpich foo=1", "mpich foo=1") check_satisfies("mpich foo=1", "mpich foo=1")
check_satisfies("mpich foo==1", "mpich foo==1")
# confirm that synonymous syntax works correctly # confirm that synonymous syntax works correctly
check_satisfies("mpich+foo", "mpich foo=True") 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==true", "mpich++foo")
check_satisfies("mpich~foo", "mpich foo=FALSE") 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==False", "mpich~foo")
check_satisfies("mpich foo=*", "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): def test_satisfies_multi_value_variant(self):
# Check quoting # Check quoting
@ -295,6 +302,7 @@ def test_satisfies_single_valued_variant(self):
# Assert that an autospec generated from a literal # Assert that an autospec generated from a literal
# gives the right result for a single valued variant # gives the right result for a single valued variant
assert "foobar=bar" in a assert "foobar=bar" in a
assert "foobar==bar" in a
assert "foobar=baz" not in a assert "foobar=baz" not in a
assert "foobar=fee" not in a assert "foobar=fee" not in a
@ -415,21 +423,32 @@ def test_unsatisfiable_variants(self):
check_satisfies("mpich", "mpich+foo", False) check_satisfies("mpich", "mpich+foo", False)
check_satisfies("mpich", "mpich~foo", False) check_satisfies("mpich", "mpich~foo", False)
check_satisfies("mpich", "mpich foo=1", 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: # 'mpich' is concrete:
check_unsatisfiable("mpich", "mpich+foo", True) check_unsatisfiable("mpich", "mpich+foo", True)
check_unsatisfiable("mpich", "mpich~foo", True) check_unsatisfiable("mpich", "mpich~foo", True)
check_unsatisfiable("mpich", "mpich foo=1", 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): def test_unsatisfiable_variant_mismatch(self):
# No matchi in specs # No matchi in specs
check_unsatisfiable("mpich~foo", "mpich+foo") check_unsatisfiable("mpich~foo", "mpich+foo")
check_unsatisfiable("mpich+foo", "mpich~foo") check_unsatisfiable("mpich+foo", "mpich~foo")
check_unsatisfiable("mpich foo=True", "mpich foo=False") 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): def test_satisfies_matching_compiler_flag(self):
check_satisfies('mpich cppflags="-O3"', 'mpich cppflags="-O3"') check_satisfies('mpich cppflags="-O3"', 'mpich cppflags="-O3"')
check_satisfies('mpich cppflags="-O3 -Wall"', 'mpich cppflags="-O3 -Wall"') 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): def test_satisfies_unconstrained_compiler_flag(self):
# only asked for mpich, no constraints. Any will do. # only asked for mpich, no constraints. Any will do.
@ -453,8 +472,9 @@ def test_copy_satisfies_transitive(self):
assert copy[s.name].satisfies(s) assert copy[s.name].satisfies(s)
def test_unsatisfiable_compiler_flag_mismatch(self): 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="-O2"')
check_unsatisfiable('mpich cppflags="-O3"', 'mpich cppflags=="-O3"')
def test_satisfies_virtual(self): def test_satisfies_virtual(self):
# Don't use check_satisfies: it checks constrain() too, and # Don't use check_satisfies: it checks constrain() too, and
@ -554,6 +574,12 @@ def test_constrain_variants(self):
check_constrain("libelf+debug~foo", "libelf+debug", "libelf~foo") 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++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): def test_constrain_multi_value_variant(self):
check_constrain( check_constrain(
'multivalue-variant foo="bar,baz"', 'multivalue-variant foo="bar,baz"',
@ -582,6 +608,17 @@ def test_constrain_compiler_flags(self):
'libelf cflags="-O3" cppflags="-Wall"', '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): def test_constrain_architecture(self):
check_constrain( check_constrain(
"libelf target=default_target os=default_os", "libelf target=default_target os=default_os",
@ -620,6 +657,7 @@ def test_constrain_changed(self):
check_constrain_changed("libelf", "~debug") check_constrain_changed("libelf", "~debug")
check_constrain_changed("libelf", "debug=2") check_constrain_changed("libelf", "debug=2")
check_constrain_changed("libelf", 'cppflags="-O3"') check_constrain_changed("libelf", 'cppflags="-O3"')
check_constrain_changed("libelf", 'cppflags=="-O3"')
platform = spack.platforms.host() platform = spack.platforms.host()
check_constrain_changed("libelf", "target=" + platform.target("default_target").name) check_constrain_changed("libelf", "target=" + platform.target("default_target").name)
@ -636,6 +674,7 @@ def test_constrain_not_changed(self):
check_constrain_not_changed("libelf debug=2", "debug=2") check_constrain_not_changed("libelf debug=2", "debug=2")
check_constrain_not_changed("libelf debug=2", "debug=*") 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"')
check_constrain_not_changed('libelf cppflags=="-O3"', 'cppflags=="-O3"')
platform = spack.platforms.host() platform = spack.platforms.host()
default_target = platform.target("default_target").name default_target = platform.target("default_target").name
@ -791,7 +830,7 @@ def test_spec_formatting_escapes(self):
spec.format(fmt_str) spec.format(fmt_str)
def test_spec_deprecated_formatting(self): def test_spec_deprecated_formatting(self):
spec = Spec("libelf cflags=-O2") spec = Spec("libelf cflags==-O2")
spec.concretize() spec.concretize()
# Since the default is the full spec see if the string rep of # Since the default is the full spec see if the string rep of

View File

@ -245,8 +245,9 @@ class AbstractVariant(object):
values. values.
""" """
def __init__(self, name, value): def __init__(self, name, value, propagate=False):
self.name = name self.name = name
self.propagate = propagate
# Stores 'value' after a bit of massaging # Stores 'value' after a bit of massaging
# done by the property setter # done by the property setter
@ -334,7 +335,7 @@ def copy(self):
>>> assert a == b >>> assert a == b
>>> assert a is not 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 @implicit_variant_conversion
def satisfies(self, other): def satisfies(self, other):
@ -401,6 +402,8 @@ def __repr__(self):
return "{0.__name__}({1}, {2})".format(cls, repr(self.name), repr(self._original_value)) return "{0.__name__}({1}, {2})".format(cls, repr(self.name), repr(self._original_value))
def __str__(self): 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)) return "{0}={1}".format(self.name, ",".join(str(x) for x in self.value))
@ -444,6 +447,9 @@ def __str__(self):
values_str = ",".join(x[:7] for x in self.value) values_str = ",".join(x[:7] for x in self.value)
else: else:
values_str = ",".join(str(x) for x in self.value) 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) return "{0}={1}".format(self.name, values_str)
@ -460,6 +466,8 @@ def _value_setter(self, value):
self._value = str(self._value[0]) self._value = str(self._value[0])
def __str__(self): def __str__(self):
if self.propagate:
return "{0}=={1}".format(self.name, self.value)
return "{0}={1}".format(self.name, self.value) return "{0}={1}".format(self.name, self.value)
@implicit_variant_conversion @implicit_variant_conversion
@ -523,6 +531,8 @@ def __contains__(self, item):
return item is self.value return item is self.value
def __str__(self): 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) return "{0}{1}".format("+" if self.value else "~", self.name)

View File

@ -2,6 +2,7 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details. # Spack Project Developers. See the top-level COPYRIGHT file for details.
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import sys
from spack.package import * from spack.package import *
@ -17,3 +18,9 @@ class Hypre(Package):
depends_on("lapack") depends_on("lapack")
depends_on("blas") depends_on("blas")
variant(
"shared",
default=(sys.platform != "darwin"),
description="Build shared library (disables static library)",
)

View File

@ -17,6 +17,8 @@ class Openblas(Package):
version("0.2.14", "b1190f3d3471685f17cfd1ec1d252ac9") version("0.2.14", "b1190f3d3471685f17cfd1ec1d252ac9")
version("0.2.13", "b1190f3d3471685f17cfd1ec1d252ac9") version("0.2.13", "b1190f3d3471685f17cfd1ec1d252ac9")
variant("shared", default=True, description="Build shared libraries")
# See #20019 for this conflict # See #20019 for this conflict
conflicts("%gcc@:4.4", when="@0.2.14:") conflicts("%gcc@:4.4", when="@0.2.14:")