Branch merge

This commit is contained in:
Éric Araujo 2011-06-11 19:56:09 +02:00
commit bc18532eee
8 changed files with 156 additions and 77 deletions

View File

@ -83,7 +83,7 @@ The class can be used to simulate nested scopes and is useful in templating.
creating subcontexts that can be updated without altering values in any
of the parent mappings.
.. attribute:: parents()
.. method:: parents()
Returns a new :class:`ChainMap` containing all of the maps in the current
instance except the first one. This is useful for skipping the first map

View File

@ -176,15 +176,19 @@ compilers
compilers =
hotcompiler.SmartCCompiler
setup_hook
defines a callable that will be called right after the
:file:`setup.cfg` file is read. The callable receives the configuration
in form of a mapping and can make some changes to it. *optional*
setup_hooks
Defines a list of callables to be called right after the :file:`setup.cfg`
file is read, before any other processing. The callables are executed in the
order they're found in the file; if one of them cannot be found, tools should
not stop, but for example produce a warning and continue with the next line.
Each callable receives the configuration as a dictionary (keys are
:file:`setup.cfg` sections, values are dictionaries of fields) and can make
any changes to it. *optional*, *multi*
Example::
[global]
setup_hook = package.setup.customize_dist
setup_hooks = package.setup.customize_dist
Metadata
@ -285,6 +289,7 @@ One extra field not present in PEP 345 is supported:
description-file
Path to a text file that will be used to fill the ``description`` field.
Multiple values are accepted; they must be separated by whitespace.
``description-file`` and ``description`` are mutually exclusive. *optional*

View File

