From d9299e97ab9ff7fa8f158740be61572e04a936fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Thu, 1 Sep 2011 07:01:13 +0200 Subject: [PATCH] Minor improvement to extensions in setup.cfg: check parent package --- Doc/packaging/setupcfg.rst | 4 +++- Lib/packaging/config.py | 20 +++++++++++++++++-- Lib/packaging/tests/test_config.py | 31 ++++++++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Doc/packaging/setupcfg.rst b/Doc/packaging/setupcfg.rst index 66a61ecc7ea..ddec41a5f44 100644 --- a/Doc/packaging/setupcfg.rst +++ b/Doc/packaging/setupcfg.rst @@ -769,7 +769,9 @@ needs to have its options defined in a dedicated section. Here's an example:: The section name must start with ``extension:``; the right-hand part is used as the full name (including a parent package, if any) of the extension. Whitespace -around the extension name is allowed. +around the extension name is allowed. If the extension module is not standalone +(e.g. ``_bisect``) but part of a package (e.g. ``thing._speedups``), the parent +package must be listed in the ``packages`` field. Valid fields and their values are listed in the documentation of the :class:`packaging.compiler.extension.Extension` class; values documented as Python lists translate to multi-line values in the configuration file. In diff --git a/Lib/packaging/config.py b/Lib/packaging/config.py index b138d082790..e02800e9605 100644 --- a/Lib/packaging/config.py +++ b/Lib/packaging/config.py @@ -16,6 +16,19 @@ from packaging.command import set_command from packaging.markers import interpret +def _check_name(name, packages): + if '.' not in name: + return + parts = name.split('.') + modname = parts[-1] + parent = '.'.join(parts[:-1]) + if parent not in packages: + # we could log a warning instead of raising, but what's the use + # of letting people build modules they can't import? + raise PackagingOptionError( + 'parent package for extension %r not found' % name) + + def _pop_values(values_dct, key): """Remove values from the dictionary and convert them as a list""" vals_str = values_dct.pop(key, '') @@ -142,7 +155,8 @@ class Config: try: hook = resolve_name(line) except ImportError as e: - logger.warning('cannot find setup hook: %s', e.args[0]) + logger.warning('cannot find setup hook: %s', + e.args[0]) else: self.setup_hooks.append(hook) self.run_hooks(content) @@ -259,8 +273,10 @@ class Config: raise PackagingOptionError( 'extension name should be given as [extension: name], ' 'not as key') + name = labels[1].strip() + _check_name(name, self.dist.packages) ext_modules.append(Extension( - labels[1].strip(), + name, _pop_values(values_dct, 'sources'), _pop_values(values_dct, 'include_dirs'), _pop_values(values_dct, 'define_macros'), diff --git a/Lib/packaging/tests/test_config.py b/Lib/packaging/tests/test_config.py index ce0162953d4..bcb55fb7276 100644 --- a/Lib/packaging/tests/test_config.py +++ b/Lib/packaging/tests/test_config.py @@ -105,6 +105,7 @@ EXT_SETUP_CFG = """ [files] packages = one two + parent.undeclared [extension:one.speed_coconuts] sources = c_src/speed_coconuts.c @@ -122,6 +123,11 @@ extra_compile_args = -fPIC -O2 -DGECODE_VERSION=$(./gecode_version) -- sys.platform != 'win32' /DGECODE_VERSION='win32' -- sys.platform == 'win32' language = cxx + +# corner case: if the parent package of an extension is declared but +# not its grandparent, it's legal +[extension: parent.undeclared._speed] +sources = parent/undeclared/_speed.c """ EXT_SETUP_CFG_BUGGY_1 = """ @@ -129,6 +135,21 @@ EXT_SETUP_CFG_BUGGY_1 = """ name = crash_here """ +EXT_SETUP_CFG_BUGGY_2 = """ +[files] +packages = ham + +[extension: spam.eggs] +""" + +EXT_SETUP_CFG_BUGGY_3 = """ +[files] +packages = ok + ok.works + +[extension: ok.works.breaks._ext] +""" + HOOKS_MODULE = """ import logging @@ -314,7 +335,7 @@ class ConfigTestCase(support.TempdirManager, dist = self.get_dist() ext_modules = dict((mod.name, mod) for mod in dist.ext_modules) - self.assertEqual(len(ext_modules), 2) + self.assertEqual(len(ext_modules), 3) ext = ext_modules.get('one.speed_coconuts') self.assertEqual(ext.sources, ['c_src/speed_coconuts.c']) self.assertEqual(ext.define_macros, ['HAVE_CAIRO', 'HAVE_GTK2']) @@ -341,6 +362,12 @@ class ConfigTestCase(support.TempdirManager, self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_1) self.assertRaises(PackagingOptionError, self.get_dist) + self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_2) + self.assertRaises(PackagingOptionError, self.get_dist) + + self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_3) + self.assertRaises(PackagingOptionError, self.get_dist) + def test_project_setup_hook_works(self): # Bug #11637: ensure the project directory is on sys.path to allow # project-specific hooks @@ -364,7 +391,7 @@ class ConfigTestCase(support.TempdirManager, self.write_setup({ 'setup-hooks': '\n packaging.tests.test_config.first_hook' '\n packaging.tests.test_config.missing_hook' - '\n packaging.tests.test_config.third_hook' + '\n packaging.tests.test_config.third_hook', }) self.write_file('README', 'yeah') dist = self.get_dist()