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:
Massimiliano Culpo
2023-05-08 19:12:26 +02:00
committed by GitHub
parent d0cba2bf35
commit 0139288ced
14 changed files with 627 additions and 135 deletions

View File

@@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -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: