Remove support for command hooks

distutils2 is long dead and many of its best ideas have been
incorporated into setuptools. One of the ideas that *has not* been
incorporated is the idea of command hooks, of either the pre or post
kind. pbr is still carrying the code for this, and there are several
issues with this:

- No one is using this functionality in OpenStack and, given the
  complete lack of documentation on the matter, it's very doubtful that
  it's being used anywhere else [1]
- It's causing issues for projects attempting to hook into the
  'distutils.commands' entry point on Python 2.7, which it seems no else
  must have attempted yet [2].
- distutils2 is dead and advanced features like this that we don't
  explicitly need should not be retained

We could attempt to fix this but why bother? Good riddance, I say.

[1] http://codesearch.openstack.org/?q=pre_hook%5C.&i=nope&files=&repos=
[2] http://codesearch.openstack.org/?q=distutils.commands&i=nope&files=&repos=

Change-Id: I01f657034cffbf55ce830b7e8dbb6b3d94c1fd18
This commit is contained in:
Stephen Finucane 2017-10-06 15:44:13 +01:00
parent 87637bdf1d
commit 32c90ba598
5 changed files with 6 additions and 159 deletions

View File

@ -141,16 +141,5 @@ def pbr(dist, attr, value):
if isinstance(dist.metadata.version, integer_types + (float,)):
# Some people apparently take "version number" too literally :)
dist.metadata.version = str(dist.metadata.version)
# This bit of hackery is necessary so that the Distribution will ignore
# normally unsupport command options (namely pre-hooks and post-hooks).
# dist.command_options is normally a dict mapping command names to
# dicts of their options. Now it will be a defaultdict that returns
# IgnoreDicts for the each command's options so we can pass through the
# unsupported options
ignore = ['pre_hook.*', 'post_hook.*']
dist.command_options = util.DefaultGetDict(
lambda: util.IgnoreDict(ignore)
)
finally:
_restore_distribution_monkeypatch()

View File

