diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/hooks/__init__.py | 67 |
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') |