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 #: These are variant names used by Spack internally; packages can't use them
reserved_names = ['patches', 'dev_path'] 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 _patch_order_index = 0
@@ -113,7 +117,7 @@ class DirectiveMeta(type):
""" """
# Set of all known directives # 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] _directives_to_be_executed = [] # type: List[str]
_when_constraints_from_context = [] # 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__: if 'spack.pkg' in cls.__module__:
# Ensure the presence of the dictionaries associated # Ensure the presence of the dictionaries associated
# with the directives # with the directives
for d in DirectiveMeta._directive_names: for d in DirectiveMeta._directive_dict_names:
setattr(cls, d, {}) setattr(cls, d, {})
# Lazily execute directives # Lazily execute directives
@@ -222,7 +226,7 @@ class Foo(Package):
Package class, and it's how Spack gets information from the Package class, and it's how Spack gets information from the
packages to the core. packages to the core.
""" """
global __all__ global directive_names
if isinstance(dicts, six.string_types): if isinstance(dicts, six.string_types):
dicts = (dicts, ) dicts = (dicts, )
@@ -232,11 +236,11 @@ class Foo(Package):
raise TypeError(message.format(type(dicts))) raise TypeError(message.format(type(dicts)))
# Add the dictionary names if not already there # 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 # This decorator just returns the directive functions
def _decorator(decorated_function): def _decorator(decorated_function):
__all__.append(decorated_function.__name__) directive_names.append(decorated_function.__name__)
@functools.wraps(decorated_function) @functools.wraps(decorated_function)
def _wrapper(*args, **kwargs): def _wrapper(*args, **kwargs):
@@ -730,3 +734,7 @@ class DependencyPatchError(DirectiveError):
class UnsupportedPackageDirective(DirectiveError): class UnsupportedPackageDirective(DirectiveError):
"""Raised when an invalid or unsupported package directive is specified.""" """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 ast
import spack.directives
import spack.paths import spack.paths
import spack.util.package_hash as ph import spack.util.package_hash as ph
from spack.spec import Spec from spack.spec import Spec
@@ -123,3 +124,29 @@ def test_remove_docstrings():
assert "SEVEN" in unparsed assert "SEVEN" in unparsed
assert "NINE" in unparsed assert "NINE" in unparsed
assert "TWELVE" 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 return (isinstance(node, ast.Expr) and
node.value and isinstance(node.value, ast.Call) and node.value 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.__all__) node.value.func.id in spack.directives.directive_names)
def is_spack_attr(self, node): def is_spack_attr(self, node):
return (isinstance(node, ast.Assign) and return (isinstance(node, ast.Assign) and