@ -39,9 +39,7 @@
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
import os
import textwrap
from testtools import content
from testtools import matchers
from pbr.tests import base
@ -56,10 +54,6 @@ class TestHooks(base.BaseTestCase):
cfg.set('global', 'setup-hooks',
'pbr_testpackage._setup_hooks.test_hook_1\n'
'pbr_testpackage._setup_hooks.test_hook_2')
cfg.set('build_ext', 'pre-hook.test_pre_hook',
'pbr_testpackage._setup_hooks.test_pre_hook')
cfg.set('build_ext', 'post-hook.test_post_hook',
'pbr_testpackage._setup_hooks.test_post_hook')
def test_global_setup_hooks(self):
"""Test setup_hooks.
@ -72,28 +66,6 @@ class TestHooks(base.BaseTestCase):
assert 'test_hook_1\ntest_hook_2' in stdout
assert return_code == 0
def test_command_hooks(self):
"""Test command hooks.
Simple test that the appropriate command hooks run at the
beginning/end of the appropriate command.
"""
stdout, _, return_code = self.run_setup('egg_info')
assert 'build_ext pre-hook' not in stdout
assert 'build_ext post-hook' not in stdout
assert return_code == 0
stdout, stderr, return_code = self.run_setup('build_ext')
self.addDetailUniqueName('stderr', content.text_content(stderr))
assert textwrap.dedent("""
running build_ext
running pre_hook pbr_testpackage._setup_hooks.test_pre_hook for command build_ext
build_ext pre-hook
""") in stdout # flake8: noqa
self.expectThat(stdout, matchers.EndsWith('build_ext post-hook'))
assert return_code == 0
def test_custom_commands_known(self):
stdout, _, return_code = self.run_setup('--help-commands')
self.assertFalse(return_code)

View File

@ -51,7 +51,3 @@ optional = True
# pbr_testpackage._setup_hooks.test_hook_1
# pbr_testpackage._setup_hooks.test_hook_2
commands = pbr_testpackage._setup_hooks.test_command
[build_ext]
#pre-hook.test_pre_hook = pbr_testpackage._setup_hooks.test_pre_hook
#post-hook.test_post_hook = pbr_testpackage._setup_hooks.test_post_hook

View File

@ -264,8 +264,6 @@ def cfg_to_args(path='setup.cfg', script_args=()):
if entry_points:
kwargs['entry_points'] = entry_points
wrap_commands(kwargs)
# Handle the [files]/extra_files option
files_extra_files = has_get_option(config, 'files', 'extra_files')
if files_extra_files:
@ -552,103 +550,6 @@ def get_entry_points(config):
for option, value in config['entry_points'].items())
def wrap_commands(kwargs):
dist = st_dist.Distribution()
# This should suffice to get the same config values and command classes
# that the actual Distribution will see (not counting cmdclass, which is
# handled below)
dist.parse_config_files()
# Setuptools doesn't patch get_command_list, and as such we do not get
# extra commands from entry_points. As we need to be compatable we deal
# with this here.
for ep in pkg_resources.iter_entry_points('distutils.commands'):
if ep.name not in dist.cmdclass:
if hasattr(ep, 'resolve'):
cmdclass = ep.resolve()
else:
# Old setuptools does not have ep.resolve, and load with
# arguments is depricated in 11+. Use resolve, 12+, if we
# can, otherwise fall back to load.
# Setuptools 11 will throw a deprication warning, as it
# uses _load instead of resolve.
cmdclass = ep.load(False)
dist.cmdclass[ep.name] = cmdclass
for cmd, _ in dist.get_command_list():
hooks = {}
for opt, val in dist.get_option_dict(cmd).items():
val = val[1]
if opt.startswith('pre_hook.') or opt.startswith('post_hook.'):
hook_type, alias = opt.split('.', 1)
hook_dict = hooks.setdefault(hook_type, {})
hook_dict[alias] = val
if not hooks:
continue
if 'cmdclass' in kwargs and cmd in kwargs['cmdclass']:
cmdclass = kwargs['cmdclass'][cmd]
else:
cmdclass = dist.get_command_class(cmd)
new_cmdclass = wrap_command(cmd, cmdclass, hooks)
kwargs.setdefault('cmdclass', {})[cmd] = new_cmdclass
def wrap_command(cmd, cmdclass, hooks):
def run(self, cmdclass=cmdclass):
self.run_command_hooks('pre_hook')
cmdclass.run(self)
self.run_command_hooks('post_hook')
return type(cmd, (cmdclass, object),
{'run': run, 'run_command_hooks': run_command_hooks,
'pre_hook': hooks.get('pre_hook'),
'post_hook': hooks.get('post_hook')})
def run_command_hooks(cmd_obj, hook_kind):
"""Run hooks registered for that command and phase.
*cmd_obj* is a finalized command object; *hook_kind* is either
'pre_hook' or 'post_hook'.
"""
if hook_kind not in ('pre_hook', 'post_hook'):
raise ValueError('invalid hook kind: %r' % hook_kind)
hooks = getattr(cmd_obj, hook_kind, None)
if hooks is None:
return
for hook in hooks.values():
if isinstance(hook, str):
try:
hook_obj = resolve_name(hook)
except ImportError:
err = sys.exc_info()[1] # For py3k
raise errors.DistutilsModuleError('cannot find hook %s: %s' %
(hook,err))
else:
hook_obj = hook
if not hasattr(hook_obj, '__call__'):
raise errors.DistutilsOptionError('hook %r is not callable' % hook)
log.info('running %s %s for command %s',
hook_kind, hook, cmd_obj.get_command_name())
try :
hook_obj(cmd_obj)
except:
e = sys.exc_info()[1]
log.error('hook %s raised exception: %s\n' % (hook, e))
log.error(traceback.format_exc())
sys.exit(1)
def has_get_option(config, section, option):
if section in config and option in config[section]:
return config[section][option]
@ -684,20 +585,3 @@ class DefaultGetDict(defaultdict):
if default is None:
default = self.default_factory()
return super(DefaultGetDict, self).setdefault(key, default)
class IgnoreDict(dict):
"""A dictionary that ignores any insertions in which the key is a string
matching any string in `ignore`. The ignore list can also contain wildcard
patterns using '*'.
"""
def __init__(self, ignore):
self.__ignore = re.compile(r'(%s)' % ('|'.join(
[pat.replace('*', '.*')
for pat in ignore])))
def __setitem__(self, key, val):
if self.__ignore.match(key):
return
super(IgnoreDict, self).__setitem__(key, val)

View File

@ -0,0 +1,6 @@
---
upgrade:
- |
Support for entry point command hooks has been removed. This feature was
poorly tested, poorly documented, and broken in some environments.
Support for global hooks is not affected.