aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml1
-rw-r--r--CHANGES.rst74
-rw-r--r--docs/conf.py70
-rw-r--r--docs/requirements.txt7
-rw-r--r--docs/setuptools.txt9
-rw-r--r--pkg_resources/tests/test_resources.py2
-rwxr-xr-xsetup.cfg2
-rwxr-xr-xsetup.py10
-rwxr-xr-xsetuptools/command/easy_install.py24
-rwxr-xr-xsetuptools/command/egg_info.py3
-rw-r--r--setuptools/command/test.py2
-rw-r--r--setuptools/command/upload_docs.py23
-rw-r--r--setuptools/config.py1
-rw-r--r--setuptools/dist.py7
-rw-r--r--setuptools/monkey.py16
-rw-r--r--setuptools/msvc.py250
-rwxr-xr-xsetuptools/package_index.py8
-rw-r--r--setuptools/py27compat.py10
-rwxr-xr-xsetuptools/sandbox.py58
-rw-r--r--setuptools/tests/test_bdist_egg.py2
-rw-r--r--setuptools/tests/test_config.py4
-rw-r--r--setuptools/tests/test_easy_install.py2
-rw-r--r--setuptools/tests/test_integration.py65
-rw-r--r--setuptools/tests/test_manifest.py103
-rw-r--r--setuptools/tests/test_sandbox.py13
-rw-r--r--setuptools/tests/test_upload_docs.py2
-rw-r--r--tests/requirements.txt4
-rw-r--r--tox.ini6
29 files changed, 565 insertions, 214 deletions
diff --git a/.gitignore b/.gitignore
index 4d77520f..b23ade08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ setuptools.egg-info
*.swp
*~
.hg*
+.cache
diff --git a/.travis.yml b/.travis.yml
index 56d1c4ef..fa26015f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,6 +15,7 @@ matrix:
- python: 2.7
env: LC_ALL=C LC_CTYPE=C
script:
+ # need tox to get started
- pip install tox
# Output the env, to verify behavior
diff --git a/CHANGES.rst b/CHANGES.rst
index c6d5556f..1ac719df 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,77 @@
+v35.0.2
+-------
+
+* #1015: Fix test failures on Python 3.7.
+
+* #1024: Add workaround for Jython #2581 in monkey module.
+
+v35.0.1
+-------
+
+* #992: Revert change introduced in v34.4.1, now
+ considered invalid.
+
+* #1016: Revert change introduced in v35.0.0 per #1014,
+ referencing #436. The approach had unintended
+ consequences, causing sdist installs to be missing
+ files.
+
+v35.0.0
+-------
+
+* #436: In egg_info.manifest_maker, no longer read
+ the file list from the manifest file, and instead
+ re-build it on each build. In this way, files removed
+ from the specification will not linger in the manifest.
+ As a result, any files manually added to the manifest
+ will be removed on subsequent egg_info invocations.
+ No projects should be manually adding files to the
+ manifest and should instead use MANIFEST.in or SCM
+ file finders to force inclusion of files in the manifest.
+
+v34.4.1
+-------
+
+* #1008: In MSVC support, use always the last version available for Windows SDK and UCRT SDK.
+
+* #1008: In MSVC support, fix "vcruntime140.dll" returned path with Visual Studio 2017.
+
+* #992: In msvc.msvc9_query_vcvarsall, ensure the
+ returned dicts have str values and not Unicode for
+ compatibilty with os.environ.
+
+v34.4.0
+-------
+
+* #995: In MSVC support, add support for "Microsoft Visual Studio 2017" and "Microsoft Visual Studio Build Tools 2017".
+
+* #999 via #1007: Extend support for declarative package
+ config in a setup.cfg file to include the options
+ ``python_requires`` and ``py_modules``.
+
+v34.3.3
+-------
+
+* #967 (and #997): Explicitly import submodules of
+ packaging to account for environments where the imports
+ of those submodules is not implied by other behavior.
+
+v34.3.2
+-------
+
+* #993: Fix documentation upload by correcting
+ rendering of content-type in _build_multipart
+ on Python 3.
+
+v34.3.1
+-------
+
+* #988: Trap ``os.unlink`` same as ``os.remove`` in
+ ``auto_chmod`` error handler.
+
+* #983: Fixes to invalid escape sequence deprecations on
+ Python 3.6.
+
v34.3.0
-------
diff --git a/docs/conf.py b/docs/conf.py
index fe684271..f7d02303 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -18,18 +18,23 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-# Allow Sphinx to find the setup command that is imported below, as referenced above.
-import os
+import subprocess
import sys
-sys.path.append(os.path.abspath('..'))
+import os
+
-import setup as setup_script
+# hack to run the bootstrap script so that jaraco.packaging.sphinx
+# can invoke setup.py
+'READTHEDOCS' in os.environ and subprocess.check_call(
+ [sys.executable, 'bootstrap.py'],
+ cwd=os.path.join(os.path.dirname(__file__), os.path.pardir),
+)
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['rst.linker', 'sphinx.ext.autosectionlabel']
+extensions = ['jaraco.packaging.sphinx', 'rst.linker', 'sphinx.ext.autosectionlabel']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -40,19 +45,6 @@ source_suffix = '.txt'
# The master toctree document.
master_doc = 'index'
-# General information about the project.
-project = 'Setuptools'
-copyright = '2009-2014, The fellowship of the packaging'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = setup_script.setup_params['version']
-# The full version, including alpha/beta/rc tags.
-release = setup_script.setup_params['version']
-
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = []
@@ -69,13 +61,6 @@ html_theme = 'nature'
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_theme']
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-html_title = "Setuptools documentation"
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-html_short_title = "Setuptools"
-
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
html_use_smartypants = True
@@ -89,9 +74,6 @@ html_use_modindex = False
# If false, no index is generated.
html_use_index = False
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Setuptoolsdoc'
-
# -- Options for LaTeX output --------------------------------------------------
# Grouping the document tree into LaTeX files. List of tuples
@@ -109,60 +91,60 @@ link_files = {
),
replace=[
dict(
- pattern=r"(Issue )?#(?P<issue>\d+)",
- url='{GH}/pypa/setuptools/issues/{issue}',
+ pattern=r'(Issue )?#(?P<issue>\d+)',
+ url='{package_url}/issues/{issue}',
),
dict(
- pattern=r"BB Pull Request ?#(?P<bb_pull_request>\d+)",
+ pattern=r'BB Pull Request ?#(?P<bb_pull_request>\d+)',
url='{BB}/pypa/setuptools/pull-request/{bb_pull_request}',
),
dict(
- pattern=r"Distribute #(?P<distribute>\d+)",
+ pattern=r'Distribute #(?P<distribute>\d+)',
url='{BB}/tarek/distribute/issue/{distribute}',
),
dict(
- pattern=r"Buildout #(?P<buildout>\d+)",
+ pattern=r'Buildout #(?P<buildout>\d+)',
url='{GH}/buildout/buildout/issues/{buildout}',
),
dict(
- pattern=r"Old Setuptools #(?P<old_setuptools>\d+)",
+ pattern=r'Old Setuptools #(?P<old_setuptools>\d+)',
url='http://bugs.python.org/setuptools/issue{old_setuptools}',
),
dict(
- pattern=r"Jython #(?P<jython>\d+)",
+ pattern=r'Jython #(?P<jython>\d+)',
url='http://bugs.jython.org/issue{jython}',
),
dict(
- pattern=r"Python #(?P<python>\d+)",
+ pattern=r'Python #(?P<python>\d+)',
url='http://bugs.python.org/issue{python}',
),
dict(
- pattern=r"Interop #(?P<interop>\d+)",
+ pattern=r'Interop #(?P<interop>\d+)',
url='{GH}/pypa/interoperability-peps/issues/{interop}',
),
dict(
- pattern=r"Pip #(?P<pip>\d+)",
+ pattern=r'Pip #(?P<pip>\d+)',
url='{GH}/pypa/pip/issues/{pip}',
),
dict(
- pattern=r"Packaging #(?P<packaging>\d+)",
+ pattern=r'Packaging #(?P<packaging>\d+)',
url='{GH}/pypa/packaging/issues/{packaging}',
),
dict(
- pattern=r"[Pp]ackaging (?P<packaging_ver>\d+(\.\d+)+)",
+ pattern=r'[Pp]ackaging (?P<packaging_ver>\d+(\.\d+)+)',
url='{GH}/pypa/packaging/blob/{packaging_ver}/CHANGELOG.rst',
),
dict(
- pattern=r"PEP[- ](?P<pep_number>\d+)",
+ pattern=r'PEP[- ](?P<pep_number>\d+)',
url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
),
dict(
- pattern=r"setuptools_svn #(?P<setuptools_svn>\d+)",
+ pattern=r'setuptools_svn #(?P<setuptools_svn>\d+)',
url='{GH}/jaraco/setuptools_svn/issues/{setuptools_svn}',
),
dict(
- pattern=r"^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n",
- with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n",
+ pattern=r'^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n',
+ with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n',
),
],
),
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 4be41887..2138c884 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,5 @@
-rst.linker>=1.6.1
-sphinx>=1.4
+sphinx
+rst.linker>=1.9
+jaraco.packaging>=3.2
+
+setuptools>=34
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index f0da6e1d..eb9fdbd3 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -1176,6 +1176,8 @@ Distributing a ``setuptools``-based project
Using ``setuptools``... Without bundling it!
---------------------------------------------
+.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
+
Your users might not have ``setuptools`` installed on their machines, or even
if they do, it might not be the right version. Fixing this is easy; just
download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
@@ -2277,6 +2279,11 @@ New in 20.1: Added keyring support.
Configuring setup() using setup.cfg files
-----------------------------------------
+.. note:: New in 30.3.0 (8 Dec 2016).
+
+.. important:: ``setup.py`` with ``setup()`` function call is still required even
+ if your configuration resides in ``setup.cfg``.
+
``Setuptools`` allows using configuration files (usually `setup.cfg`)
to define package’s metadata and other options which are normally supplied
to ``setup()`` function.
@@ -2425,6 +2432,7 @@ zip_safe bool
setup_requires list-semi
install_requires list-semi
extras_require section
+python_requires str
entry_points file:, section
use_2to3 bool
use_2to3_fixers list-comma
@@ -2440,6 +2448,7 @@ package_dir dict
package_data section
exclude_package_data section
namespace_packages list-comma
+py_modules list-comma
======================= =====
.. note::
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
index 00ca7426..0b05343c 100644
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -593,7 +593,7 @@ class TestParsing:
[Requirement('Twis-Ted>=1.2-1')]
)
assert (
- list(parse_requirements('Twisted >=1.2, \ # more\n<2.0'))
+ list(parse_requirements('Twisted >=1.2, \\ # more\n<2.0'))
==
[Requirement('Twisted>=1.2,<2.0')]
)
diff --git a/setup.cfg b/setup.cfg
index 9fbe41ee..cb247aaf 100755
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 34.3.0
+current_version = 35.0.2
commit = True
tag = True
diff --git a/setup.py b/setup.py
index 02cafd55..ea9b7e74 100755
--- a/setup.py
+++ b/setup.py
@@ -15,8 +15,12 @@ here = os.path.dirname(__file__)
def require_metadata():
"Prevent improper installs without necessary metadata. See #659"
- if not os.path.exists('setuptools.egg-info'):
- msg = "Cannot build setuptools without metadata. Run bootstrap.py"
+ egg_info_dir = os.path.join(here, 'setuptools.egg-info')
+ if not os.path.exists(egg_info_dir):
+ msg = (
+ "Cannot build setuptools without metadata. "
+ "Run `bootstrap.py`."
+ )
raise RuntimeError(msg)
@@ -85,7 +89,7 @@ def pypi_link(pkg_filename):
setup_params = dict(
name="setuptools",
- version="34.3.0",
+ version="35.0.2",
description="Easily download, build, install, upgrade, and uninstall "
"Python packages",
author="Python Packaging Authority",
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 36e7f359..e319f77c 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -474,8 +474,7 @@ class easy_install(Command):
else:
self.pth_file = None
- PYTHONPATH = os.environ.get('PYTHONPATH', '').split(os.pathsep)
- if instdir not in map(normalize_path, filter(None, PYTHONPATH)):
+ if instdir not in map(normalize_path, _pythonpath()):
# only PYTHONPATH dirs need a site.py, so pretend it's there
self.sitepy_installed = True
elif self.multi_version and not os.path.exists(pth_file):
@@ -1348,10 +1347,21 @@ class easy_install(Command):
setattr(self, attr, val)
+def _pythonpath():
+ items = os.environ.get('PYTHONPATH', '').split(os.pathsep)
+ return filter(None, items)
+
+
def get_site_dirs():
- # return a list of 'site' dirs
- sitedirs = [_f for _f in os.environ.get('PYTHONPATH',
- '').split(os.pathsep) if _f]
+ """
+ Return a list of 'site' dirs
+ """
+
+ sitedirs = []
+
+ # start with PYTHONPATH
+ sitedirs.extend(_pythonpath())
+
prefixes = [sys.prefix]
if sys.exec_prefix != sys.prefix:
prefixes.append(sys.exec_prefix)
@@ -1675,7 +1685,7 @@ def _first_line_re():
def auto_chmod(func, arg, exc):
- if func is os.remove and os.name == 'nt':
+ if func in [os.unlink, os.remove] and os.name == 'nt':
chmod(arg, stat.S_IWRITE)
return func(arg)
et, ev, _ = sys.exc_info()
@@ -2013,7 +2023,7 @@ class ScriptWriter(object):
gui apps.
"""
- template = textwrap.dedent("""
+ template = textwrap.dedent(r"""
# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
__requires__ = %(spec)r
import re
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index a32c42f8..6c00b0b7 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -112,7 +112,8 @@ def translate_pattern(glob):
if not last_chunk:
pat += sep
- return re.compile(pat + r'\Z(?ms)')
+ pat += r'\Z'
+ return re.compile(pat, flags=re.MULTILINE|re.DOTALL)
class egg_info(Command):
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index ef0af12f..b8863fdc 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -67,7 +67,7 @@ class test(Command):
user_options = [
('test-module=', 'm', "Run 'test_suite' in specified module"),
('test-suite=', 's',
- "Test suite to run (e.g. 'some_module.test_suite')"),
+ "Run single test, case or suite (e.g. 'module.test_suite')"),
('test-runner=', 'r', "Test runner to use"),
]
diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py
index 269dc2d5..24a017cf 100644
--- a/setuptools/command/upload_docs.py
+++ b/setuptools/command/upload_docs.py
@@ -77,9 +77,8 @@ class upload_docs(upload):
self.mkpath(self.target_dir) # just in case
for root, dirs, files in os.walk(self.target_dir):
if root == self.target_dir and not files:
- raise DistutilsOptionError(
- "no files found in upload directory '%s'"
- % self.target_dir)
+ tmpl = "no files found in upload directory '%s'"
+ raise DistutilsOptionError(tmpl % self.target_dir)
for name in files:
full = os.path.join(root, name)
relative = root[len(self.target_dir):].lstrip(os.path.sep)
@@ -138,7 +137,7 @@ class upload_docs(upload):
part_groups = map(builder, data.items())
parts = itertools.chain.from_iterable(part_groups)
body_items = itertools.chain(parts, end_items)
- content_type = 'multipart/form-data; boundary=%s' % boundary
+ content_type = 'multipart/form-data; boundary=%s' % boundary.decode('ascii')
return b''.join(body_items), content_type
def upload_file(self, filename):
@@ -159,8 +158,8 @@ class upload_docs(upload):
body, ct = self._build_multipart(data)
- self.announce("Submitting documentation to %s" % (self.repository),
- log.INFO)
+ msg = "Submitting documentation to %s" % (self.repository)
+ self.announce(msg, log.INFO)
# build the Request
# We can't use urllib2 since we need to send the Basic
@@ -191,16 +190,16 @@ class upload_docs(upload):
r = conn.getresponse()
if r.status == 200:
- self.announce('Server response (%s): %s' % (r.status, r.reason),
- log.INFO)
+ msg = 'Server response (%s): %s' % (r.status, r.reason)
+ self.announce(msg, log.INFO)
elif r.status == 301:
location = r.getheader('Location')
if location is None:
location = 'https://pythonhosted.org/%s/' % meta.get_name()
- self.announce('Upload successful. Visit %s' % location,
- log.INFO)
+ msg = 'Upload successful. Visit %s' % location
+ self.announce(msg, log.INFO)
else:
- self.announce('Upload failed (%s): %s' % (r.status, r.reason),
- log.ERROR)
+ msg = 'Upload failed (%s): %s' % (r.status, r.reason)
+ self.announce(msg, log.ERROR)
if self.show_response:
print('-' * 75, r.read(), '-' * 75)
diff --git a/setuptools/config.py b/setuptools/config.py
index 0149316c..06a61d16 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -462,6 +462,7 @@ class ConfigOptionsHandler(ConfigHandler):
'tests_require': parse_list_semicolon,
'packages': self._parse_packages,
'entry_points': self._parse_file,
+ 'py_modules': parse_list,
}
def _parse_packages(self, value):
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 159464be..6b97ed33 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -16,6 +16,9 @@ from setuptools.extern import six
from setuptools.extern.six.moves import map
from pkg_resources.extern import packaging
+__import__('pkg_resources.extern.packaging.specifiers')
+__import__('pkg_resources.extern.packaging.version')
+
from setuptools.depends import Require
from setuptools import windows_support
from setuptools.monkey import get_unpatched
@@ -165,7 +168,7 @@ def check_specifier(dist, attr, value):
packaging.specifiers.SpecifierSet(value)
except packaging.specifiers.InvalidSpecifier as error:
tmpl = (
- "{attr!r} must be a string or list of strings "
+ "{attr!r} must be a string "
"containing valid version specifiers; {error}"
)
raise DistutilsSetupError(tmpl.format(attr=attr, error=error))
@@ -352,6 +355,8 @@ class Distribution(Distribution_parse_config_files, _Distribution):
_Distribution.parse_config_files(self, filenames=filenames)
parse_configuration(self, self.command_options)
+ if getattr(self, 'python_requires', None):
+ self.metadata.python_requires = self.python_requires
def parse_command_line(self):
"""Process features after parsing command line options"""
diff --git a/setuptools/monkey.py b/setuptools/monkey.py
index 94f22a56..6d3711ec 100644
--- a/setuptools/monkey.py
+++ b/setuptools/monkey.py
@@ -21,6 +21,20 @@ if you think you need this functionality.
"""
+def _get_mro(cls):
+ """
+ Returns the bases classes for cls sorted by the MRO.
+
+ Works around an issue on Jython where inspect.getmro will not return all
+ base classes if multiple classes share the same name. Instead, this
+ function will return a tuple containing the class itself, and the contents
+ of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024.
+ """
+ if platform.python_implementation() == "Jython":
+ return (cls,) + cls.__bases__
+ return inspect.getmro(cls)
+
+
def get_unpatched(item):
lookup = (
get_unpatched_class if isinstance(item, six.class_types) else
@@ -38,7 +52,7 @@ def get_unpatched_class(cls):
"""
external_bases = (
cls
- for cls in inspect.getmro(cls)
+ for cls in _get_mro(cls)
if not cls.__module__.startswith('setuptools')
)
base = next(external_bases)
diff --git a/setuptools/msvc.py b/setuptools/msvc.py
index 447ddb38..729021ac 100644
--- a/setuptools/msvc.py
+++ b/setuptools/msvc.py
@@ -4,15 +4,17 @@ Improved support for Microsoft Visual C++ compilers.
Known supported compilers:
--------------------------
Microsoft Visual C++ 9.0:
- Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64);
- Microsoft Windows SDK 7.0 (x86, x64, ia64);
+ Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
Microsoft Windows SDK 6.1 (x86, x64, ia64)
+ Microsoft Windows SDK 7.0 (x86, x64, ia64)
Microsoft Visual C++ 10.0:
Microsoft Windows SDK 7.1 (x86, x64, ia64)
Microsoft Visual C++ 14.0:
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
+ Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
+ Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
"""
import os
@@ -94,7 +96,7 @@ def msvc9_find_vcvarsall(version):
def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
"""
- Patched "distutils.msvc9compiler.query_vcvarsall" for support standalones
+ Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
compilers.
Set environment without use of "vcvarsall.bat".
@@ -102,9 +104,9 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
Known supported compilers
-------------------------
Microsoft Visual C++ 9.0:
- Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64);
- Microsoft Windows SDK 7.0 (x86, x64, ia64);
+ Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
Microsoft Windows SDK 6.1 (x86, x64, ia64)
+ Microsoft Windows SDK 7.0 (x86, x64, ia64)
Microsoft Visual C++ 10.0:
Microsoft Windows SDK 7.1 (x86, x64, ia64)
@@ -141,7 +143,7 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
def msvc14_get_vc_env(plat_spec):
"""
- Patched "distutils._msvccompiler._get_vc_env" for support standalones
+ Patched "distutils._msvccompiler._get_vc_env" for support extra
compilers.
Set environment without use of "vcvarsall.bat".
@@ -150,6 +152,8 @@ def msvc14_get_vc_env(plat_spec):
-------------------------
Microsoft Visual C++ 14.0:
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
+ Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
+ Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
Parameters
----------
@@ -272,7 +276,7 @@ class PlatformInfo:
)
def target_dir(self, hidex86=False, x64=False):
- """
+ r"""
Target platform specific subfolder.
Parameters
@@ -294,7 +298,7 @@ class PlatformInfo:
)
def cross_dir(self, forcex86=False):
- """
+ r"""
Cross platform specific subfolder.
Parameters
@@ -411,7 +415,7 @@ class RegistryInfo:
------
str: value
"""
- node64 = '' if self.pi.current_is_x86() or x86 else r'\Wow6432Node'
+ node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
return os.path.join('Software', node64, 'Microsoft', key)
def lookup(self, key, name):
@@ -470,25 +474,26 @@ class SystemInfo:
def __init__(self, registry_info, vc_ver=None):
self.ri = registry_info
self.pi = self.ri.pi
- if vc_ver:
- self.vc_ver = vc_ver
- else:
- try:
- self.vc_ver = self.find_available_vc_vers()[-1]
- except IndexError:
- err = 'No Microsoft Visual C++ version found'
- raise distutils.errors.DistutilsPlatformError(err)
+ self.vc_ver = vc_ver or self._find_latest_available_vc_ver()
+
+ def _find_latest_available_vc_ver(self):
+ try:
+ return self.find_available_vc_vers()[-1]
+ except IndexError:
+ err = 'No Microsoft Visual C++ version found'
+ raise distutils.errors.DistutilsPlatformError(err)
def find_available_vc_vers(self):
"""
Find all available Microsoft Visual C++ versions.
"""
- vckeys = (self.ri.vc, self.ri.vc_for_python)
+ ms = self.ri.microsoft
+ vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
vc_vers = []
for hkey in self.ri.HKEYS:
for key in vckeys:
try:
- bkey = winreg.OpenKey(hkey, key, 0, winreg.KEY_READ)
+ bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ)
except (OSError, IOError):
continue
subkeys, values, _ = winreg.QueryInfoKey(bkey)
@@ -525,9 +530,9 @@ class SystemInfo:
"""
Microsoft Visual C++ directory.
"""
- # Default path
- default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver
- guess_vc = os.path.join(self.ProgramFilesx86, default)
+ self.VSInstallDir
+
+ guess_vc = self._guess_vc() or self._guess_vc_legacy()
# Try to get "VC++ for Python" path from registry as default path
reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
@@ -543,12 +548,34 @@ class SystemInfo:
return path
+ def _guess_vc(self):
+ """
+ Locate Visual C for 2017
+ """
+ if self.vc_ver <= 14.0:
+ return
+
+ default = r'VC\Tools\MSVC'
+ guess_vc = os.path.join(self.VSInstallDir, default)
+ # Subdir with VC exact version as name
+ try:
+ vc_exact_ver = os.listdir(guess_vc)[-1]
+ return os.path.join(guess_vc, vc_exact_ver)
+ except (OSError, IOError, IndexError):
+ pass
+
+ def _guess_vc_legacy(self):
+ """
+ Locate Visual C for versions prior to 2017
+ """
+ default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver
+ return os.path.join(self.ProgramFilesx86, default)
+
@property
def WindowsSdkVersion(self):
"""
- Microsoft Windows SDK versions.
+ Microsoft Windows SDK versions for specified MSVC++ version.
"""
- # Set Windows SDK versions for specified MSVC++ version
if self.vc_ver <= 9.0:
return ('7.0', '6.1', '6.0a')
elif self.vc_ver == 10.0:
@@ -561,6 +588,14 @@ class SystemInfo:
return ('10.0', '8.1')
@property
+ def WindowsSdkLastVersion(self):
+ """
+ Microsoft Windows SDK last version
+ """
+ return self._use_last_dir_name(os.path.join(
+ self.WindowsSdkDir, 'lib'))
+
+ @property
def WindowsSdkDir(self):
"""
Microsoft Windows SDK directory.
@@ -658,6 +693,14 @@ class SystemInfo:
return sdkdir or ''
@property
+ def UniversalCRTSdkLastVersion(self):
+ """
+ Microsoft Universal C Runtime SDK last version
+ """
+ return self._use_last_dir_name(os.path.join(
+ self.UniversalCRTSdkDir, 'lib'))
+
+ @property
def NetFxSdkVersion(self):
"""
Microsoft .NET Framework SDK versions.
@@ -716,7 +759,7 @@ class SystemInfo:
"""
return self._find_dot_net_versions(64)
- def _find_dot_net_versions(self, bits=32):
+ def _find_dot_net_versions(self, bits):
"""
Find Microsoft .NET Framework versions.
@@ -725,8 +768,10 @@ class SystemInfo:
bits: int
Platform number of bits: 32 or 64.
"""
- # Find actual .NET version
- ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) or ''
+ # Find actual .NET version in registry
+ reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits)
+ dot_net_dir = getattr(self, 'FrameworkDir%d' % bits)
+ ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
# Set .NET versions for specified MSVC++ version
if self.vc_ver >= 12.0:
@@ -740,6 +785,25 @@ class SystemInfo:
frameworkver = ('v3.0', 'v2.0.50727')
return frameworkver
+ def _use_last_dir_name(self, path, prefix=''):
+ """
+ Return name of the last dir in path or '' if no dir found.
+
+ Parameters
+ ----------
+ path: str
+ Use dirs in this path
+ prefix: str
+ Use only dirs startings by this prefix
+ """
+ matching_dirs = (
+ dir_name
+ for dir_name in reversed(os.listdir(path))
+ if os.path.isdir(os.path.join(path, dir_name)) and
+ dir_name.startswith(prefix)
+ )
+ return next(matching_dirs, None) or ''
+
class EnvironmentInfo:
"""
@@ -765,15 +829,14 @@ class EnvironmentInfo:
# Variables and properties in this class use originals CamelCase variables
# names from Microsoft source files for more easy comparaison.
- def __init__(self, arch, vc_ver=None, vc_min_ver=None):
+ def __init__(self, arch, vc_ver=None, vc_min_ver=0):
self.pi = PlatformInfo(arch)
self.ri = RegistryInfo(self.pi)
self.si = SystemInfo(self.ri, vc_ver)
- if vc_min_ver:
- if self.vc_ver < vc_min_ver:
- err = 'No suitable Microsoft Visual C++ version found'
- raise distutils.errors.DistutilsPlatformError(err)
+ if self.vc_ver < vc_min_ver:
+ err = 'No suitable Microsoft Visual C++ version found'
+ raise distutils.errors.DistutilsPlatformError(err)
@property
def vc_ver(self):
@@ -810,7 +873,10 @@ class EnvironmentInfo:
"""
Microsoft Visual C++ & Microsoft Foundation Class Libraries
"""
- arch_subdir = self.pi.target_dir(hidex86=True)
+ if self.vc_ver >= 15.0:
+ arch_subdir = self.pi.target_dir(x64=True)
+ else:
+ arch_subdir = self.pi.target_dir(hidex86=True)
paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir]
if self.vc_ver >= 14.0:
@@ -840,10 +906,20 @@ class EnvironmentInfo:
if arch_subdir:
tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
- if self.vc_ver >= 14.0:
+ if self.vc_ver == 14.0:
path = 'Bin%s' % self.pi.current_dir(hidex86=True)
tools += [os.path.join(si.VCInstallDir, path)]
+ elif self.vc_ver >= 15.0:
+ host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else
+ r'bin\HostX64%s')
+ tools += [os.path.join(
+ si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))]
+
+ if self.pi.current_cpu != self.pi.target_cpu:
+ tools += [os.path.join(
+ si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))]
+
else:
tools += [os.path.join(si.VCInstallDir, 'Bin')]
@@ -861,8 +937,8 @@ class EnvironmentInfo:
else:
arch_subdir = self.pi.target_dir(x64=True)
lib = os.path.join(self.si.WindowsSdkDir, 'lib')
- libver = self._get_content_dirname(lib)
- return [os.path.join(lib, '%sum%s' % (libver, arch_subdir))]
+ libver = self._sdk_subdir
+ return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))]
@property
def OSIncludes(self):
@@ -876,7 +952,7 @@ class EnvironmentInfo:
else:
if self.vc_ver >= 14.0:
- sdkver = self._get_content_dirname(include)
+ sdkver = self._sdk_subdir
else:
sdkver = ''
return [os.path.join(include, '%sshared' % sdkver),
@@ -933,13 +1009,20 @@ class EnvironmentInfo:
"""
Microsoft Windows SDK Tools
"""
- bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86'
- tools = [os.path.join(self.si.WindowsSdkDir, bin_dir)]
+ return list(self._sdk_tools())
+
+ def _sdk_tools(self):
+ """
+ Microsoft Windows SDK Tools paths generator
+ """
+ if self.vc_ver < 15.0:
+ bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86'
+ yield os.path.join(self.si.WindowsSdkDir, bin_dir)
if not self.pi.current_is_x86():
arch_subdir = self.pi.current_dir(x64=True)
path = 'Bin%s' % arch_subdir
- tools += [os.path.join(self.si.WindowsSdkDir, path)]
+ yield os.path.join(self.si.WindowsSdkDir, path)
if self.vc_ver == 10.0 or self.vc_ver == 11.0:
if self.pi.target_is_x86():
@@ -947,12 +1030,24 @@ class EnvironmentInfo:
else:
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir
- tools += [os.path.join(self.si.WindowsSdkDir, path)]
+ yield os.path.join(self.si.WindowsSdkDir, path)
+
+ elif self.vc_ver >= 15.0:
+ path = os.path.join(self.si.WindowsSdkDir, 'Bin')
+ arch_subdir = self.pi.current_dir(x64=True)
+ sdkver = self.si.WindowsSdkLastVersion
+ yield os.path.join(path, '%s%s' % (sdkver, arch_subdir))
if self.si.WindowsSDKExecutablePath:
- tools += [self.si.WindowsSDKExecutablePath]
+ yield self.si.WindowsSDKExecutablePath
- return tools
+ @property
+ def _sdk_subdir(self):
+ """
+ Microsoft Windows SDK version subdir
+ """
+ ucrtver = self.si.WindowsSdkLastVersion
+ return ('%s\\' % ucrtver) if ucrtver else ''
@property
def SdkSetup(self):
@@ -1023,10 +1118,21 @@ class EnvironmentInfo:
"""
if self.vc_ver < 12.0:
return []
+ elif self.vc_ver < 15.0:
+ base_path = self.si.ProgramFilesx86
+ arch_subdir = self.pi.current_dir(hidex86=True)
+ else:
+ base_path = self.si.VSInstallDir
+ arch_subdir = ''
- arch_subdir = self.pi.current_dir(hidex86=True)
path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir)
- return [os.path.join(self.si.ProgramFilesx86, path)]
+ build = [os.path.join(base_path, path)]
+
+ if self.vc_ver >= 15.0:
+ # Add Roslyn C# & Visual Basic Compiler
+ build += [os.path.join(base_path, path, 'Roslyn')]
+
+ return build
@property
def HTMLHelpWorkshop(self):
@@ -1041,27 +1147,34 @@ class EnvironmentInfo:
@property
def UCRTLibraries(self):
"""
- Microsoft Universal CRT Libraries
+ Microsoft Universal C Runtime SDK Libraries
"""
if self.vc_ver < 14.0:
return []
arch_subdir = self.pi.target_dir(x64=True)
lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib')
- ucrtver = self._get_content_dirname(lib)
+ ucrtver = self._ucrt_subdir
return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
@property
def UCRTIncludes(self):
"""
- Microsoft Universal CRT Include
+ Microsoft Universal C Runtime SDK Include
"""
if self.vc_ver < 14.0:
return []
include = os.path.join(self.si.UniversalCRTSdkDir, 'include')
- ucrtver = self._get_content_dirname(include)
- return [os.path.join(include, '%sucrt' % ucrtver)]
+ return [os.path.join(include, '%sucrt' % self._ucrt_subdir)]
+
+ @property
+ def _ucrt_subdir(self):
+ """
+ Microsoft Universal C Runtime SDK version subdir
+ """
+ ucrtver = self.si.UniversalCRTSdkLastVersion
+ return ('%s\\' % ucrtver) if ucrtver else ''
@property
def FSharp(self):
@@ -1079,9 +1192,18 @@ class EnvironmentInfo:
Microsoft Visual C++ runtime redistribuable dll
"""
arch_subdir = self.pi.target_dir(x64=True)
- vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
- vcruntime = vcruntime % (arch_subdir, self.vc_ver, self.vc_ver)
- return os.path.join(self.si.VCInstallDir, vcruntime)
+ if self.vc_ver < 15:
+ redist_path = self.si.VCInstallDir
+ vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
+ else:
+ redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist')
+ vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
+
+ # Visual Studio 2017 is still Visual C++ 14.0
+ dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver
+
+ vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver)
+ return os.path.join(redist_path, vcruntime)
def return_env(self, exists=True):
"""
@@ -1169,25 +1291,3 @@ class EnvironmentInfo:
if k not in seen:
seen_add(k)
yield element
-
- def _get_content_dirname(self, path):
- """
- Return name of the first dir in path or '' if no dir found.
-
- Parameters
- ----------
- path: str
- Path where search dir.
-
- Return
- ------
- foldername: str
- "name\" or ""
- """
- try:
- name = os.listdir(path)
- if name:
- return '%s\\' % name[0]
- return ''
- except (OSError, IOError):
- return ''
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index faef5377..2acc817a 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -34,8 +34,8 @@ EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$')
HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I)
# this is here to fix emacs' cruddy broken syntax highlighting
PYPI_MD5 = re.compile(
- '<a href="([^"#]+)">([^<]+)</a>\n\s+\\(<a (?:title="MD5 hash"\n\s+)'
- 'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)'
+ '<a href="([^"#]+)">([^<]+)</a>\n\\s+\\(<a (?:title="MD5 hash"\n\\s+)'
+ 'href="[^?]+\\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)'
)
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match
EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
@@ -161,7 +161,7 @@ def interpret_distro_name(
# versions in distribution archive names (sdist and bdist).
parts = basename.split('-')
- if not py_version and any(re.match('py\d\.\d$', p) for p in parts[2:]):
+ if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]):
# it is a bdist_dumb, not an sdist -- bail out
return
@@ -205,7 +205,7 @@ def unique_values(func):
return wrapper
-REL = re.compile("""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
+REL = re.compile(r"""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
# this line is here to fix emacs' cruddy broken syntax highlighting
diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py
index f0a80a8e..701283c8 100644
--- a/setuptools/py27compat.py
+++ b/setuptools/py27compat.py
@@ -2,9 +2,10 @@
Compatibility Support for Python 2.7 and earlier
"""
-import sys
import platform
+import six
+
def get_all_headers(message, key):
"""
@@ -13,15 +14,14 @@ def get_all_headers(message, key):
return message.get_all(key)
-if sys.version_info < (3,):
-
+if six.PY2:
def get_all_headers(message, key):
return message.getheaders(key)
linux_py2_ascii = (
- platform.system() == 'Linux' and
- sys.version_info < (3,)
+ platform.system() == 'Linux' and
+ six.PY2
)
rmtree_safe = str if linux_py2_ascii else lambda x: x
diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py
index 817a3afa..f99c13c4 100755
--- a/setuptools/sandbox.py
+++ b/setuptools/sandbox.py
@@ -7,6 +7,7 @@ import itertools
import re
import contextlib
import pickle
+import textwrap
from setuptools.extern import six
from setuptools.extern.six.moves import builtins, map
@@ -215,7 +216,7 @@ def _needs_hiding(mod_name):
>>> _needs_hiding('Cython')
True
"""
- pattern = re.compile('(setuptools|pkg_resources|distutils|Cython)(\.|$)')
+ pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)')
return bool(pattern.match(mod_name))
@@ -248,11 +249,9 @@ def run_setup(setup_script, args):
setup_script.encode(sys.getfilesystemencoding())
)
- def runner():
+ with DirectorySandbox(setup_dir):
ns = dict(__file__=dunder_file, __name__='__main__')
_execfile(setup_script, ns)
-
- DirectorySandbox(setup_dir).run(runner)
except SystemExit as v:
if v.args and v.args[0]:
raise
@@ -274,21 +273,24 @@ class AbstractSandbox:
for name in self._attrs:
setattr(os, name, getattr(source, name))
+ def __enter__(self):
+ self._copy(self)
+ if _file:
+ builtins.file = self._file
+ builtins.open = self._open
+ self._active = True
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self._active = False
+ if _file:
+ builtins.file = _file
+ builtins.open = _open
+ self._copy(_os)
+
def run(self, func):
"""Run 'func' under os sandboxing"""
- try:
- self._copy(self)
- if _file:
- builtins.file = self._file
- builtins.open = self._open
- self._active = True
+ with self:
return func()
- finally:
- self._active = False
- if _file:
- builtins.file = _file
- builtins.open = _open
- self._copy(_os)
def _mk_dual_path_wrapper(name):
original = getattr(_os, name)
@@ -391,7 +393,7 @@ class DirectorySandbox(AbstractSandbox):
_exception_patterns = [
# Allow lib2to3 to attempt to save a pickled grammar object (#121)
- '.*lib2to3.*\.pickle$',
+ r'.*lib2to3.*\.pickle$',
]
"exempt writing to paths that match the pattern"
@@ -476,16 +478,18 @@ WRITE_FLAGS = functools.reduce(
class SandboxViolation(DistutilsError):
"""A setup script attempted to modify the filesystem outside the sandbox"""
- def __str__(self):
- return """SandboxViolation: %s%r %s
-
-The package setup script has attempted to modify files on your system
-that are not within the EasyInstall build area, and has been aborted.
+ tmpl = textwrap.dedent("""
+ SandboxViolation: {cmd}{args!r} {kwargs}
-This package cannot be safely installed by EasyInstall, and may not
-support alternate installation locations even if you run its setup
-script by hand. Please inform the package's author and the EasyInstall
-maintainers to find out if a fix or workaround is available.""" % self.args
+ The package setup script has attempted to modify files on your system
+ that are not within the EasyInstall build area, and has been aborted.
+ This package cannot be safely installed by EasyInstall, and may not
+ support alternate installation locations even if you run its setup
+ script by hand. Please inform the package's author and the EasyInstall
+ maintainers to find out if a fix or workaround is available.
+ """).lstrip()
-#
+ def __str__(self):
+ cmd, args, kwargs = self.args
+ return self.tmpl.format(**locals())
diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
index 5aabf404..d24aa366 100644
--- a/setuptools/tests/test_bdist_egg.py
+++ b/setuptools/tests/test_bdist_egg.py
@@ -41,4 +41,4 @@ class Test:
# let's see if we got our egg link at the right place
[content] = os.listdir('dist')
- assert re.match('foo-0.0.0-py[23].\d.egg$', content)
+ assert re.match(r'foo-0.0.0-py[23].\d.egg$', content)
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 799fb165..8bd2a494 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -312,6 +312,8 @@ class TestOptions:
'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'
+ 'python_requires = >=1.0, !=2.8\n'
+ 'py_modules = module1, module2\n'
)
with get_dist(tmpdir) as dist:
assert dist.zip_safe
@@ -340,6 +342,8 @@ class TestOptions:
'there'
])
assert dist.tests_require == ['mock==0.7.2', 'pytest']
+ assert dist.python_requires == '>=1.0, !=2.8'
+ assert dist.py_modules == ['module1', 'module2']
def test_multiline(self, tmpdir):
fake_env(
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index 52db16f6..2d9682a9 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -65,7 +65,7 @@ class TestEasyInstallTest:
def test_get_script_args(self):
header = ei.CommandSpec.best().from_environment().as_header()
- expected = header + DALS("""
+ expected = header + DALS(r"""
# EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name'
__requires__ = 'spec'
import re
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 78fb0627..3a9a6c50 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -98,3 +98,68 @@ def test_pbr(install_context):
def test_python_novaclient(install_context):
_install_one('python-novaclient', install_context,
'novaclient', 'base.py')
+
+
+def test_pyuri(install_context):
+ """
+ Install the pyuri package (version 0.3.1 at the time of writing).
+
+ This is also a regression test for issue #1016.
+ """
+ _install_one('pyuri', install_context, 'pyuri', 'uri.py')
+
+ pyuri = install_context.installed_projects['pyuri']
+
+ # The package data should be installed.
+ 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']
+@pytest.mark.parametrize("build_dep", build_deps)
+@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
+ setuptools.
+ """
+ if 'pyparsing' in build_dep:
+ pytest.xfail(reason="Project imports setuptools unconditionally")
+ build_target = tmpdir_factory.mktemp('source')
+ build_dir = download_and_extract(request, build_dep, build_target)
+ install_target = tmpdir_factory.mktemp('target')
+ output = install(build_dir, install_target)
+ for line in output.splitlines():
+ match = re.search('Unknown distribution option: (.*)', line)
+ allowed_unknowns = [
+ 'test_suite',
+ 'tests_require',
+ 'install_requires',
+ ]
+ assert not match or match.group(1).strip('"\'') in allowed_unknowns
+
+
+def install(pkg_dir, install_dir):
+ with open(os.path.join(pkg_dir, 'setuptools.py'), 'w') as breaker:
+ 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)
+ return output.decode('utf-8')
+
+
+def download_and_extract(request, req, target):
+ 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))
+ opener = zipfile.ZipFile if filename.endswith('.zip') else tarfile.open
+ with opener(filename) as archive:
+ archive.extractall(target)
+ return os.path.join(target, os.listdir(target)[0])
diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py
index f17cf6a6..ab9b3469 100644
--- a/setuptools/tests/test_manifest.py
+++ b/setuptools/tests/test_manifest.py
@@ -6,6 +6,7 @@ import os
import shutil
import sys
import tempfile
+import itertools
from distutils import log
from distutils.errors import DistutilsTemplateError
@@ -65,32 +66,94 @@ default_files = frozenset(map(make_local_path, [
]))
-def get_pattern(glob):
- return translate_pattern(make_local_path(glob)).pattern
-
-
-def test_translated_pattern_test():
- l = make_local_path
- assert get_pattern('foo') == r'foo\Z(?ms)'
- assert get_pattern(l('foo/bar')) == l(r'foo\/bar\Z(?ms)')
+translate_specs = [
+ ('foo', ['foo'], ['bar', 'foobar']),
+ ('foo/bar', ['foo/bar'], ['foo/bar/baz', './foo/bar', 'foo']),
# Glob matching
- assert get_pattern('*.txt') == l(r'[^\/]*\.txt\Z(?ms)')
- assert get_pattern('dir/*.txt') == l(r'dir\/[^\/]*\.txt\Z(?ms)')
- assert get_pattern('*/*.py') == l(r'[^\/]*\/[^\/]*\.py\Z(?ms)')
- assert get_pattern('docs/page-?.txt') \
- == l(r'docs\/page\-[^\/]\.txt\Z(?ms)')
+ ('*.txt', ['foo.txt', 'bar.txt'], ['foo/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']),
# Globstars change what they mean depending upon where they are
- assert get_pattern(l('foo/**/bar')) == l(r'foo\/(?:[^\/]+\/)*bar\Z(?ms)')
- assert get_pattern(l('foo/**')) == l(r'foo\/.*\Z(?ms)')
- assert get_pattern(l('**')) == r'.*\Z(?ms)'
+ (
+ 'foo/**/bar',
+ ['foo/bing/bar', 'foo/bing/bang/bar', 'foo/bar'],
+ ['foo/abar'],
+ ),
+ (
+ 'foo/**',
+ ['foo/bar/bing.py', 'foo/x'],
+ ['/foo/x'],
+ ),
+ (
+ '**',
+ ['x', 'abc/xyz', '@nything'],
+ [],
+ ),
# Character classes
- assert get_pattern('pre[one]post') == r'pre[one]post\Z(?ms)'
- assert get_pattern('hello[!one]world') == r'hello[^one]world\Z(?ms)'
- assert get_pattern('[]one].txt') == r'[\]one]\.txt\Z(?ms)'
- assert get_pattern('foo[!]one]bar') == r'foo[^\]one]bar\Z(?ms)'
+ (
+ 'pre[one]post',
+ ['preopost', 'prenpost', 'preepost'],
+ ['prepost', 'preonepost'],
+ ),
+
+ (
+ 'hello[!one]world',
+ ['helloxworld', 'helloyworld'],
+ ['hellooworld', 'helloworld', 'hellooneworld'],
+ ),
+
+ (
+ '[]one].txt',
+ ['o.txt', '].txt', 'e.txt'],
+ ['one].txt'],
+ ),
+
+ (
+ 'foo[!]one]bar',
+ ['fooybar'],
+ ['foo]bar', 'fooobar', 'fooebar'],
+ ),
+
+]
+"""
+A spec of inputs for 'translate_pattern' and matches and mismatches
+for that input.
+"""
+
+match_params = itertools.chain.from_iterable(
+ zip(itertools.repeat(pattern), matches)
+ for pattern, matches, mismatches in translate_specs
+)
+
+
+@pytest.fixture(params=match_params)
+def pattern_match(request):
+ return map(make_local_path, request.param)
+
+
+mismatch_params = itertools.chain.from_iterable(
+ zip(itertools.repeat(pattern), mismatches)
+ for pattern, matches, mismatches in translate_specs
+)
+
+
+@pytest.fixture(params=mismatch_params)
+def pattern_mismatch(request):
+ return map(make_local_path, request.param)
+
+
+def test_translated_pattern_match(pattern_match):
+ pattern, target = pattern_match
+ assert translate_pattern(pattern).match(target)
+
+
+def test_translated_pattern_mismatch(pattern_mismatch):
+ pattern, target = pattern_mismatch
+ assert not translate_pattern(pattern).match(target)
class TempDirTestCase(object):
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index 929f0a5b..a3f1206d 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -7,13 +7,12 @@ import pytest
import pkg_resources
import setuptools.sandbox
-from setuptools.sandbox import DirectorySandbox
class TestSandbox:
def test_devnull(self, tmpdir):
- sandbox = DirectorySandbox(str(tmpdir))
- sandbox.run(self._file_writer(os.devnull))
+ with setuptools.sandbox.DirectorySandbox(str(tmpdir)):
+ self._file_writer(os.devnull)
@staticmethod
def _file_writer(path):
@@ -116,13 +115,17 @@ class TestExceptionSaver:
with open('/etc/foo', 'w'):
pass
- sandbox = DirectorySandbox(str(tmpdir))
with pytest.raises(setuptools.sandbox.SandboxViolation) as caught:
with setuptools.sandbox.save_modules():
setuptools.sandbox.hide_setuptools()
- sandbox.run(write_file)
+ with setuptools.sandbox.DirectorySandbox(str(tmpdir)):
+ write_file()
cmd, args, kwargs = caught.value.args
assert cmd == 'open'
assert args == ('/etc/foo', 'w')
assert kwargs == {}
+
+ msg = str(caught.value)
+ assert 'open' in msg
+ assert "('/etc/foo', 'w')" in msg
diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py
index 5d50bb0b..a26e32a6 100644
--- a/setuptools/tests/test_upload_docs.py
+++ b/setuptools/tests/test_upload_docs.py
@@ -64,6 +64,8 @@ class TestUploadDocsTest:
)
body, content_type = upload_docs._build_multipart(data)
assert 'form-data' in content_type
+ assert "b'" not in content_type
+ assert 'b"' not in content_type
assert isinstance(body, bytes)
assert b'foo' in body
assert b'content' in body
diff --git a/tests/requirements.txt b/tests/requirements.txt
index d07e9cde..6e2e78e2 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,4 +1,4 @@
pytest-flake8
pytest>=3.0.2
-setuptools[ssl]
-backports.unittest_mock>=1.2
+# pinned to 1.2 as temporary workaround for #1038
+backports.unittest_mock>=1.2,<1.3
diff --git a/tox.ini b/tox.ini
index cae9c745..c3ea462b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,3 +1,9 @@
+# Note: Run "python bootstrap.py" before running Tox, to generate metadata.
+#
+# To run Tox against all supported Python interpreters, you can set:
+#
+# export TOXENV='py2{6,7},py3{3,4,5,6},pypy'
+
[testenv]
deps=-rtests/requirements.txt
passenv=APPDATA USERPROFILE HOMEDRIVE HOMEPATH windir APPVEYOR