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
This commit is contained in:
parent
24c87e07b5
commit
985e101507
@ -2,58 +2,72 @@
|
|||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
"""This package contains modules with hooks for various stages in the
|
"""This package contains modules with hooks for various stages in the
|
||||||
Spack install process. You can add modules here and they'll be
|
Spack install process. You can add modules here and they'll be
|
||||||
executed by package at various times during the package lifecycle.
|
executed by package at various times during the package lifecycle.
|
||||||
|
|
||||||
Each hook is just a function that takes a package as a parameter.
|
Each hook is just a function that takes a package as a parameter.
|
||||||
Hooks are not executed in any particular order.
|
Hooks are not executed in any particular order.
|
||||||
|
|
||||||
Currently the following hooks are supported:
|
Currently the following hooks are supported:
|
||||||
|
|
||||||
* pre_install(spec)
|
* pre_install(spec)
|
||||||
* post_install(spec)
|
* post_install(spec)
|
||||||
* pre_uninstall(spec)
|
* pre_uninstall(spec)
|
||||||
* post_uninstall(spec)
|
* post_uninstall(spec)
|
||||||
* on_install_failure(exception)
|
* on_install_start(spec)
|
||||||
|
* on_install_success(spec)
|
||||||
|
* on_install_failure(spec)
|
||||||
|
* on_phase_success(pkg, phase_name, log_file)
|
||||||
|
* on_phase_error(pkg, phase_name, log_file)
|
||||||
|
* on_phase_error(pkg, phase_name, log_file)
|
||||||
|
* on_analyzer_save(pkg, result)
|
||||||
|
|
||||||
This can be used to implement support for things like module
|
This can be used to implement support for things like module
|
||||||
systems (e.g. modules, lmod, etc.) or to add other custom
|
systems (e.g. modules, lmod, etc.) or to add other custom
|
||||||
features.
|
features.
|
||||||
"""
|
"""
|
||||||
import os.path
|
import llnl.util.lang
|
||||||
|
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.util.imp as simp
|
|
||||||
from llnl.util.lang import memoized, list_modules
|
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
class _HookRunner(object):
|
||||||
def all_hook_modules():
|
#: Stores all hooks on first call, shared among
|
||||||
modules = []
|
#: all HookRunner objects
|
||||||
for name in list_modules(spack.paths.hooks_path):
|
_hooks = None
|
||||||
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):
|
|
||||||
|
|
||||||
def __init__(self, hook_name):
|
def __init__(self, hook_name):
|
||||||
self.hook_name = 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):
|
def __call__(self, *args, **kwargs):
|
||||||
for module in all_hook_modules():
|
for _, module in self.hooks:
|
||||||
if hasattr(module, self.hook_name):
|
if hasattr(module, self.hook_name):
|
||||||
hook = getattr(module, self.hook_name)
|
hook = getattr(module, self.hook_name)
|
||||||
if hasattr(hook, '__call__'):
|
if hasattr(hook, '__call__'):
|
||||||
@ -61,19 +75,19 @@ def __call__(self, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
# pre/post install and run by the install subprocess
|
# pre/post install and run by the install subprocess
|
||||||
pre_install = HookRunner('pre_install')
|
pre_install = _HookRunner('pre_install')
|
||||||
post_install = HookRunner('post_install')
|
post_install = _HookRunner('post_install')
|
||||||
|
|
||||||
# These hooks are run within an install subprocess
|
# These hooks are run within an install subprocess
|
||||||
pre_uninstall = HookRunner('pre_uninstall')
|
pre_uninstall = _HookRunner('pre_uninstall')
|
||||||
post_uninstall = HookRunner('post_uninstall')
|
post_uninstall = _HookRunner('post_uninstall')
|
||||||
on_phase_success = HookRunner('on_phase_success')
|
on_phase_success = _HookRunner('on_phase_success')
|
||||||
on_phase_error = HookRunner('on_phase_error')
|
on_phase_error = _HookRunner('on_phase_error')
|
||||||
|
|
||||||
# These are hooks in installer.py, before starting install subprocess
|
# These are hooks in installer.py, before starting install subprocess
|
||||||
on_install_start = HookRunner('on_install_start')
|
on_install_start = _HookRunner('on_install_start')
|
||||||
on_install_success = HookRunner('on_install_success')
|
on_install_success = _HookRunner('on_install_success')
|
||||||
on_install_failure = HookRunner('on_install_failure')
|
on_install_failure = _HookRunner('on_install_failure')
|
||||||
|
|
||||||
# Analyzer hooks
|
# Analyzer hooks
|
||||||
on_analyzer_save = HookRunner('on_analyzer_save')
|
on_analyzer_save = _HookRunner('on_analyzer_save')
|
||||||
|
Loading…
Reference in New Issue
Block a user