@ -9,7 +9,8 @@ from configparser import RawConfigParser
from packaging import logger
from packaging.errors import PackagingOptionError
from packaging.compiler.extension import Extension
from packaging.util import check_environ, iglob, resolve_name, strtobool
from packaging.util import (check_environ, iglob, resolve_name, strtobool,
split_multiline)
from packaging.compiler import set_compiler
from packaging.command import set_command
from packaging.markers import interpret
@ -60,17 +61,15 @@ def get_resources_dests(resources_root, rules):
class Config:
"""Reads configuration files and work with the Distribution instance
"""
"""Class used to work with configuration files"""
def __init__(self, dist):
self.dist = dist
self.setup_hook = None
self.setup_hooks = []
def run_hook(self, config):
if self.setup_hook is None:
return
# the hook gets only the config
self.setup_hook(config)
def run_hooks(self, config):
"""Run setup hooks in the order defined in the spec."""
for hook in self.setup_hooks:
hook(config)
def find_config_files(self):
"""Find as many configuration files as should be processed for this
@ -124,29 +123,26 @@ class Config:
# XXX
return value
def _multiline(self, value):
value = [v for v in
[v.strip() for v in value.split('\n')]
if v != '']
return value
def _read_setup_cfg(self, parser, cfg_filename):
cfg_directory = os.path.dirname(os.path.abspath(cfg_filename))
content = {}
for section in parser.sections():
content[section] = dict(parser.items(section))
# global:setup_hook is called *first*
# global setup hooks are called first
if 'global' in content:
if 'setup_hook' in content['global']:
setup_hook = content['global']['setup_hook']
try:
self.setup_hook = resolve_name(setup_hook)
except ImportError as e:
logger.warning('could not import setup_hook: %s',
e.args[0])
else:
self.run_hook(content)
if 'setup_hooks' in content['global']:
setup_hooks = split_multiline(content['global']['setup_hooks'])
for line in setup_hooks:
try:
hook = resolve_name(line)
except ImportError as e:
logger.warning('cannot find setup hook: %s', e.args[0])
else:
self.setup_hooks.append(hook)
self.run_hooks(content)
metadata = self.dist.metadata
@ -155,7 +151,7 @@ class Config:
for key, value in content['metadata'].items():
key = key.replace('_', '-')
if metadata.is_multi_field(key):
value = self._multiline(value)
value = split_multiline(value)
if key == 'project-url':
value = [(label.strip(), url.strip())
@ -168,21 +164,18 @@ class Config:
"mutually exclusive")
raise PackagingOptionError(msg)
if isinstance(value, list):
filenames = value
else:
filenames = value.split()
filenames = value.split()
# concatenate each files
value = ''
# concatenate all files
value = []
for filename in filenames:
# will raise if file not found
with open(filename) as description_file:
value += description_file.read().strip() + '\n'
value.append(description_file.read().strip())
# add filename as a required file
if filename not in metadata.requires_files:
metadata.requires_files.append(filename)
value = value.strip()
value = '\n'.join(value).strip()
key = 'description'
if metadata.is_metadata_field(key):
@ -192,7 +185,7 @@ class Config:
files = content['files']
self.dist.package_dir = files.pop('packages_root', None)
files = dict((key, self._multiline(value)) for key, value in
files = dict((key, split_multiline(value)) for key, value in
files.items())
self.dist.packages = []
@ -310,7 +303,7 @@ class Config:
opt = opt.replace('-', '_')
if opt == 'sub_commands':
val = self._multiline(val)
val = split_multiline(val)
if isinstance(val, str):
val = [val]
@ -348,14 +341,14 @@ class Config:
raise PackagingOptionError(msg)
def _load_compilers(self, compilers):
compilers = self._multiline(compilers)
compilers = split_multiline(compilers)
if isinstance(compilers, str):
compilers = [compilers]
for compiler in compilers:
set_compiler(compiler.strip())
def _load_commands(self, commands):
commands = self._multiline(commands)
commands = split_multiline(commands)
if isinstance(commands, str):
commands = [commands]
for command in commands:

View File

@ -90,7 +90,7 @@ commands =
compilers =
packaging.tests.test_config.DCompiler
setup_hook = %(setup-hook)s
setup_hooks = %(setup-hooks)s
@ -135,8 +135,16 @@ class DCompiler:
pass
def hook(content):
content['metadata']['version'] += '.dev1'
def version_hook(config):
config['metadata']['version'] += '.dev1'
def first_hook(config):
config['files']['modules'] += '\n first'
def third_hook(config):
config['files']['modules'] += '\n third'
class FooBarBazTest:
@ -186,7 +194,7 @@ class ConfigTestCase(support.TempdirManager,
def write_setup(self, kwargs=None):
opts = {'description-file': 'README', 'extra-files': '',
'setup-hook': 'packaging.tests.test_config.hook'}
'setup-hooks': 'packaging.tests.test_config.version_hook'}
if kwargs:
opts.update(kwargs)
self.write_file('setup.cfg', SETUP_CFG % opts, encoding='utf-8')
@ -318,16 +326,30 @@ class ConfigTestCase(support.TempdirManager,
self.assertEqual(ext.extra_compile_args, cargs)
self.assertEqual(ext.language, 'cxx')
def test_missing_setuphook_warns(self):
self.write_setup({'setup-hook': 'this.does._not.exist'})
def test_missing_setup_hook_warns(self):
self.write_setup({'setup-hooks': 'this.does._not.exist'})
self.write_file('README', 'yeah')
dist = self.get_dist()
logs = self.get_logs(logging.WARNING)
self.assertEqual(1, len(logs))
self.assertIn('could not import setup_hook', logs[0])
self.assertIn('cannot find setup hook', logs[0])
def test_multiple_setup_hooks(self):
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'
})
self.write_file('README', 'yeah')
dist = self.get_dist()
self.assertEqual(['haven', 'first', 'third'], dist.py_modules)
logs = self.get_logs(logging.WARNING)
self.assertEqual(1, len(logs))
self.assertIn('cannot find setup hook', logs[0])
def test_metadata_requires_description_files_missing(self):
self.write_setup({'description-file': 'README\n README2'})
self.write_setup({'description-file': 'README README2'})
self.write_file('README', 'yeah')
self.write_file('README2', 'yeah')
os.mkdir('src')

View File

@ -8,16 +8,18 @@ import subprocess
from io import StringIO
from packaging.tests import support, unittest
from packaging.tests.test_config import SETUP_CFG
from packaging.errors import (
PackagingPlatformError, PackagingByteCompileError, PackagingFileError,
PackagingExecError, InstallationException)
from packaging import util
from packaging.dist import Distribution
from packaging.util import (
convert_path, change_root, split_quoted, strtobool, rfc822_escape,
get_compiler_versions, _MAC_OS_X_LD_VERSION, byte_compile, find_packages,
spawn, get_pypirc_path, generate_pypirc, read_pypirc, resolve_name, iglob,
RICH_GLOB, egginfo_to_distinfo, is_setuptools, is_distutils, is_packaging,
get_install_method)
get_install_method, cfg_to_args)
PYPIRC = """\
@ -88,13 +90,15 @@ class UtilTestCase(support.EnvironRestorer,
support.LoggingCatcher,
unittest.TestCase):
restore_environ = ['HOME']
restore_environ = ['HOME', 'PLAT']
def setUp(self):
super(UtilTestCase, self).setUp()
self.tmp_dir = self.mkdtemp()
self.rc = os.path.join(self.tmp_dir, '.pypirc')
os.environ['HOME'] = self.tmp_dir
self.addCleanup(os.chdir, os.getcwd())
tempdir = self.mkdtemp()
self.rc = os.path.join(tempdir, '.pypirc')
os.environ['HOME'] = tempdir
os.chdir(tempdir)
# saving the environment
self.name = os.name
self.platform = sys.platform
@ -103,7 +107,6 @@ class UtilTestCase(support.EnvironRestorer,
self.join = os.path.join
self.isabs = os.path.isabs
self.splitdrive = os.path.splitdrive
#self._config_vars = copy(sysconfig._config_vars)
# patching os.uname
if hasattr(os, 'uname'):
@ -137,7 +140,6 @@ class UtilTestCase(support.EnvironRestorer,
os.uname = self.uname
else:
del os.uname
#sysconfig._config_vars = copy(self._config_vars)
util.find_executable = self.old_find_executable
subprocess.Popen = self.old_popen
sys.old_stdout = self.old_stdout
@ -491,6 +493,38 @@ class UtilTestCase(support.EnvironRestorer,
content = f.read()
self.assertEqual(content, WANTED)
def test_cfg_to_args(self):
opts = {'description-file': 'README', 'extra-files': '',
'setup-hooks': 'packaging.tests.test_config.version_hook'}
self.write_file('setup.cfg', SETUP_CFG % opts)
self.write_file('README', 'loooong description')
args = cfg_to_args()
# use Distribution to get the contents of the setup.cfg file
dist = Distribution()
dist.parse_config_files()
metadata = dist.metadata
self.assertEqual(args['name'], metadata['Name'])
# + .dev1 because the test SETUP_CFG also tests a hook function in
# test_config.py for appending to the version string
self.assertEqual(args['version'] + '.dev1', metadata['Version'])
self.assertEqual(args['author'], metadata['Author'])
self.assertEqual(args['author_email'], metadata['Author-Email'])
self.assertEqual(args['maintainer'], metadata['Maintainer'])
self.assertEqual(args['maintainer_email'],
metadata['Maintainer-Email'])
self.assertEqual(args['description'], metadata['Summary'])
self.assertEqual(args['long_description'], metadata['Description'])
self.assertEqual(args['classifiers'], metadata['Classifier'])
self.assertEqual(args['requires'], metadata['Requires-Dist'])
self.assertEqual(args['provides'], metadata['Provides-Dist'])
self.assertEqual(args['package_dir'].get(''), dist.package_dir)
self.assertEqual(args['packages'], dist.packages)
self.assertEqual(args['scripts'], dist.scripts)
self.assertEqual(args['py_modules'], dist.py_modules)
class GlobTestCaseBase(support.TempdirManager,
support.LoggingCatcher,

View File

@ -250,6 +250,14 @@ def split_quoted(s):
return words
def split_multiline(value):
"""Split a multiline string into a list, excluding blank lines."""
return [element for element in
(line.strip() for line in value.split('\n'))
if element]
def execute(func, args, msg=None, verbose=0, dry_run=False):
"""Perform some action that affects the outside world.
@ -542,18 +550,15 @@ def write_file(filename, contents):
def _is_package(path):
if not os.path.isdir(path):
return False
return os.path.isfile(os.path.join(path, '__init__.py'))
return os.path.isdir(path) and os.path.isfile(
os.path.join(path, '__init__.py'))
# Code taken from the pip project
def _is_archive_file(name):
archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
ext = splitext(name)[1].lower()
if ext in archives:
return True
return False
return ext in archives
def _under(path, root):
@ -772,12 +777,13 @@ def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
Raise PackagingExecError if running the program fails in any way; just
return on success.
"""
logger.info(' '.join(cmd))
logger.debug('spawn: running %r', cmd)
if dry_run:
logging.debug('dry run, no process actually spawned')
return
exit_status = subprocess.call(cmd, env=env)
if exit_status != 0:
msg = "command '%s' failed with exit status %d"
msg = "command %r failed with exit status %d"
raise PackagingExecError(msg % (cmd, exit_status))
@ -1010,16 +1016,20 @@ def cfg_to_args(path='setup.cfg'):
"requires": ("metadata", "requires_dist"),
"provides": ("metadata", "provides_dist"), # **
"obsoletes": ("metadata", "obsoletes_dist"), # **
"package_dir": ("files", 'packages_root'),
"packages": ("files",),
"scripts": ("files",),
"py_modules": ("files", "modules"), # **
}
MULTI_FIELDS = ("classifiers",
"requires",
"platforms",
"requires",
"provides",
"obsoletes",
"packages",
"scripts")
"scripts",
"py_modules")
def has_get_option(config, section, option):
if config.has_option(section, option):
@ -1031,9 +1041,9 @@ def cfg_to_args(path='setup.cfg'):
# The real code starts here
config = RawConfigParser()
if not os.path.exists(file):
if not os.path.exists(path):
raise PackagingFileError("file '%s' does not exist" %
os.path.abspath(file))
os.path.abspath(path))
config.read(path)
kwargs = {}
@ -1050,17 +1060,24 @@ def cfg_to_args(path='setup.cfg'):
in_cfg_value = has_get_option(config, section, option)
if not in_cfg_value:
# There is no such option in the setup.cfg
if arg == "long_description":
filename = has_get_option(config, section, "description_file")
if filename:
with open(filename) as fp:
in_cfg_value = fp.read()
if arg == 'long_description':
filenames = has_get_option(config, section, 'description-file')
if filenames:
filenames = split_multiline(filenames)
in_cfg_value = []
for filename in filenames:
with open(filename) as fp:
in_cfg_value.append(fp.read())
in_cfg_value = '\n\n'.join(in_cfg_value)
else:
continue
if arg == 'package_dir' and in_cfg_value:
in_cfg_value = {'': in_cfg_value}
if arg in MULTI_FIELDS:
# support multiline options
in_cfg_value = in_cfg_value.strip().split('\n')
in_cfg_value = split_multiline(in_cfg_value)
kwargs[arg] = in_cfg_value

View File

@ -116,6 +116,7 @@ Monty Brandenberg
Georg Brandl
Christopher Brannon
Terrence Brannon
Erik Bray
Brian Brazil
Dave Brennan
Tom Bridgman

View File

@ -187,6 +187,13 @@ Core and Builtins
Library
-------
- Issue #12240: Allow multiple setup hooks in packaging's setup.cfg files.
Original patch by Erik Bray.
- Issue #11595: Fix assorted bugs in packaging.util.cfg_to_args, a
compatibility helper for the distutils-packaging transition. Original patch
by Erik Bray.
- Issue #12287: In ossaudiodev, check that the device isn't closed in several
methods.