From d54c0e4d2765b30d5638ece80e6be93b399c5c00 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Wed, 30 Dec 2020 07:03:37 +0900 Subject: [PATCH] Imported Upstream version 40.7.0 --- .mergify.yml | 11 ++ CHANGES.rst | 10 ++ docs/_templates/indexsidebar.html | 4 +- docs/developer-guide.txt | 17 ++- docs/requirements.txt | 2 +- docs/setuptools.txt | 4 +- pyproject.toml | 1 + setup.cfg | 2 +- setup.py | 2 +- setuptools/__init__.py | 35 +++++- setuptools/build_meta.py | 23 ++-- setuptools/command/develop.py | 25 ++-- setuptools/command/upload.py | 4 +- setuptools/config.py | 37 ++++-- setuptools/dist.py | 126 +++++++++++++++++++- setuptools/package_index.py | 16 ++- setuptools/pep425tags.py | 2 +- setuptools/py36compat.py | 82 ------------- setuptools/ssl_support.py | 2 +- setuptools/tests/files.py | 9 +- setuptools/tests/server.py | 14 ++- setuptools/tests/test_build_clib.py | 9 +- setuptools/tests/test_build_meta.py | 27 ++++- setuptools/tests/test_config.py | 111 ++++++++++++++++-- setuptools/tests/test_depends.py | 18 +-- setuptools/tests/test_dist.py | 15 ++- setuptools/tests/test_easy_install.py | 6 +- setuptools/tests/test_egg_info.py | 86 +++++++++++++- setuptools/tests/test_find_packages.py | 11 +- setuptools/tests/test_install_scripts.py | 6 +- setuptools/tests/test_integration.py | 23 ++-- setuptools/tests/test_manifest.py | 133 +++++++++++----------- setuptools/tests/test_msvc.py | 5 +- setuptools/tests/test_namespaces.py | 1 - setuptools/tests/test_packageindex.py | 74 +++++++++++- setuptools/tests/test_pep425tags.py | 4 +- setuptools/tests/test_sandbox.py | 3 +- setuptools/tests/test_setuptools.py | 9 +- setuptools/tests/test_test.py | 1 - setuptools/tests/test_upload.py | 1 - setuptools/tests/test_virtualenv.py | 3 +- setuptools/tests/test_wheel.py | 2 + setuptools/tests/test_windows_wrappers.py | 13 ++- setuptools/unicode_utils.py | 13 +++ setuptools/wheel.py | 13 ++- tests/requirements.txt | 4 +- tox.ini | 2 +- 47 files changed, 728 insertions(+), 293 deletions(-) create mode 100644 .mergify.yml delete mode 100644 setuptools/py36compat.py diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000..7f0df53 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,11 @@ +pull_request_rules: +- name: auto-merge + conditions: + - base=master + - label=auto-merge + - status-success=continuous-integration/appveyor/pr + - status-success=continuous-integration/travis-ci/pr + - status-success=deploy/netlify + actions: + merge: + method: merge diff --git a/CHANGES.rst b/CHANGES.rst index 48a176a..ca7122e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,13 @@ +v40.7.0 +------- + +* #1551: File inputs for the `license` field in `setup.cfg` files now explicitly raise an error. +* #1180: Add support for non-ASCII in setup.cfg (#1062). Add support for native strings on some parameters (#1136). +* #1499: ``setuptools.package_index`` no longer relies on the deprecated ``urllib.parse.splituser`` per Python #27485. +* #1544: Added tests for PackageIndex.download (for git URLs). +* #1625: In PEP 517 build_meta builder, ensure that sdists are built as gztar per the spec. + + v40.6.3 ------- diff --git a/docs/_templates/indexsidebar.html b/docs/_templates/indexsidebar.html index b8c6148..504de6b 100644 --- a/docs/_templates/indexsidebar.html +++ b/docs/_templates/indexsidebar.html @@ -5,11 +5,11 @@

Questions? Suggestions? Contributions?

-

Visit the Setuptools project page

+

Visit the Project page

Professional support

Professionally-supported {{ project }} is available with the -Tidelift Subscription. +Tidelift Subscription.

