aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2017-01-07 13:37:37 -0500
committerJason R. Coombs <jaraco@jaraco.com>2017-01-07 13:37:37 -0500
commita0d35d47761a8bd7394cb9f2738169b07be3a005 (patch)
tree375334a5e61eb137b5411f92942b510f6024cea3 /setuptools
parentbbf6ac250a05fd51aa2a44ed3196837491bc9d60 (diff)
parent3c182f9f1eea89040fbfc88d1ccbed31ece6a00b (diff)
downloadexternal_python_setuptools-issue-97.tar.gz
external_python_setuptools-issue-97.tar.bz2
external_python_setuptools-issue-97.zip
Merge branch 'master' into issue-97issue-97
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/command/build_ext.py14
-rw-r--r--setuptools/command/build_py.py2
-rwxr-xr-xsetuptools/command/develop.py6
-rwxr-xr-xsetuptools/command/easy_install.py17
-rwxr-xr-xsetuptools/command/egg_info.py53
-rwxr-xr-xsetuptools/command/install_egg_info.py61
-rwxr-xr-xsetuptools/command/sdist.py10
-rw-r--r--setuptools/command/test.py11
-rw-r--r--setuptools/config.py558
-rw-r--r--setuptools/depends.py43
-rw-r--r--setuptools/dist.py13
-rw-r--r--setuptools/monkey.py13
-rwxr-xr-xsetuptools/namespaces.py108
-rwxr-xr-xsetuptools/package_index.py8
-rw-r--r--setuptools/py27compat.py11
-rw-r--r--setuptools/py33compat.py46
-rw-r--r--setuptools/py36compat.py82
-rwxr-xr-xsetuptools/sandbox.py17
-rw-r--r--setuptools/site-patch.py2
-rw-r--r--setuptools/tests/environment.py2
-rw-r--r--setuptools/tests/mod_with_constant.py1
-rw-r--r--setuptools/tests/namespaces.py42
-rw-r--r--setuptools/tests/test_config.py539
-rw-r--r--setuptools/tests/test_depends.py16
-rw-r--r--setuptools/tests/test_develop.py59
-rw-r--r--setuptools/tests/test_easy_install.py4
-rw-r--r--setuptools/tests/test_egg_info.py18
-rw-r--r--setuptools/tests/test_namespaces.py105
-rw-r--r--setuptools/tests/test_sandbox.py16
-rw-r--r--setuptools/tests/test_sdist.py9
-rw-r--r--setuptools/version.py2
31 files changed, 1699 insertions, 189 deletions
diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py
index 12dc88cd..36f53f0d 100644
--- a/setuptools/command/build_ext.py
+++ b/setuptools/command/build_ext.py
@@ -62,7 +62,7 @@ if_dl = lambda s: s if have_rtld else ''
def get_abi3_suffix():
"""Return the file extension for an abi3-compliant Extension()"""
for suffix, _, _ in (s for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION):
- if '.abi3' in suffix: # Unix
+ if '.abi3' in suffix: # Unix
return suffix
elif suffix == '.pyd': # Windows
return suffix
@@ -109,7 +109,7 @@ class build_ext(_build_ext):
and get_abi3_suffix()
)
if use_abi3:
- so_ext = get_config_var('EXT_SUFFIX')
+ so_ext = _get_config_var_837('EXT_SUFFIX')
filename = filename[:-len(so_ext)]
filename = filename + get_abi3_suffix()
if isinstance(ext, Library):
@@ -316,3 +316,13 @@ else:
self.create_static_lib(
objects, basename, output_dir, debug, target_lang
)
+
+
+def _get_config_var_837(name):
+ """
+ In https://github.com/pypa/setuptools/pull/837, we discovered
+ Python 3.3.0 exposes the extension suffix under the name 'SO'.
+ """
+ if sys.version_info < (3, 3, 1):
+ name = 'SO'
+ return get_config_var(name)
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
index 289e6fb8..b0314fd4 100644
--- a/setuptools/command/build_py.py
+++ b/setuptools/command/build_py.py
@@ -219,7 +219,7 @@ class build_py(orig.build_py, Mixin2to3):
@staticmethod
def _get_platform_patterns(spec, package, src_dir):
"""
- yield platfrom-specific path patterns (suitable for glob
+ yield platform-specific path patterns (suitable for glob
or fn_match) from a glob-based spec (such as
self.package_data or self.exclude_package_data)
matching package in src_dir.
diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py
index 3eb86120..aa82f959 100755
--- a/setuptools/command/develop.py
+++ b/setuptools/command/develop.py
@@ -9,10 +9,11 @@ from setuptools.extern import six
from pkg_resources import Distribution, PathMetadata, normalize_path
from setuptools.command.easy_install import easy_install
+from setuptools import namespaces
import setuptools
-class develop(easy_install):
+class develop(namespaces.DevelopInstaller, easy_install):
"""Set up package for development"""
description = "install package in 'development mode'"
@@ -30,6 +31,7 @@ class develop(easy_install):
if self.uninstall:
self.multi_version = True
self.uninstall_link()
+ self.uninstall_namespaces()
else:
self.install_for_development()
self.warn_deprecated_options()
@@ -123,6 +125,8 @@ class develop(easy_install):
self.easy_install(setuptools.bootstrap_install_from)
setuptools.bootstrap_install_from = None
+ self.install_namespaces()
+
# create an .egg-link in the installation dir, pointing to our egg
log.info("Creating %s (link to %s)", self.egg_link, self.egg_base)
if not self.dry_run:
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 03dd6768..36e7f359 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -46,6 +46,7 @@ from setuptools.extern.six.moves import configparser, map
from setuptools import Command
from setuptools.sandbox import run_setup
from setuptools.py31compat import get_path, get_config_vars
+from setuptools.py27compat import rmtree_safe
from setuptools.command import setopt
from setuptools.archive_util import unpack_archive
from setuptools.package_index import (
@@ -627,12 +628,20 @@ class easy_install(Command):
(spec.key, self.build_directory)
)
+ @contextlib.contextmanager
+ def _tmpdir(self):
+ tmpdir = tempfile.mkdtemp(prefix=six.u("easy_install-"))
+ try:
+ # cast to str as workaround for #709 and #710 and #712
+ yield str(tmpdir)
+ finally:
+ os.path.exists(tmpdir) and rmtree(rmtree_safe(tmpdir))
+
def easy_install(self, spec, deps=False):
- tmpdir = tempfile.mkdtemp(prefix="easy_install-")
if not self.editable:
self.install_site_py()
- try:
+ with self._tmpdir() as tmpdir:
if not isinstance(spec, Requirement):
if URL_SCHEME(spec):
# It's a url, download it to tmpdir and process
@@ -664,10 +673,6 @@ class easy_install(Command):
else:
return self.install_item(spec, dist.location, tmpdir, deps)
- finally:
- if os.path.exists(tmpdir):
- rmtree(tmpdir)
-
def install_item(self, spec, download, tmpdir, deps, install_needed=False):
# Installation is also needed if file in tmpdir or is not an egg
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 6cc8f4c4..ca6a4348 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -32,11 +32,6 @@ from setuptools.glob import glob
from pkg_resources.extern import packaging
-try:
- from setuptools_svn import svn_utils
-except ImportError:
- pass
-
def translate_pattern(glob):
"""
@@ -126,18 +121,13 @@ class egg_info(Command):
user_options = [
('egg-base=', 'e', "directory containing .egg-info directories"
" (default: top of the source tree)"),
- ('tag-svn-revision', 'r',
- "Add subversion revision ID to version number"),
('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
('tag-build=', 'b', "Specify explicit tag to add to version number"),
- ('no-svn-revision', 'R',
- "Don't add subversion revision ID [default]"),
('no-date', 'D', "Don't include date stamp [default]"),
]
- boolean_options = ['tag-date', 'tag-svn-revision']
+ boolean_options = ['tag-date']
negative_opt = {
- 'no-svn-revision': 'tag-svn-revision',
'no-date': 'tag-date',
}
@@ -147,15 +137,26 @@ class egg_info(Command):
self.egg_base = None
self.egg_info = None
self.tag_build = None
- self.tag_svn_revision = 0
self.tag_date = 0
self.broken_egg_info = False
self.vtags = None
+ ####################################
+ # allow the 'tag_svn_revision' to be detected and
+ # set, supporting sdists built on older Setuptools.
+ @property
+ def tag_svn_revision(self):
+ pass
+
+ @tag_svn_revision.setter
+ def tag_svn_revision(self, value):
+ pass
+ ####################################
+
def save_version_info(self, filename):
"""
- Materialize the values of svn_revision and date into the
- build tag. Install these keys in a deterministic order
+ Materialize the value of date into the
+ build tag. Install build keys in a deterministic order
to avoid arbitrary reordering on subsequent builds.
"""
# python 2.6 compatibility
@@ -165,7 +166,6 @@ class egg_info(Command):
# when PYTHONHASHSEED=0
egg_info['tag_build'] = self.tags()
egg_info['tag_date'] = 0
- egg_info['tag_svn_revision'] = 0
edit_config(filename, dict(egg_info=egg_info))
def finalize_options(self):
@@ -282,22 +282,10 @@ class egg_info(Command):
version = ''
if self.tag_build:
version += self.tag_build
- if self.tag_svn_revision:
- warnings.warn(
- "tag_svn_revision is deprecated and will not be honored "
- "in a future release"
- )
- version += '-r%s' % self.get_svn_revision()
if self.tag_date:
version += time.strftime("-%Y%m%d")
return version
- @staticmethod
- def get_svn_revision():
- if 'svn_utils' not in globals():
- return "0"
- return str(svn_utils.SvnInfo.load(os.curdir).get_revision())
-
def find_sources(self):
"""Generate SOURCES.txt manifest file"""
manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
@@ -554,10 +542,17 @@ class manifest_maker(sdist):
msg = "writing manifest file '%s'" % self.manifest
self.execute(write_file, (self.manifest, files), msg)
- def warn(self, msg): # suppress missing-file warnings from sdist
- if not msg.startswith("standard file not found:"):
+ def warn(self, msg):
+ if not self._should_suppress_warning(msg):
sdist.warn(self, msg)
+ @staticmethod
+ def _should_suppress_warning(msg):
+ """
+ suppress missing-file warnings from sdist
+ """
+ return re.match(r"standard file .*not found", msg)
+
def add_defaults(self):
sdist.add_defaults(self)
self.filelist.append(self.template)
diff --git a/setuptools/command/install_egg_info.py b/setuptools/command/install_egg_info.py
index 7834e107..edc4718b 100755
--- a/setuptools/command/install_egg_info.py
+++ b/setuptools/command/install_egg_info.py
@@ -1,14 +1,13 @@
from distutils import log, dir_util
import os
-from setuptools.extern.six.moves import map
-
from setuptools import Command
+from setuptools import namespaces
from setuptools.archive_util import unpack_archive
import pkg_resources
-class install_egg_info(Command):
+class install_egg_info(namespaces.Installer, Command):
"""Install an .egg-info directory for the package"""
description = "Install an .egg-info directory for the package"
@@ -61,59 +60,3 @@ class install_egg_info(Command):
return dst
unpack_archive(self.source, self.target, skimmer)
-
- def install_namespaces(self):
- nsp = self._get_all_ns_packages()
- if not nsp:
- return
- filename, ext = os.path.splitext(self.target)
- filename += '-nspkg.pth'
- self.outputs.append(filename)
- log.info("Installing %s", filename)
- lines = map(self._gen_nspkg_line, nsp)
-
- if self.dry_run:
- # always generate the lines, even in dry run
- list(lines)
- return
-
- with open(filename, 'wt') as f:
- f.writelines(lines)
-
- _nspkg_tmpl = (
- "import sys, types, os",
- "pep420 = sys.version_info > (3, 3)",
- "p = os.path.join(sys._getframe(1).f_locals['sitedir'], *%(pth)r)",
- "ie = os.path.exists(os.path.join(p,'__init__.py'))",
- "m = not ie and not pep420 and "
- "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))",
- "mp = (m or []) and m.__dict__.setdefault('__path__',[])",
- "(p not in mp) and mp.append(p)",
- )
- "lines for the namespace installer"
-
- _nspkg_tmpl_multi = (
- 'm and setattr(sys.modules[%(parent)r], %(child)r, m)',
- )
- "additional line(s) when a parent package is indicated"
-
- @classmethod
- def _gen_nspkg_line(cls, pkg):
- # ensure pkg is not a unicode string under Python 2.7
- pkg = str(pkg)
- pth = tuple(pkg.split('.'))
- tmpl_lines = cls._nspkg_tmpl
- parent, sep, child = pkg.rpartition('.')
- if parent:
- tmpl_lines += cls._nspkg_tmpl_multi
- return ';'.join(tmpl_lines) % locals() + '\n'
-
- def _get_all_ns_packages(self):
- """Return sorted list of all package namespaces"""
- nsp = set()
- for pkg in self.distribution.namespace_packages or []:
- pkg = pkg.split('.')
- while pkg:
- nsp.add('.'.join(pkg))
- pkg.pop()
- return sorted(nsp)
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index 9975753d..84e29a1b 100755
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -142,9 +142,13 @@ class sdist(sdist_add_defaults, orig.sdist):
for filename in filenames])
def _add_defaults_data_files(self):
- """
- Don't add any data files, but why?
- """
+ try:
+ if six.PY2:
+ sdist_add_defaults._add_defaults_data_files(self)
+ else:
+ super()._add_defaults_data_files()
+ except TypeError:
+ log.warn("data_files contains unexpected objects")
def check_readme(self):
for f in self.READMES:
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index 270674e2..ef0af12f 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -3,7 +3,8 @@ import operator
import sys
import contextlib
import itertools
-from distutils.errors import DistutilsOptionError
+from distutils.errors import DistutilsError, DistutilsOptionError
+from distutils import log
from unittest import TestLoader
from setuptools.extern import six
@@ -225,11 +226,17 @@ class test(Command):
del_modules.append(name)
list(map(sys.modules.__delitem__, del_modules))
- unittest_main(
+ exit_kwarg = {} if sys.version_info < (2, 7) else {"exit": False}
+ test = unittest_main(
None, None, self._argv,
testLoader=self._resolve_as_ep(self.test_loader),
testRunner=self._resolve_as_ep(self.test_runner),
+ **exit_kwarg
)
+ if not test.result.wasSuccessful():
+ msg = 'Test failed: %s' % test.result
+ self.announce(msg, log.ERROR)
+ raise DistutilsError(msg)
@property
def _argv(self):
diff --git a/setuptools/config.py b/setuptools/config.py
new file mode 100644
index 00000000..d71ff028
--- /dev/null
+++ b/setuptools/config.py
@@ -0,0 +1,558 @@
+from __future__ import absolute_import, unicode_literals
+import io
+import os
+import sys
+from collections import defaultdict
+from functools import partial
+
+from distutils.errors import DistutilsOptionError, DistutilsFileError
+from setuptools.py26compat import import_module
+from setuptools.extern.six import string_types
+
+
+def read_configuration(
+ filepath, find_others=False, ignore_option_errors=False):
+ """Read given configuration file and returns options from it as a dict.
+
+ :param str|unicode filepath: Path to configuration file
+ to get options from.
+
+ :param bool find_others: Whether to search for other configuration files
+ which could be on in various places.
+
+ :param bool ignore_option_errors: Whether to silently ignore
+ options, values of which could not be resolved (e.g. due to exceptions
+ in directives such as file:, attr:, etc.).
+ If False exceptions are propagated as expected.
+
+ :rtype: dict
+ """
+ from setuptools.dist import Distribution, _Distribution
+
+ filepath = os.path.abspath(filepath)
+
+ if not os.path.isfile(filepath):
+ raise DistutilsFileError(
+ 'Configuration file %s does not exist.' % filepath)
+
+ current_directory = os.getcwd()
+ os.chdir(os.path.dirname(filepath))
+
+ try:
+ dist = Distribution()
+
+ filenames = dist.find_config_files() if find_others else []
+ if filepath not in filenames:
+ filenames.append(filepath)
+
+ _Distribution.parse_config_files(dist, filenames=filenames)
+
+ handlers = parse_configuration(
+ dist, dist.command_options,
+ ignore_option_errors=ignore_option_errors)
+
+ finally:
+ os.chdir(current_directory)
+
+ return configuration_to_dict(handlers)
+
+
+def configuration_to_dict(handlers):
+ """Returns configuration data gathered by given handlers as a dict.
+
+ :param list[ConfigHandler] handlers: Handlers list,
+ usually from parse_configuration()
+
+ :rtype: dict
+ """
+ config_dict = defaultdict(dict)
+
+ for handler in handlers:
+
+ obj_alias = handler.section_prefix
+ target_obj = handler.target_obj
+
+ for option in handler.set_options:
+ getter = getattr(target_obj, 'get_%s' % option, None)
+
+ if getter is None:
+ value = getattr(target_obj, option)
+
+ else:
+ value = getter()
+
+ config_dict[obj_alias][option] = value
+
+ return config_dict
+
+
+def parse_configuration(
+ distribution, command_options, ignore_option_errors=False):
+ """Performs additional parsing of configuration options
+ for a distribution.
+
+ Returns a list of used option handlers.
+
+ :param Distribution distribution:
+ :param dict command_options:
+ :param bool ignore_option_errors: Whether to silently ignore
+ options, values of which could not be resolved (e.g. due to exceptions
+ in directives such as file:, attr:, etc.).
+ If False exceptions are propagated as expected.
+ :rtype: list
+ """
+ meta = ConfigMetadataHandler(
+ distribution.metadata, command_options, ignore_option_errors)
+ meta.parse()
+
+ options = ConfigOptionsHandler(
+ distribution, command_options, ignore_option_errors)
+ options.parse()
+
+ return [meta, options]
+
+
+class ConfigHandler(object):
+ """Handles metadata supplied in configuration files."""
+
+ section_prefix = None
+ """Prefix for config sections handled by this handler.
+ Must be provided by class heirs.
+
+ """
+
+ aliases = {}
+ """Options aliases.
+ For compatibility with various packages. E.g.: d2to1 and pbr.
+ Note: `-` in keys is replaced with `_` by config parser.
+
+ """
+
+ def __init__(self, target_obj, options, ignore_option_errors=False):
+ sections = {}
+
+ section_prefix = self.section_prefix
+ for section_name, section_options in options.items():
+ if not section_name.startswith(section_prefix):
+ continue
+
+ section_name = section_name.replace(section_prefix, '').strip('.')
+ sections[section_name] = section_options
+
+ self.ignore_option_errors = ignore_option_errors
+ self.target_obj = target_obj
+ self.sections = sections
+ self.set_options = []
+
+ @property
+ def parsers(self):
+ """Metadata item name to parser function mapping."""
+ raise NotImplementedError(
+ '%s must provide .parsers property' % self.__class__.__name__)
+
+ def __setitem__(self, option_name, value):
+ unknown = tuple()
+ target_obj = self.target_obj
+
+ # Translate alias into real name.
+ option_name = self.aliases.get(option_name, option_name)
+
+ current_value = getattr(target_obj, option_name, unknown)
+
+ if current_value is unknown:
+ raise KeyError(option_name)
+
+ if current_value:
+ # Already inhabited. Skipping.
+ return
+
+ skip_option = False
+ parser = self.parsers.get(option_name)
+ if parser:
+ try:
+ value = parser(value)
+
+ except Exception:
+ skip_option = True
+ if not self.ignore_option_errors:
+ raise
+
+ if skip_option:
+ return
+
+ setter = getattr(target_obj, 'set_%s' % option_name, None)
+ if setter is None:
+ setattr(target_obj, option_name, value)
+ else:
+ setter(value)
+
+ self.set_options.append(option_name)
+
+ @classmethod
+ def _parse_list(cls, value, separator=','):
+ """Represents value as a list.
+
+ Value is split either by separator (defaults to comma) or by lines.
+
+ :param value:
+ :param separator: List items separator character.
+ :rtype: list
+ """
+ if isinstance(value, list): # _get_parser_compound case
+ return value
+
+ if '\n' in value:
+ value = value.splitlines()
+ else:
+ value = value.split(separator)
+
+ return [chunk.strip() for chunk in value if chunk.strip()]
+
+ @classmethod
+ def _parse_dict(cls, value):
+ """Represents value as a dict.
+
+ :param value:
+ :rtype: dict
+ """
+ separator = '='
+ result = {}
+ for line in cls._parse_list(value):
+ key, sep, val = line.partition(separator)
+ if sep != separator:
+ raise DistutilsOptionError(
+ 'Unable to parse option value to dict: %s' % value)
+ result[key.strip()] = val.strip()
+
+ return result
+
+ @classmethod
+ def _parse_bool(cls, value):
+ """Represents value as boolean.
+
+ :param value:
+ :rtype: bool
+ """
+ value = value.lower()
+ return value in ('1', 'true', 'yes')
+
+ @classmethod
+ def _parse_file(cls, value):
+ """Represents value as a string, allowing including text
+ from nearest files using `file:` directive.
+
+ Directive is sandboxed and won't reach anything outside
+ directory with setup.py.
+
+ Examples:
+ include: LICENSE
+ include: src/file.txt
+
+ :param str value:
+ :rtype: str
+ """
+ if not isinstance(value, string_types):
+ return value
+
+ include_directive = 'file:'
+ if not value.startswith(include_directive):
+ return value
+
+ current_directory = os.getcwd()
+
+ filepath = value.replace(include_directive, '').strip()
+ filepath = os.path.abspath(filepath)
+
+ if not filepath.startswith(current_directory):
+ raise DistutilsOptionError(
+ '`file:` directive can not access %s' % filepath)
+
+ if os.path.isfile(filepath):
+ with io.open(filepath, encoding='utf-8') as f:
+ value = f.read()
+
+ return value
+
+ @classmethod
+ def _parse_attr(cls, value):
+ """Represents value as a module attribute.
+
+ Examples:
+ attr: package.attr
+ attr: package.module.attr
+
+ :param str value:
+ :rtype: str
+ """
+ attr_directive = 'attr:'
+ if not value.startswith(attr_directive):
+ return value
+
+ attrs_path = value.replace(attr_directive, '').strip().split('.')
+ attr_name = attrs_path.pop()
+
+ module_name = '.'.join(attrs_path)
+ module_name = module_name or '__init__'
+
+ sys.path.insert(0, os.getcwd())
+ try:
+ module = import_module(module_name)
+ value = getattr(module, attr_name)
+
+ finally:
+ sys.path = sys.path[1:]
+
+ return value
+
+ @classmethod
+ def _get_parser_compound(cls, *parse_methods):
+ """Returns parser function to represents value as a list.
+
+ Parses a value applying given methods one after another.
+
+ :param parse_methods:
+ :rtype: callable
+ """
+ def parse(value):
+ parsed = value
+
+ for method in parse_methods:
+ parsed = method(parsed)
+
+ return parsed
+
+ return parse
+
+ @classmethod
+ def _parse_section_to_dict(cls, section_options, values_parser=None):
+ """Parses section options into a dictionary.
+
+ Optionally applies a given parser to values.
+
+ :param dict section_options:
+ :param callable values_parser:
+ :rtype: dict
+ """
+ value = {}
+ values_parser = values_parser or (lambda val: val)
+ for key, (_, val) in section_options.items():
+ value[key] = values_parser(val)
+ return value
+
+ def parse_section(self, section_options):
+ """Parses configuration file section.
+
+ :param dict section_options:
+ """
+ for (name, (_, value)) in section_options.items():
+ try:
+ self[name] = value
+
+ except KeyError:
+ pass # Keep silent for a new option may appear anytime.
+
+ def parse(self):
+ """Parses configuration file items from one
+ or more related sections.
+
+ """
+ for section_name, section_options in self.sections.items():
+
+ method_postfix = ''
+ if section_name: # [section.option] variant
+ method_postfix = '_%s' % section_name
+
+ section_parser_method = getattr(
+ self,
+ # Dots in section names are tranlsated into dunderscores.
+ ('parse_section%s' % method_postfix).replace('.', '__'),
+ None)
+
+ if section_parser_method is None:
+ raise DistutilsOptionError(
+ 'Unsupported distribution option section: [%s.%s]' % (
+ self.section_prefix, section_name))
+
+ section_parser_method(section_options)
+
+
+class ConfigMetadataHandler(ConfigHandler):
+
+ section_prefix = 'metadata'
+
+ aliases = {
+ 'home_page': 'url',
+ 'summary': 'description',
+ 'classifier': 'classifiers',
+ 'platform': 'platforms',
+ }
+
+ strict_mode = False
+ """We need to keep it loose, to be partially compatible with
+ `pbr` and `d2to1` packages which also uses `metadata` section.
+
+ """
+
+ @property
+ def parsers(self):
+ """Metadata item name to parser function mapping."""
+ parse_list = self._parse_list
+ parse_file = self._parse_file
+
+ return {
+ 'platforms': parse_list,
+ 'keywords': parse_list,
+ 'provides': parse_list,
+ 'requires': parse_list,
+ 'obsoletes': parse_list,
+ 'classifiers': self._get_parser_compound(parse_file, parse_list),
+ 'license': parse_file,
+ 'description': parse_file,
+ 'long_description': parse_file,
+ 'version': self._parse_version,
+ }
+
+ def parse_section_classifiers(self, section_options):
+ """Parses configuration file section.
+
+ :param dict section_options:
+ """
+ classifiers = []
+ for begin, (_, rest) in section_options.items():
+ classifiers.append('%s :%s' % (begin.title(), rest))
+
+ self['classifiers'] = classifiers
+
+ def _parse_version(self, value):
+ """Parses `version` option value.
+
+ :param value:
+ :rtype: str
+
+ """
+ version = self._parse_attr(value)
+
+ if callable(version):
+ version = version()
+
+ if not isinstance(version, string_types):
+ if hasattr(version, '__iter__'):
+ version = '.'.join(map(str, version))
+ else:
+ version = '%s' % version
+
+ return version
+
+
+class ConfigOptionsHandler(ConfigHandler):
+
+ section_prefix = 'options'
+
+ @property
+ def parsers(self):
+ """Metadata item name to parser function mapping."""
+ parse_list = self._parse_list
+ parse_list_semicolon = partial(self._parse_list, separator=';')
+ parse_bool = self._parse_bool
+ parse_dict = self._parse_dict
+
+ return {
+ 'zip_safe': parse_bool,
+ 'use_2to3': parse_bool,
+ 'include_package_data': parse_bool,
+ 'package_dir': parse_dict,
+ 'use_2to3_fixers': parse_list,
+ 'use_2to3_exclude_fixers': parse_list,
+ 'convert_2to3_doctests': parse_list,
+ 'scripts': parse_list,
+ 'eager_resources': parse_list,
+ 'dependency_links': parse_list,
+ 'namespace_packages': parse_list,
+ 'install_requires': parse_list_semicolon,
+ 'setup_requires': parse_list_semicolon,
+ 'tests_require': parse_list_semicolon,
+ 'packages': self._parse_packages,
+ 'entry_points': self._parse_file,
+ }
+
+ def _parse_packages(self, value):
+ """Parses `packages` option value.
+
+ :param value:
+ :rtype: list
+ """
+ find_directive = 'find:'
+
+ if not value.startswith(find_directive):
+ return self._parse_list(value)
+
+ # Read function arguments from a dedicated section.
+ find_kwargs = self.parse_section_packages__find(
+ self.sections.get('packages.find', {}))
+
+ from setuptools import find_packages
+
+ return find_packages(**find_kwargs)
+
+ def parse_section_packages__find(self, section_options):
+ """Parses `packages.find` configuration file section.
+
+ To be used in conjunction with _parse_packages().
+
+ :param dict section_options:
+ """
+ section_data = self._parse_section_to_dict(
+ section_options, self._parse_list)
+
+ valid_keys = ['where', 'include', 'exclude']
+
+ find_kwargs = dict(
+ [(k, v) for k, v in section_data.items() if k in valid_keys and v])
+
+ where = find_kwargs.get('where')
+ if where is not None:
+ find_kwargs['where'] = where[0] # cast list to single val
+
+ return find_kwargs
+
+ def parse_section_entry_points(self, section_options):
+ """Parses `entry_points` configuration file section.
+
+ :param dict section_options:
+ """
+ parsed = self._parse_section_to_dict(section_options, self._parse_list)
+ self['entry_points'] = parsed
+
+ def _parse_package_data(self, section_options):
+ parsed = self._parse_section_to_dict(section_options, self._parse_list)
+
+ root = parsed.get('*')
+ if root:
+ parsed[''] = root
+ del parsed['*']
+
+ return parsed
+
+ def parse_section_package_data(self, section_options):
+ """Parses `package_data` configuration file section.
+
+ :param dict section_options:
+ """
+ self['package_data'] = self._parse_package_data(section_options)
+
+ def parse_section_exclude_package_data(self, section_options):
+ """Parses `exclude_package_data` configuration file section.
+
+ :param dict section_options:
+ """
+ self['exclude_package_data'] = self._parse_package_data(
+ section_options)
+
+ def parse_section_extras_require(self, section_options):
+ """Parses `extras_require` configuration file section.
+
+ :param dict section_options:
+ """
+ parse_list = partial(self._parse_list, separator=';')
+ self['extras_require'] = self._parse_section_to_dict(
+ section_options, parse_list)
diff --git a/setuptools/depends.py b/setuptools/depends.py
index 89d39a50..45e7052d 100644
--- a/setuptools/depends.py
+++ b/setuptools/depends.py
@@ -4,7 +4,8 @@ import marshal
from distutils.version import StrictVersion
from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN
-from setuptools.extern import six
+from .py33compat import Bytecode
+
__all__ = [
'Require', 'find_module', 'get_module_constant', 'extract_constant'
@@ -78,39 +79,6 @@ class Require:
return self.version_ok(version)
-def _iter_code(code):
- """Yield '(op,arg)' pair for each operation in code object 'code'"""
-
- from array import array
- from dis import HAVE_ARGUMENT, EXTENDED_ARG
-
- bytes = array('b', code.co_code)
- eof = len(code.co_code)
-
- ptr = 0
- extended_arg = 0
-
- while ptr < eof:
-
- op = bytes[ptr]
-
- if op >= HAVE_ARGUMENT:
-
- arg = bytes[ptr + 1] + bytes[ptr + 2] * 256 + extended_arg
- ptr += 3
-
- if op == EXTENDED_ARG:
- long_type = six.integer_types[-1]
- extended_arg = arg * long_type(65536)
- continue
-
- else:
- arg = None
- ptr += 1
-
- yield op, arg
-
-
def find_module(module, paths=None):
"""Just like 'imp.find_module()', but with package support"""
@@ -176,9 +144,8 @@ def extract_constant(code, symbol, default=-1):
only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
must be present in 'code.co_names'.
"""
-
if symbol not in code.co_names:
- # name's not there, can't possibly be an assigment
+ # name's not there, can't possibly be an assignment
return None
name_idx = list(code.co_names).index(symbol)
@@ -189,7 +156,9 @@ def extract_constant(code, symbol, default=-1):
const = default
- for op, arg in _iter_code(code):
+ for byte_code in Bytecode(code):
+ op = byte_code.opcode
+ arg = byte_code.arg
if op == LOAD_CONST:
const = code.co_consts[arg]
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 8058ee7c..60cf6d9d 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -19,7 +19,9 @@ from pkg_resources.extern import packaging
from setuptools.depends import Require
from setuptools import windows_support
from setuptools.monkey import get_unpatched
+from setuptools.config import parse_configuration
import pkg_resources
+from .py36compat import Distribution_parse_config_files
def _get_unpatched(cls):
@@ -220,7 +222,7 @@ def check_packages(dist, attr, value):
_Distribution = get_unpatched(distutils.core.Distribution)
-class Distribution(_Distribution):
+class Distribution(Distribution_parse_config_files, _Distribution):
"""Distribution with support for features, tests, and package data
This is an enhanced version of 'distutils.dist.Distribution' that
@@ -350,6 +352,15 @@ class Distribution(_Distribution):
if getattr(self, 'python_requires', None):
self.metadata.python_requires = self.python_requires
+ def parse_config_files(self, filenames=None):
+ """Parses configuration files from various levels
+ and loads configuration.
+
+ """
+ _Distribution.parse_config_files(self, filenames=filenames)
+
+ parse_configuration(self, self.command_options)
+
def parse_command_line(self):
"""Process features after parsing command line options"""
result = _Distribution.parse_command_line(self)
diff --git a/setuptools/monkey.py b/setuptools/monkey.py
index aabc280f..dbe9a617 100644
--- a/setuptools/monkey.py
+++ b/setuptools/monkey.py
@@ -7,6 +7,7 @@ import distutils.filelist
import platform
import types
import functools
+import inspect
from .py26compat import import_module
from setuptools.extern import six
@@ -35,12 +36,16 @@ def get_unpatched_class(cls):
Also ensures that no other distutils extension monkeypatched the distutils
first.
"""
- while cls.__module__.startswith('setuptools'):
- cls, = cls.__bases__
- if not cls.__module__.startswith('distutils'):
+ external_bases = (
+ cls
+ for cls in inspect.getmro(cls)
+ if not cls.__module__.startswith('setuptools')
+ )
+ base = next(external_bases)
+ if not base.__module__.startswith('distutils'):
msg = "distutils has already been patched by %r" % cls
raise AssertionError(msg)
- return cls
+ return base
def patch_all():
diff --git a/setuptools/namespaces.py b/setuptools/namespaces.py
new file mode 100755
index 00000000..0a889f22
--- /dev/null
+++ b/setuptools/namespaces.py
@@ -0,0 +1,108 @@
+import os
+import sys
+from distutils import log
+import itertools
+
+from setuptools.extern.six.moves import map
+
+
+flatten = itertools.chain.from_iterable
+
+
+class Installer:
+
+ nspkg_ext = '-nspkg.pth'
+
+ def install_namespaces(self):
+ nsp = self._get_all_ns_packages()
+ if not nsp:
+ return
+ filename, ext = os.path.splitext(self._get_target())
+ filename += self.nspkg_ext
+ self.outputs.append(filename)
+ log.info("Installing %s", filename)
+ lines = map(self._gen_nspkg_line, nsp)
+
+ if self.dry_run:
+ # always generate the lines, even in dry run
+ list(lines)
+ return
+
+ with open(filename, 'wt') as f:
+ f.writelines(lines)
+
+ def uninstall_namespaces(self):
+ filename, ext = os.path.splitext(self._get_target())
+ filename += self.nspkg_ext
+ if not os.path.exists(filename):
+ return
+ log.info("Removing %s", filename)
+ os.remove(filename)
+
+ def _get_target(self):
+ return self.target
+
+ _nspkg_tmpl = (
+ "import sys, types, os",
+ "has_mfs = sys.version_info > (3, 5)",
+ "p = os.path.join(%(root)s, *%(pth)r)",
+ "importlib = has_mfs and __import__('importlib.util')",
+ "has_mfs and __import__('importlib.machinery')",
+ "m = has_mfs and "
+ "sys.modules.setdefault(%(pkg)r, "
+ "importlib.util.module_from_spec("
+ "importlib.machinery.PathFinder.find_spec(%(pkg)r, "
+ "[os.path.dirname(p)])))",
+ "m = m or not has_mfs and "
+ "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))",
+ "mp = (m or []) and m.__dict__.setdefault('__path__',[])",
+ "(p not in mp) and mp.append(p)",
+ )
+ "lines for the namespace installer"
+
+ _nspkg_tmpl_multi = (
+ 'm and setattr(sys.modules[%(parent)r], %(child)r, m)',
+ )
+ "additional line(s) when a parent package is indicated"
+
+ def _get_root(self):
+ return "sys._getframe(1).f_locals['sitedir']"
+
+ def _gen_nspkg_line(self, pkg):
+ # ensure pkg is not a unicode string under Python 2.7
+ pkg = str(pkg)
+ pth = tuple(pkg.split('.'))
+ root = self._get_root()
+ tmpl_lines = self._nspkg_tmpl
+ parent, sep, child = pkg.rpartition('.')
+ if parent:
+ tmpl_lines += self._nspkg_tmpl_multi
+ return ';'.join(tmpl_lines) % locals() + '\n'
+
+ def _get_all_ns_packages(self):
+ """Return sorted list of all package namespaces"""
+ pkgs = self.distribution.namespace_packages or []
+ return sorted(flatten(map(self._pkg_names, pkgs)))
+
+ @staticmethod
+ def _pkg_names(pkg):
+ """
+ Given a namespace package, yield the components of that
+ package.
+
+ >>> names = Installer._pkg_names('a.b.c')
+ >>> set(names) == set(['a', 'a.b', 'a.b.c'])
+ True
+ """
+ parts = pkg.split('.')
+ while parts:
+ yield '.'.join(parts)
+ parts.pop()
+
+
+class DevelopInstaller(Installer):
+ def _get_root(self):
+ return repr(str(self.egg_path))
+
+ def _get_target(self):
+ return self.egg_link
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index e5249b27..d80d43bc 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -515,10 +515,10 @@ class PackageIndex(Environment):
"""Add `urls` to the list that will be prescanned for searches"""
for url in urls:
if (
- self.to_scan is None # if we have already "gone online"
- or not URL_SCHEME(url) # or it's a local file/directory
+ self.to_scan is None # if we have already "gone online"
+ or not URL_SCHEME(url) # or it's a local file/directory
or url.startswith('file:')
- or list(distros_for_url(url)) # or a direct package link
+ or list(distros_for_url(url)) # or a direct package link
):
# then go ahead and process it now
self.scan_url(url)
@@ -768,7 +768,7 @@ class PackageIndex(Environment):
'down, %s' %
(url, v.line)
)
- except http_client.HTTPException as v:
+ except (http_client.HTTPException, socket.error) as v:
if warning:
self.warn(warning, v)
else:
diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py
index 4e3e4ab3..a71a936e 100644
--- a/setuptools/py27compat.py
+++ b/setuptools/py27compat.py
@@ -3,6 +3,7 @@ Compatibility Support for Python 2.7 and earlier
"""
import sys
+import platform
def get_all_headers(message, key):
@@ -16,3 +17,13 @@ if sys.version_info < (3,):
def get_all_headers(message, key):
return message.getheaders(key)
+
+
+linux_py2_ascii = (
+ platform.system() == 'Linux' and
+ sys.getfilesystemencoding() == 'ascii' and
+ sys.version_info < (3,)
+)
+
+rmtree_safe = str if linux_py2_ascii else lambda x: x
+"""Workaround for http://bugs.python.org/issue24672"""
diff --git a/setuptools/py33compat.py b/setuptools/py33compat.py
new file mode 100644
index 00000000..2588d680
--- /dev/null
+++ b/setuptools/py33compat.py
@@ -0,0 +1,46 @@
+import dis
+import code
+import array
+import collections
+
+from setuptools.extern import six
+
+
+OpArg = collections.namedtuple('OpArg', 'opcode arg')
+
+
+class Bytecode_compat(object):
+ def __init__(self, code):
+ self.code = code
+
+ def __iter__(self):
+ """Yield '(op,arg)' pair for each operation in code object 'code'"""
+
+ bytes = array.array('b', self.code.co_code)
+ eof = len(self.code.co_code)
+
+ ptr = 0
+ extended_arg = 0
+
+ while ptr < eof:
+
+ op = bytes[ptr]
+
+ if op >= dis.HAVE_ARGUMENT:
+
+ arg = bytes[ptr + 1] + bytes[ptr + 2] * 256 + extended_arg
+ ptr += 3
+
+ if op == dis.EXTENDED_ARG:
+ long_type = six.integer_types[-1]
+ extended_arg = arg * long_type(65536)
+ continue
+
+ else:
+ arg = None
+ ptr += 1
+
+ yield OpArg(op, arg)
+
+
+Bytecode = getattr(dis, 'Bytecode', Bytecode_compat)
diff --git a/setuptools/py36compat.py b/setuptools/py36compat.py
new file mode 100644
index 00000000..f5279696
--- /dev/null
+++ b/setuptools/py36compat.py
@@ -0,0 +1,82 @@
+import sys
+from distutils.errors import DistutilsOptionError
+from distutils.util import strtobool
+from distutils.debug import DEBUG
+
+
+class Distribution_parse_config_files:
+ """
+ Mix-in providing forward-compatibility for functionality to be
+ included by default on Python 3.7.
+
+ Do not edit the code in this class except to update functionality
+ as implemented in distutils.
+ """
+ def parse_config_files(self, filenames=None):
+ from configparser import ConfigParser
+
+ # Ignore install directory options if we have a venv
+ if sys.prefix != sys.base_prefix:
+ ignore_options = [
+ 'install-base', 'install-platbase', 'install-lib',
+ 'install-platlib', 'install-purelib', 'install-headers',
+ 'install-scripts', 'install-data', 'prefix', 'exec-prefix',
+ 'home', 'user', 'root']
+ else:
+ ignore_options = []
+
+ ignore_options = frozenset(ignore_options)
+
+ if filenames is None:
+ filenames = self.find_config_files()
+
+ if DEBUG:
+ self.announce("Distribution.parse_config_files():")
+
+ parser = ConfigParser(interpolation=None)
+ for filename in filenames:
+ if DEBUG:
+ self.announce(" reading %s" % filename)
+ parser.read(filename)
+ for section in parser.sections():
+ options = parser.options(section)
+ opt_dict = self.get_option_dict(section)
+
+ for opt in options:
+ if opt != '__name__' and opt not in ignore_options:
+ val = parser.get(section,opt)
+ opt = opt.replace('-', '_')
+ opt_dict[opt] = (filename, val)
+
+ # Make the ConfigParser forget everything (so we retain
+ # the original filenames that options come from)
+ parser.__init__()
+
+ # If there was a "global" section in the config file, use it
+ # to set Distribution options.
+
+ if 'global' in self.command_options:
+ for (opt, (src, val)) in self.command_options['global'].items():
+ alias = self.negative_opt.get(opt)
+ try:
+ if alias:
+ setattr(self, alias, not strtobool(val))
+ elif opt in ('verbose', 'dry_run'): # ugh!
+ setattr(self, opt, strtobool(val))
+ else:
+ setattr(self, opt, val)
+ except ValueError as msg:
+ raise DistutilsOptionError(msg)
+
+
+if sys.version_info < (3,):
+ # Python 2 behavior is sufficient
+ class Distribution_parse_config_files:
+ pass
+
+
+if False:
+ # When updated behavior is available upstream,
+ # disable override here.
+ class Distribution_parse_config_files:
+ pass
diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py
index 39afd57e..817a3afa 100755
--- a/setuptools/sandbox.py
+++ b/setuptools/sandbox.py
@@ -241,8 +241,15 @@ def run_setup(setup_script, args):
working_set.__init__()
working_set.callbacks.append(lambda dist: dist.activate())
+ # __file__ should be a byte string on Python 2 (#712)
+ dunder_file = (
+ setup_script
+ if isinstance(setup_script, str) else
+ setup_script.encode(sys.getfilesystemencoding())
+ )
+
def runner():
- ns = dict(__file__=setup_script, __name__='__main__')
+ ns = dict(__file__=dunder_file, __name__='__main__')
_execfile(setup_script, ns)
DirectorySandbox(setup_dir).run(runner)
@@ -373,14 +380,6 @@ if hasattr(os, 'devnull'):
else:
_EXCEPTIONS = []
-try:
- from win32com.client.gencache import GetGeneratePath
- _EXCEPTIONS.append(GetGeneratePath())
- del GetGeneratePath
-except ImportError:
- # it appears pywin32 is not installed, so no need to exclude.
- pass
-
class DirectorySandbox(AbstractSandbox):
"""Restrict operations to a single subdirectory - pseudo-chroot"""
diff --git a/setuptools/site-patch.py b/setuptools/site-patch.py
index 92194abd..0d2d2ff8 100644
--- a/setuptools/site-patch.py
+++ b/setuptools/site-patch.py
@@ -13,7 +13,7 @@ def __boot():
for item in stdpath:
if item == mydir or not item:
- continue # skip if current dir. on Windows, or my own directory
+ continue # skip if current dir. on Windows, or my own directory
importer = pic.get(item)
if importer is not None:
loader = importer.find_module('site')
diff --git a/setuptools/tests/environment.py b/setuptools/tests/environment.py
index b0e3bd36..c67898ca 100644
--- a/setuptools/tests/environment.py
+++ b/setuptools/tests/environment.py
@@ -56,5 +56,5 @@ def run_setup_py(cmd, pypath=None, path=None,
data = data.decode()
data = unicodedata.normalize('NFC', data)
- # communciate calls wait()
+ # communicate calls wait()
return proc.returncode, data
diff --git a/setuptools/tests/mod_with_constant.py b/setuptools/tests/mod_with_constant.py
new file mode 100644
index 00000000..ef755dd1
--- /dev/null
+++ b/setuptools/tests/mod_with_constant.py
@@ -0,0 +1 @@
+value = 'three, sir!'
diff --git a/setuptools/tests/namespaces.py b/setuptools/tests/namespaces.py
new file mode 100644
index 00000000..ef5ecdad
--- /dev/null
+++ b/setuptools/tests/namespaces.py
@@ -0,0 +1,42 @@
+from __future__ import absolute_import, unicode_literals
+
+import textwrap
+
+
+def build_namespace_package(tmpdir, name):
+ src_dir = tmpdir / name
+ src_dir.mkdir()
+ setup_py = src_dir / 'setup.py'
+ namespace, sep, rest = name.partition('.')
+ script = textwrap.dedent("""
+ import setuptools
+ setuptools.setup(
+ name={name!r},
+ version="1.0",
+ namespace_packages=[{namespace!r}],
+ packages=[{namespace!r}],
+ )
+ """).format(**locals())
+ setup_py.write_text(script, encoding='utf-8')
+ ns_pkg_dir = src_dir / namespace
+ ns_pkg_dir.mkdir()
+ pkg_init = ns_pkg_dir / '__init__.py'
+ tmpl = '__import__("pkg_resources").declare_namespace({namespace!r})'
+ decl = tmpl.format(**locals())
+ pkg_init.write_text(decl, encoding='utf-8')
+ pkg_mod = ns_pkg_dir / (rest + '.py')
+ some_functionality = 'name = {rest!r}'.format(**locals())
+ pkg_mod.write_text(some_functionality, encoding='utf-8')
+ return src_dir
+
+
+def make_site_dir(target):
+ """
+ Add a sitecustomize.py module in target to cause
+ target to be added to site dirs such that .pth files
+ are processed there.
+ """
+ sc = target / 'sitecustomize.py'
+ target_str = str(target)
+ tmpl = '__import__("site").addsitedir({target_str!r})'
+ sc.write_text(tmpl.format(**locals()), encoding='utf-8')
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
new file mode 100644
index 00000000..fa8d523b
--- /dev/null
+++ b/setuptools/tests/test_config.py
@@ -0,0 +1,539 @@
+import contextlib
+import pytest
+from distutils.errors import DistutilsOptionError, DistutilsFileError
+from setuptools.dist import Distribution
+from setuptools.config import ConfigHandler, read_configuration
+
+
+class ErrConfigHandler(ConfigHandler):
+ """Erroneous handler. Fails to implement required methods."""
+
+
+def make_package_dir(name, base_dir):
+ dir_package = base_dir.mkdir(name)
+ init_file = dir_package.join('__init__.py')
+ init_file.write('')
+ return dir_package, init_file
+
+
+def fake_env(tmpdir, setup_cfg, setup_py=None):
+
+ if setup_py is None:
+ setup_py = (
+ 'from setuptools import setup\n'
+ 'setup()\n'
+ )
+
+ tmpdir.join('setup.py').write(setup_py)
+ config = tmpdir.join('setup.cfg')
+ config.write(setup_cfg)
+
+ package_dir, init_file = make_package_dir('fake_package', tmpdir)
+
+ init_file.write(
+ 'VERSION = (1, 2, 3)\n'
+ '\n'
+ 'VERSION_MAJOR = 1'
+ '\n'
+ 'def get_version():\n'
+ ' return [3, 4, 5, "dev"]\n'
+ '\n'
+ )
+ return package_dir, config
+
+
+@contextlib.contextmanager
+def get_dist(tmpdir, kwargs_initial=None, parse=True):
+ kwargs_initial = kwargs_initial or {}
+
+ with tmpdir.as_cwd():
+ dist = Distribution(kwargs_initial)
+ dist.script_name = 'setup.py'
+ parse and dist.parse_config_files()
+
+ yield dist
+
+
+def test_parsers_implemented():
+
+ with pytest.raises(NotImplementedError):
+ handler = ErrConfigHandler(None, {})
+ handler.parsers
+
+
+class TestConfigurationReader:
+
+ def test_basic(self, tmpdir):
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = 10.1.1\n'
+ 'keywords = one, two\n'
+ '\n'
+ '[options]\n'
+ 'scripts = bin/a.py, bin/b.py\n'
+ )
+ config_dict = read_configuration('%s' % config)
+ assert config_dict['metadata']['version'] == '10.1.1'
+ assert config_dict['metadata']['keywords'] == ['one', 'two']
+ assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py']
+
+ def test_no_config(self, tmpdir):
+ with pytest.raises(DistutilsFileError):
+ read_configuration('%s' % tmpdir.join('setup.cfg'))
+
+ def test_ignore_errors(self, tmpdir):
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: none.VERSION\n'
+ 'keywords = one, two\n'
+ )
+ with pytest.raises(ImportError):
+ read_configuration('%s' % config)
+
+ config_dict = read_configuration(
+ '%s' % config, ignore_option_errors=True)
+
+ assert config_dict['metadata']['keywords'] == ['one', 'two']
+ assert 'version' not in config_dict['metadata']
+
+ config.remove()
+
+
+class TestMetadata:
+
+ def test_basic(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = 10.1.1\n'
+ 'description = Some description\n'
+ 'long_description = file: README\n'
+ 'name = fake_name\n'
+ 'keywords = one, two\n'
+ 'provides = package, package.sub\n'
+ 'license = otherlic\n'
+ 'download_url = http://test.test.com/test/\n'
+ 'maintainer_email = test@test.com\n'
+ )
+
+ tmpdir.join('README').write('readme contents\nline2')
+
+ meta_initial = {
+ # This will be used so `otherlic` won't replace it.
+ 'license': 'BSD 3-Clause License',
+ }
+
+ with get_dist(tmpdir, meta_initial) as dist:
+ metadata = dist.metadata
+
+ assert metadata.version == '10.1.1'
+ assert metadata.description == 'Some description'
+ assert metadata.long_description == 'readme contents\nline2'
+ assert metadata.provides == ['package', 'package.sub']
+ assert metadata.license == 'BSD 3-Clause License'
+ assert metadata.name == 'fake_name'
+ assert metadata.keywords == ['one', 'two']
+ assert metadata.download_url == 'http://test.test.com/test/'
+ assert metadata.maintainer_email == 'test@test.com'
+
+ def test_file_sandboxed(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'long_description = file: ../../README\n'
+ )
+
+ with get_dist(tmpdir, parse=False) as dist:
+ with pytest.raises(DistutilsOptionError):
+ dist.parse_config_files() # file: out of sandbox
+
+ def test_aliases(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'author-email = test@test.com\n'
+ 'home-page = http://test.test.com/test/\n'
+ 'summary = Short summary\n'
+ 'platform = a, b\n'
+ 'classifier =\n'
+ ' Framework :: Django\n'
+ ' Programming Language :: Python :: 3.5\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+ assert metadata.author_email == 'test@test.com'
+ assert metadata.url == 'http://test.test.com/test/'
+ assert metadata.description == 'Short summary'
+ assert metadata.platforms == ['a', 'b']
+ assert metadata.classifiers == [
+ 'Framework :: Django',
+ 'Programming Language :: Python :: 3.5',
+ ]
+
+ def test_multiline(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'name = fake_name\n'
+ 'keywords =\n'
+ ' one\n'
+ ' two\n'
+ 'classifiers =\n'
+ ' Framework :: Django\n'
+ ' Programming Language :: Python :: 3.5\n'
+ )
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+ assert metadata.keywords == ['one', 'two']
+ assert metadata.classifiers == [
+ 'Framework :: Django',
+ 'Programming Language :: Python :: 3.5',
+ ]
+
+ def test_version(self, tmpdir):
+
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: fake_package.VERSION\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ config.write(
+ '[metadata]\n'
+ 'version = attr: fake_package.get_version\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '3.4.5.dev'
+
+ config.write(
+ '[metadata]\n'
+ 'version = attr: fake_package.VERSION_MAJOR\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1'
+
+ subpack = tmpdir.join('fake_package').mkdir('subpackage')
+ subpack.join('__init__.py').write('')
+ subpack.join('submodule.py').write('VERSION = (2016, 11, 26)')
+
+ config.write(
+ '[metadata]\n'
+ 'version = attr: fake_package.subpackage.submodule.VERSION\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '2016.11.26'
+
+ def test_unknown_meta_item(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'name = fake_name\n'
+ 'unknown = some\n'
+ )
+ with get_dist(tmpdir, parse=False) as dist:
+ dist.parse_config_files() # Skip unknown.
+
+ def test_usupported_section(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[metadata.some]\n'
+ 'key = val\n'
+ )
+ with get_dist(tmpdir, parse=False) as dist:
+ with pytest.raises(DistutilsOptionError):
+ dist.parse_config_files()
+
+ def test_classifiers(self, tmpdir):
+ expected = set([
+ 'Framework :: Django',
+ 'Programming Language :: Python :: 3.5',
+ ])
+
+ # From file.
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'classifiers = file: classifiers\n'
+ )
+
+ tmpdir.join('classifiers').write(
+ 'Framework :: Django\n'
+ 'Programming Language :: Python :: 3.5\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert set(dist.metadata.classifiers) == expected
+
+ # From section.
+ config.write(
+ '[metadata.classifiers]\n'
+ 'Framework :: Django\n'
+ 'Programming Language :: Python :: 3.5\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert set(dist.metadata.classifiers) == expected
+
+
+class TestOptions:
+
+ def test_basic(self, tmpdir):
+
+ fake_env(
+ tmpdir,
+ '[options]\n'
+ 'zip_safe = True\n'
+ 'use_2to3 = 1\n'
+ 'include_package_data = yes\n'
+ 'package_dir = b=c, =src\n'
+ 'packages = pack_a, pack_b.subpack\n'
+ 'namespace_packages = pack1, pack2\n'
+ 'use_2to3_fixers = your.fixers, or.here\n'
+ 'use_2to3_exclude_fixers = one.here, two.there\n'
+ 'convert_2to3_doctests = src/tests/one.txt, src/two.txt\n'
+ 'scripts = bin/one.py, bin/two.py\n'
+ 'eager_resources = bin/one.py, bin/two.py\n'
+ 'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n'
+ 'tests_require = mock==0.7.2; pytest\n'
+ 'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
+ 'dependency_links = http://some.com/here/1, '
+ 'http://some.com/there/2\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.zip_safe
+ assert dist.use_2to3
+ assert dist.include_package_data
+ assert dist.package_dir == {'': 'src', 'b': 'c'}
+ assert dist.packages == ['pack_a', 'pack_b.subpack']
+ assert dist.namespace_packages == ['pack1', 'pack2']
+ assert dist.use_2to3_fixers == ['your.fixers', 'or.here']
+ assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there']
+ assert dist.convert_2to3_doctests == ([
+ 'src/tests/one.txt', 'src/two.txt'])
+ assert dist.scripts == ['bin/one.py', 'bin/two.py']
+ assert dist.dependency_links == ([
+ 'http://some.com/here/1',
+ 'http://some.com/there/2'
+ ])
+ assert dist.install_requires == ([
+ 'docutils>=0.3',
+ 'pack ==1.1, ==1.3',
+ 'hey'
+ ])
+ assert dist.setup_requires == ([
+ 'docutils>=0.3',
+ 'spack ==1.1, ==1.3',
+ 'there'
+ ])
+ assert dist.tests_require == ['mock==0.7.2', 'pytest']
+
+ def test_multiline(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options]\n'
+ 'package_dir = \n'
+ ' b=c\n'
+ ' =src\n'
+ 'packages = \n'
+ ' pack_a\n'
+ ' pack_b.subpack\n'
+ 'namespace_packages = \n'
+ ' pack1\n'
+ ' pack2\n'
+ 'use_2to3_fixers = \n'
+ ' your.fixers\n'
+ ' or.here\n'
+ 'use_2to3_exclude_fixers = \n'
+ ' one.here\n'
+ ' two.there\n'
+ 'convert_2to3_doctests = \n'
+ ' src/tests/one.txt\n'
+ ' src/two.txt\n'
+ 'scripts = \n'
+ ' bin/one.py\n'
+ ' bin/two.py\n'
+ 'eager_resources = \n'
+ ' bin/one.py\n'
+ ' bin/two.py\n'
+ 'install_requires = \n'
+ ' docutils>=0.3\n'
+ ' pack ==1.1, ==1.3\n'
+ ' hey\n'
+ 'tests_require = \n'
+ ' mock==0.7.2\n'
+ ' pytest\n'
+ 'setup_requires = \n'
+ ' docutils>=0.3\n'
+ ' spack ==1.1, ==1.3\n'
+ ' there\n'
+ 'dependency_links = \n'
+ ' http://some.com/here/1\n'
+ ' http://some.com/there/2\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.package_dir == {'': 'src', 'b': 'c'}
+ assert dist.packages == ['pack_a', 'pack_b.subpack']
+ assert dist.namespace_packages == ['pack1', 'pack2']
+ assert dist.use_2to3_fixers == ['your.fixers', 'or.here']
+ assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there']
+ assert dist.convert_2to3_doctests == (
+ ['src/tests/one.txt', 'src/two.txt'])
+ assert dist.scripts == ['bin/one.py', 'bin/two.py']
+ assert dist.dependency_links == ([
+ 'http://some.com/here/1',
+ 'http://some.com/there/2'
+ ])
+ assert dist.install_requires == ([
+ 'docutils>=0.3',
+ 'pack ==1.1, ==1.3',
+ 'hey'
+ ])
+ assert dist.setup_requires == ([
+ 'docutils>=0.3',
+ 'spack ==1.1, ==1.3',
+ 'there'
+ ])
+ assert dist.tests_require == ['mock==0.7.2', 'pytest']
+
+ def test_package_dir_fail(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options]\n'
+ 'package_dir = a b\n'
+ )
+ with get_dist(tmpdir, parse=False) as dist:
+ with pytest.raises(DistutilsOptionError):
+ dist.parse_config_files()
+
+ def test_package_data(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.package_data]\n'
+ '* = *.txt, *.rst\n'
+ 'hello = *.msg\n'
+ '\n'
+ '[options.exclude_package_data]\n'
+ '* = fake1.txt, fake2.txt\n'
+ 'hello = *.dat\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.package_data == {
+ '': ['*.txt', '*.rst'],
+ 'hello': ['*.msg'],
+ }
+ assert dist.exclude_package_data == {
+ '': ['fake1.txt', 'fake2.txt'],
+ 'hello': ['*.dat'],
+ }
+
+ def test_packages(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options]\n'
+ 'packages = find:\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.packages == ['fake_package']
+
+ def test_find_directive(self, tmpdir):
+ dir_package, config = fake_env(
+ tmpdir,
+ '[options]\n'
+ 'packages = find:\n'
+ )
+
+ dir_sub_one, _ = make_package_dir('sub_one', dir_package)
+ dir_sub_two, _ = make_package_dir('sub_two', dir_package)
+
+ with get_dist(tmpdir) as dist:
+ assert set(dist.packages) == set([
+ 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'
+ ])
+
+ config.write(
+ '[options]\n'
+ 'packages = find:\n'
+ '\n'
+ '[options.packages.find]\n'
+ 'where = .\n'
+ 'include =\n'
+ ' fake_package.sub_one\n'
+ ' two\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.packages == ['fake_package.sub_one']
+
+ config.write(
+ '[options]\n'
+ 'packages = find:\n'
+ '\n'
+ '[options.packages.find]\n'
+ 'exclude =\n'
+ ' fake_package.sub_one\n'
+ )
+ with get_dist(tmpdir) as dist:
+ assert set(dist.packages) == set(
+ ['fake_package', 'fake_package.sub_two'])
+
+ def test_extras_require(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.extras_require]\n'
+ 'pdf = ReportLab>=1.2; RXP\n'
+ 'rest = \n'
+ ' docutils>=0.3\n'
+ ' pack ==1.1, ==1.3\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.extras_require == {
+ 'pdf': ['ReportLab>=1.2', 'RXP'],
+ 'rest': ['docutils>=0.3', 'pack ==1.1, ==1.3']
+ }
+
+ def test_entry_points(self, tmpdir):
+ _, config = fake_env(
+ tmpdir,
+ '[options.entry_points]\n'
+ 'group1 = point1 = pack.module:func, '
+ '.point2 = pack.module2:func_rest [rest]\n'
+ 'group2 = point3 = pack.module:func2\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.entry_points == {
+ 'group1': [
+ 'point1 = pack.module:func',
+ '.point2 = pack.module2:func_rest [rest]',
+ ],
+ 'group2': ['point3 = pack.module:func2']
+ }
+
+ expected = (
+ '[blogtool.parsers]\n'
+ '.rst = some.nested.module:SomeClass.some_classmethod[reST]\n'
+ )
+
+ tmpdir.join('entry_points').write(expected)
+
+ # From file.
+ config.write(
+ '[options]\n'
+ 'entry_points = file: entry_points\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.entry_points == expected
diff --git a/setuptools/tests/test_depends.py b/setuptools/tests/test_depends.py
new file mode 100644
index 00000000..e0cfa880
--- /dev/null
+++ b/setuptools/tests/test_depends.py
@@ -0,0 +1,16 @@
+import sys
+
+from setuptools import depends
+
+
+class TestGetModuleConstant:
+
+ def test_basic(self):
+ """
+ Invoke get_module_constant on a module in
+ the test package.
+ """
+ mod_name = 'setuptools.tests.mod_with_constant'
+ val = depends.get_module_constant(mod_name, 'value')
+ assert val == 'three, sir!'
+ assert 'setuptools.tests.mod_with_constant' not in sys.modules
diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py
index 4cf483f2..5dd72aae 100644
--- a/setuptools/tests/test_develop.py
+++ b/setuptools/tests/test_develop.py
@@ -1,17 +1,23 @@
"""develop tests
"""
+
+from __future__ import absolute_import, unicode_literals
+
import os
import site
import sys
import io
+import subprocess
from setuptools.extern import six
+from setuptools.command import test
import pytest
from setuptools.command.develop import develop
from setuptools.dist import Distribution
from . import contexts
+from . import namespaces
SETUP_PY = """\
from setuptools import setup
@@ -114,3 +120,56 @@ class TestDevelop:
cmd.install_dir = tmpdir
cmd.run()
# assert '0.0' not in foocmd_text
+
+
+class TestNamespaces:
+
+ @staticmethod
+ def install_develop(src_dir, target):
+
+ develop_cmd = [
+ sys.executable,
+ 'setup.py',
+ 'develop',
+ '--install-dir', str(target),
+ ]
+ with src_dir.as_cwd():
+ with test.test.paths_on_pythonpath([str(target)]):
+ subprocess.check_call(develop_cmd)
+
+ @pytest.mark.skipif(bool(os.environ.get("APPVEYOR")),
+ reason="https://github.com/pypa/setuptools/issues/851")
+ def test_namespace_package_importable(self, tmpdir):
+ """
+ Installing two packages sharing the same namespace, one installed
+ naturally using pip or `--single-version-externally-managed`
+ and the other installed using `develop` should leave the namespace
+ in tact and both packages reachable by import.
+ """
+ pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ pkg_B = namespaces.build_namespace_package(tmpdir, 'myns.pkgB')
+ target = tmpdir / 'packages'
+ # use pip to install to the target directory
+ install_cmd = [
+ 'pip',
+ 'install',
+ str(pkg_A),
+ '-t', str(target),
+ ]
+ subprocess.check_call(install_cmd)
+ self.install_develop(pkg_B, target)
+ namespaces.make_site_dir(target)
+ try_import = [
+ sys.executable,
+ '-c', 'import myns.pkgA; import myns.pkgB',
+ ]
+ with test.test.paths_on_pythonpath([str(target)]):
+ subprocess.check_call(try_import)
+
+ # additionally ensure that pkg_resources import works
+ pkg_resources_imp = [
+ sys.executable,
+ '-c', 'import pkg_resources',
+ ]
+ with test.test.paths_on_pythonpath([str(target)]):
+ subprocess.check_call(pkg_resources_imp)
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index 209e6b78..1ea33b08 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -169,10 +169,6 @@ class TestEasyInstallTest:
sdist_zip.close()
return str(sdist)
- @pytest.mark.xfail(reason="#709 and #710")
- # also
- #@pytest.mark.xfail(setuptools.tests.is_ascii,
- # reason="https://github.com/pypa/setuptools/issues/706")
def test_unicode_filename_in_sdist(self, sdist_unicode, tmpdir, monkeypatch):
"""
The install command should execute correctly even if
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 12c10497..a32b981d 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -4,7 +4,7 @@ import re
import stat
import sys
-from setuptools.command.egg_info import egg_info
+from setuptools.command.egg_info import egg_info, manifest_maker
from setuptools.dist import Distribution
from setuptools.extern.six.moves import map
@@ -88,9 +88,8 @@ class TestEggInfo(object):
assert '[egg_info]' in content
assert 'tag_build =' in content
assert 'tag_date = 0' in content
- assert 'tag_svn_revision = 0' in content
- expected_order = 'tag_build', 'tag_date', 'tag_svn_revision'
+ expected_order = 'tag_build', 'tag_date',
self._validate_content_order(content, expected_order)
@@ -117,7 +116,6 @@ class TestEggInfo(object):
[egg_info]
tag_build =
tag_date = 0
- tag_svn_revision = 0
"""),
})
dist = Distribution()
@@ -131,9 +129,8 @@ class TestEggInfo(object):
assert '[egg_info]' in content
assert 'tag_build =' in content
assert 'tag_date = 0' in content
- assert 'tag_svn_revision = 0' in content
- expected_order = 'tag_build', 'tag_date', 'tag_svn_revision'
+ expected_order = 'tag_build', 'tag_date',
self._validate_content_order(content, expected_order)
@@ -237,6 +234,15 @@ class TestEggInfo(object):
pkginfo = os.path.join(egg_info_dir, 'PKG-INFO')
assert 'Requires-Python: >=1.2.3' in open(pkginfo).read().split('\n')
+ def test_manifest_maker_warning_suppression(self):
+ fixtures = [
+ "standard file not found: should have one of foo.py, bar.py",
+ "standard file 'setup.py' not found"
+ ]
+
+ for msg in fixtures:
+ assert manifest_maker._should_suppress_warning(msg)
+
def _run_install_command(self, tmpdir_cwd, env, cmd=None, output=None):
environ = os.environ.copy().update(
HOME=env.paths['home'],
diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py
new file mode 100644
index 00000000..721cad1e
--- /dev/null
+++ b/setuptools/tests/test_namespaces.py
@@ -0,0 +1,105 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+import sys
+import subprocess
+
+import pytest
+
+from . import namespaces
+from setuptools.command import test
+
+
+class TestNamespaces:
+
+ @pytest.mark.xfail(sys.version_info < (3, 5),
+ reason="Requires importlib.util.module_from_spec")
+ @pytest.mark.skipif(bool(os.environ.get("APPVEYOR")),
+ reason="https://github.com/pypa/setuptools/issues/851")
+ def test_mixed_site_and_non_site(self, tmpdir):
+ """
+ Installing two packages sharing the same namespace, one installed
+ to a site dir and the other installed just to a path on PYTHONPATH
+ should leave the namespace in tact and both packages reachable by
+ import.
+ """
+ pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ pkg_B = namespaces.build_namespace_package(tmpdir, 'myns.pkgB')
+ site_packages = tmpdir / 'site-packages'
+ path_packages = tmpdir / 'path-packages'
+ targets = site_packages, path_packages
+ # use pip to install to the target directory
+ install_cmd = [
+ 'pip',
+ 'install',
+ str(pkg_A),
+ '-t', str(site_packages),
+ ]
+ subprocess.check_call(install_cmd)
+ namespaces.make_site_dir(site_packages)
+ install_cmd = [
+ 'pip',
+ 'install',
+ str(pkg_B),
+ '-t', str(path_packages),
+ ]
+ subprocess.check_call(install_cmd)
+ try_import = [
+ sys.executable,
+ '-c', 'import myns.pkgA; import myns.pkgB',
+ ]
+ with test.test.paths_on_pythonpath(map(str, targets)):
+ subprocess.check_call(try_import)
+
+ @pytest.mark.skipif(bool(os.environ.get("APPVEYOR")),
+ reason="https://github.com/pypa/setuptools/issues/851")
+ def test_pkg_resources_import(self, tmpdir):
+ """
+ Ensure that a namespace package doesn't break on import
+ of pkg_resources.
+ """
+ pkg = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ target = tmpdir / 'packages'
+ target.mkdir()
+ install_cmd = [
+ sys.executable,
+ '-m', 'easy_install',
+ '-d', str(target),
+ str(pkg),
+ ]
+ with test.test.paths_on_pythonpath([str(target)]):
+ subprocess.check_call(install_cmd)
+ namespaces.make_site_dir(target)
+ try_import = [
+ sys.executable,
+ '-c', 'import pkg_resources',
+ ]
+ with test.test.paths_on_pythonpath([str(target)]):
+ subprocess.check_call(try_import)
+
+ @pytest.mark.skipif(bool(os.environ.get("APPVEYOR")),
+ reason="https://github.com/pypa/setuptools/issues/851")
+ def test_namespace_package_installed_and_cwd(self, tmpdir):
+ """
+ Installing a namespace packages but also having it in the current
+ working directory, only one version should take precedence.
+ """
+ pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA')
+ target = tmpdir / 'packages'
+ # use pip to install to the target directory
+ install_cmd = [
+ 'pip',
+ 'install',
+ str(pkg_A),
+ '-t', str(target),
+ ]
+ subprocess.check_call(install_cmd)
+ namespaces.make_site_dir(target)
+
+ # ensure that package imports and pkg_resources imports
+ pkg_resources_imp = [
+ sys.executable,
+ '-c', 'import pkg_resources; import myns.pkgA',
+ ]
+ with test.test.paths_on_pythonpath([str(target)]):
+ subprocess.check_call(pkg_resources_imp, cwd=str(pkg_A))
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index b92a477a..929f0a5b 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -23,22 +23,6 @@ class TestSandbox:
return do_write
- def test_win32com(self, tmpdir):
- """
- win32com should not be prevented from caching COM interfaces
- in gen_py.
- """
- win32com = pytest.importorskip('win32com')
- gen_py = win32com.__gen_path__
- target = os.path.join(gen_py, 'test_write')
- sandbox = DirectorySandbox(str(tmpdir))
- try:
- # attempt to create gen_py file
- sandbox.run(self._file_writer(target))
- finally:
- if os.path.exists(target):
- os.remove(target)
-
def test_setup_py_with_BOM(self):
"""
It should be possible to execute a setup.py with a Byte Order Mark
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index 609c7830..f34068dc 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -26,7 +26,8 @@ SETUP_ATTRS = {
'name': 'sdist_test',
'version': '0.0',
'packages': ['sdist_test'],
- 'package_data': {'sdist_test': ['*.txt']}
+ 'package_data': {'sdist_test': ['*.txt']},
+ 'data_files': [("data", [os.path.join("d", "e.dat")])],
}
SETUP_PY = """\
@@ -95,9 +96,12 @@ class TestSdistTest:
# Set up the rest of the test package
test_pkg = os.path.join(self.temp_dir, 'sdist_test')
os.mkdir(test_pkg)
+ data_folder = os.path.join(self.temp_dir, "d")
+ os.mkdir(data_folder)
# *.rst was not included in package_data, so c.rst should not be
# automatically added to the manifest when not under version control
- for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']:
+ for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst',
+ os.path.join(data_folder, "e.dat")]:
# Just touch the files; their contents are irrelevant
open(os.path.join(test_pkg, fname), 'w').close()
@@ -126,6 +130,7 @@ class TestSdistTest:
assert os.path.join('sdist_test', 'a.txt') in manifest
assert os.path.join('sdist_test', 'b.txt') in manifest
assert os.path.join('sdist_test', 'c.rst') not in manifest
+ assert os.path.join('d', 'e.dat') in manifest
def test_defaults_case_sensitivity(self):
"""
diff --git a/setuptools/version.py b/setuptools/version.py
index f2b40722..95e18696 100644
--- a/setuptools/version.py
+++ b/setuptools/version.py
@@ -1,6 +1,6 @@
import pkg_resources
try:
- __version__ = pkg_resources.require('setuptools')[0].version
+ __version__ = pkg_resources.get_distribution('setuptools').version
except Exception:
__version__ = 'unknown'