Fix module reloading

This commit is contained in:
Zanie 2024-01-17 09:00:55 -06:00
parent 9c25fc9787
commit b8fd8bfb4b
1 changed files with 22 additions and 13 deletions

View File

@ -13,8 +13,8 @@ import errno
import os import os
import sys import sys
import time import time
from types import ModuleType
from contextlib import ExitStack, contextmanager from contextlib import ExitStack, contextmanager
from functools import lru_cache as cache
from typing import Any, TextIO, TYPE_CHECKING from typing import Any, TextIO, TYPE_CHECKING
Path = str # We don't use `pathlib` for a modest speedup Path = str # We don't use `pathlib` for a modest speedup
@ -29,6 +29,7 @@ if TYPE_CHECKING:
Self = Any Self = Any
DEBUG = os.getenv("HOOKD_DEBUG") DEBUG = os.getenv("HOOKD_DEBUG")
INIT_MODULES = set(sys.modules.keys())
def main(): def main():
@ -108,10 +109,6 @@ def run_once(stdin: TextIO, stdout: TextIO):
end = time.perf_counter() end = time.perf_counter()
send_debug(stdout, f"Parsed hook inputs in {(end - start)*1000.0:.2f}ms") send_debug(stdout, f"Parsed hook inputs in {(end - start)*1000.0:.2f}ms")
# We must invalidate caches or dependencies that were installed since the daemon
# started may be ignored
importlib.invalidate_caches()
with ExitStack() as hook_ctx: with ExitStack() as hook_ctx:
hook_ctx.enter_context(update_sys_path(backend_path)) hook_ctx.enter_context(update_sys_path(backend_path))
hook_stdout = hook_ctx.enter_context(redirect_sys_stream("stdout")) hook_stdout = hook_ctx.enter_context(redirect_sys_stream("stdout"))
@ -120,7 +117,9 @@ def run_once(stdin: TextIO, stdout: TextIO):
send_redirect(stdout, "stderr", str(hook_stderr)) send_redirect(stdout, "stderr", str(hook_stderr))
try: try:
build_backend = import_build_backend(build_backend_name, backend_path) build_backend = import_build_backend(
stdout, build_backend_name, backend_path
)
except Exception as exc: except Exception as exc:
if not isinstance(exc, HookdError): if not isinstance(exc, HookdError):
# Wrap unhandled errors in a generic one # Wrap unhandled errors in a generic one
@ -138,25 +137,35 @@ def run_once(stdin: TextIO, stdout: TextIO):
# Respect SIGTERM and SIGINT # Respect SIGTERM and SIGINT
if ( if (
isinstance(exc, SystemExit) isinstance(exc, (SystemExit, KeyboardInterrupt))
# setutools will throw `SystemExit` on incorrect usage so do not treat
# it as a SIGTERM
and build_backend_name != "setuptools.build_meta:__legacy__" and build_backend_name != "setuptools.build_meta:__legacy__"
): ):
raise raise
elif isinstance(exc, KeyboardInterrupt):
raise
raise HookRuntimeError(exc) from exc raise HookRuntimeError(exc) from exc
else: else:
send_ok(stdout, result) send_ok(stdout, result)
@cache() def import_build_backend(
def import_build_backend(backend_name: str, backend_path: tuple[str]) -> object: stdout,
backend_name: str,
backend_path: tuple[str],
) -> object:
""" """
See: https://peps.python.org/pep-0517/#source-trees See: https://peps.python.org/pep-0517/#source-trees
""" """
# Invalidate the module caches before resetting modules
importlib.invalidate_caches()
for module in tuple(sys.modules.keys()):
# We remove all of the modules that have been added since the daemon started
# If a new dependency is added without resetting modules, build backends
# can end up in a broken state. We cannot simply reset the backend module
# using `importtools.reload` because types can become out of sync across
# packages e.g. breaking `isinstance` calls.
if module not in INIT_MODULES:
sys.modules.pop(module)
parts = backend_name.split(":") parts = backend_name.split(":")
if len(parts) == 1: if len(parts) == 1: