Add missing MultiMethodMeta metaclass in builders (#45879)
* Add missing MultiMethodMeta metaclass in builders and remove the Python 2 fallback option in favor of hard errors to catch similar issues going forward. The fallback option can cause about 10K stat calls due to use of `realpath` in the inspect module, depending on how deep Spack itself is nested in the file system, which is ... undesirable. * code shuffling to avoid circular import * more reshuffling * move reserved variant names into variants module
This commit is contained in:
parent
ed34dfca96
commit
d40f847497
@ -84,20 +84,6 @@ def index_by(objects, *funcs):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def caller_locals():
|
|
||||||
"""This will return the locals of the *parent* of the caller.
|
|
||||||
This allows a function to insert variables into its caller's
|
|
||||||
scope. Yes, this is some black magic, and yes it's useful
|
|
||||||
for implementing things like depends_on and provides.
|
|
||||||
"""
|
|
||||||
# Passing zero here skips line context for speed.
|
|
||||||
stack = inspect.stack(0)
|
|
||||||
try:
|
|
||||||
return stack[2][0].f_locals
|
|
||||||
finally:
|
|
||||||
del stack
|
|
||||||
|
|
||||||
|
|
||||||
def attr_setdefault(obj, name, value):
|
def attr_setdefault(obj, name, value):
|
||||||
"""Like dict.setdefault, but for objects."""
|
"""Like dict.setdefault, but for objects."""
|
||||||
if not hasattr(obj, name):
|
if not hasattr(obj, name):
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
from llnl.util import lang
|
from llnl.util import lang
|
||||||
|
|
||||||
import spack.build_environment
|
import spack.build_environment
|
||||||
|
import spack.multimethod
|
||||||
|
|
||||||
#: Builder classes, as registered by the "builder" decorator
|
#: Builder classes, as registered by the "builder" decorator
|
||||||
BUILDER_CLS = {}
|
BUILDER_CLS = {}
|
||||||
@ -295,7 +296,11 @@ def _decorator(fn):
|
|||||||
return _decorator
|
return _decorator
|
||||||
|
|
||||||
|
|
||||||
class BuilderMeta(PhaseCallbacksMeta, type(collections.abc.Sequence)): # type: ignore
|
class BuilderMeta(
|
||||||
|
PhaseCallbacksMeta,
|
||||||
|
spack.multimethod.MultiMethodMeta,
|
||||||
|
type(collections.abc.Sequence), # type: ignore
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,10 +32,9 @@ class OpenMpi(Package):
|
|||||||
"""
|
"""
|
||||||
import collections
|
import collections
|
||||||
import collections.abc
|
import collections.abc
|
||||||
import functools
|
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Set, Tuple, Union
|
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import llnl.util.lang
|
import llnl.util.lang
|
||||||
import llnl.util.tty.color
|
import llnl.util.tty.color
|
||||||
@ -48,6 +47,7 @@ class OpenMpi(Package):
|
|||||||
import spack.util.crypto
|
import spack.util.crypto
|
||||||
import spack.variant
|
import spack.variant
|
||||||
from spack.dependency import Dependency
|
from spack.dependency import Dependency
|
||||||
|
from spack.directives_meta import DirectiveError, DirectiveMeta
|
||||||
from spack.fetch_strategy import from_kwargs
|
from spack.fetch_strategy import from_kwargs
|
||||||
from spack.resource import Resource
|
from spack.resource import Resource
|
||||||
from spack.version import (
|
from spack.version import (
|
||||||
@ -80,22 +80,6 @@ class OpenMpi(Package):
|
|||||||
"redistribute",
|
"redistribute",
|
||||||
]
|
]
|
||||||
|
|
||||||
#: These are variant names used by Spack internally; packages can't use them
|
|
||||||
reserved_names = [
|
|
||||||
"arch",
|
|
||||||
"architecture",
|
|
||||||
"dev_path",
|
|
||||||
"namespace",
|
|
||||||
"operating_system",
|
|
||||||
"os",
|
|
||||||
"patches",
|
|
||||||
"platform",
|
|
||||||
"target",
|
|
||||||
]
|
|
||||||
|
|
||||||
#: Names of possible directives. This list is mostly populated using the @directive decorator.
|
|
||||||
#: Some directives leverage others and in that case are not automatically added.
|
|
||||||
directive_names = ["build_system"]
|
|
||||||
|
|
||||||
_patch_order_index = 0
|
_patch_order_index = 0
|
||||||
|
|
||||||
@ -155,219 +139,6 @@ def _make_when_spec(value: WhenType) -> Optional["spack.spec.Spec"]:
|
|||||||
return spack.spec.Spec(value)
|
return spack.spec.Spec(value)
|
||||||
|
|
||||||
|
|
||||||
class DirectiveMeta(type):
|
|
||||||
"""Flushes the directives that were temporarily stored in the staging
|
|
||||||
area into the package.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Set of all known directives
|
|
||||||
_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
|
|
||||||
# to be executed. Here we go reversed because we want to execute
|
|
||||||
# commands:
|
|
||||||
# 1. in the order they were defined
|
|
||||||
# 2. following the MRO
|
|
||||||
attr_dict["_directives_to_be_executed"] = []
|
|
||||||
for base in reversed(bases):
|
|
||||||
try:
|
|
||||||
directive_from_base = base._directives_to_be_executed
|
|
||||||
attr_dict["_directives_to_be_executed"].extend(directive_from_base)
|
|
||||||
except AttributeError:
|
|
||||||
# The base class didn't have the required attribute.
|
|
||||||
# Continue searching
|
|
||||||
pass
|
|
||||||
|
|
||||||
# De-duplicates directives from base classes
|
|
||||||
attr_dict["_directives_to_be_executed"] = [
|
|
||||||
x for x in llnl.util.lang.dedupe(attr_dict["_directives_to_be_executed"])
|
|
||||||
]
|
|
||||||
|
|
||||||
# Move things to be executed from module scope (where they
|
|
||||||
# are collected first) to class scope
|
|
||||||
if DirectiveMeta._directives_to_be_executed:
|
|
||||||
attr_dict["_directives_to_be_executed"].extend(
|
|
||||||
DirectiveMeta._directives_to_be_executed
|
|
||||||
)
|
|
||||||
DirectiveMeta._directives_to_be_executed = []
|
|
||||||
|
|
||||||
return super(DirectiveMeta, cls).__new__(cls, name, bases, attr_dict)
|
|
||||||
|
|
||||||
def __init__(cls, name, bases, attr_dict):
|
|
||||||
# The instance is being initialized: if it is a package we must ensure
|
|
||||||
# that the directives are called to set it up.
|
|
||||||
|
|
||||||
if "spack.pkg" in cls.__module__:
|
|
||||||
# Ensure the presence of the dictionaries associated with the directives.
|
|
||||||
# All dictionaries are defaultdicts that create lists for missing keys.
|
|
||||||
for d in DirectiveMeta._directive_dict_names:
|
|
||||||
setattr(cls, d, {})
|
|
||||||
|
|
||||||
# Lazily execute directives
|
|
||||||
for directive in cls._directives_to_be_executed:
|
|
||||||
directive(cls)
|
|
||||||
|
|
||||||
# Ignore any directives executed *within* top-level
|
|
||||||
# directives by clearing out the queue they're appended to
|
|
||||||
DirectiveMeta._directives_to_be_executed = []
|
|
||||||
|
|
||||||
super(DirectiveMeta, cls).__init__(name, bases, attr_dict)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def push_to_context(when_spec):
|
|
||||||
"""Add a spec to the context constraints."""
|
|
||||||
DirectiveMeta._when_constraints_from_context.append(when_spec)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pop_from_context():
|
|
||||||
"""Pop the last constraint from the context"""
|
|
||||||
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.
|
|
||||||
|
|
||||||
Spack directives allow you to modify a package while it is being
|
|
||||||
defined, e.g. to add version or dependency information. Directives
|
|
||||||
are one of the key pieces of Spack's package "language", which is
|
|
||||||
embedded in python.
|
|
||||||
|
|
||||||
Here's an example directive:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@directive(dicts='versions')
|
|
||||||
version(pkg, ...):
|
|
||||||
...
|
|
||||||
|
|
||||||
This directive allows you write:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class Foo(Package):
|
|
||||||
version(...)
|
|
||||||
|
|
||||||
The ``@directive`` decorator handles a couple things for you:
|
|
||||||
|
|
||||||
1. Adds the class scope (pkg) as an initial parameter when
|
|
||||||
called, like a class method would. This allows you to modify
|
|
||||||
a package from within a directive, while the package is still
|
|
||||||
being defined.
|
|
||||||
|
|
||||||
2. It automatically adds a dictionary called "versions" to the
|
|
||||||
package so that you can refer to pkg.versions.
|
|
||||||
|
|
||||||
The ``(dicts='versions')`` part ensures that ALL packages in Spack
|
|
||||||
will have a ``versions`` attribute after they're constructed, and
|
|
||||||
that if no directive actually modified it, it will just be an
|
|
||||||
empty dict.
|
|
||||||
|
|
||||||
This is just a modular way to add storage attributes to the
|
|
||||||
Package class, and it's how Spack gets information from the
|
|
||||||
packages to the core.
|
|
||||||
"""
|
|
||||||
global directive_names
|
|
||||||
|
|
||||||
if isinstance(dicts, str):
|
|
||||||
dicts = (dicts,)
|
|
||||||
|
|
||||||
if not isinstance(dicts, collections.abc.Sequence):
|
|
||||||
message = "dicts arg must be list, tuple, or string. Found {0}"
|
|
||||||
raise TypeError(message.format(type(dicts)))
|
|
||||||
|
|
||||||
# Add the dictionary names if not already there
|
|
||||||
DirectiveMeta._directive_dict_names |= set(dicts)
|
|
||||||
|
|
||||||
# This decorator just returns the directive functions
|
|
||||||
def _decorator(decorated_function):
|
|
||||||
directive_names.append(decorated_function.__name__)
|
|
||||||
|
|
||||||
@functools.wraps(decorated_function)
|
|
||||||
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
|
|
||||||
# are not used inside the context manager
|
|
||||||
if decorated_function.__name__ == "version":
|
|
||||||
msg = (
|
|
||||||
'directive "{0}" cannot be used within a "when"'
|
|
||||||
' context since it does not support a "when=" '
|
|
||||||
"argument"
|
|
||||||
)
|
|
||||||
msg = msg.format(decorated_function.__name__)
|
|
||||||
raise DirectiveError(msg)
|
|
||||||
|
|
||||||
when_constraints = [
|
|
||||||
spack.spec.Spec(x) for x in DirectiveMeta._when_constraints_from_context
|
|
||||||
]
|
|
||||||
if kwargs.get("when"):
|
|
||||||
when_constraints.append(spack.spec.Spec(kwargs["when"]))
|
|
||||||
when_spec = spack.spec.merge_abstract_anonymous_specs(*when_constraints)
|
|
||||||
|
|
||||||
kwargs["when"] = when_spec
|
|
||||||
|
|
||||||
# If any of the arguments are executors returned by a
|
|
||||||
# directive passed as an argument, don't execute them
|
|
||||||
# lazily. Instead, let the called directive handle them.
|
|
||||||
# This allows nested directive calls in packages. The
|
|
||||||
# caller can return the directive if it should be queued.
|
|
||||||
def remove_directives(arg):
|
|
||||||
directives = DirectiveMeta._directives_to_be_executed
|
|
||||||
if isinstance(arg, (list, tuple)):
|
|
||||||
# Descend into args that are lists or tuples
|
|
||||||
for a in arg:
|
|
||||||
remove_directives(a)
|
|
||||||
else:
|
|
||||||
# Remove directives args from the exec queue
|
|
||||||
remove = next((d for d in directives if d is arg), None)
|
|
||||||
if remove is not None:
|
|
||||||
directives.remove(remove)
|
|
||||||
|
|
||||||
# Nasty, but it's the best way I can think of to avoid
|
|
||||||
# side effects if directive results are passed as args
|
|
||||||
remove_directives(args)
|
|
||||||
remove_directives(list(kwargs.values()))
|
|
||||||
|
|
||||||
# A directive returns either something that is callable on a
|
|
||||||
# package or a sequence of them
|
|
||||||
result = decorated_function(*args, **kwargs)
|
|
||||||
|
|
||||||
# ...so if it is not a sequence make it so
|
|
||||||
values = result
|
|
||||||
if not isinstance(values, collections.abc.Sequence):
|
|
||||||
values = (values,)
|
|
||||||
|
|
||||||
DirectiveMeta._directives_to_be_executed.extend(values)
|
|
||||||
|
|
||||||
# wrapped function returns same result as original so
|
|
||||||
# that we can nest directives
|
|
||||||
return result
|
|
||||||
|
|
||||||
return _wrapper
|
|
||||||
|
|
||||||
return _decorator
|
|
||||||
|
|
||||||
|
|
||||||
SubmoduleCallback = Callable[["spack.package_base.PackageBase"], Union[str, List[str], bool]]
|
SubmoduleCallback = Callable[["spack.package_base.PackageBase"], Union[str, List[str], bool]]
|
||||||
directive = DirectiveMeta.directive
|
directive = DirectiveMeta.directive
|
||||||
|
|
||||||
@ -846,7 +617,7 @@ def format_error(msg, pkg):
|
|||||||
msg += " @*r{{[{0}, variant '{1}']}}"
|
msg += " @*r{{[{0}, variant '{1}']}}"
|
||||||
return llnl.util.tty.color.colorize(msg.format(pkg.name, name))
|
return llnl.util.tty.color.colorize(msg.format(pkg.name, name))
|
||||||
|
|
||||||
if name in reserved_names:
|
if name in spack.variant.reserved_names:
|
||||||
|
|
||||||
def _raise_reserved_name(pkg):
|
def _raise_reserved_name(pkg):
|
||||||
msg = "The name '%s' is reserved by Spack" % name
|
msg = "The name '%s' is reserved by Spack" % name
|
||||||
@ -1110,10 +881,6 @@ def _execute_languages(pkg: "spack.package_base.PackageBase"):
|
|||||||
return _execute_languages
|
return _execute_languages
|
||||||
|
|
||||||
|
|
||||||
class DirectiveError(spack.error.SpackError):
|
|
||||||
"""This is raised when something is wrong with a package directive."""
|
|
||||||
|
|
||||||
|
|
||||||
class DependencyError(DirectiveError):
|
class DependencyError(DirectiveError):
|
||||||
"""This is raised when a dependency specification is invalid."""
|
"""This is raised when a dependency specification is invalid."""
|
||||||
|
|
||||||
|
234
lib/spack/spack/directives_meta.py
Normal file
234
lib/spack/spack/directives_meta.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
import collections.abc
|
||||||
|
import functools
|
||||||
|
from typing import List, Set
|
||||||
|
|
||||||
|
import llnl.util.lang
|
||||||
|
|
||||||
|
import spack.error
|
||||||
|
import spack.spec
|
||||||
|
|
||||||
|
#: Names of possible directives. This list is mostly populated using the @directive decorator.
|
||||||
|
#: Some directives leverage others and in that case are not automatically added.
|
||||||
|
directive_names = ["build_system"]
|
||||||
|
|
||||||
|
|
||||||
|
class DirectiveMeta(type):
|
||||||
|
"""Flushes the directives that were temporarily stored in the staging
|
||||||
|
area into the package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Set of all known directives
|
||||||
|
_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
|
||||||
|
# to be executed. Here we go reversed because we want to execute
|
||||||
|
# commands:
|
||||||
|
# 1. in the order they were defined
|
||||||
|
# 2. following the MRO
|
||||||
|
attr_dict["_directives_to_be_executed"] = []
|
||||||
|
for base in reversed(bases):
|
||||||
|
try:
|
||||||
|
directive_from_base = base._directives_to_be_executed
|
||||||
|
attr_dict["_directives_to_be_executed"].extend(directive_from_base)
|
||||||
|
except AttributeError:
|
||||||
|
# The base class didn't have the required attribute.
|
||||||
|
# Continue searching
|
||||||
|
pass
|
||||||
|
|
||||||
|
# De-duplicates directives from base classes
|
||||||
|
attr_dict["_directives_to_be_executed"] = [
|
||||||
|
x for x in llnl.util.lang.dedupe(attr_dict["_directives_to_be_executed"])
|
||||||
|
]
|
||||||
|
|
||||||
|
# Move things to be executed from module scope (where they
|
||||||
|
# are collected first) to class scope
|
||||||
|
if DirectiveMeta._directives_to_be_executed:
|
||||||
|
attr_dict["_directives_to_be_executed"].extend(
|
||||||
|
DirectiveMeta._directives_to_be_executed
|
||||||
|
)
|
||||||
|
DirectiveMeta._directives_to_be_executed = []
|
||||||
|
|
||||||
|
return super(DirectiveMeta, cls).__new__(cls, name, bases, attr_dict)
|
||||||
|
|
||||||
|
def __init__(cls, name, bases, attr_dict):
|
||||||
|
# The instance is being initialized: if it is a package we must ensure
|
||||||
|
# that the directives are called to set it up.
|
||||||
|
|
||||||
|
if "spack.pkg" in cls.__module__:
|
||||||
|
# Ensure the presence of the dictionaries associated with the directives.
|
||||||
|
# All dictionaries are defaultdicts that create lists for missing keys.
|
||||||
|
for d in DirectiveMeta._directive_dict_names:
|
||||||
|
setattr(cls, d, {})
|
||||||
|
|
||||||
|
# Lazily execute directives
|
||||||
|
for directive in cls._directives_to_be_executed:
|
||||||
|
directive(cls)
|
||||||
|
|
||||||
|
# Ignore any directives executed *within* top-level
|
||||||
|
# directives by clearing out the queue they're appended to
|
||||||
|
DirectiveMeta._directives_to_be_executed = []
|
||||||
|
|
||||||
|
super(DirectiveMeta, cls).__init__(name, bases, attr_dict)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def push_to_context(when_spec):
|
||||||
|
"""Add a spec to the context constraints."""
|
||||||
|
DirectiveMeta._when_constraints_from_context.append(when_spec)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pop_from_context():
|
||||||
|
"""Pop the last constraint from the context"""
|
||||||
|
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.
|
||||||
|
|
||||||
|
Spack directives allow you to modify a package while it is being
|
||||||
|
defined, e.g. to add version or dependency information. Directives
|
||||||
|
are one of the key pieces of Spack's package "language", which is
|
||||||
|
embedded in python.
|
||||||
|
|
||||||
|
Here's an example directive:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@directive(dicts='versions')
|
||||||
|
version(pkg, ...):
|
||||||
|
...
|
||||||
|
|
||||||
|
This directive allows you write:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Foo(Package):
|
||||||
|
version(...)
|
||||||
|
|
||||||
|
The ``@directive`` decorator handles a couple things for you:
|
||||||
|
|
||||||
|
1. Adds the class scope (pkg) as an initial parameter when
|
||||||
|
called, like a class method would. This allows you to modify
|
||||||
|
a package from within a directive, while the package is still
|
||||||
|
being defined.
|
||||||
|
|
||||||
|
2. It automatically adds a dictionary called "versions" to the
|
||||||
|
package so that you can refer to pkg.versions.
|
||||||
|
|
||||||
|
The ``(dicts='versions')`` part ensures that ALL packages in Spack
|
||||||
|
will have a ``versions`` attribute after they're constructed, and
|
||||||
|
that if no directive actually modified it, it will just be an
|
||||||
|
empty dict.
|
||||||
|
|
||||||
|
This is just a modular way to add storage attributes to the
|
||||||
|
Package class, and it's how Spack gets information from the
|
||||||
|
packages to the core.
|
||||||
|
"""
|
||||||
|
global directive_names
|
||||||
|
|
||||||
|
if isinstance(dicts, str):
|
||||||
|
dicts = (dicts,)
|
||||||
|
|
||||||
|
if not isinstance(dicts, collections.abc.Sequence):
|
||||||
|
message = "dicts arg must be list, tuple, or string. Found {0}"
|
||||||
|
raise TypeError(message.format(type(dicts)))
|
||||||
|
|
||||||
|
# Add the dictionary names if not already there
|
||||||
|
DirectiveMeta._directive_dict_names |= set(dicts)
|
||||||
|
|
||||||
|
# This decorator just returns the directive functions
|
||||||
|
def _decorator(decorated_function):
|
||||||
|
directive_names.append(decorated_function.__name__)
|
||||||
|
|
||||||
|
@functools.wraps(decorated_function)
|
||||||
|
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
|
||||||
|
# are not used inside the context manager
|
||||||
|
if decorated_function.__name__ == "version":
|
||||||
|
msg = (
|
||||||
|
'directive "{0}" cannot be used within a "when"'
|
||||||
|
' context since it does not support a "when=" '
|
||||||
|
"argument"
|
||||||
|
)
|
||||||
|
msg = msg.format(decorated_function.__name__)
|
||||||
|
raise DirectiveError(msg)
|
||||||
|
|
||||||
|
when_constraints = [
|
||||||
|
spack.spec.Spec(x) for x in DirectiveMeta._when_constraints_from_context
|
||||||
|
]
|
||||||
|
if kwargs.get("when"):
|
||||||
|
when_constraints.append(spack.spec.Spec(kwargs["when"]))
|
||||||
|
when_spec = spack.spec.merge_abstract_anonymous_specs(*when_constraints)
|
||||||
|
|
||||||
|
kwargs["when"] = when_spec
|
||||||
|
|
||||||
|
# If any of the arguments are executors returned by a
|
||||||
|
# directive passed as an argument, don't execute them
|
||||||
|
# lazily. Instead, let the called directive handle them.
|
||||||
|
# This allows nested directive calls in packages. The
|
||||||
|
# caller can return the directive if it should be queued.
|
||||||
|
def remove_directives(arg):
|
||||||
|
directives = DirectiveMeta._directives_to_be_executed
|
||||||
|
if isinstance(arg, (list, tuple)):
|
||||||
|
# Descend into args that are lists or tuples
|
||||||
|
for a in arg:
|
||||||
|
remove_directives(a)
|
||||||
|
else:
|
||||||
|
# Remove directives args from the exec queue
|
||||||
|
remove = next((d for d in directives if d is arg), None)
|
||||||
|
if remove is not None:
|
||||||
|
directives.remove(remove)
|
||||||
|
|
||||||
|
# Nasty, but it's the best way I can think of to avoid
|
||||||
|
# side effects if directive results are passed as args
|
||||||
|
remove_directives(args)
|
||||||
|
remove_directives(list(kwargs.values()))
|
||||||
|
|
||||||
|
# A directive returns either something that is callable on a
|
||||||
|
# package or a sequence of them
|
||||||
|
result = decorated_function(*args, **kwargs)
|
||||||
|
|
||||||
|
# ...so if it is not a sequence make it so
|
||||||
|
values = result
|
||||||
|
if not isinstance(values, collections.abc.Sequence):
|
||||||
|
values = (values,)
|
||||||
|
|
||||||
|
DirectiveMeta._directives_to_be_executed.extend(values)
|
||||||
|
|
||||||
|
# wrapped function returns same result as original so
|
||||||
|
# that we can nest directives
|
||||||
|
return result
|
||||||
|
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
return _decorator
|
||||||
|
|
||||||
|
|
||||||
|
class DirectiveError(spack.error.SpackError):
|
||||||
|
"""This is raised when something is wrong with a package directive."""
|
@ -28,11 +28,9 @@
|
|||||||
import inspect
|
import inspect
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from llnl.util.lang import caller_locals
|
import spack.directives_meta
|
||||||
|
|
||||||
import spack.directives
|
|
||||||
import spack.error
|
import spack.error
|
||||||
from spack.spec import Spec
|
import spack.spec
|
||||||
|
|
||||||
|
|
||||||
class MultiMethodMeta(type):
|
class MultiMethodMeta(type):
|
||||||
@ -165,9 +163,9 @@ def __init__(self, condition):
|
|||||||
condition (str): condition to be met
|
condition (str): condition to be met
|
||||||
"""
|
"""
|
||||||
if isinstance(condition, bool):
|
if isinstance(condition, bool):
|
||||||
self.spec = Spec() if condition else None
|
self.spec = spack.spec.Spec() if condition else None
|
||||||
else:
|
else:
|
||||||
self.spec = Spec(condition)
|
self.spec = spack.spec.Spec(condition)
|
||||||
|
|
||||||
def __call__(self, method):
|
def __call__(self, method):
|
||||||
"""This annotation lets packages declare multiple versions of
|
"""This annotation lets packages declare multiple versions of
|
||||||
@ -229,11 +227,9 @@ def install(self, prefix):
|
|||||||
platform-specific versions. There's not much we can do to get
|
platform-specific versions. There's not much we can do to get
|
||||||
around this because of the way decorators work.
|
around this because of the way decorators work.
|
||||||
"""
|
"""
|
||||||
# In Python 2, Get the first definition of the method in the
|
assert (
|
||||||
# calling scope by looking at the caller's locals. In Python 3,
|
MultiMethodMeta._locals is not None
|
||||||
# we handle this using MultiMethodMeta.__prepare__.
|
), "cannot use multimethod, missing MultiMethodMeta metaclass?"
|
||||||
if MultiMethodMeta._locals is None:
|
|
||||||
MultiMethodMeta._locals = caller_locals()
|
|
||||||
|
|
||||||
# Create a multimethod with this name if there is not one already
|
# Create a multimethod with this name if there is not one already
|
||||||
original_method = MultiMethodMeta._locals.get(method.__name__)
|
original_method = MultiMethodMeta._locals.get(method.__name__)
|
||||||
@ -266,17 +262,17 @@ def __enter__(self):
|
|||||||
and add their constraint to whatever may be already present in the directive
|
and add their constraint to whatever may be already present in the directive
|
||||||
`when=` argument.
|
`when=` argument.
|
||||||
"""
|
"""
|
||||||
spack.directives.DirectiveMeta.push_to_context(str(self.spec))
|
spack.directives_meta.DirectiveMeta.push_to_context(str(self.spec))
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
spack.directives.DirectiveMeta.pop_from_context()
|
spack.directives_meta.DirectiveMeta.pop_from_context()
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def default_args(**kwargs):
|
def default_args(**kwargs):
|
||||||
spack.directives.DirectiveMeta.push_default_args(kwargs)
|
spack.directives_meta.DirectiveMeta.push_default_args(kwargs)
|
||||||
yield
|
yield
|
||||||
spack.directives.DirectiveMeta.pop_default_args()
|
spack.directives_meta.DirectiveMeta.pop_default_args()
|
||||||
|
|
||||||
|
|
||||||
class MultiMethodError(spack.error.SpackError):
|
class MultiMethodError(spack.error.SpackError):
|
||||||
|
@ -32,7 +32,6 @@
|
|||||||
import spack.config
|
import spack.config
|
||||||
import spack.config as sc
|
import spack.config as sc
|
||||||
import spack.deptypes as dt
|
import spack.deptypes as dt
|
||||||
import spack.directives
|
|
||||||
import spack.environment as ev
|
import spack.environment as ev
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.package_base
|
import spack.package_base
|
||||||
@ -1878,8 +1877,7 @@ def _spec_clauses(
|
|||||||
|
|
||||||
# validate variant value only if spec not concrete
|
# validate variant value only if spec not concrete
|
||||||
if not spec.concrete:
|
if not spec.concrete:
|
||||||
reserved_names = spack.directives.reserved_names
|
if not spec.virtual and vname not in spack.variant.reserved_names:
|
||||||
if not spec.virtual and vname not in reserved_names:
|
|
||||||
pkg_cls = self.pkg_class(spec.name)
|
pkg_cls = self.pkg_class(spec.name)
|
||||||
try:
|
try:
|
||||||
variant_def, _ = pkg_cls.variants[vname]
|
variant_def, _ = pkg_cls.variants[vname]
|
||||||
|
@ -1639,7 +1639,7 @@ def _add_flag(self, name, value, propagate):
|
|||||||
Known flags currently include "arch"
|
Known flags currently include "arch"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if propagate and name in spack.directives.reserved_names:
|
if propagate and name in vt.reserved_names:
|
||||||
raise UnsupportedPropagationError(
|
raise UnsupportedPropagationError(
|
||||||
f"Propagation with '==' is not supported for '{name}'."
|
f"Propagation with '==' is not supported for '{name}'."
|
||||||
)
|
)
|
||||||
@ -2935,9 +2935,7 @@ def ensure_valid_variants(spec):
|
|||||||
pkg_variants = pkg_cls.variants
|
pkg_variants = pkg_cls.variants
|
||||||
# reserved names are variants that may be set on any package
|
# reserved names are variants that may be set on any package
|
||||||
# but are not necessarily recorded by the package's class
|
# but are not necessarily recorded by the package's class
|
||||||
not_existing = set(spec.variants) - (
|
not_existing = set(spec.variants) - (set(pkg_variants) | set(vt.reserved_names))
|
||||||
set(pkg_variants) | set(spack.directives.reserved_names)
|
|
||||||
)
|
|
||||||
if not_existing:
|
if not_existing:
|
||||||
raise vt.UnknownVariantError(spec, not_existing)
|
raise vt.UnknownVariantError(spec, not_existing)
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import spack.directives
|
import spack.directives
|
||||||
|
import spack.directives_meta
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.repo
|
import spack.repo
|
||||||
import spack.util.package_hash as ph
|
import spack.util.package_hash as ph
|
||||||
@ -211,13 +212,13 @@ def foo():
|
|||||||
|
|
||||||
{directives}
|
{directives}
|
||||||
""".format(
|
""".format(
|
||||||
directives="\n".join(" %s()" % name for name in spack.directives.directive_names)
|
directives="\n".join(" %s()" % name for name in spack.directives_meta.directive_names)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_remove_all_directives():
|
def test_remove_all_directives():
|
||||||
"""Ensure all directives are removed from packages before hashing."""
|
"""Ensure all directives are removed from packages before hashing."""
|
||||||
for name in spack.directives.directive_names:
|
for name in spack.directives_meta.directive_names:
|
||||||
assert name in many_directives
|
assert name in many_directives
|
||||||
|
|
||||||
tree = ast.parse(many_directives)
|
tree = ast.parse(many_directives)
|
||||||
@ -225,7 +226,7 @@ def test_remove_all_directives():
|
|||||||
tree = ph.RemoveDirectives(spec).visit(tree)
|
tree = ph.RemoveDirectives(spec).visit(tree)
|
||||||
unparsed = unparse(tree, py_ver_consistent=True)
|
unparsed = unparse(tree, py_ver_consistent=True)
|
||||||
|
|
||||||
for name in spack.directives.directive_names:
|
for name in spack.directives_meta.directive_names:
|
||||||
assert name not in unparsed
|
assert name not in unparsed
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
import spack.directives
|
import spack.directives_meta
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.package_base
|
import spack.package_base
|
||||||
import spack.repo
|
import spack.repo
|
||||||
@ -82,7 +82,7 @@ def visit_Expr(self, node):
|
|||||||
node.value
|
node.value
|
||||||
and isinstance(node.value, ast.Call)
|
and isinstance(node.value, ast.Call)
|
||||||
and isinstance(node.value.func, ast.Name)
|
and isinstance(node.value.func, ast.Name)
|
||||||
and node.value.func.id in spack.directives.directive_names
|
and node.value.func.id in spack.directives_meta.directive_names
|
||||||
)
|
)
|
||||||
else node
|
else node
|
||||||
)
|
)
|
||||||
|
@ -17,10 +17,22 @@
|
|||||||
import llnl.util.tty.color
|
import llnl.util.tty.color
|
||||||
from llnl.string import comma_or
|
from llnl.string import comma_or
|
||||||
|
|
||||||
import spack.directives
|
|
||||||
import spack.error as error
|
import spack.error as error
|
||||||
import spack.parser
|
import spack.parser
|
||||||
|
|
||||||
|
#: These are variant names used by Spack internally; packages can't use them
|
||||||
|
reserved_names = [
|
||||||
|
"arch",
|
||||||
|
"architecture",
|
||||||
|
"dev_path",
|
||||||
|
"namespace",
|
||||||
|
"operating_system",
|
||||||
|
"os",
|
||||||
|
"patches",
|
||||||
|
"platform",
|
||||||
|
"target",
|
||||||
|
]
|
||||||
|
|
||||||
special_variant_values = [None, "none", "*"]
|
special_variant_values = [None, "none", "*"]
|
||||||
|
|
||||||
|
|
||||||
@ -679,7 +691,7 @@ def substitute_abstract_variants(spec):
|
|||||||
# in $spack/lib/spack/spack/spec_list.py
|
# in $spack/lib/spack/spack/spec_list.py
|
||||||
failed = []
|
failed = []
|
||||||
for name, v in spec.variants.items():
|
for name, v in spec.variants.items():
|
||||||
if name in spack.directives.reserved_names:
|
if name in reserved_names:
|
||||||
if name == "dev_path":
|
if name == "dev_path":
|
||||||
new_variant = SingleValuedVariant(name, v._original_value)
|
new_variant = SingleValuedVariant(name, v._original_value)
|
||||||
spec.variants.substitute(new_variant)
|
spec.variants.substitute(new_variant)
|
||||||
|
@ -76,18 +76,15 @@ def flag_handler(self, name, flags):
|
|||||||
return (None, flags, None)
|
return (None, flags, None)
|
||||||
|
|
||||||
|
|
||||||
class BuildEnvironment:
|
class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder):
|
||||||
# Need to make sure that locale is UTF-8 in order to process source
|
|
||||||
# files in UTF-8.
|
configure_directory = "source"
|
||||||
|
|
||||||
|
# Need to make sure that locale is UTF-8 in order to process source files in UTF-8.
|
||||||
@when("@59:")
|
@when("@59:")
|
||||||
def setup_build_environment(self, env):
|
def setup_build_environment(self, env):
|
||||||
env.set("LC_ALL", "en_US.UTF-8")
|
env.set("LC_ALL", "en_US.UTF-8")
|
||||||
|
|
||||||
|
|
||||||
class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder, BuildEnvironment):
|
|
||||||
|
|
||||||
configure_directory = "source"
|
|
||||||
|
|
||||||
def configure_args(self):
|
def configure_args(self):
|
||||||
args = []
|
args = []
|
||||||
|
|
||||||
@ -104,7 +101,12 @@ def configure_args(self):
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
class MSBuildBuilder(spack.build_systems.msbuild.MSBuildBuilder, BuildEnvironment):
|
class MSBuildBuilder(spack.build_systems.msbuild.MSBuildBuilder):
|
||||||
|
# Need to make sure that locale is UTF-8 in order to process source files in UTF-8.
|
||||||
|
@when("@59:")
|
||||||
|
def setup_build_environment(self, env):
|
||||||
|
env.set("LC_ALL", "en_US.UTF-8")
|
||||||
|
|
||||||
def msbuild_args(self):
|
def msbuild_args(self):
|
||||||
return [
|
return [
|
||||||
"allinone.sln",
|
"allinone.sln",
|
||||||
|
Loading…
Reference in New Issue
Block a user