diff --git a/docs/developer-guide.txt b/docs/developer-guide.txt index c011491..a5942c8 100644 --- a/docs/developer-guide.txt +++ b/docs/developer-guide.txt @@ -67,8 +67,8 @@ All PRs with code changes should include tests. All changes should include a changelog entry. ``setuptools`` uses `towncrier `_ -for changelog managment, so when making a PR, please add a news fragment in the -``changelog.d/`` folder. Changelog files are written in Restructured Text and +for changelog management, so when making a PR, please add a news fragment in the +``changelog.d/`` folder. Changelog files are written in reStructuredText and should be a 1 or 2 sentence description of the substantive changes in the PR. They should be named ``..rst``, where the categories are: @@ -76,7 +76,7 @@ They should be named ``..rst``, where the categories are: - ``breaking``: Any backwards-compatibility breaking change - ``doc``: A change to the documentation - ``misc``: Changes internal to the repo like CI, test and build changes -- ``deprecation``: For deprecations of an existing feature of behavior +- ``deprecation``: For deprecations of an existing feature or behavior A pull request may have more than one of these components, for example a code change may introduce a new feature that deprecates an old feature, in which @@ -89,6 +89,17 @@ code changes. See the following for an example news fragment: $ cat changelog.d/1288.change.rst Add support for maintainer in PKG-INFO +------------------- +Auto-Merge Requests +------------------- + +To support running all code through CI, even lightweight contributions, +the project employs Mergify to auto-merge pull requests tagged as +auto-merge. + +Use ``hub pull-request -l auto-merge`` to create such a pull request +from the command line after pushing a new branch. + ------- Testing ------- diff --git a/docs/requirements.txt b/docs/requirements.txt index c6d594e..bc27165 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ sphinx!=1.8.0 rst.linker>=1.9 -jaraco.packaging>=3.2 +jaraco.packaging>=6.1 setuptools>=34 diff --git a/docs/setuptools.txt b/docs/setuptools.txt index bca211b..cf166e9 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2358,7 +2358,7 @@ Metadata but their use is not advised. ============================== ================= ================= =============== ===== -Key Aliases Type Minumum Version Notes +Key Aliases Type Minimum Version Notes ============================== ================= ================= =============== ===== name str version attr:, file:, str 39.2.0 (1) @@ -2370,7 +2370,7 @@ author_email author-email str maintainer str maintainer_email maintainer-email str classifiers classifier file:, list-comma -license file:, str +license str description summary file:, str long_description long-description file:, str long_description_content_type str 38.6.0 diff --git a/pyproject.toml b/pyproject.toml index 07c23bb..4ef804e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [build-system] requires = ["wheel"] +build-backend = "setuptools.build_meta" [tool.towncrier] package = "setuptools" diff --git a/setup.cfg b/setup.cfg index 57ab623..78eb759 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 40.6.3 +current_version = 40.7.0 commit = True tag = True diff --git a/setup.py b/setup.py index 67d5691..00db0f0 100755 --- a/setup.py +++ b/setup.py @@ -89,7 +89,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="40.6.3", + version="40.7.0", description=( "Easily download, build, install, upgrade, and uninstall " "Python packages" diff --git a/setuptools/__init__.py b/setuptools/__init__.py index e438036..a71b2bb 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -5,12 +5,14 @@ import sys import functools import distutils.core import distutils.filelist +import re +from distutils.errors import DistutilsOptionError from distutils.util import convert_path from fnmatch import fnmatchcase from ._deprecation_warning import SetuptoolsDeprecationWarning -from setuptools.extern.six import PY3 +from setuptools.extern.six import PY3, string_types from setuptools.extern.six.moves import filter, map import setuptools.version @@ -161,6 +163,37 @@ class Command(_Command): _Command.__init__(self, dist) vars(self).update(kw) + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, string_types): + raise DistutilsOptionError("'%s' must be a %s (got `%s`)" + % (option, what, val)) + return val + + def ensure_string_list(self, option): + r"""Ensure that 'option' is a list of strings. If 'option' is + currently a string, we split it either on /,\s*/ or /\s+/, so + "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become + ["foo", "bar", "baz"]. + """ + val = getattr(self, option) + if val is None: + return + elif isinstance(val, string_types): + setattr(self, option, re.split(r',\s*|\s+', val)) + else: + if isinstance(val, list): + ok = all(isinstance(v, string_types) for v in val) + else: + ok = False + if not ok: + raise DistutilsOptionError( + "'%s' must be a list of strings (got %r)" + % (option, val)) + def reinitialize_command(self, command, reinit_subcommands=0, **kw): cmd = _Command.reinitialize_command(self, command, reinit_subcommands) vars(cmd).update(kw) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 463d375..c883d92 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -149,6 +149,15 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): return dist_infos[0] +def _file_with_extension(directory, extension): + matching = ( + f for f in os.listdir(directory) + if f.endswith(extension) + ) + file, = matching + return file + + def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): config_settings = _fix_config(config_settings) @@ -160,23 +169,15 @@ def build_wheel(wheel_directory, config_settings=None, shutil.rmtree(wheel_directory) shutil.copytree('dist', wheel_directory) - wheels = [f for f in os.listdir(wheel_directory) - if f.endswith('.whl')] - - assert len(wheels) == 1 - return wheels[0] + return _file_with_extension(wheel_directory, '.whl') def build_sdist(sdist_directory, config_settings=None): config_settings = _fix_config(config_settings) sdist_directory = os.path.abspath(sdist_directory) - sys.argv = sys.argv[:1] + ['sdist'] + \ + sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \ config_settings["--global-option"] + \ ["--dist-dir", sdist_directory] _run_setup() - sdists = [f for f in os.listdir(sdist_directory) - if f.endswith('.tar.gz')] - - assert len(sdists) == 1 - return sdists[0] + return _file_with_extension(sdist_directory, '.tar.gz') diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py index fdc9fc4..009e4f9 100644 --- a/setuptools/command/develop.py +++ b/setuptools/command/develop.py @@ -7,7 +7,7 @@ import io from setuptools.extern import six -from pkg_resources import Distribution, PathMetadata, normalize_path +import pkg_resources from setuptools.command.easy_install import easy_install from setuptools import namespaces import setuptools @@ -65,9 +65,9 @@ class develop(namespaces.DevelopInstaller, easy_install): if self.egg_path is None: self.egg_path = os.path.abspath(ei.egg_base) - target = normalize_path(self.egg_base) - egg_path = normalize_path(os.path.join(self.install_dir, - self.egg_path)) + target = pkg_resources.normalize_path(self.egg_base) + egg_path = pkg_resources.normalize_path( + os.path.join(self.install_dir, self.egg_path)) if egg_path != target: raise DistutilsOptionError( "--egg-path must be a relative path from the install" @@ -75,9 +75,9 @@ class develop(namespaces.DevelopInstaller, easy_install): ) # Make a distribution for the package's source - self.dist = Distribution( + self.dist = pkg_resources.Distribution( target, - PathMetadata(target, os.path.abspath(ei.egg_info)), + pkg_resources.PathMetadata(target, os.path.abspath(ei.egg_info)), project_name=ei.egg_name ) @@ -97,13 +97,14 @@ class develop(namespaces.DevelopInstaller, easy_install): path_to_setup = egg_base.replace(os.sep, '/').rstrip('/') if path_to_setup != os.curdir: path_to_setup = '../' * (path_to_setup.count('/') + 1) - resolved = normalize_path( + resolved = pkg_resources.normalize_path( os.path.join(install_dir, egg_path, path_to_setup) ) - if resolved != normalize_path(os.curdir): + if resolved != pkg_resources.normalize_path(os.curdir): raise DistutilsOptionError( "Can't get a consistent path to setup script from" - " installation directory", resolved, normalize_path(os.curdir)) + " installation directory", resolved, + pkg_resources.normalize_path(os.curdir)) return path_to_setup def install_for_development(self): @@ -114,7 +115,7 @@ class develop(namespaces.DevelopInstaller, easy_install): self.reinitialize_command('build_py', inplace=0) self.run_command('build_py') bpy_cmd = self.get_finalized_command("build_py") - build_path = normalize_path(bpy_cmd.build_lib) + build_path = pkg_resources.normalize_path(bpy_cmd.build_lib) # Build extensions self.reinitialize_command('egg_info', egg_base=build_path) @@ -128,7 +129,8 @@ class develop(namespaces.DevelopInstaller, easy_install): self.egg_path = build_path self.dist.location = build_path # XXX - self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info) + self.dist._provider = pkg_resources.PathMetadata( + build_path, ei_cmd.egg_info) else: # Without 2to3 inplace works fine: self.run_command('egg_info') @@ -200,6 +202,7 @@ class VersionlessRequirement: name as the 'requirement' so that scripts will work across multiple versions. + >>> from pkg_resources import Distribution >>> dist = Distribution(project_name='foo', version='1.0') >>> str(dist.as_requirement()) 'foo==1.0' diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index dd17f7a..6db8888 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -2,7 +2,6 @@ import io import os import hashlib import getpass -import platform from base64 import standard_b64encode @@ -16,6 +15,7 @@ from setuptools.extern.six.moves.urllib.request import urlopen, Request from setuptools.extern.six.moves.urllib.error import HTTPError from setuptools.extern.six.moves.urllib.parse import urlparse + class upload(orig.upload): """ Override default upload behavior to obtain password @@ -80,7 +80,7 @@ class upload(orig.upload): 'version': meta.get_version(), # file content - 'content': (os.path.basename(filename),content), + 'content': (os.path.basename(filename), content), 'filetype': command, 'pyversion': pyversion, 'md5_digest': hashlib.md5(content).hexdigest(), diff --git a/setuptools/config.py b/setuptools/config.py index d1ac673..b662604 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -246,6 +246,26 @@ class ConfigHandler: value = value.lower() return value in ('1', 'true', 'yes') + @classmethod + def _exclude_files_parser(cls, key): + """Returns a parser function to make sure field inputs + are not files. + + Parses a value after getting the key so error messages are + more informative. + + :param key: + :rtype: callable + """ + def parser(value): + exclude_directive = 'file:' + if value.startswith(exclude_directive): + raise ValueError( + 'Only strings are accepted for the {0} field, ' + 'files are not accepted'.format(key)) + return value + return parser + @classmethod def _parse_file(cls, value): """Represents value as a string, allowing including text @@ -255,7 +275,6 @@ class ConfigHandler: directory with setup.py. Examples: - file: LICENSE file: README.rst, CHANGELOG.md, src/file.txt :param str value: @@ -394,7 +413,7 @@ class ConfigHandler: section_parser_method = getattr( self, - # Dots in section names are tranlsated into dunderscores. + # Dots in section names are translated into dunderscores. ('parse_section%s' % method_postfix).replace('.', '__'), None) @@ -407,8 +426,8 @@ class ConfigHandler: def _deprecated_config_handler(self, func, msg, warning_class): """ this function will wrap around parameters that are deprecated - - :param msg: deprecation message + + :param msg: deprecation message :param warning_class: class of warning exception to be raised :param func: function to be wrapped around """ @@ -416,7 +435,7 @@ class ConfigHandler: def config_handler(*args, **kwargs): warnings.warn(msg, warning_class) return func(*args, **kwargs) - + return config_handler @@ -449,18 +468,20 @@ class ConfigMetadataHandler(ConfigHandler): parse_list = self._parse_list parse_file = self._parse_file parse_dict = self._parse_dict + exclude_files_parser = self._exclude_files_parser return { 'platforms': parse_list, 'keywords': parse_list, 'provides': parse_list, - 'requires': self._deprecated_config_handler(parse_list, - "The requires parameter is deprecated, please use " + + 'requires': self._deprecated_config_handler( + parse_list, + "The requires parameter is deprecated, please use " "install_requires for runtime dependencies.", DeprecationWarning), 'obsoletes': parse_list, 'classifiers': self._get_parser_compound(parse_file, parse_list), - 'license': parse_file, + 'license': exclude_files_parser('license'), 'description': parse_file, 'long_description': parse_file, 'version': self._parse_version, diff --git a/setuptools/dist.py b/setuptools/dist.py index 7062ae8..b855122 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- __all__ = ['Distribution'] +import io +import sys import re import os import warnings @@ -9,9 +11,12 @@ import distutils.log import distutils.core import distutils.cmd import distutils.dist +from distutils.errors import DistutilsOptionError +from distutils.util import strtobool +from distutils.debug import DEBUG +from distutils.fancy_getopt import translate_longopt import itertools - from collections import defaultdict from email import message_from_file @@ -31,8 +36,8 @@ from setuptools.depends import Require from setuptools import windows_support from setuptools.monkey import get_unpatched from setuptools.config import parse_configuration +from .unicode_utils import detect_encoding import pkg_resources -from .py36compat import Distribution_parse_config_files __import__('setuptools.extern.packaging.specifiers') __import__('setuptools.extern.packaging.version') @@ -332,7 +337,7 @@ def check_packages(dist, attr, value): _Distribution = get_unpatched(distutils.core.Distribution) -class Distribution(Distribution_parse_config_files, _Distribution): +class Distribution(_Distribution): """Distribution with support for features, tests, and package data This is an enhanced version of 'distutils.dist.Distribution' that @@ -556,12 +561,125 @@ class Distribution(Distribution_parse_config_files, _Distribution): req.marker = None return req + def _parse_config_files(self, filenames=None): + """ + Adapted from distutils.dist.Distribution.parse_config_files, + this method provides the same functionality in subtly-improved + ways. + """ + from setuptools.extern.six.moves.configparser import ConfigParser + + # Ignore install directory options if we have a venv + if six.PY3 and 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() + for filename in filenames: + with io.open(filename, 'rb') as fp: + encoding = detect_encoding(fp) + if DEBUG: + self.announce(" reading %s [%s]" % ( + filename, encoding or 'locale') + ) + reader = io.TextIOWrapper(fp, encoding=encoding) + (parser.read_file if six.PY3 else parser.readfp)(reader) + 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) + + def _set_command_options(self, command_obj, option_dict=None): + """ + Set the options for 'command_obj' from 'option_dict'. Basically + this means copying elements of a dictionary ('option_dict') to + attributes of an instance ('command'). + + 'command_obj' must be a Command instance. If 'option_dict' is not + supplied, uses the standard option dictionary for this command + (from 'self.command_options'). + + (Adopted from distutils.dist.Distribution._set_command_options) + """ + command_name = command_obj.get_command_name() + if option_dict is None: + option_dict = self.get_option_dict(command_name) + + if DEBUG: + self.announce(" setting options for '%s' command:" % command_name) + for (option, (source, value)) in option_dict.items(): + if DEBUG: + self.announce(" %s = %s (from %s)" % (option, value, + source)) + try: + bool_opts = [translate_longopt(o) + for o in command_obj.boolean_options] + except AttributeError: + bool_opts = [] + try: + neg_opt = command_obj.negative_opt + except AttributeError: + neg_opt = {} + + try: + is_string = isinstance(value, six.string_types) + if option in neg_opt and is_string: + setattr(command_obj, neg_opt[option], not strtobool(value)) + elif option in bool_opts and is_string: + setattr(command_obj, option, strtobool(value)) + elif hasattr(command_obj, option): + setattr(command_obj, option, value) + else: + raise DistutilsOptionError( + "error in %s: command '%s' has no such option '%s'" + % (source, command_name, option)) + except ValueError as msg: + raise DistutilsOptionError(msg) + def parse_config_files(self, filenames=None, ignore_option_errors=False): """Parses configuration files from various levels and loads configuration. """ - _Distribution.parse_config_files(self, filenames=filenames) + self._parse_config_files(filenames=filenames) parse_configuration(self, self.command_options, ignore_option_errors=ignore_option_errors) diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 1608b91..7e9517c 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -850,13 +850,16 @@ class PackageIndex(Environment): def _download_svn(self, url, filename): warnings.warn("SVN download support is deprecated", UserWarning) + def splituser(host): + user, delim, host = host.rpartition('@') + return user, host url = url.split('#', 1)[0] # remove any fragment for svn's sake creds = '' if url.lower().startswith('svn:') and '@' in url: scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) if not netloc and path.startswith('//') and '/' in path[2:]: netloc, path = path[2:].split('/', 1) - auth, host = urllib.parse.splituser(netloc) + auth, host = splituser(netloc) if auth: if ':' in auth: user, pw = auth.split(':', 1) @@ -1047,15 +1050,16 @@ class PyPIConfig(configparser.RawConfigParser): def open_with_auth(url, opener=urllib.request.urlopen): """Open a urllib2 request, handling HTTP authentication""" - scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url) + parsed = urllib.parse.urlparse(url) + scheme, netloc, path, params, query, frag = parsed # Double scheme does not raise on Mac OS X as revealed by a # failing test. We would expect "nonnumeric port". Refs #20. if netloc.endswith(':'): raise http_client.InvalidURL("nonnumeric port: ''") - if scheme in ('http', 'https'): - auth, host = urllib.parse.splituser(netloc) + if scheme in ('http', 'https') and parsed.username: + auth = ':'.join((parsed.username, parsed.password)) else: auth = None @@ -1068,7 +1072,7 @@ def open_with_auth(url, opener=urllib.request.urlopen): if auth: auth = "Basic " + _encode_auth(auth) - parts = scheme, host, path, params, query, frag + parts = scheme, parsed.hostname, path, params, query, frag new_url = urllib.parse.urlunparse(parts) request = urllib.request.Request(new_url) request.add_header("Authorization", auth) @@ -1082,7 +1086,7 @@ def open_with_auth(url, opener=urllib.request.urlopen): # Put authentication info back into request URL if same host, # so that links found on the page will work s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url) - if s2 == scheme and h2 == host: + if s2 == scheme and h2 == parsed.hostname: parts = s2, netloc, path2, param2, query2, frag2 fp.url = urllib.parse.urlunparse(parts) diff --git a/setuptools/pep425tags.py b/setuptools/pep425tags.py index 8bf4277..48745a2 100644 --- a/setuptools/pep425tags.py +++ b/setuptools/pep425tags.py @@ -161,7 +161,7 @@ def is_manylinux1_compatible(): def get_darwin_arches(major, minor, machine): """Return a list of supported arches (including group arches) for - the given major, minor and machine architecture of an macOS machine. + the given major, minor and machine architecture of a macOS machine. """ arches = [] diff --git a/setuptools/py36compat.py b/setuptools/py36compat.py deleted file mode 100644 index f527969..0000000 --- a/setuptools/py36compat.py +++ /dev/null @@ -1,82 +0,0 @@ -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/ssl_support.py b/setuptools/ssl_support.py index 6362f1f..226db69 100644 --- a/setuptools/ssl_support.py +++ b/setuptools/ssl_support.py @@ -59,7 +59,7 @@ if not match_hostname: def _dnsname_match(dn, hostname, max_wildcards=1): """Matching according to RFC 6125, section 6.4.3 - http://tools.ietf.org/html/rfc6125#section-6.4.3 + https://tools.ietf.org/html/rfc6125#section-6.4.3 """ pats = [] if not dn: diff --git a/setuptools/tests/files.py b/setuptools/tests/files.py index 465a6b4..bad2189 100644 --- a/setuptools/tests/files.py +++ b/setuptools/tests/files.py @@ -6,10 +6,13 @@ import pkg_resources.py31compat def build_files(file_defs, prefix=""): """ - Build a set of files/directories, as described by the file_defs dictionary. + Build a set of files/directories, as described by the + file_defs dictionary. - Each key/value pair in the dictionary is interpreted as a filename/contents - pair. If the contents value is a dictionary, a directory is created, and the + Each key/value pair in the dictionary is interpreted as + a filename/contents + pair. If the contents value is a dictionary, a directory + is created, and the dictionary interpreted as the files within it, recursively. For example: diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py index 3531212..fc3a597 100644 --- a/setuptools/tests/server.py +++ b/setuptools/tests/server.py @@ -19,10 +19,11 @@ class IndexServer(BaseHTTPServer.HTTPServer): s.stop() """ - def __init__(self, server_address=('', 0), + def __init__( + self, server_address=('', 0), RequestHandlerClass=SimpleHTTPServer.SimpleHTTPRequestHandler): - BaseHTTPServer.HTTPServer.__init__(self, server_address, - RequestHandlerClass) + BaseHTTPServer.HTTPServer.__init__( + self, server_address, RequestHandlerClass) self._run = True def start(self): @@ -56,10 +57,11 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread): A simple HTTP Server that records the requests made to it. """ - def __init__(self, server_address=('', 0), + def __init__( + self, server_address=('', 0), RequestHandlerClass=RequestRecorder): - BaseHTTPServer.HTTPServer.__init__(self, server_address, - RequestHandlerClass) + BaseHTTPServer.HTTPServer.__init__( + self, server_address, RequestHandlerClass) threading.Thread.__init__(self) self.setDaemon(True) self.requests = [] diff --git a/setuptools/tests/test_build_clib.py b/setuptools/tests/test_build_clib.py index aebcc35..3779e67 100644 --- a/setuptools/tests/test_build_clib.py +++ b/setuptools/tests/test_build_clib.py @@ -1,6 +1,4 @@ import pytest -import os -import shutil import mock from distutils.errors import DistutilsSetupError @@ -40,13 +38,14 @@ class TestBuildCLib: # with that out of the way, let's see if the crude dependency # system works cmd.compiler = mock.MagicMock(spec=cmd.compiler) - mock_newer.return_value = ([],[]) + mock_newer.return_value = ([], []) obj_deps = {'': ('global.h',), 'example.c': ('example.h',)} - libs = [('example', {'sources': ['example.c'] ,'obj_deps': obj_deps})] + libs = [('example', {'sources': ['example.c'], 'obj_deps': obj_deps})] cmd.build_libraries(libs) - assert [['example.c', 'global.h', 'example.h']] in mock_newer.call_args[0] + assert [['example.c', 'global.h', 'example.h']] in \ + mock_newer.call_args[0] assert not cmd.compiler.compile.called assert cmd.compiler.create_static_lib.call_count == 1 diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 2a07944..82b44c8 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -172,18 +172,21 @@ def test_build_sdist_version_change(build_backend): sdist_name = build_backend.build_sdist(sdist_into_directory) assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name)) - # if the setup.py changes subsequent call of the build meta should still succeed, given the + # if the setup.py changes subsequent call of the build meta + # should still succeed, given the # sdist_directory the frontend specifies is empty with open(os.path.abspath("setup.py"), 'rt') as file_handler: content = file_handler.read() with open(os.path.abspath("setup.py"), 'wt') as file_handler: - file_handler.write(content.replace("version='0.0.0'", "version='0.0.1'")) + file_handler.write( + content.replace("version='0.0.0'", "version='0.0.1'")) shutil.rmtree(sdist_into_directory) os.makedirs(sdist_into_directory) sdist_name = build_backend.build_sdist("out_sdist") - assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name)) + assert os.path.isfile( + os.path.join(os.path.abspath("out_sdist"), sdist_name)) def test_build_sdist_setup_py_exists(tmpdir_cwd): @@ -215,3 +218,21 @@ def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd): with tarfile.open(os.path.join("temp", targz_path)) as tar: assert not any('setup.py' in name for name in tar.getnames()) + +def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd): + files = { + 'setup.py': DALS(""" + __import__('setuptools').setup( + name='foo', + version='0.0.0', + py_modules=['hello'] + )"""), + 'hello.py': '', + 'setup.cfg': DALS(""" + [sdist] + formats=zip + """) + } + + build_files(files) + build_sdist("temp") diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 736c184..6b17770 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -1,10 +1,18 @@ +# -*- coding: UTF-8 -*- +from __future__ import unicode_literals + import contextlib import pytest + from distutils.errors import DistutilsOptionError, DistutilsFileError from mock import patch from setuptools.dist import Distribution, _Distribution from setuptools.config import ConfigHandler, read_configuration +from setuptools.extern.six.moves.configparser import InterpolationMissingOptionError +from setuptools.tests import is_ascii from . import py2_only, py3_only +from .textwrap import DALS + class ErrConfigHandler(ConfigHandler): """Erroneous handler. Fails to implement required methods.""" @@ -16,12 +24,12 @@ def make_package_dir(name, base_dir, ns=False): dir_package = dir_package.mkdir(dir_name) init_file = None if not ns: - init_file = dir_package.join('__init__.py') - init_file.write('') + init_file = dir_package.join('__init__.py') + init_file.write('') return dir_package, init_file -def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'): +def fake_env(tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'): if setup_py is None: setup_py = ( @@ -31,7 +39,7 @@ def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'): tmpdir.join('setup.py').write(setup_py) config = tmpdir.join('setup.cfg') - config.write(setup_cfg) + config.write(setup_cfg.encode(encoding), mode='wb') package_dir, init_file = make_package_dir(package_path, tmpdir) @@ -146,6 +154,24 @@ class TestMetadata: assert metadata.download_url == 'http://test.test.com/test/' assert metadata.maintainer_email == 'test@test.com' + def test_license_cfg(self, tmpdir): + fake_env( + tmpdir, + DALS(""" + [metadata] + name=foo + version=0.0.1 + license=Apache 2.0 + """) + ) + + with get_dist(tmpdir) as dist: + metadata = dist.metadata + + assert metadata.name == "foo" + assert metadata.version == "0.0.1" + assert metadata.license == "Apache 2.0" + def test_file_mixed(self, tmpdir): fake_env( @@ -288,7 +314,7 @@ class TestMetadata: tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n') with pytest.raises(DistutilsOptionError): with get_dist(tmpdir) as dist: - _ = dist.metadata.version + dist.metadata.version def test_version_with_package_dir_simple(self, tmpdir): @@ -408,6 +434,72 @@ class TestMetadata: assert metadata.description == 'Some description' assert metadata.requires == ['some', 'requirement'] + def test_interpolation(self, tmpdir): + fake_env( + tmpdir, + '[metadata]\n' + 'description = %(message)s\n' + ) + with pytest.raises(InterpolationMissingOptionError): + with get_dist(tmpdir): + pass + + skip_if_not_ascii = pytest.mark.skipif(not is_ascii, reason='Test not supported with this locale') + + @skip_if_not_ascii + def test_non_ascii_1(self, tmpdir): + fake_env( + tmpdir, + '[metadata]\n' + 'description = éàïôñ\n', + encoding='utf-8' + ) + with pytest.raises(UnicodeDecodeError): + with get_dist(tmpdir): + pass + + def test_non_ascii_2(self, tmpdir): + fake_env( + tmpdir, + '# -*- coding: invalid\n' + ) + with pytest.raises(LookupError): + with get_dist(tmpdir): + pass + + def test_non_ascii_3(self, tmpdir): + fake_env( + tmpdir, + '\n' + '# -*- coding: invalid\n' + ) + with get_dist(tmpdir): + pass + + @skip_if_not_ascii + def test_non_ascii_4(self, tmpdir): + fake_env( + tmpdir, + '# -*- coding: utf-8\n' + '[metadata]\n' + 'description = éàïôñ\n', + encoding='utf-8' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.description == 'éàïôñ' + + @skip_if_not_ascii + def test_non_ascii_5(self, tmpdir): + fake_env( + tmpdir, + '# vim: set fileencoding=iso-8859-15 :\n' + '[metadata]\n' + 'description = éàïôñ\n', + encoding='iso-8859-15' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.description == 'éàïôñ' + class TestOptions: @@ -431,7 +523,7 @@ class TestOptions: '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' + 'http://some.com/there/2\n' 'python_requires = >=1.0, !=2.8\n' 'py_modules = module1, module2\n' ) @@ -639,7 +731,7 @@ class TestOptions: dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True) with get_dist(tmpdir) as dist: - assert set(dist.packages) == { + assert set(dist.packages) == { 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one' } @@ -691,7 +783,7 @@ class TestOptions: tmpdir, '[options.entry_points]\n' 'group1 = point1 = pack.module:func, ' - '.point2 = pack.module2:func_rest [rest]\n' + '.point2 = pack.module2:func_rest [rest]\n' 'group2 = point3 = pack.module:func2\n' ) @@ -737,7 +829,10 @@ class TestOptions: ] assert sorted(dist.data_files) == sorted(expected) + saved_dist_init = _Distribution.__init__ + + class TestExternalSetters: # During creation of the setuptools Distribution() object, we call # the init of the parent distutils Distribution object via diff --git a/setuptools/tests/test_depends.py b/setuptools/tests/test_depends.py index e0cfa88..bff1dfb 100644 --- a/setuptools/tests/test_depends.py +++ b/setuptools/tests/test_depends.py @@ -5,12 +5,12 @@ 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 + 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_dist.py b/setuptools/tests/test_dist.py index cf830b4..390c3df 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -14,6 +14,7 @@ from .test_easy_install import make_nspkg_sdist import pytest + def test_dist_fetch_build_egg(tmpdir): """ Check multiple calls to `Distribution.fetch_build_egg` work as expected. @@ -90,30 +91,32 @@ def __read_test_cases(): 'classifiers': [ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', - 'License :: OSI Approved :: MIT License' - ]})), + 'License :: OSI Approved :: MIT License', + ]})), ('Metadata version 1.1: Download URL', merge_dicts(base_attrs, { 'download_url': 'https://example.com' })), ('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, { 'python_requires': '>=3.7' })), - pytest.param('Metadata Version 1.2: Project-Url', + pytest.param( + 'Metadata Version 1.2: Project-Url', merge_dicts(base_attrs, { 'project_urls': { 'Foo': 'https://example.bar' } }), marks=pytest.mark.xfail( reason="Issue #1578: project_urls not read" - )), + )), ('Metadata Version 2.1: Long Description Content Type', merge_dicts(base_attrs, { 'long_description_content_type': 'text/x-rst; charset=UTF-8' })), - pytest.param('Metadata Version 2.1: Provides Extra', + pytest.param( + 'Metadata Version 2.1: Provides Extra', merge_dicts(base_attrs, { 'provides_extras': ['foo', 'bar'] - }), marks=pytest.mark.xfail(reason="provides_extras not read")), + }), marks=pytest.mark.xfail(reason="provides_extras not read")), ('Missing author, missing author e-mail', {'name': 'foo', 'version': '1.0.0'}), ('Missing author', diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 2cf65ae..c3fd1c6 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -15,7 +15,9 @@ import distutils.errors import io import zipfile import mock -from setuptools.command.easy_install import EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter +from setuptools.command.easy_install import ( + EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter, +) import time from setuptools.extern import six from setuptools.extern.six.moves import urllib @@ -287,7 +289,6 @@ class TestEasyInstallTest: cmd.easy_install(sdist_script) assert (target / 'mypkg_script').exists() - def test_dist_get_script_args_deprecated(self): with pytest.warns(EasyInstallDeprecationWarning): ScriptWriter.get_script_args(None, None) @@ -304,6 +305,7 @@ class TestEasyInstallTest: with pytest.warns(EasyInstallDeprecationWarning): WindowsScriptWriter.get_writer() + @pytest.mark.filterwarnings('ignore:Unbuilt egg') class TestPTHFileWriter: def test_add_from_cwd_site_sets_dirty(self): diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index f97b3f1..d5fa255 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -1,4 +1,3 @@ -import datetime import sys import ast import os @@ -7,7 +6,9 @@ import re import stat import time -from setuptools.command.egg_info import egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision +from setuptools.command.egg_info import ( + egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision, +) from setuptools.dist import Distribution from setuptools.extern.six.moves import map @@ -148,6 +149,37 @@ class TestEggInfo: ] assert sorted(actual) == expected + def test_license_is_a_string(self, tmpdir_cwd, env): + setup_config = DALS(""" + [metadata] + name=foo + version=0.0.1 + license=file:MIT + """) + + setup_script = DALS(""" + from setuptools import setup + + setup() + """) + + build_files({'setup.py': setup_script, + 'setup.cfg': setup_config}) + + # This command should fail with a ValueError, but because it's + # currently configured to use a subprocess, the actual traceback + # object is lost and we need to parse it from stderr + with pytest.raises(AssertionError) as exc: + self._run_egg_info_command(tmpdir_cwd, env) + + # Hopefully this is not too fragile: the only argument to the + # assertion error should be a traceback, ending with: + # ValueError: .... + # + # assert not 1 + tb = exc.value.args[0].split('\n') + assert tb[-3].lstrip().startswith('ValueError') + def test_rebuilt(self, tmpdir_cwd, env): """Ensure timestamps are updated when the command is re-run.""" self._create_project() @@ -597,8 +629,8 @@ class TestEggInfo: data_stream=1, env=environ, ) - if code: - raise AssertionError(data) + assert not code, data + if output: assert output in data @@ -620,3 +652,49 @@ class TestEggInfo: def test_get_pkg_info_revision_deprecated(self): pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision) + + EGG_INFO_TESTS = ( + # Check for issue #1136: invalid string type when + # reading declarative `setup.cfg` under Python 2. + { + 'setup.py': DALS( + """ + from setuptools import setup + setup( + name="foo", + ) + """), + 'setup.cfg': DALS( + """ + [options] + package_dir = + = src + """), + 'src': {}, + }, + # Check Unicode can be used in `setup.py` under Python 2. + { + 'setup.py': DALS( + """ + # -*- coding: utf-8 -*- + from __future__ import unicode_literals + from setuptools import setup, find_packages + setup( + name="foo", + package_dir={'': 'src'}, + ) + """), + 'src': {}, + } + ) + + @pytest.mark.parametrize('package_files', EGG_INFO_TESTS) + def test_egg_info(self, tmpdir_cwd, env, package_files): + """ + """ + build_files(package_files) + code, data = environment.run_setup_py( + cmd=['egg_info'], + data_stream=1, + ) + assert not code, data diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py index b08f91c..ab26b4f 100644 --- a/setuptools/tests/test_find_packages.py +++ b/setuptools/tests/test_find_packages.py @@ -12,10 +12,10 @@ from . import py3_only from setuptools.extern.six import PY3 from setuptools import find_packages if PY3: - from setuptools import find_namespace_packages + from setuptools import find_namespace_packages -# modeled after CPython's test.support.can_symlink +# modeled after CPython's test.support.can_symlink def can_symlink(): TESTFN = tempfile.mktemp() symlink_path = TESTFN + "can_symlink" @@ -164,12 +164,14 @@ class TestFindPackages: def test_pep420_ns_package_no_includes(self): packages = find_namespace_packages( self.dist_dir, exclude=['pkg.subpkg.assets']) - self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg']) + self._assert_packages( + packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg']) @py3_only def test_pep420_ns_package_no_includes_or_excludes(self): packages = find_namespace_packages(self.dist_dir) - expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] + expected = [ + 'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets'] self._assert_packages(packages, expected) @py3_only @@ -185,4 +187,3 @@ class TestFindPackages: shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets')) packages = find_namespace_packages(self.dist_dir) self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg']) - diff --git a/setuptools/tests/test_install_scripts.py b/setuptools/tests/test_install_scripts.py index 727ad65..4338c79 100644 --- a/setuptools/tests/test_install_scripts.py +++ b/setuptools/tests/test_install_scripts.py @@ -64,7 +64,8 @@ class TestInstallScripts: @pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only') def test_executable_with_spaces_escaping_unix(self, tmpdir): """ - Ensure that shebang on Unix is not quoted, even when a value with spaces + Ensure that shebang on Unix is not quoted, even when + a value with spaces is specified using --executable. """ expected = '#!%s\n' % self.unix_spaces_exe @@ -77,7 +78,8 @@ class TestInstallScripts: @pytest.mark.skipif(sys.platform != 'win32', reason='Windows only') def test_executable_arg_escaping_win32(self, tmpdir): """ - Ensure that shebang on Windows is quoted when getting a path with spaces + Ensure that shebang on Windows is quoted when + getting a path with spaces from --executable, that is itself properly quoted. """ expected = '#!"%s"\n' % self.win32_exe diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 3a9a6c5..e54f320 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -6,6 +6,11 @@ Try to install a few packages. import glob import os import sys +import re +import subprocess +import functools +import tarfile +import zipfile from setuptools.extern.six.moves import urllib import pytest @@ -114,15 +119,12 @@ def test_pyuri(install_context): assert os.path.exists(os.path.join(pyuri.location, 'pyuri', 'uri.regex')) -import re -import subprocess -import functools -import tarfile, zipfile +build_deps = ['appdirs', 'packaging', 'pyparsing', 'six'] -build_deps = ['appdirs', 'packaging', 'pyparsing', 'six'] @pytest.mark.parametrize("build_dep", build_deps) -@pytest.mark.skipif(sys.version_info < (3, 6), reason='run only on late versions') +@pytest.mark.skipif( + sys.version_info < (3, 6), reason='run only on late versions') def test_build_deps_on_distutils(request, tmpdir_factory, build_dep): """ All setuptools build dependencies must build without @@ -149,13 +151,16 @@ def install(pkg_dir, install_dir): breaker.write('raise ImportError()') cmd = [sys.executable, 'setup.py', 'install', '--prefix', install_dir] env = dict(os.environ, PYTHONPATH=pkg_dir) - output = subprocess.check_output(cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT) + output = subprocess.check_output( + cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT) return output.decode('utf-8') def download_and_extract(request, req, target): - cmd = [sys.executable, '-m', 'pip', 'download', '--no-deps', - '--no-binary', ':all:', req] + cmd = [ + sys.executable, '-m', 'pip', 'download', '--no-deps', + '--no-binary', ':all:', req, + ] output = subprocess.check_output(cmd, encoding='utf-8') filename = re.search('Saved (.*)', output).group(1) request.addfinalizer(functools.partial(os.remove, filename)) diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py index 5edfbea..2a0e9c8 100644 --- a/setuptools/tests/test_manifest.py +++ b/setuptools/tests/test_manifest.py @@ -15,7 +15,6 @@ from setuptools.command.egg_info import FileList, egg_info, translate_pattern from setuptools.dist import Distribution from setuptools.extern import six from setuptools.tests.textwrap import DALS -from . import py3_only import pytest @@ -74,7 +73,9 @@ translate_specs = [ # Glob matching ('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']), - ('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']), + ( + 'dir/*.txt', + ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']), ('*/*.py', ['bin/start.py'], []), ('docs/page-?.txt', ['docs/page-9.txt'], ['docs/page-10.txt']), @@ -243,77 +244,77 @@ class TestManifestTest(TempDirTestCase): def test_exclude(self): """Include everything in app/ except the text files""" - l = make_local_path + ml = make_local_path self.make_manifest( """ include app/* exclude app/*.txt """) - files = default_files | set([l('app/c.rst')]) + files = default_files | set([ml('app/c.rst')]) assert files == self.get_files() def test_include_multiple(self): """Include with multiple patterns.""" - l = make_local_path + ml = make_local_path self.make_manifest("include app/*.txt app/static/*") files = default_files | set([ - l('app/a.txt'), l('app/b.txt'), - l('app/static/app.js'), l('app/static/app.js.map'), - l('app/static/app.css'), l('app/static/app.css.map')]) + ml('app/a.txt'), ml('app/b.txt'), + ml('app/static/app.js'), ml('app/static/app.js.map'), + ml('app/static/app.css'), ml('app/static/app.css.map')]) assert files == self.get_files() def test_graft(self): """Include the whole app/static/ directory.""" - l = make_local_path + ml = make_local_path self.make_manifest("graft app/static") files = default_files | set([ - l('app/static/app.js'), l('app/static/app.js.map'), - l('app/static/app.css'), l('app/static/app.css.map')]) + ml('app/static/app.js'), ml('app/static/app.js.map'), + ml('app/static/app.css'), ml('app/static/app.css.map')]) assert files == self.get_files() def test_graft_glob_syntax(self): """Include the whole app/static/ directory.""" - l = make_local_path + ml = make_local_path self.make_manifest("graft */static") files = default_files | set([ - l('app/static/app.js'), l('app/static/app.js.map'), - l('app/static/app.css'), l('app/static/app.css.map')]) + ml('app/static/app.js'), ml('app/static/app.js.map'), + ml('app/static/app.css'), ml('app/static/app.css.map')]) assert files == self.get_files() def test_graft_global_exclude(self): """Exclude all *.map files in the project.""" - l = make_local_path + ml = make_local_path self.make_manifest( """ graft app/static global-exclude *.map """) files = default_files | set([ - l('app/static/app.js'), l('app/static/app.css')]) + ml('app/static/app.js'), ml('app/static/app.css')]) assert files == self.get_files() def test_global_include(self): """Include all *.rst, *.js, and *.css files in the whole tree.""" - l = make_local_path + ml = make_local_path self.make_manifest( """ global-include *.rst *.js *.css """) files = default_files | set([ - '.hidden.rst', 'testing.rst', l('app/c.rst'), - l('app/static/app.js'), l('app/static/app.css')]) + '.hidden.rst', 'testing.rst', ml('app/c.rst'), + ml('app/static/app.js'), ml('app/static/app.css')]) assert files == self.get_files() def test_graft_prune(self): """Include all files in app/, except for the whole app/static/ dir.""" - l = make_local_path + ml = make_local_path self.make_manifest( """ graft app prune app/static """) files = default_files | set([ - l('app/a.txt'), l('app/b.txt'), l('app/c.rst')]) + ml('app/a.txt'), ml('app/b.txt'), ml('app/c.rst')]) assert files == self.get_files() @@ -369,7 +370,7 @@ class TestFileListTest(TempDirTestCase): def test_process_template_line(self): # testing all MANIFEST.in template patterns file_list = FileList() - l = make_local_path + ml = make_local_path # simulated file list self.make_files([ @@ -377,16 +378,16 @@ class TestFileListTest(TempDirTestCase): 'buildout.cfg', # filelist does not filter out VCS directories, # it's sdist that does - l('.hg/last-message.txt'), - l('global/one.txt'), - l('global/two.txt'), - l('global/files.x'), - l('global/here.tmp'), - l('f/o/f.oo'), - l('dir/graft-one'), - l('dir/dir2/graft2'), - l('dir3/ok'), - l('dir3/sub/ok.txt'), + ml('.hg/last-message.txt'), + ml('global/one.txt'), + ml('global/two.txt'), + ml('global/files.x'), + ml('global/here.tmp'), + ml('f/o/f.oo'), + ml('dir/graft-one'), + ml('dir/dir2/graft2'), + ml('dir3/ok'), + ml('dir3/sub/ok.txt'), ]) MANIFEST_IN = DALS("""\ @@ -413,12 +414,12 @@ class TestFileListTest(TempDirTestCase): 'buildout.cfg', 'four.txt', 'ok', - l('.hg/last-message.txt'), - l('dir/graft-one'), - l('dir/dir2/graft2'), - l('f/o/f.oo'), - l('global/one.txt'), - l('global/two.txt'), + ml('.hg/last-message.txt'), + ml('dir/graft-one'), + ml('dir/dir2/graft2'), + ml('f/o/f.oo'), + ml('global/one.txt'), + ml('global/two.txt'), ] file_list.sort() @@ -475,10 +476,10 @@ class TestFileListTest(TempDirTestCase): assert False, "Should have thrown an error" def test_include(self): - l = make_local_path + ml = make_local_path # include file_list = FileList() - self.make_files(['a.py', 'b.txt', l('d/c.py')]) + self.make_files(['a.py', 'b.txt', ml('d/c.py')]) file_list.process_template_line('include *.py') file_list.sort() @@ -491,42 +492,42 @@ class TestFileListTest(TempDirTestCase): self.assertWarnings() def test_exclude(self): - l = make_local_path + ml = make_local_path # exclude file_list = FileList() - file_list.files = ['a.py', 'b.txt', l('d/c.py')] + file_list.files = ['a.py', 'b.txt', ml('d/c.py')] file_list.process_template_line('exclude *.py') file_list.sort() - assert file_list.files == ['b.txt', l('d/c.py')] + assert file_list.files == ['b.txt', ml('d/c.py')] self.assertNoWarnings() file_list.process_template_line('exclude *.rb') file_list.sort() - assert file_list.files == ['b.txt', l('d/c.py')] + assert file_list.files == ['b.txt', ml('d/c.py')] self.assertWarnings() def test_global_include(self): - l = make_local_path + ml = make_local_path # global-include file_list = FileList() - self.make_files(['a.py', 'b.txt', l('d/c.py')]) + self.make_files(['a.py', 'b.txt', ml('d/c.py')]) file_list.process_template_line('global-include *.py') file_list.sort() - assert file_list.files == ['a.py', l('d/c.py')] + assert file_list.files == ['a.py', ml('d/c.py')] self.assertNoWarnings() file_list.process_template_line('global-include *.rb') file_list.sort() - assert file_list.files == ['a.py', l('d/c.py')] + assert file_list.files == ['a.py', ml('d/c.py')] self.assertWarnings() def test_global_exclude(self): - l = make_local_path + ml = make_local_path # global-exclude file_list = FileList() - file_list.files = ['a.py', 'b.txt', l('d/c.py')] + file_list.files = ['a.py', 'b.txt', ml('d/c.py')] file_list.process_template_line('global-exclude *.py') file_list.sort() @@ -539,65 +540,65 @@ class TestFileListTest(TempDirTestCase): self.assertWarnings() def test_recursive_include(self): - l = make_local_path + ml = make_local_path # recursive-include file_list = FileList() - self.make_files(['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')]) + self.make_files(['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')]) file_list.process_template_line('recursive-include d *.py') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertNoWarnings() file_list.process_template_line('recursive-include e *.py') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertWarnings() def test_recursive_exclude(self): - l = make_local_path + ml = make_local_path # recursive-exclude file_list = FileList() - file_list.files = ['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')] + file_list.files = ['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')] file_list.process_template_line('recursive-exclude d *.py') file_list.sort() - assert file_list.files == ['a.py', l('d/c.txt')] + assert file_list.files == ['a.py', ml('d/c.txt')] self.assertNoWarnings() file_list.process_template_line('recursive-exclude e *.py') file_list.sort() - assert file_list.files == ['a.py', l('d/c.txt')] + assert file_list.files == ['a.py', ml('d/c.txt')] self.assertWarnings() def test_graft(self): - l = make_local_path + ml = make_local_path # graft file_list = FileList() - self.make_files(['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')]) + self.make_files(['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')]) file_list.process_template_line('graft d') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertNoWarnings() file_list.process_template_line('graft e') file_list.sort() - assert file_list.files == [l('d/b.py'), l('d/d/e.py')] + assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] self.assertWarnings() def test_prune(self): - l = make_local_path + ml = make_local_path # prune file_list = FileList() - file_list.files = ['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')] + file_list.files = ['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')] file_list.process_template_line('prune d') file_list.sort() - assert file_list.files == ['a.py', l('f/f.py')] + assert file_list.files == ['a.py', ml('f/f.py')] self.assertNoWarnings() file_list.process_template_line('prune e') file_list.sort() - assert file_list.files == ['a.py', l('f/f.py')] + assert file_list.files == ['a.py', ml('f/f.py')] self.assertWarnings() diff --git a/setuptools/tests/test_msvc.py b/setuptools/tests/test_msvc.py index 32d7a90..24e38ea 100644 --- a/setuptools/tests/test_msvc.py +++ b/setuptools/tests/test_msvc.py @@ -49,7 +49,8 @@ def mock_reg(hkcu=None, hklm=None): for k in hive if k.startswith(key.lower()) ) - return mock.patch.multiple(distutils.msvc9compiler.Reg, + return mock.patch.multiple( + distutils.msvc9compiler.Reg, read_keys=read_keys, read_values=read_values) @@ -61,7 +62,7 @@ class TestModulePatch: """ key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir' - key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir' + key_64 = key_32.replace(r'\microsoft', r'\wow6432node\microsoft') def test_patched(self): "Test the module is actually patched" diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index 670ccee..f937d98 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -1,6 +1,5 @@ from __future__ import absolute_import, unicode_literals -import os import sys import subprocess diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index 63b9294..ab37188 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -6,6 +6,8 @@ import distutils.errors from setuptools.extern import six from setuptools.extern.six.moves import urllib, http_client +import mock +import pytest import pkg_resources import setuptools.package_index @@ -42,7 +44,10 @@ class TestPackageIndex: hosts=('www.example.com',) ) - url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk' + url = ( + 'url:%20https://svn.plone.org/svn' + '/collective/inquant.contentmirror.plone/trunk' + ) try: v = index.open_url(url) except Exception as v: @@ -61,9 +66,9 @@ class TestPackageIndex: index.opener = _urlopen url = 'http://example.com' try: - v = index.open_url(url) - except Exception as v: - assert 'line' in str(v) + index.open_url(url) + except Exception as exc: + assert 'line' in str(exc) else: raise AssertionError('Should have raise here!') @@ -81,7 +86,11 @@ class TestPackageIndex: index.open_url(url) except distutils.errors.DistutilsError as error: msg = six.text_type(error) - assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg + assert ( + 'nonnumeric port' in msg + or 'getaddrinfo failed' in msg + or 'Name or service not known' in msg + ) return raise RuntimeError("Did not raise") @@ -223,6 +232,61 @@ class TestPackageIndex: assert dists[0].version == '' assert dists[1].version == vc + def test_download_git_with_rev(self, tmpdir): + url = 'git+https://github.example/group/project@master#egg=foo' + index = setuptools.package_index.PackageIndex() + + with mock.patch("os.system") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project@master') + expected = ( + 'git clone --quiet ' + 'https://github.example/group/project {expected_dir}' + ).format(**locals()) + first_call_args = os_system_mock.call_args_list[0][0] + assert first_call_args == (expected,) + + tmpl = '(cd {expected_dir} && git checkout --quiet master)' + expected = tmpl.format(**locals()) + assert os_system_mock.call_args_list[1][0] == (expected,) + assert result == expected_dir + + def test_download_git_no_rev(self, tmpdir): + url = 'git+https://github.example/group/project#egg=foo' + index = setuptools.package_index.PackageIndex() + + with mock.patch("os.system") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project') + expected = ( + 'git clone --quiet ' + 'https://github.example/group/project {expected_dir}' + ).format(**locals()) + os_system_mock.assert_called_once_with(expected) + + def test_download_svn(self, tmpdir): + url = 'svn+https://svn.example/project#egg=foo' + index = setuptools.package_index.PackageIndex() + + with pytest.warns(UserWarning): + with mock.patch("os.system") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project') + expected = ( + 'svn checkout -q ' + 'svn+https://svn.example/project {expected_dir}' + ).format(**locals()) + os_system_mock.assert_called_once_with(expected) + class TestContentCheckers: def test_md5(self): diff --git a/setuptools/tests/test_pep425tags.py b/setuptools/tests/test_pep425tags.py index f558a0d..30afdec 100644 --- a/setuptools/tests/test_pep425tags.py +++ b/setuptools/tests/test_pep425tags.py @@ -32,7 +32,9 @@ class TestPEP425Tags: if sys.version_info < (3, 3): config_vars.update({'Py_UNICODE_SIZE': 2}) mock_gcf = self.mock_get_config_var(**config_vars) - with patch('setuptools.pep425tags.sysconfig.get_config_var', mock_gcf): + with patch( + 'setuptools.pep425tags.sysconfig.get_config_var', + mock_gcf): abi_tag = pep425tags.get_abi_tag() assert abi_tag == base + flags diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py index d867542..99398cd 100644 --- a/setuptools/tests/test_sandbox.py +++ b/setuptools/tests/test_sandbox.py @@ -26,7 +26,8 @@ class TestSandbox: """ It should be possible to execute a setup.py with a Byte Order Mark """ - target = pkg_resources.resource_filename(__name__, + target = pkg_resources.resource_filename( + __name__, 'script-with-bom.py') namespace = types.ModuleType('namespace') setuptools.sandbox._execfile(target, vars(namespace)) diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py index 7aae3a1..5896a69 100644 --- a/setuptools/tests/test_setuptools.py +++ b/setuptools/tests/test_setuptools.py @@ -77,7 +77,8 @@ class TestDepends: from json import __version__ assert dep.get_module_constant('json', '__version__') == __version__ assert dep.get_module_constant('sys', 'version') == sys.version - assert dep.get_module_constant('setuptools.tests.test_setuptools', '__doc__') == __doc__ + assert dep.get_module_constant( + 'setuptools.tests.test_setuptools', '__doc__') == __doc__ @needs_bytecode def testRequire(self): @@ -216,7 +217,8 @@ class TestFeatures: self.req = Require('Distutils', '1.0.3', 'distutils') self.dist = makeSetup( features={ - 'foo': Feature("foo", standard=True, require_features=['baz', self.req]), + 'foo': Feature( + "foo", standard=True, require_features=['baz', self.req]), 'bar': Feature("bar", standard=True, packages=['pkg.bar'], py_modules=['bar_et'], remove=['bar.ext'], ), @@ -252,7 +254,8 @@ class TestFeatures: ('with-dwim', None, 'include DWIM') in dist.feature_options ) assert ( - ('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options + ('without-dwim', None, 'exclude DWIM (default)') + in dist.feature_options ) assert ( ('with-bar', None, 'include bar (default)') in dist.feature_options diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index 8d1425e..faaa6ba 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals from distutils import log import os -import sys import pytest diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py index cc0e8a0..320c695 100644 --- a/setuptools/tests/test_upload.py +++ b/setuptools/tests/test_upload.py @@ -4,7 +4,6 @@ import re from distutils import log from distutils.errors import DistutilsError -from distutils.version import StrictVersion import pytest diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index 7b5fea1..3d5c84b 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -1,4 +1,3 @@ -import distutils.command import glob import os import sys @@ -141,7 +140,7 @@ def test_no_missing_dependencies(bare_virtualenv): """ Quick and dirty test to ensure all external dependencies are vendored. """ - for command in ('upload',):#sorted(distutils.command.__all__): + for command in ('upload',): # sorted(distutils.command.__all__): bare_virtualenv.run(' && '.join(( 'cd {source}', 'python setup.py {command} -h', diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index 6db5fa1..e85a4a7 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -63,6 +63,7 @@ WHEEL_INFO_TESTS = ( }), ) + @pytest.mark.parametrize( ('filename', 'info'), WHEEL_INFO_TESTS, ids=[t[0] for t in WHEEL_INFO_TESTS] @@ -487,6 +488,7 @@ WHEEL_INSTALL_TESTS = ( ) + @pytest.mark.parametrize( 'params', WHEEL_INSTALL_TESTS, ids=list(params['id'] for params in WHEEL_INSTALL_TESTS), diff --git a/setuptools/tests/test_windows_wrappers.py b/setuptools/tests/test_windows_wrappers.py index d2871c0..2553394 100644 --- a/setuptools/tests/test_windows_wrappers.py +++ b/setuptools/tests/test_windows_wrappers.py @@ -97,7 +97,8 @@ class TestCLI(WrapperTester): 'arg 4\\', 'arg5 a\\\\b', ] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii')) actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(r""" @@ -134,7 +135,11 @@ class TestCLI(WrapperTester): with (tmpdir / 'foo-script.py').open('w') as f: f.write(self.prep_script(tmpl)) cmd = [str(tmpdir / 'foo.exe')] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT) stdout, stderr = proc.communicate() actual = stdout.decode('ascii').replace('\r\n', '\n') expected = textwrap.dedent(r""" @@ -172,7 +177,9 @@ class TestGUI(WrapperTester): str(tmpdir / 'test_output.txt'), 'Test Argument', ] - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.STDOUT) stdout, stderr = proc.communicate() assert not stdout assert not stderr diff --git a/setuptools/unicode_utils.py b/setuptools/unicode_utils.py index 7c63efd..3b8179a 100644 --- a/setuptools/unicode_utils.py +++ b/setuptools/unicode_utils.py @@ -1,5 +1,6 @@ import unicodedata import sys +import re from setuptools.extern import six @@ -42,3 +43,15 @@ def try_encode(string, enc): return string.encode(enc) except UnicodeEncodeError: return None + + +CODING_RE = re.compile(br'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)') + + +def detect_encoding(fp): + first_line = fp.readline() + fp.seek(0) + m = CODING_RE.match(first_line) + if m is None: + return None + return m.group(1).decode('ascii') diff --git a/setuptools/wheel.py b/setuptools/wheel.py index 95a794a..e11f0a1 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -8,10 +8,11 @@ import posixpath import re import zipfile -from pkg_resources import Distribution, PathMetadata, parse_version +import pkg_resources +import setuptools +from pkg_resources import parse_version from setuptools.extern.packaging.utils import canonicalize_name from setuptools.extern.six import PY3 -from setuptools import Distribution as SetuptoolsDistribution from setuptools import pep425tags from setuptools.command.egg_info import write_requirements @@ -79,7 +80,7 @@ class Wheel: return next((True for t in self.tags() if t in supported_tags), False) def egg_name(self): - return Distribution( + return pkg_resources.Distribution( project_name=self.project_name, version=self.version, platform=(None if self.platform == 'any' else get_platform()), ).egg_name() + '.egg' @@ -130,9 +131,9 @@ class Wheel: zf.extractall(destination_eggdir) # Convert metadata. dist_info = os.path.join(destination_eggdir, dist_info) - dist = Distribution.from_location( + dist = pkg_resources.Distribution.from_location( destination_eggdir, dist_info, - metadata=PathMetadata(destination_eggdir, dist_info), + metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info), ) # Note: Evaluate and strip markers now, @@ -155,7 +156,7 @@ class Wheel: os.path.join(egg_info, 'METADATA'), os.path.join(egg_info, 'PKG-INFO'), ) - setup_dist = SetuptoolsDistribution( + setup_dist = setuptools.Distribution( attrs=dict( install_requires=install_requires, extras_require=extras_require, diff --git a/tests/requirements.txt b/tests/requirements.txt index 0c6c3e5..f944df2 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,9 +3,11 @@ pytest-flake8; python_version!="3.4" pytest-flake8<=1.0.0; python_version=="3.4" virtualenv>=13.0.0 pytest-virtualenv>=1.2.7 -pytest>=3.0.2 +# pytest pinned to <4 due to #1638 +pytest>=3.7,<4 wheel coverage>=4.5.1 pytest-cov>=2.5.1 paver; python_version>="3.6" futures; python_version=="2.7" +pip==18.1 # Temporary workaround for #1644. diff --git a/tox.ini b/tox.ini index a2f850d..a31cb1c 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ deps=-rtests/requirements.txt # Changed from default (`python -m pip ...`) # to prevent the current working directory # from being added to `sys.path`. -install_command={envbindir}/pip install {opts} {packages} +install_command=python -c 'import sys; sys.path.remove(""); from pkg_resources import load_entry_point; load_entry_point("pip", "console_scripts", "pip")()' install {opts} {packages} # Same as above. list_dependencies_command={envbindir}/pip freeze setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname} -- 2.34.1