diff --git a/requirements/dev-requirements.in b/requirements/dev-requirements.in index aa86c12fc..d25bd6178 100644 --- a/requirements/dev-requirements.in +++ b/requirements/dev-requirements.in @@ -7,3 +7,4 @@ pytest-env pytest-xdist pytest-timeout pyftpdlib +setuptools diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index f0d74d83f..a5973835a 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -7,6 +7,7 @@ pytest-env==1.1.5 pytest-xdist==3.6.1 pytest-timeout==2.3.1 pyftpdlib==2.0.1 +setuptools==75.6.0 ## The following requirements were added by pip freeze: astroid==3.3.5 dill==0.3.9 diff --git a/requirements/requirements.in b/requirements/requirements.in index 8d9a8ec0b..129ec1b15 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -1,11 +1,12 @@ Click >= 7.0 grpcio Jinja2 >= 2.10 +importlib_metadata >= 3.6; python_version < "3.10" +packaging pluginbase protobuf >= 3.19 psutil ruamel.yaml >= 0.16.7 ruamel.yaml.clib >= 0.1.2 -setuptools pyroaring ujson diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 5852fb750..910b71755 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,12 +1,12 @@ click==8.1.7 grpcio==1.68.0 Jinja2==3.1.4 +packaging==24.2 pluginbase==1.0.1 protobuf==5.28.3 psutil==6.1.0 ruamel.yaml==0.18.6 ruamel.yaml.clib==0.2.12 -setuptools==75.6.0 pyroaring==1.0.0 ujson==5.10.0 ## The following requirements were added by pip freeze: diff --git a/src/buildstream/_pluginfactory/pluginoriginpip.py b/src/buildstream/_pluginfactory/pluginoriginpip.py index 3bded89ab..507339b7e 100644 --- a/src/buildstream/_pluginfactory/pluginoriginpip.py +++ b/src/buildstream/_pluginfactory/pluginoriginpip.py @@ -12,6 +12,7 @@ # limitations under the License. # import os +import sys from .._exceptions import PluginError @@ -32,14 +33,12 @@ def __init__(self): def get_plugin_paths(self, kind, plugin_type): - import pkg_resources + from packaging.requirements import Requirement, InvalidRequirement - try: - # For setuptools >= 70 - import packaging - except ImportError: - # For setuptools < 70 - from pkg_resources.extern import packaging + if sys.version_info >= (3, 10): + from importlib.metadata import distribution, PackageNotFoundError + else: + from importlib_metadata import distribution, PackageNotFoundError # Sources and elements are looked up in separate # entrypoint groups from the same package. @@ -53,38 +52,38 @@ def get_plugin_paths(self, kind, plugin_type): else: assert False, "unreachable" - # key by a tuple to avoid collision try: - package = pkg_resources.get_entry_info(self._package_name, entrypoint_group, kind) - except pkg_resources.DistributionNotFound as e: + package = Requirement(self._package_name) + except InvalidRequirement as e: + raise PluginError( + "{}: Malformed package-name '{}' encountered: {}".format( + self.provenance_node.get_provenance(), self._package_name, e + ), + reason="package-malformed-requirement", + ) from e + + try: + dist = distribution(package.name) + except PackageNotFoundError as e: raise PluginError( "{}: Failed to load {} plugin '{}': {}".format( self.provenance_node.get_provenance(), plugin_type, kind, e ), reason="package-not-found", ) from e - except pkg_resources.VersionConflict as e: + + if dist.version not in package.specifier: raise PluginError( "{}: Version conflict encountered while loading {} plugin '{}'".format( self.provenance_node.get_provenance(), plugin_type, kind ), - detail=e.report(), + detail="{} {} is installed but {} is required".format(dist.name, dist.version, package), reason="package-version-conflict", - ) from e - except ( - # For setuptools < 49.0.0 - pkg_resources.RequirementParseError, - # For setuptools >= 49.0.0 - packaging.requirements.InvalidRequirement, - ) as e: - raise PluginError( - "{}: Malformed package-name '{}' encountered: {}".format( - self.provenance_node.get_provenance(), self._package_name, e - ), - reason="package-malformed-requirement", - ) from e + ) - if package is None: + try: + entrypoint = dist.entry_points.select(group=entrypoint_group)[kind] + except KeyError as e: raise PluginError( "{}: Pip package {} does not contain a {} plugin named '{}'".format( self.provenance_node.get_provenance(), self._package_name, plugin_type, kind @@ -92,24 +91,17 @@ def get_plugin_paths(self, kind, plugin_type): reason="plugin-not-found", ) - location = package.dist.get_resource_filename( - pkg_resources._manager, package.module_name.replace(".", os.sep) + ".py" - ) + location = dist.locate_file(entrypoint.module.replace(".", os.sep) + ".py") + defaults = dist.locate_file(entrypoint.module.replace(".", os.sep) + ".yaml") - # Also load the defaults - required since setuptools - # may need to extract the file. - try: - defaults = package.dist.get_resource_filename( - pkg_resources._manager, package.module_name.replace(".", os.sep) + ".yaml" - ) - except KeyError: + if not defaults.exists(): # The plugin didn't have an accompanying YAML file defaults = None return ( os.path.dirname(location), - defaults, - "python package '{}' at: {}".format(package.dist, package.dist.location), + str(defaults), + "python package '{}' at: {}".format(dist, dist.locate_file("")), ) def load_config(self, origin_node):