Add a "requires" directive, extend functionality of package requirements (#36286)
Add a "require" directive to packages, which functions exactly like requirements specified in packages.yaml (uses the same fact-generation logic); update both to allow making the requirement conditional. * Packages may now use "require" to add constraints. This can be useful for something like "require(%gcc)" (where before we had to add a conflict for every compiler except gcc). * Requirements (in packages.yaml or in a "require" directive) can be conditional on a spec, e.g. "require(%gcc, when=@1.0.0)" (version 1.0.0 can only build with gcc). * Requirements may include a message which clarifies why they are needed. The concretizer assigns a high priority to errors which generate these messages (in particular over errors for unsatisfied requirements that do not produce messages, but also over a number of more-generic errors).
This commit is contained in:

committed by
GitHub

parent
d0cba2bf35
commit
0139288ced
@@ -325,42 +325,99 @@ on the command line, because it can specify constraints on packages
|
||||
is not possible to specify constraints on dependencies while also keeping
|
||||
those dependencies optional.
|
||||
|
||||
The package requirements configuration is specified in ``packages.yaml``
|
||||
keyed by package name:
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Requirements syntax
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The package requirements configuration is specified in ``packages.yaml``,
|
||||
keyed by package name and expressed using the Spec syntax. In the simplest
|
||||
case you can specify attributes that you always want the package to have
|
||||
by providing a single spec string to ``require``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
libfabric:
|
||||
require: "@1.13.2"
|
||||
|
||||
In the above example, ``libfabric`` will always build with version 1.13.2. If you
|
||||
need to compose multiple configuration scopes ``require`` accepts a list of
|
||||
strings:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
libfabric:
|
||||
require:
|
||||
- "@1.13.2"
|
||||
- "%gcc"
|
||||
|
||||
In this case ``libfabric`` will always build with version 1.13.2 **and** using GCC
|
||||
as a compiler.
|
||||
|
||||
For more complex use cases, require accepts also a list of objects. These objects
|
||||
must have either a ``any_of`` or a ``one_of`` field, containing a list of spec strings,
|
||||
and they can optionally have a ``when`` and a ``message`` attribute:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
openmpi:
|
||||
require:
|
||||
- any_of: ["~cuda", "%gcc"]
|
||||
- any_of: ["@4.1.5", "%gcc"]
|
||||
message: "in this example only 4.1.5 can build with other compilers"
|
||||
|
||||
``any_of`` is a list of specs. One of those specs must be satisfied
|
||||
and it is also allowed for the concretized spec to match more than one.
|
||||
In the above example, that means you could build ``openmpi@4.1.5%gcc``,
|
||||
``openmpi@4.1.5%clang`` or ``openmpi@3.9%gcc``, but
|
||||
not ``openmpi@3.9%clang``.
|
||||
|
||||
If a custom message is provided, and the requirement is not satisfiable,
|
||||
Spack will print the custom error message:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack spec openmpi@3.9%clang
|
||||
==> Error: in this example only 4.1.5 can build with other compilers
|
||||
|
||||
We could express a similar requirement using the ``when`` attribute:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
openmpi:
|
||||
require:
|
||||
- any_of: ["%gcc"]
|
||||
when: "@:4.1.4"
|
||||
message: "in this example only 4.1.5 can build with other compilers"
|
||||
|
||||
In the example above, if the version turns out to be 4.1.4 or less, we require the compiler to be GCC.
|
||||
For readability, Spack also allows a ``spec`` key accepting a string when there is only a single
|
||||
constraint:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
openmpi:
|
||||
require:
|
||||
- spec: "%gcc"
|
||||
when: "@:4.1.4"
|
||||
message: "in this example only 4.1.5 can build with other compilers"
|
||||
|
||||
This code snippet and the one before it are semantically equivalent.
|
||||
|
||||
Finally, instead of ``any_of`` you can use ``one_of`` which also takes a list of specs. The final
|
||||
concretized spec must match one and only one of them:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
mpich:
|
||||
require:
|
||||
- one_of: ["+cuda", "+rocm"]
|
||||
require:
|
||||
- one_of: ["+cuda", "+rocm"]
|
||||
|
||||
Requirements are expressed using Spec syntax (the same as what is provided
|
||||
to ``spack install``). In the simplest case, you can specify attributes
|
||||
that you always want the package to have by providing a single spec to
|
||||
``require``; in the above example, ``libfabric`` will always build
|
||||
with version 1.13.2.
|
||||
|
||||
You can provide a more-relaxed constraint and allow the concretizer to
|
||||
choose between a set of options using ``any_of`` or ``one_of``:
|
||||
|
||||
* ``any_of`` is a list of specs. One of those specs must be satisfied
|
||||
and it is also allowed for the concretized spec to match more than one.
|
||||
In the above example, that means you could build ``openmpi+cuda%gcc``,
|
||||
``openmpi~cuda%clang`` or ``openmpi~cuda%gcc`` (in the last case,
|
||||
note that both specs in the ``any_of`` for ``openmpi`` are
|
||||
satisfied).
|
||||
* ``one_of`` is also a list of specs, and the final concretized spec
|
||||
must match exactly one of them. In the above example, that means
|
||||
you could build ``mpich+cuda`` or ``mpich+rocm`` but not
|
||||
``mpich+cuda+rocm`` (note the current package definition for
|
||||
``mpich`` already includes a conflict, so this is redundant but
|
||||
still demonstrates the concept).
|
||||
In the example above, that means you could build ``mpich+cuda`` or ``mpich+rocm`` but not ``mpich+cuda+rocm``.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -368,6 +425,13 @@ choose between a set of options using ``any_of`` or ``one_of``:
|
||||
preference: items that appear earlier in the list are preferred
|
||||
(note that these preferences can be ignored in favor of others).
|
||||
|
||||
.. note::
|
||||
|
||||
When using a conditional requirement, Spack is allowed to actively avoid the triggering
|
||||
condition (the ``when=...`` spec) if that leads to a concrete spec with better scores in
|
||||
the optimization criteria. To check the current optimization criteria and their
|
||||
priorities you can run ``spack solve zlib``.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Setting default requirements
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@@ -2669,9 +2669,9 @@ to allow dependencies to run correctly:
|
||||
|
||||
.. _packaging_conflicts:
|
||||
|
||||
---------
|
||||
Conflicts
|
||||
---------
|
||||
--------------------------
|
||||
Conflicts and requirements
|
||||
--------------------------
|
||||
|
||||
Sometimes packages have known bugs, or limitations, that would prevent them
|
||||
to build e.g. against other dependencies or with certain compilers. Spack
|
||||
@@ -2681,27 +2681,51 @@ Adding the following to a package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
conflicts("%intel", when="@:1.2",
|
||||
msg="<myNicePackage> <= v1.2 cannot be built with Intel ICC, "
|
||||
"please use a newer release.")
|
||||
conflicts(
|
||||
"%intel",
|
||||
when="@:1.2",
|
||||
msg="<myNicePackage> <= v1.2 cannot be built with Intel ICC, "
|
||||
"please use a newer release."
|
||||
)
|
||||
|
||||
we express the fact that the current package *cannot be built* with the Intel
|
||||
compiler when we are trying to install a version "<=1.2". The ``when`` argument
|
||||
can be omitted, in which case the conflict will always be active.
|
||||
Conflicts are always evaluated after the concretization step has been performed,
|
||||
and if any match is found a detailed error message is shown to the user.
|
||||
You can add an additional message via the ``msg=`` parameter to a conflict that
|
||||
provideds more specific instructions for users.
|
||||
compiler when we are trying to install a version "<=1.2".
|
||||
|
||||
Similarly, packages that only build on a subset of platforms can use the
|
||||
``conflicts`` directive to express that limitation, for example:
|
||||
The ``when`` argument can be omitted, in which case the conflict will always be active.
|
||||
|
||||
An optional custom error message can be added via the ``msg=`` parameter, and will be printed
|
||||
by Spack in case the conflict cannot be avoided and leads to a concretization error.
|
||||
|
||||
Sometimes, packages allow only very specific choices and they can't use the rest. In those cases
|
||||
the ``requires`` directive can be used:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
for platform in ["cray", "darwin", "windows"]:
|
||||
conflicts("platform={0}".format(platform), msg="Only 'linux' is supported")
|
||||
requires(
|
||||
"%apple-clang",
|
||||
when="platform=darwin",
|
||||
msg="<myNicePackage> builds only with Apple-Clang on Darwin"
|
||||
)
|
||||
|
||||
In the example above, our package can only be built with Apple-Clang on Darwin.
|
||||
The ``requires`` directive is effectively the opposite of the ``conflicts`` directive, and takes
|
||||
the same optional ``when`` and ``msg`` arguments.
|
||||
|
||||
If a package needs to express more complex requirements, involving more than a single spec,
|
||||
that can also be done using the ``requires`` directive. To express that a package can be built
|
||||
either with GCC or with Clang we can write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
requires(
|
||||
"%gcc", "%clang",
|
||||
policy="one_of"
|
||||
msg="<myNicePackage> builds only with GCC or Clang"
|
||||
)
|
||||
|
||||
When using multiple specs in a ``requires`` directive, it is advised to set the ``policy=``
|
||||
argument explicitly. That argument can take either the value ``any_of`` or the value ``one_of``,
|
||||
and the semantic is the same as for :ref:`package-requirements`.
|
||||
|
||||
.. _packaging_extensions:
|
||||
|
||||
|
Reference in New Issue
Block a user