hooks: run in clear, fixed order (#47329)

Currently the order in which hooks are run is arbitrary.

This can be fixed by sorted(list_modules(...)) but I think it is much
more clear to just have a static list.

Hooks are not extensible other than modifying Spack code, which
means it's unlikely people maintain custom hooks since they'd have
to fork Spack. And if they fork Spack, they might as well add an entry
to the list when they're continuously rebasing.
This commit is contained in:
Harmen Stoppels 2024-10-30 19:57:49 +01:00 committed by GitHub
parent 02d2c4a9ff
commit c3435b4e7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 25 additions and 32 deletions

View File

@ -333,13 +333,9 @@ inserting them at different places in the spack code base. Whenever a hook
type triggers by way of a function call, we find all the hooks of that type, type triggers by way of a function call, we find all the hooks of that type,
and run them. and run them.
Spack defines hooks by way of a module at ``lib/spack/spack/hooks`` where we can define Spack defines hooks by way of a module in the ``lib/spack/spack/hooks`` directory.
types of hooks in the ``__init__.py``, and then python files in that folder This module has to be registered in ``__init__.py`` so that Spack is aware of it.
can use hook functions. The files are automatically parsed, so if you write This section will cover the basic kind of hooks, and how to write them.
a new file for some integration (e.g., ``lib/spack/spack/hooks/myintegration.py``
you can then write hook functions in that file that will be automatically detected,
and run whenever your hook is called. This section will cover the basic kind
of hooks, and how to write them.
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
Types of Hooks Types of Hooks

View File

@ -21,43 +21,40 @@
features. features.
""" """
import importlib import importlib
import types
from llnl.util.lang import ensure_last, list_modules from typing import List, Optional
import spack.paths
class _HookRunner: class _HookRunner:
#: Stores all hooks on first call, shared among #: Order in which hooks are executed
#: all HookRunner objects HOOK_ORDER = [
_hooks = None "spack.hooks.module_file_generation",
"spack.hooks.licensing",
"spack.hooks.sbang",
"spack.hooks.windows_runtime_linkage",
"spack.hooks.drop_redundant_rpaths",
"spack.hooks.absolutify_elf_sonames",
"spack.hooks.permissions_setters",
# after all mutations to the install prefix, write metadata
"spack.hooks.write_install_manifest",
# after all metadata is written
"spack.hooks.autopush",
]
#: Contains all hook modules after first call, shared among all HookRunner objects
_hooks: Optional[List[types.ModuleType]] = None
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(list_modules(spack.paths.hooks_path))
# Ensure that write_install_manifest comes last
ensure_last(relative_names, "absolutify_elf_sonames", "write_install_manifest")
for name in relative_names:
module_name = __name__ + "." + name
module_obj = importlib.import_module(module_name)
cls._hooks.append((module_name, module_obj))
@property @property
def hooks(self): def hooks(self) -> List[types.ModuleType]:
if not self._hooks: if not self._hooks:
self._populate_hooks() self._hooks = [importlib.import_module(module_name) for module_name in self.HOOK_ORDER]
return self._hooks return self._hooks
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
for _, module in self.hooks: 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__"):