1 # Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # Copyright (C) 2013 Association of Universities for Research in Astronomy
19 # Redistribution and use in source and binary forms, with or without
20 # modification, are permitted provided that the following conditions are met:
22 # 1. Redistributions of source code must retain the above copyright
23 # notice, this list of conditions and the following disclaimer.
25 # 2. Redistributions in binary form must reproduce the above
26 # copyright notice, this list of conditions and the following
27 # disclaimer in the documentation and/or other materials provided
28 # with the distribution.
30 # 3. The name of AURA and its representatives may not be used to
31 # endorse or promote products derived from this software without
32 # specific prior written permission.
34 # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
35 # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
36 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
37 # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
38 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
39 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
40 # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
42 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
43 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46 """The code in this module is mostly copy/pasted out of the distutils2 source
47 code, as recommended by Tarek Ziade. As such, it may be subject to some change
48 as distutils2 development continues, and will have to be kept up to date.
50 I didn't want to use it directly from distutils2 itself, since I do not want it
51 to be an installation dependency for our packages yet--it is still too unstable
52 (the latest version on PyPI doesn't even install).
55 # These first two imports are not used, but are needed to get around an
56 # irritating Python bug that can crop up when using ./setup.py test.
57 # See: http://www.eby-sarna.com/pipermail/peak/2010-May/003355.html
59 import multiprocessing # noqa
64 from collections import defaultdict
72 import distutils.ccompiler
73 from distutils import errors
74 from distutils import log
76 from setuptools import dist as st_dist
77 from setuptools import extension
80 import ConfigParser as configparser
84 from pbr import extra_files
87 # A simplified RE for this; just checks that the line ends with version
89 _VERSION_SPEC_RE = re.compile(r'\s*(.*?)\s*\((.*)\)\s*$')
92 # Mappings from setup() keyword arguments to setup.cfg options;
93 # The values are (section, option) tuples, or simply (section,) tuples if
94 # the option has the same name as the setup() argument
96 "name": ("metadata",),
97 "version": ("metadata",),
98 "author": ("metadata",),
99 "author_email": ("metadata",),
100 "maintainer": ("metadata",),
101 "maintainer_email": ("metadata",),
102 "url": ("metadata", "home_page"),
103 "project_urls": ("metadata",),
104 "description": ("metadata", "summary"),
105 "keywords": ("metadata",),
106 "long_description": ("metadata", "description"),
107 "long_description_content_type": ("metadata", "description_content_type"),
108 "download_url": ("metadata",),
109 "classifiers": ("metadata", "classifier"),
110 "platforms": ("metadata", "platform"), # **
111 "license": ("metadata",),
112 # Use setuptools install_requires, not
113 # broken distutils requires
114 "install_requires": ("metadata", "requires_dist"),
115 "setup_requires": ("metadata", "setup_requires_dist"),
116 "python_requires": ("metadata",),
117 "provides": ("metadata", "provides_dist"), # **
118 "provides_extras": ("metadata",),
119 "obsoletes": ("metadata", "obsoletes_dist"), # **
120 "package_dir": ("files", 'packages_root'),
121 "packages": ("files",),
122 "package_data": ("files",),
123 "namespace_packages": ("files",),
124 "data_files": ("files",),
125 "scripts": ("files",),
126 "py_modules": ("files", "modules"), # **
127 "cmdclass": ("global", "commands"),
128 # Not supported in distutils2, but provided for
129 # backwards compatibility with setuptools
130 "use_2to3": ("backwards_compat", "use_2to3"),
131 "zip_safe": ("backwards_compat", "zip_safe"),
132 "tests_require": ("backwards_compat", "tests_require"),
133 "dependency_links": ("backwards_compat",),
134 "include_package_data": ("backwards_compat",),
137 # setup() arguments that can have multiple values in setup.cfg
138 MULTI_FIELDS = ("classifiers",
143 "namespace_packages",
156 # setup() arguments that can have mapping values in setup.cfg
157 MAP_FIELDS = ("project_urls",)
159 # setup() arguments that contain boolean values
160 BOOL_FIELDS = ("use_2to3", "zip_safe", "include_package_data")
165 def shlex_split(path):
167 # shlex cannot handle paths that contain backslashes, treating those
168 # as escape characters.
169 path = path.replace("\\", "/")
170 return [x.replace("/", "\\") for x in shlex.split(path)]
172 return shlex.split(path)
175 def resolve_name(name):
176 """Resolve a name like ``module.object`` to an object and return it.
178 Raise ImportError if the module or name is not found.
181 parts = name.split('.')
182 cursor = len(parts) - 1
183 module_name = parts[:cursor]
184 attr_name = parts[-1]
188 ret = __import__('.'.join(module_name), fromlist=[attr_name])
194 module_name = parts[:cursor]
195 attr_name = parts[cursor]
198 for part in parts[cursor:]:
200 ret = getattr(ret, part)
201 except AttributeError:
202 raise ImportError(name)
207 def cfg_to_args(path='setup.cfg', script_args=()):
208 """Distutils2 to distutils1 compatibility util.
210 This method uses an existing setup.cfg to generate a dictionary of
211 keywords that can be used by distutils.core.setup(kwargs**).
216 List of commands setup.py was called with.
217 :raises DistutilsFileError:
218 When the setup.cfg file is not found.
221 # The method source code really starts here.
222 if sys.version_info >= (3, 2):
223 parser = configparser.ConfigParser()
225 parser = configparser.SafeConfigParser()
226 if not os.path.exists(path):
227 raise errors.DistutilsFileError("file '%s' does not exist" %
228 os.path.abspath(path))
230 parser.read(path, encoding='utf-8')
232 # Python 2 doesn't accept the encoding kwarg
235 for section in parser.sections():
236 config[section] = dict()
237 for k, value in parser.items(section):
238 config[section][k.replace('-', '_')] = value
240 # Run setup_hooks, if configured
241 setup_hooks = has_get_option(config, 'global', 'setup_hooks')
242 package_dir = has_get_option(config, 'files', 'packages_root')
244 # Add the source package directory to sys.path in case it contains
245 # additional hooks, and to make sure it's on the path before any existing
246 # installations of the package
248 package_dir = os.path.abspath(package_dir)
249 sys.path.insert(0, package_dir)
254 hook for hook in split_multiline(setup_hooks)
255 if hook != 'pbr.hooks.setup_hook']
256 for hook in setup_hooks:
257 hook_fn = resolve_name(hook)
261 log.error('setup hook %s terminated the installation')
263 e = sys.exc_info()[1]
264 log.error('setup hook %s raised exception: %s\n' %
266 log.error(traceback.format_exc())
270 pbr.hooks.setup_hook(config)
272 kwargs = setup_cfg_to_setup_kwargs(config, script_args)
274 # Set default config overrides
275 kwargs['include_package_data'] = True
276 kwargs['zip_safe'] = False
278 register_custom_compilers(config)
280 ext_modules = get_extension_modules(config)
282 kwargs['ext_modules'] = ext_modules
284 entry_points = get_entry_points(config)
286 kwargs['entry_points'] = entry_points
288 # Handle the [files]/extra_files option
289 files_extra_files = has_get_option(config, 'files', 'extra_files')
290 if files_extra_files:
291 extra_files.set_extra_files(split_multiline(files_extra_files))
294 # Perform cleanup if any paths were added to sys.path
301 def setup_cfg_to_setup_kwargs(config, script_args=()):
302 """Convert config options to kwargs.
304 Processes the setup.cfg options and converts them to arguments accepted
305 by setuptools' setup() function.
310 # Temporarily holds install_requires and extra_requires while we
312 all_requirements = {}
314 for arg in D1_D2_SETUP_ARGS:
315 if len(D1_D2_SETUP_ARGS[arg]) == 2:
316 # The distutils field name is different than distutils2's.
317 section, option = D1_D2_SETUP_ARGS[arg]
319 elif len(D1_D2_SETUP_ARGS[arg]) == 1:
320 # The distutils field name is the same thant distutils2's.
321 section = D1_D2_SETUP_ARGS[arg][0]
324 in_cfg_value = has_get_option(config, section, option)
326 # There is no such option in the setup.cfg
327 if arg == "long_description":
328 in_cfg_value = has_get_option(config, section,
331 in_cfg_value = split_multiline(in_cfg_value)
333 for filename in in_cfg_value:
334 description_file = io.open(filename, encoding='utf-8')
336 value += description_file.read().strip() + '\n\n'
338 description_file.close()
343 if arg in CSV_FIELDS:
344 in_cfg_value = split_csv(in_cfg_value)
345 if arg in MULTI_FIELDS:
346 in_cfg_value = split_multiline(in_cfg_value)
347 elif arg in MAP_FIELDS:
349 for i in split_multiline(in_cfg_value):
350 k, v = i.split('=', 1)
351 in_cfg_map[k.strip()] = v.strip()
352 in_cfg_value = in_cfg_map
353 elif arg in BOOL_FIELDS:
354 # Provide some flexibility here...
355 if in_cfg_value.lower() in ('true', 't', '1', 'yes', 'y'):
361 if arg in ('install_requires', 'tests_require'):
362 # Replaces PEP345-style version specs with the sort expected by
364 in_cfg_value = [_VERSION_SPEC_RE.sub(r'\1\2', pred)
365 for pred in in_cfg_value]
366 if arg == 'install_requires':
367 # Split install_requires into package,env_marker tuples
368 # These will be re-assembled later
369 install_requires = []
370 requirement_pattern = (
371 r'(?P<package>[^;]*);?(?P<env_marker>[^#]*?)(?:\s*#.*)?$')
372 for requirement in in_cfg_value:
373 m = re.match(requirement_pattern, requirement)
374 requirement_package = m.group('package').strip()
375 env_marker = m.group('env_marker').strip()
376 install_requires.append((requirement_package, env_marker))
377 all_requirements[''] = install_requires
378 elif arg == 'package_dir':
379 in_cfg_value = {'': in_cfg_value}
380 elif arg in ('package_data', 'data_files'):
384 for line in in_cfg_value:
386 key, value = line.split('=', 1)
387 key_unquoted = shlex_split(key.strip())[0]
388 key, value = (key_unquoted, value.strip())
389 if key in data_files:
390 # Multiple duplicates of the same package name;
391 # this is for backwards compatibility of the old
392 # format prior to d2to1 0.2.6.
393 prev = data_files[key]
394 prev.extend(shlex_split(value))
396 prev = data_files[key.strip()] = shlex_split(value)
398 raise errors.DistutilsOptionError(
399 'malformed package_data first line %r (misses '
402 prev.extend(shlex_split(line.strip()))
404 if arg == 'data_files':
405 # the data_files value is a pointlessly different structure
406 # from the package_data value
407 data_files = data_files.items()
408 in_cfg_value = data_files
409 elif arg == 'cmdclass':
411 dist = st_dist.Distribution()
412 for cls_name in in_cfg_value:
413 cls = resolve_name(cls_name)
415 cmdclass[cmd.get_command_name()] = cls
416 in_cfg_value = cmdclass
418 kwargs[arg] = in_cfg_value
420 # Transform requirements with embedded environment markers to
421 # setuptools' supported marker-per-requirement format.
423 # install_requires are treated as a special case of extras, before
424 # being put back in the expected place
429 # -> {'fred': ['bar'], 'fred:marker':['foo']}
431 if 'extras' in config:
432 requirement_pattern = (
433 r'(?P<package>[^:]*):?(?P<env_marker>[^#]*?)(?:\s*#.*)?$')
434 extras = config['extras']
435 # Add contents of test-requirements, if any, into an extra named
436 # 'test' if one does not already exist.
437 if 'test' not in extras:
438 from pbr import packaging
439 extras['test'] = "\n".join(packaging.parse_requirements(
440 packaging.TEST_REQUIREMENTS_FILES)).replace(';', ':')
443 extra_requirements = []
444 requirements = split_multiline(extras[extra])
445 for requirement in requirements:
446 m = re.match(requirement_pattern, requirement)
447 extras_value = m.group('package').strip()
448 env_marker = m.group('env_marker')
449 extra_requirements.append((extras_value, env_marker))
450 all_requirements[extra] = extra_requirements
452 # Transform the full list of requirements into:
453 # - install_requires, for those that have no extra and no
455 # - named extras, for those with an extra name (which may include
457 # - and as a special case, install_requires with an env_marker are
458 # treated as named extras where the name is the empty string
461 for req_group in all_requirements:
462 for requirement, env_marker in all_requirements[req_group]:
464 extras_key = '%s:(%s)' % (req_group, env_marker)
465 # We do not want to poison wheel creation with locally
466 # evaluated markers. sdists always re-create the egg_info
467 # and as such do not need guarded, and pip will never call
468 # multiple setup.py commands at once.
469 if 'bdist_wheel' not in script_args:
471 if pkg_resources.evaluate_marker('(%s)' % env_marker):
472 extras_key = req_group
475 "Marker evaluation failed, see the following "
476 "error. For more information see: "
477 "http://docs.openstack.org/"
478 "pbr/latest/user/using.html#environment-markers"
482 extras_key = req_group
483 extras_require.setdefault(extras_key, []).append(requirement)
485 kwargs['install_requires'] = extras_require.pop('', [])
486 kwargs['extras_require'] = extras_require
491 def register_custom_compilers(config):
492 """Handle custom compilers.
494 This has no real equivalent in distutils, where additional compilers could
495 only be added programmatically, so we have to hack it in somehow.
498 compilers = has_get_option(config, 'global', 'compilers')
500 compilers = split_multiline(compilers)
501 for compiler in compilers:
502 compiler = resolve_name(compiler)
504 # In distutils2 compilers these class attributes exist; for
505 # distutils1 we just have to make something up
506 if hasattr(compiler, 'name'):
509 name = compiler.__name__
510 if hasattr(compiler, 'description'):
511 desc = compiler.description
513 desc = 'custom compiler %s' % name
515 module_name = compiler.__module__
516 # Note; this *will* override built in compilers with the same name
517 # TODO(embray): Maybe display a warning about this?
518 cc = distutils.ccompiler.compiler_class
519 cc[name] = (module_name, compiler.__name__, desc)
521 # HACK!!!! Distutils assumes all compiler modules are in the
523 sys.modules['distutils.' + module_name] = sys.modules[module_name]
526 def get_extension_modules(config):
527 """Handle extension modules"""
529 EXTENSION_FIELDS = ("sources",
535 "runtime_library_dirs",
537 "extra_compile_args",
544 for section in config:
546 labels = section.split(':', 1)
548 # Backwards compatibility for old syntax; don't use this though
549 labels = section.split('=', 1)
550 labels = [l.strip() for l in labels]
551 if (len(labels) == 2) and (labels[0] == 'extension'):
553 for field in EXTENSION_FIELDS:
554 value = has_get_option(config, section, field)
555 # All extension module options besides name can have multiple
559 value = split_multiline(value)
560 if field == 'define_macros':
563 macro = macro.split('=', 1)
565 macro = (macro[0].strip(), None)
567 macro = (macro[0].strip(), macro[1].strip())
570 ext_args[field] = value
572 if 'name' not in ext_args:
573 ext_args['name'] = labels[1]
574 ext_modules.append(extension.Extension(ext_args.pop('name'),
579 def get_entry_points(config):
580 """Process the [entry_points] section of setup.cfg.
582 Processes setup.cfg to handle setuptools entry points. This is, of course,
583 not a standard feature of distutils2/packaging, but as there is not
584 currently a standard alternative in packaging, we provide support for them.
587 if 'entry_points' not in config:
590 return dict((option, split_multiline(value))
591 for option, value in config['entry_points'].items())
594 def has_get_option(config, section, option):
595 if section in config and option in config[section]:
596 return config[section][option]
601 def split_multiline(value):
602 """Special behaviour when we have a multi line options"""
604 value = [element for element in
605 (line.strip() for line in value.split('\n'))
606 if element and not element.startswith('#')]
610 def split_csv(value):
611 """Special behaviour when we have a comma separated options"""
613 value = [element for element in
614 (chunk.strip() for chunk in value.split(','))
619 # The following classes are used to hack Distribution.command_options a bit
620 class DefaultGetDict(defaultdict):
621 """Like defaultdict, but get() also sets and returns the default value."""
623 def get(self, key, default=None):
625 default = self.default_factory()
626 return super(DefaultGetDict, self).setdefault(key, default)