summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2021-04-28 01:55:07 +0200
committerTodd Gamblin <tgamblin@llnl.gov>2021-05-22 11:51:21 -0700
commit13fed376f20f11221b83a806cc66e4ae82918c4d (patch)
tree4b8f898bd978ccf9dc0c66dfdacfb5c54c4436d6
parentfb27c7ad0c5542c10de98de6c825fbd0c7c8a88f (diff)
downloadspack-13fed376f20f11221b83a806cc66e4ae82918c4d.tar.gz
spack-13fed376f20f11221b83a806cc66e4ae82918c4d.tar.bz2
spack-13fed376f20f11221b83a806cc66e4ae82918c4d.tar.xz
spack-13fed376f20f11221b83a806cc66e4ae82918c4d.zip
Import hooks using Python's built-in machinery (#23288)
The function we coded in Spack to load Python modules with arbitrary names from a file seem to have issues with local imports. For loading hooks though it is unnecessary to use such functions, since we don't care to bind a custom name to a module nor we have to load it from an unknown location. This PR thus modifies spack.hook in the following ways: - Use __import__ instead of spack.util.imp.load_source (this addresses #20005) - Sync module docstring with all the hooks we have - Avoid using memoization in a module function - Marked with a leading underscore all the names that are supposed to stay local
-rw-r--r--lib/spack/spack/hooks/__init__.py67
1 files changed, 38 insertions, 29 deletions
diff --git a/lib/spack/spack/hooks/__init__.py b/lib/spack/spack/hooks/__init__.py
index 5a39f242fd..5a5a04b49f 100644
--- a/lib/spack/spack/hooks/__init__.py
+++ b/lib/spack/spack/hooks/__init__.py
@@ -2,8 +2,8 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
-
"""This package contains modules with hooks for various stages in the
+
Spack install process. You can add modules here and they'll be
executed by package at various times during the package lifecycle.
@@ -21,46 +21,55 @@
systems (e.g. modules, lmod, etc.) or to add other custom
features.
"""
-import os.path
-
+import llnl.util.lang
import spack.paths
-import spack.util.imp as simp
-from llnl.util.lang import memoized, list_modules
-
-@memoized
-def all_hook_modules():
- modules = []
- for name in list_modules(spack.paths.hooks_path):
- mod_name = __name__ + '.' + name
- path = os.path.join(spack.paths.hooks_path, name) + ".py"
- mod = simp.load_source(mod_name, path)
- if name == 'write_install_manifest':
- last_mod = mod
- else:
- modules.append(mod)
-
- # put `write_install_manifest` as the last hook to run
- modules.append(last_mod)
- return modules
-
-
-class HookRunner(object):
+class _HookRunner(object):
+ #: Stores all hooks on first call, shared among
+ #: all HookRunner objects
+ _hooks = None
def __init__(self, hook_name):
self.hook_name = hook_name
+ @classmethod
+ def _populate_hooks(cls):
+ # Lazily populate the list of hooks
+ cls._hooks = []
+ relative_names = list(llnl.util.lang.list_modules(
+ spack.paths.hooks_path
+ ))
+
+ # We want this hook to be the last registered
+ relative_names.sort(key=lambda x: x == 'write_install_manifest')
+ assert relative_names[-1] == 'write_install_manifest'
+
+ for name in relative_names:
+ module_name = __name__ + '.' + name
+ # When importing a module from a package, __import__('A.B', ...)
+ # returns package A when 'fromlist' is empty. If fromlist is not
+ # empty it returns the submodule B instead
+ # See: https://stackoverflow.com/a/2725668/771663
+ module_obj = __import__(module_name, fromlist=[None])
+ cls._hooks.append((module_name, module_obj))
+
+ @property
+ def hooks(self):
+ if not self._hooks:
+ self._populate_hooks()
+ return self._hooks
+
def __call__(self, *args, **kwargs):
- for module in all_hook_modules():
+ for _, module in self.hooks:
if hasattr(module, self.hook_name):
hook = getattr(module, self.hook_name)
if hasattr(hook, '__call__'):
hook(*args, **kwargs)
-pre_install = HookRunner('pre_install')
-post_install = HookRunner('post_install')
+pre_install = _HookRunner('pre_install')
+post_install = _HookRunner('post_install')
-pre_uninstall = HookRunner('pre_uninstall')
-post_uninstall = HookRunner('post_uninstall')
+pre_uninstall = _HookRunner('pre_uninstall')
+post_uninstall = _HookRunner('post_uninstall')