Multi-valued variants: better support for combinations (#9481)
This enforces conventions that allow for correct handling of multi-valued variants where specifying no value is an option, and adds convenience functionality for specifying multi-valued variants with conflicting sets of values. This also adds a notion of "feature values" for variants, which are those that are understood by the build system (e.g. those that would appear as configure options). In more detail: * Add documentation on variants to the packaging guide * Forbid usage of '' or None as a possible variant value, in particular as a default. To indicate choosing no value, the user must explicitly define an option like 'none'. Without this, multi-valued variants with default set to None were not parsable from the command line (Fixes #6314) * Add "disjoint_sets" function to support the declaration of multi-valued variants with conflicting sets of options. For example a variant "foo" with possible values "a", "b", and "c" where "c" is exclusive of the other values ("foo=a,b" and "foo=c" are valid but "foo=a,c" is not). * Add "any_combination_of" function to support the declaration of multi-valued variants where it is valid to choose none of the values. This automatically defines "none" as an option (exclusive with all other choices); this value does not appear when iterating over the variant's values, for example in "with_or_without" (which constructs autotools option strings from variant values). * The "disjoint_sets" and "any_combination_of" methods return an object which tracks the possible values. It is also possible to indicate that some of these values do not correspond to options understood by the package's build system, such that methods like "with_or_without" will not define options for those values (this occurs automatically for "none") * Add documentation for usage of new functions for specifying multi-valued variants
This commit is contained in:
committed by
Peter Scheibel
parent
051057caa3
commit
c4521535e7
@@ -1077,6 +1077,174 @@ Go cannot be used to fetch a particular commit or branch, it always
|
||||
downloads the head of the repository. This download method is untrusted,
|
||||
and is not recommended. Use another fetch strategy whenever possible.
|
||||
|
||||
--------
|
||||
Variants
|
||||
--------
|
||||
|
||||
Many software packages can be configured to enable optional
|
||||
features, which often come at the expense of additional dependencies or
|
||||
longer build-times. To be flexible enough and support a wide variety of
|
||||
use cases, Spack permits to expose to the end-user the ability to choose
|
||||
which features should be activated in a package at the time it is installed.
|
||||
The mechanism to be employed is the :py:func:`spack.directives.variant` directive.
|
||||
|
||||
^^^^^^^^^^^^^^^^
|
||||
Boolean variants
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
In their simplest form variants are boolean options specified at the package
|
||||
level:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Hdf5(AutotoolsPackage):
|
||||
...
|
||||
variant(
|
||||
'shared', default=True, description='Builds a shared version of the library'
|
||||
)
|
||||
|
||||
with a default value and a description of their meaning / use in the package.
|
||||
*Variants can be tested in any context where a spec constraint is expected.*
|
||||
In the example above the ``shared`` variant is tied to the build of shared dynamic
|
||||
libraries. To pass the right option at configure time we can branch depending on
|
||||
its value:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def configure_args(self):
|
||||
...
|
||||
if '+shared' in self.spec:
|
||||
extra_args.append('--enable-shared')
|
||||
else:
|
||||
extra_args.append('--disable-shared')
|
||||
extra_args.append('--enable-static-exec')
|
||||
|
||||
As explained in :ref:`basic-variants` the constraint ``+shared`` means
|
||||
that the boolean variant is set to ``True``, while ``~shared`` means it is set
|
||||
to ``False``.
|
||||
Another common example is the optional activation of an extra dependency
|
||||
which requires to use the variant in the ``when`` argument of
|
||||
:py:func:`spack.directives.depends_on`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Hdf5(AutotoolsPackage):
|
||||
...
|
||||
variant('szip', default=False, description='Enable szip support')
|
||||
depends_on('szip', when='+szip')
|
||||
|
||||
as shown in the snippet above where ``szip`` is modeled to be an optional
|
||||
dependency of ``hdf5``.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
Multi-valued variants
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If need be, Spack can go beyond Boolean variants and permit an arbitrary
|
||||
number of allowed values. This might be useful when modeling
|
||||
options that are tightly related to each other.
|
||||
The values in this case are passed to the :py:func:`spack.directives.variant`
|
||||
directive as a tuple:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Blis(Package):
|
||||
...
|
||||
variant(
|
||||
'threads', default='none', description='Multithreading support',
|
||||
values=('pthreads', 'openmp', 'none'), multi=False
|
||||
)
|
||||
|
||||
In the example above the argument ``multi`` is set to ``False`` to indicate
|
||||
that only one among all the variant values can be active at any time. This
|
||||
constraint is enforced by the parser and an error is emitted if a user
|
||||
specifies two or more values at the same time:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack spec blis threads=openmp,pthreads
|
||||
Input spec
|
||||
--------------------------------
|
||||
blis threads=openmp,pthreads
|
||||
|
||||
Concretized
|
||||
--------------------------------
|
||||
==> Error: multiple values are not allowed for variant "threads"
|
||||
|
||||
Another useful note is that *Python's* ``None`` *is not allowed as a default value*
|
||||
and therefore it should not be used to denote that no feature was selected.
|
||||
Users should instead select another value, like ``'none'``, and handle it explicitly
|
||||
within the package recipe if need be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if self.spec.variants['threads'].value == 'none':
|
||||
options.append('--no-threads')
|
||||
|
||||
In cases where multiple values can be selected at the same time ``multi`` should
|
||||
be set to ``True``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Gcc(AutotoolsPackage):
|
||||
...
|
||||
variant(
|
||||
'languages', default='c,c++,fortran',
|
||||
values=('ada', 'brig', 'c', 'c++', 'fortran',
|
||||
'go', 'java', 'jit', 'lto', 'objc', 'obj-c++'),
|
||||
multi=True,
|
||||
description='Compilers and runtime libraries to build'
|
||||
)
|
||||
|
||||
Within a package recipe a multi-valued variant is tested using a ``key=value`` syntax:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if 'languages=jit' in spec:
|
||||
options.append('--enable-host-shared')
|
||||
|
||||
"""""""""""""""""""""""""""""""""""""""""""
|
||||
Complex validation logic for variant values
|
||||
"""""""""""""""""""""""""""""""""""""""""""
|
||||
To cover complex use cases, the :py:func:`spack.directives.variant` directive
|
||||
could accept as the ``values`` argument a full-fledged object which has
|
||||
``default`` and other arguments of the directive embedded as attributes.
|
||||
|
||||
An example, already implemented in Spack's core, is :py:class:`spack.variant.DisjointSetsOfValues`.
|
||||
This class is used to implement a few convenience functions, like
|
||||
:py:func:`spack.variant.any_combination_of`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Adios(AutotoolsPackage):
|
||||
...
|
||||
variant(
|
||||
'staging',
|
||||
values=any_combination_of('flexpath', 'dataspaces'),
|
||||
description='Enable dataspaces and/or flexpath staging transports'
|
||||
)
|
||||
|
||||
that allows any combination of the specified values, and also allows the
|
||||
user to specify ``'none'`` (as a string) to choose none of them.
|
||||
The objects returned by these functions can be modified at will by chaining
|
||||
method calls to change the default value, customize the error message or
|
||||
other similar operations:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Mvapich2(AutotoolsPackage):
|
||||
...
|
||||
variant(
|
||||
'process_managers',
|
||||
description='List of the process managers to activate',
|
||||
values=disjoint_sets(
|
||||
('auto',), ('slurm',), ('hydra', 'gforker', 'remshell')
|
||||
).prohibit_empty_set().with_error(
|
||||
"'slurm' or 'auto' cannot be activated along with "
|
||||
"other process managers"
|
||||
).with_default('auto').with_non_feature_values('auto'),
|
||||
)
|
||||
|
||||
------------------------------------
|
||||
Resources (expanding extra tarballs)
|
||||
------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user