Drop optional dependencies of Spack (#43081)

Remove dependency on `importlib_metadata` and `pkg_resources`, which can be problematic if the version in PYTHONPATH is incompatible with the interpreter Spack is running under.
This commit is contained in:
Tim Fuller 2024-03-07 10:52:49 -07:00 committed by GitHub
parent bca4d37d76
commit c090bc5ebe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 48 deletions

View file

@ -12,7 +12,6 @@
import re import re
import sys import sys
import traceback import traceback
import warnings
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Callable, Iterable, List, Tuple from typing import Any, Callable, Iterable, List, Tuple
@ -847,43 +846,25 @@ def __repr__(self):
def get_entry_points(*, group: str): def get_entry_points(*, group: str):
"""Wrapper for ``importlib.metadata.entry_points`` """Wrapper for ``importlib.metadata.entry_points``
Adapted from https://github.com/HypothesisWorks/hypothesis/blob/0a90ed6edf56319149956c7321d4110078a5c228/hypothesis-python/src/hypothesis/entry_points.py
Args: Args:
group (str): the group of entry points to select group: entry points to select
Returns: Returns:
EntryPoints for ``group`` EntryPoints for ``group`` or empty list if unsupported
""" """
try: try:
try: import importlib.metadata # type: ignore # novermin
from importlib import metadata as importlib_metadata # type: ignore # novermin
except ImportError: except ImportError:
import importlib_metadata # type: ignore # mypy thinks this is a redefinition return []
try: try:
entry_points = importlib_metadata.entry_points(group=group) return importlib.metadata.entry_points(group=group)
except TypeError: except TypeError:
# Prior to Python 3.10, entry_points accepted no parameters and always # Prior to Python 3.10, entry_points accepted no parameters and always
# returned a dictionary of entry points, keyed by group. See # returned a dictionary of entry points, keyed by group. See
# https://docs.python.org/3/library/importlib.metadata.html#entry-points # https://docs.python.org/3/library/importlib.metadata.html#entry-points
entry_points = importlib_metadata.entry_points().get(group, []) return importlib.metadata.entry_points().get(group, [])
yield from entry_points
except ImportError:
# But if we're not on Python >= 3.8 and the importlib_metadata backport
# is not installed, we fall back to pkg_resources anyway.
try:
import pkg_resources # type: ignore
except ImportError:
warnings.warn(
"Under Python <= 3.7, Spack requires either the importlib_metadata "
"or setuptools package in order to load extensions via entrypoints.",
ImportWarning,
)
yield from ()
else:
yield from pkg_resources.iter_entry_points(group)
def load_module_from_file(module_name, module_path): def load_module_from_file(module_name, module_path):

View file

@ -8,6 +8,8 @@
import pytest import pytest
import llnl.util.lang
import spack.config import spack.config
import spack.extensions import spack.extensions
@ -64,24 +66,12 @@ def entry_points(group=None):
@pytest.fixture() @pytest.fixture()
def mock_entry_points(tmp_path, monkeypatch): def mock_get_entry_points(tmp_path, monkeypatch):
entry_points = entry_points_factory(tmp_path) entry_points = entry_points_factory(tmp_path)
try: monkeypatch.setattr(llnl.util.lang, "get_entry_points", entry_points)
try:
import importlib.metadata as importlib_metadata # type: ignore # novermin
except ImportError:
import importlib_metadata
monkeypatch.setattr(importlib_metadata, "entry_points", entry_points)
except ImportError:
try:
import pkg_resources # type: ignore
except ImportError:
return
monkeypatch.setattr(pkg_resources, "iter_entry_points", entry_points)
@pytest.mark.skipif(sys.version_info[:2] < (3, 8), reason="Python>=3.8 required") def test_spack_entry_point_config(tmp_path, mock_get_entry_points):
def test_spack_entry_point_config(tmp_path, mock_entry_points):
"""Test config scope entry point""" """Test config scope entry point"""
config_paths = dict(spack.config.config_paths_from_entry_points()) config_paths = dict(spack.config.config_paths_from_entry_points())
config_path = config_paths.get("plugin-mypackage_config") config_path = config_paths.get("plugin-mypackage_config")
@ -94,8 +84,7 @@ def test_spack_entry_point_config(tmp_path, mock_entry_points):
assert config.get("config:install_tree:root", scope="plugin-mypackage_config") == "/spam/opt" assert config.get("config:install_tree:root", scope="plugin-mypackage_config") == "/spam/opt"
@pytest.mark.skipif(sys.version_info[:2] < (3, 8), reason="Python>=3.8 required") def test_spack_entry_point_extension(tmp_path, mock_get_entry_points):
def test_spack_entry_point_extension(tmp_path, mock_entry_points):
"""Test config scope entry point""" """Test config scope entry point"""
my_ext = tmp_path / "spack/spack-myext" my_ext = tmp_path / "spack/spack-myext"
extensions = spack.extensions.get_extension_paths() extensions = spack.extensions.get_extension_paths()
@ -110,3 +99,16 @@ def test_spack_entry_point_extension(tmp_path, mock_entry_points):
assert os.path.samefile(root, my_ext) assert os.path.samefile(root, my_ext)
module = spack.extensions.get_module("spam") module = spack.extensions.get_module("spam")
assert module is not None assert module is not None
@pytest.mark.skipif(sys.version_info[:2] < (3, 8), reason="Python>=3.8 required")
def test_llnl_util_lang_get_entry_points(tmp_path, monkeypatch):
import importlib.metadata # type: ignore # novermin
monkeypatch.setattr(importlib.metadata, "entry_points", entry_points_factory(tmp_path))
entry_points = list(llnl.util.lang.get_entry_points(group="spack.config"))
assert isinstance(entry_points[0], MockConfigEntryPoint)
entry_points = list(llnl.util.lang.get_entry_points(group="spack.extensions"))
assert isinstance(entry_points[0], MockExtensionsEntryPoint)