package_hash: rework RemoveDirectives and add a test

Previously we used `directives.__all__` to get directive names, but it wasn't
quite right -- it included `DirectiveMeta`, etc.  It's not wrong, but it's also
not the clearest way to do this.

- [x] Refactor `@directive` to track names in `directive_names` global
- [x] Rename `_directive_names` to `_directive_dict_names` in `DirectiveMeta`
- [x] Add a test for `RemoveDirectives`

Co-authored-by: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com>
This commit is contained in:
Todd Gamblin 2021-12-23 00:29:06 -08:00 committed by Greg Becker
parent 8880a00862
commit 93a6c51d88
3 changed files with 41 additions and 6 deletions

View file

@ -59,6 +59,10 @@ class OpenMpi(Package):
#: These are variant names used by Spack internally; packages can't use them
reserved_names = ['patches', 'dev_path']
#: Names of possible directives. This list is populated elsewhere in the file and then
#: added to `__all__` at the bottom.
directive_names = []
_patch_order_index = 0
@ -113,7 +117,7 @@ class DirectiveMeta(type):
"""
# Set of all known directives
_directive_names = set() # type: Set[str]
_directive_dict_names = set() # type: Set[str]
_directives_to_be_executed = [] # type: List[str]
_when_constraints_from_context = [] # type: List[str]
@ -156,7 +160,7 @@ def __init__(cls, name, bases, attr_dict):
if 'spack.pkg' in cls.__module__:
# Ensure the presence of the dictionaries associated
# with the directives
for d in DirectiveMeta._directive_names:
for d in DirectiveMeta._directive_dict_names:
setattr(cls, d, {})
# Lazily execute directives
@ -222,7 +226,7 @@ class Foo(Package):
Package class, and it's how Spack gets information from the
packages to the core.
"""
global __all__
global directive_names
if isinstance(dicts, six.string_types):
dicts = (dicts, )
@ -232,11 +236,11 @@ class Foo(Package):
raise TypeError(message.format(type(dicts)))
# Add the dictionary names if not already there
DirectiveMeta._directive_names |= set(dicts)
DirectiveMeta._directive_dict_names |= set(dicts)
# This decorator just returns the directive functions
def _decorator(decorated_function):
__all__.append(decorated_function.__name__)
directive_names.append(decorated_function.__name__)
@functools.wraps(decorated_function)
def _wrapper(*args, **kwargs):
@ -730,3 +734,7 @@ class DependencyPatchError(DirectiveError):
class UnsupportedPackageDirective(DirectiveError):
"""Raised when an invalid or unsupported package directive is specified."""
#: add all directive names to __all__
__all__.extend(directive_names)

View file

@ -5,6 +5,7 @@
import ast
import spack.directives
import spack.paths
import spack.util.package_hash as ph
from spack.spec import Spec
@ -123,3 +124,29 @@ def test_remove_docstrings():
assert "SEVEN" in unparsed
assert "NINE" in unparsed
assert "TWELVE" in unparsed
many_directives = """\
class HasManyDirectives:
{directives}
def foo():
# just a method to get in the way
pass
{directives}
""".format(directives="\n".join(
" %s()" % name for name in spack.directives.directive_names
))
def test_remove_directives():
"""Ensure all directives are removed from packages before hashing."""
tree = ast.parse(many_directives)
spec = Spec("has-many-directives")
tree = ph.RemoveDirectives(spec).visit(tree)
unparsed = unparse(tree)
for name in spack.directives.directive_names:
assert name not in unparsed

View file

@ -66,7 +66,7 @@ def is_directive(self, node):
return (isinstance(node, ast.Expr) and
node.value and isinstance(node.value, ast.Call) and
isinstance(node.value.func, ast.Name) and
node.value.func.id in spack.directives.__all__)
node.value.func.id in spack.directives.directive_names)
def is_spack_attr(self, node):
return (isinstance(node, ast.Assign) and