summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHarmen Stoppels <me@harmenstoppels.nl>2023-11-06 19:22:29 +0100
committerGitHub <noreply@github.com>2023-11-06 10:22:29 -0800
commit1235084c20f1efabbca680c03f9f4dc023b44c5d (patch)
tree9ffad09e95686a17ca7cc030a5bc4eb1530dd1b5 /lib
parentb5538960c325a849bddc35506e4c219cee40a1d8 (diff)
downloadspack-1235084c20f1efabbca680c03f9f4dc023b44c5d.tar.gz
spack-1235084c20f1efabbca680c03f9f4dc023b44c5d.tar.bz2
spack-1235084c20f1efabbca680c03f9f4dc023b44c5d.tar.xz
spack-1235084c20f1efabbca680c03f9f4dc023b44c5d.zip
Introduce `default_args` context manager (#39964)
This adds a rather trivial context manager that lets you deduplicate repeated arguments in directives, e.g. ```python depends_on("py-x@1", when="@1", type=("build", "run")) depends_on("py-x@2", when="@2", type=("build", "run")) depends_on("py-x@3", when="@3", type=("build", "run")) depends_on("py-x@4", when="@4", type=("build", "run")) ``` can be condensed to ```python with default_args(type=("build", "run")): depends_on("py-x@1", when="@1") depends_on("py-x@2", when="@2") depends_on("py-x@3", when="@3") depends_on("py-x@4", when="@4") ``` The advantage is it's clear for humans, the downside it's less clear for type checkers due to type erasure.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/packaging_guide.rst50
-rw-r--r--lib/spack/spack/directives.py19
-rw-r--r--lib/spack/spack/multimethod.py8
-rw-r--r--lib/spack/spack/package.py2
4 files changed, 77 insertions, 2 deletions
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index 3b05ce8932..3dd1c7952d 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -3503,6 +3503,56 @@ is equivalent to:
Constraints from nested context managers are also combined together, but they are rarely
needed or recommended.
+.. _default_args:
+
+------------------------
+Common default arguments
+------------------------
+
+Similarly, if directives have a common set of default arguments, you can
+group them together in a ``with default_args()`` block:
+
+.. code-block:: python
+
+ class PyExample(PythonPackage):
+
+ with default_args(type=("build", "run")):
+ depends_on("py-foo")
+ depends_on("py-foo@2:", when="@2:")
+ depends_on("py-bar")
+ depends_on("py-bz")
+
+The above is short for:
+
+.. code-block:: python
+
+ class PyExample(PythonPackage):
+
+ depends_on("py-foo", type=("build", "run"))
+ depends_on("py-foo@2:", when="@2:", type=("build", "run"))
+ depends_on("py-bar", type=("build", "run"))
+ depends_on("py-bz", type=("build", "run"))
+
+.. note::
+
+ The ``with when()`` context manager is composable, while ``with default_args()``
+ merely overrides the default. For example:
+
+ .. code-block:: python
+
+ with default_args(when="+feature"):
+ depends_on("foo")
+ depends_on("bar")
+ depends_on("baz", when="+baz")
+
+ is equivalent to:
+
+ .. code-block:: python
+
+ depends_on("foo", when="+feature")
+ depends_on("bar", when="+feature")
+ depends_on("baz", when="+baz") # Note: not when="+feature+baz"
+
.. _install-method:
------------------
diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
index bfd57fc6f9..fcd72d5bfc 100644
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -137,6 +137,7 @@ class DirectiveMeta(type):
_directive_dict_names: Set[str] = set()
_directives_to_be_executed: List[str] = []
_when_constraints_from_context: List[str] = []
+ _default_args: List[dict] = []
def __new__(cls, name, bases, attr_dict):
# Initialize the attribute containing the list of directives
@@ -200,6 +201,16 @@ class DirectiveMeta(type):
return DirectiveMeta._when_constraints_from_context.pop()
@staticmethod
+ def push_default_args(default_args):
+ """Push default arguments"""
+ DirectiveMeta._default_args.append(default_args)
+
+ @staticmethod
+ def pop_default_args():
+ """Pop default arguments"""
+ return DirectiveMeta._default_args.pop()
+
+ @staticmethod
def directive(dicts=None):
"""Decorator for Spack directives.
@@ -259,7 +270,13 @@ class DirectiveMeta(type):
directive_names.append(decorated_function.__name__)
@functools.wraps(decorated_function)
- def _wrapper(*args, **kwargs):
+ def _wrapper(*args, **_kwargs):
+ # First merge default args with kwargs
+ kwargs = dict()
+ for default_args in DirectiveMeta._default_args:
+ kwargs.update(default_args)
+ kwargs.update(_kwargs)
+
# Inject when arguments from the context
if DirectiveMeta._when_constraints_from_context:
# Check that directives not yet supporting the when= argument
diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py
index d3453beb79..0c66117242 100644
--- a/lib/spack/spack/multimethod.py
+++ b/lib/spack/spack/multimethod.py
@@ -26,6 +26,7 @@ so package authors should use their judgement.
"""
import functools
import inspect
+from contextlib import contextmanager
from llnl.util.lang import caller_locals
@@ -271,6 +272,13 @@ class when:
spack.directives.DirectiveMeta.pop_from_context()
+@contextmanager
+def default_args(**kwargs):
+ spack.directives.DirectiveMeta.push_default_args(kwargs)
+ yield
+ spack.directives.DirectiveMeta.pop_default_args()
+
+
class MultiMethodError(spack.error.SpackError):
"""Superclass for multimethod dispatch errors"""
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 9bf01be5d4..c537a7103a 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -85,7 +85,7 @@ from spack.installer import (
UpstreamPackageError,
)
from spack.mixins import filter_compiler_wrappers
-from spack.multimethod import when
+from spack.multimethod import default_args, when
from spack.package_base import (
DependencyConflictError,
build_system_flags,