Skip to content

Commit

Permalink
Revert from using importlib.metadata back to pkg_resources (#587)
Browse files Browse the repository at this point in the history
It appears that we accidentally shipped this incompatible change on
Ubuntu Bionic, which is currently under ESM. While we do intend to drop
mainline support for Bionic, we should leave it in a good state.

This reverts commit a9de909.
This reverts commit 5f9f5ff.
This reverts commit 7b70e61.
  • Loading branch information
cottsay committed Oct 2, 2023
1 parent 2ce42f1 commit 731092b
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 107 deletions.
78 changes: 38 additions & 40 deletions colcon_core/extension_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,11 @@
import os
import traceback

try:
from importlib.metadata import distributions
from importlib.metadata import EntryPoint
from importlib.metadata import entry_points
except ImportError:
# TODO: Drop this with Python 3.7 support
from importlib_metadata import distributions
from importlib_metadata import EntryPoint
from importlib_metadata import entry_points

from colcon_core.environment_variable import EnvironmentVariable
from colcon_core.logging import colcon_logger
from pkg_resources import EntryPoint
from pkg_resources import iter_entry_points
from pkg_resources import WorkingSet

"""Environment variable to block extensions"""
EXTENSION_BLOCKLIST_ENVIRONMENT_VARIABLE = EnvironmentVariable(
Expand Down Expand Up @@ -51,26 +44,27 @@ def get_all_extension_points():
colcon_extension_points.setdefault(EXTENSION_POINT_GROUP_NAME, None)

entry_points = defaultdict(dict)
seen = set()
for dist in distributions():
dist_name = dist.metadata['Name']
if dist_name in seen:
continue
seen.add(dist_name)
for entry_point in dist.entry_points:
working_set = WorkingSet()
for dist in sorted(working_set):
entry_map = dist.get_entry_map()
for group_name in entry_map.keys():
# skip groups which are not registered as extension points
if entry_point.group not in colcon_extension_points:
if group_name not in colcon_extension_points:
continue

if entry_point.name in entry_points[entry_point.group]:
previous = entry_points[entry_point.group][entry_point.name]
logger.error(
f"Entry point '{entry_point.group}.{entry_point.name}' is "
f"declared multiple times, '{entry_point.value}' "
f"from '{dist._path}' "
f"overwriting '{previous}'")
entry_points[entry_point.group][entry_point.name] = \
(entry_point.value, dist_name, dist.version)
group = entry_map[group_name]
for entry_point_name, entry_point in group.items():
if entry_point_name in entry_points[group_name]:
previous = entry_points[group_name][entry_point_name]
logger.error(
f"Entry point '{group_name}.{entry_point_name}' is "
f"declared multiple times, '{entry_point}' "
f"overwriting '{previous}'")
value = entry_point.module_name
if entry_point.attrs:
value += f":{'.'.join(entry_point.attrs)}"
entry_points[group_name][entry_point_name] = (
value, dist.project_name, getattr(dist, 'version', None))
return entry_points


Expand All @@ -82,21 +76,19 @@ def get_extension_points(group):
:returns: mapping of extension point names to extension point values
:rtype: dict
"""
extension_points = {}
try:
# Python 3.10 and newer
query = entry_points(group=group)
except TypeError:
query = entry_points().get(group, ())
for entry_point in query:
if entry_point.name in extension_points:
previous_entry_point = extension_points[entry_point.name]
entry_points = {}
for entry_point in iter_entry_points(group=group):
if entry_point.name in entry_points:
previous_entry_point = entry_points[entry_point.name]
logger.error(
f"Entry point '{group}.{entry_point.name}' is declared "
f"multiple times, '{entry_point.value}' overwriting "
f"multiple times, '{entry_point}' overwriting "
f"'{previous_entry_point}'")
extension_points[entry_point.name] = entry_point.value
return extension_points
value = entry_point.module_name
if entry_point.attrs:
value += f":{'.'.join(entry_point.attrs)}"
entry_points[entry_point.name] = value
return entry_points


def load_extension_points(group, *, excludes=None):
Expand Down Expand Up @@ -154,4 +146,10 @@ def load_extension_point(name, value, group):
raise RuntimeError(
'The entry point name is listed in the environment variable '
f"'{EXTENSION_BLOCKLIST_ENVIRONMENT_VARIABLE.name}'")
return EntryPoint(name, value, group).load()
if ':' in value:
module_name, attr = value.split(':', 1)
attrs = attr.split('.')
else:
module_name = value
attrs = ()
return EntryPoint(name, module_name, attrs).resolve()
14 changes: 4 additions & 10 deletions colcon_core/package_identification/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,11 @@ def get_configuration(setup_cfg):
except ImportError:
from setuptools.config import read_configuration
except ImportError as e:
try:
from importlib.metadata import distribution
except ImportError:
from importlib_metadata import distribution
from packaging.version import Version
try:
setuptools_version = distribution('setuptools').version
except ModuleNotFoundError:
setuptools_version = '0'
from pkg_resources import get_distribution
from pkg_resources import parse_version
setuptools_version = get_distribution('setuptools').version
minimum_version = '30.3.0'
if Version(setuptools_version) < Version(minimum_version):
if parse_version(setuptools_version) < parse_version(minimum_version):
e.msg += ', ' \
"'setuptools' needs to be at least version " \
f'{minimum_version}, if a newer version is not available ' \
Expand Down
8 changes: 4 additions & 4 deletions colcon_core/plugin_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from colcon_core.extension_point import load_extension_points
from colcon_core.logging import colcon_logger
from packaging.version import Version
from pkg_resources import parse_version

logger = colcon_logger.getChild(__name__)

Expand Down Expand Up @@ -166,8 +166,8 @@ def satisfies_version(version, caret_range):
:raises RuntimeError: if the version doesn't match the caret range
"""
assert caret_range.startswith('^'), 'Only supports caret ranges'
extension_point_version = Version(version)
extension_version = Version(caret_range[1:])
extension_point_version = parse_version(version)
extension_version = parse_version(caret_range[1:])
next_extension_version = _get_upper_bound_caret_version(
extension_version)

Expand All @@ -192,4 +192,4 @@ def _get_upper_bound_caret_version(version):
minor = 0
else:
minor += 1
return Version('%d.%d.0' % (major, minor))
return parse_version('%d.%d.0' % (major, minor))
6 changes: 3 additions & 3 deletions colcon_core/task/python/test/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from colcon_core.task.python.test import has_test_dependency
from colcon_core.task.python.test import PythonTestingStepExtensionPoint
from colcon_core.verb.test import logger
from packaging.version import Version
from pkg_resources import parse_version


class PytestPythonTestingStep(PythonTestingStepExtensionPoint):
Expand Down Expand Up @@ -64,7 +64,7 @@ async def step(self, context, env, setup_py_data): # noqa: D102
# use -o option only when available
# https://github.com/pytest-dev/pytest/blob/3.3.0/CHANGELOG.rst
from pytest import __version__ as pytest_version
if Version(pytest_version) >= Version('3.3.0'):
if parse_version(pytest_version) >= parse_version('3.3.0'):
args += [
'-o', 'cache_dir=' + str(PurePosixPath(
*(Path(context.args.build_base).parts)) / '.pytest_cache'),
Expand Down Expand Up @@ -95,7 +95,7 @@ async def step(self, context, env, setup_py_data): # noqa: D102
]
# use --cov-branch option only when available
# https://github.com/pytest-dev/pytest-cov/blob/v2.5.0/CHANGELOG.rst
if Version(pytest_cov_version) >= Version('2.5.0'):
if parse_version(pytest_cov_version) >= parse_version('2.5.0'):
args += [
'--cov-branch',
]
Expand Down
6 changes: 3 additions & 3 deletions debian/patches/setup.cfg.patch
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ Author: Dirk Thomas <web@dirk-thomas.net>

--- setup.cfg 2018-05-27 11:22:33.000000000 -0700
+++ setup.cfg.patched 2018-05-27 11:22:33.000000000 -0700
@@ -33,9 +33,12 @@
importlib-metadata; python_version < "3.8"
packaging
@@ -31,9 +31,12 @@
distlib
EmPy
pytest
- pytest-cov
- pytest-repeat
Expand Down
4 changes: 1 addition & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ install_requires =
coloredlogs; sys_platform == 'win32'
distlib
EmPy
importlib-metadata; python_version < "3.8"
packaging
# the pytest dependency and its extensions are provided for convenience
# even though they are only conditional
pytest
Expand Down Expand Up @@ -69,7 +67,7 @@ filterwarnings =
error
# Suppress deprecation warnings in other packages
ignore:lib2to3 package is deprecated::scspell
ignore:pkg_resources is deprecated as an API::colcon_core.entry_point
ignore:pkg_resources is deprecated as an API
ignore:SelectableGroups dict interface is deprecated::flake8
ignore:The loop argument is deprecated::asyncio
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated::pydocstyle
Expand Down
2 changes: 1 addition & 1 deletion stdeb.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[colcon-core]
No-Python2:
Depends3: python3-distlib, python3-empy, python3-packaging, python3-pytest, python3-setuptools, python3 (>= 3.8) | python3-importlib-metadata
Depends3: python3-distlib, python3-empy, python3-packaging, python3-pytest, python3-setuptools
Recommends3: python3-pytest-cov
Suggests3: python3-pytest-repeat, python3-pytest-rerunfailures
Suite: bionic focal jammy stretch buster bullseye
Expand Down
2 changes: 1 addition & 1 deletion test/spell_check.words
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ addopts
apache
argparse
asyncio
attrs
autouse
basepath
bazqux
Expand Down Expand Up @@ -46,7 +47,6 @@ hardcodes
hookimpl
hookwrapper
https
importlib
isatty
iterdir
junit
Expand Down
80 changes: 38 additions & 42 deletions test/test_extension_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,100 +17,96 @@
from .environment_context import EnvironmentContext


Group1 = EntryPoint('group1', 'g1', EXTENSION_POINT_GROUP_NAME)
Group2 = EntryPoint('group2', 'g2', EXTENSION_POINT_GROUP_NAME)
ExtA = EntryPoint('extA', 'eA', Group1.name)
ExtB = EntryPoint('extB', 'eB', Group1.name)
Group1 = EntryPoint('group1', 'g1')
Group2 = EntryPoint('group2', 'g2')


class Dist():

version = '0.0.0'
project_name = 'dist'

def __init__(self, entry_points):
self.metadata = {'Name': f'dist-{id(self)}'}
self._entry_points = entry_points
def __init__(self, group_name, group):
self._group_name = group_name
self._group = group

@property
def entry_points(self):
return list(self._entry_points)
def __lt__(self, other):
return self._group_name < other._group_name

@property
def name(self):
return self.metadata['Name']
def get_entry_map(self):
return self._group


def iter_entry_points(*, group=None):
def iter_entry_points(*, group):
if group == EXTENSION_POINT_GROUP_NAME:
return [Group1, Group2]
elif group == Group1.name:
return [ExtA, ExtB]
assert not group
return {
EXTENSION_POINT_GROUP_NAME: [Group1, Group2],
Group1.name: [ExtA, ExtB],
}
assert group == Group1.name
ep1 = EntryPoint('extA', 'eA')
ep2 = EntryPoint('extB', 'eB')
return [ep1, ep2]


def distributions():
def working_set():
return [
Dist(iter_entry_points(group='group1')),
Dist([EntryPoint('extC', 'eC', Group2.name)]),
Dist([EntryPoint('extD', 'eD', 'groupX')]),
Dist('group1', {
'group1': {ep.name: ep for ep in iter_entry_points(group='group1')}
}),
Dist('group2', {'group2': {'extC': EntryPoint('extC', 'eC')}}),
Dist('groupX', {'groupX': {'extD': EntryPoint('extD', 'eD')}}),
]


def test_all_extension_points():
with patch(
'colcon_core.extension_point.entry_points',
'colcon_core.extension_point.iter_entry_points',
side_effect=iter_entry_points
):
with patch(
'colcon_core.extension_point.distributions',
side_effect=distributions
'colcon_core.extension_point.WorkingSet',
side_effect=working_set
):
# successfully load a known entry point
extension_points = get_all_extension_points()
assert set(extension_points.keys()) == {'group1', 'group2'}
assert set(extension_points['group1'].keys()) == {'extA', 'extB'}
assert extension_points['group1']['extA'][0] == 'eA'
assert extension_points['group1']['extA'] == (
'eA', Dist.project_name, None)


def test_extension_point_blocklist():
# successful loading of extension point without a blocklist
with patch(
'colcon_core.extension_point.entry_points',
'colcon_core.extension_point.iter_entry_points',
side_effect=iter_entry_points
):
with patch(
'colcon_core.extension_point.distributions',
side_effect=distributions
'colcon_core.extension_point.WorkingSet',
side_effect=working_set
):
extension_points = get_extension_points('group1')
assert 'extA' in extension_points.keys()
extension_point = extension_points['extA']
assert extension_point == 'eA'

with patch.object(EntryPoint, 'load', return_value=None) as load:
with patch.object(EntryPoint, 'resolve', return_value=None) as resolve:
load_extension_point('extA', 'eA', 'group1')
assert load.call_count == 1
assert resolve.call_count == 1

# successful loading of entry point not in blocklist
load.reset_mock()
resolve.reset_mock()
with EnvironmentContext(COLCON_EXTENSION_BLOCKLIST=os.pathsep.join([
'group1.extB', 'group2.extC'])
):
load_extension_point('extA', 'eA', 'group1')
assert load.call_count == 1
assert resolve.call_count == 1

# entry point in a blocked group can't be loaded
load.reset_mock()
resolve.reset_mock()
with EnvironmentContext(COLCON_EXTENSION_BLOCKLIST='group1'):
with pytest.raises(RuntimeError) as e:
load_extension_point('extA', 'eA', 'group1')
assert 'The entry point group name is listed in the environment ' \
'variable' in str(e.value)
assert load.call_count == 0
assert resolve.call_count == 0

# entry point listed in the blocklist can't be loaded
with EnvironmentContext(COLCON_EXTENSION_BLOCKLIST=os.pathsep.join([
Expand All @@ -120,10 +116,10 @@ def test_extension_point_blocklist():
load_extension_point('extA', 'eA', 'group1')
assert 'The entry point name is listed in the environment ' \
'variable' in str(e.value)
assert load.call_count == 0
assert resolve.call_count == 0


def entry_point_load(self, *args, **kwargs):
def entry_point_resolve(self, *args, **kwargs):
if self.name == 'exception':
raise Exception('entry point raising exception')
if self.name == 'runtime_error':
Expand All @@ -133,7 +129,7 @@ def entry_point_load(self, *args, **kwargs):
return DEFAULT


@patch.object(EntryPoint, 'load', entry_point_load)
@patch.object(EntryPoint, 'resolve', entry_point_resolve)
@patch(
'colcon_core.extension_point.get_extension_points',
return_value={'exception': 'a', 'runtime_error': 'b', 'success': 'c'}
Expand Down

0 comments on commit 731092b

Please sign in to comment.