Commit fcdf12ee authored by Jason R. Coombs's avatar Jason R. Coombs

Merge branch 'master' into feature/re-vendor-sadface

parents 3d0cc355 4dc2c76b
...@@ -13,3 +13,4 @@ setuptools.egg-info ...@@ -13,3 +13,4 @@ setuptools.egg-info
*.swp *.swp
*~ *~
.hg* .hg*
.cache
...@@ -15,6 +15,7 @@ matrix: ...@@ -15,6 +15,7 @@ matrix:
- python: 2.7 - python: 2.7
env: LC_ALL=C LC_CTYPE=C env: LC_ALL=C LC_CTYPE=C
script: script:
# need tox to get started
- pip install tox - pip install tox
# Output the env, to verify behavior # Output the env, to verify behavior
......
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 v34.3.0
------- -------
......
...@@ -18,18 +18,23 @@ ...@@ -18,18 +18,23 @@
# add these directories to sys.path here. If the directory is relative to the # 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. # 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 subprocess
import os
import sys 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 ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # 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. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']
...@@ -40,19 +45,6 @@ source_suffix = '.txt' ...@@ -40,19 +45,6 @@ source_suffix = '.txt'
# The master toctree document. # The master toctree document.
master_doc = 'index' 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 # List of directories, relative to source directory, that shouldn't be searched
# for source files. # for source files.
exclude_trees = [] exclude_trees = []
...@@ -69,13 +61,6 @@ html_theme = 'nature' ...@@ -69,13 +61,6 @@ html_theme = 'nature'
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_theme'] 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 # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
html_use_smartypants = True html_use_smartypants = True
...@@ -89,9 +74,6 @@ html_use_modindex = False ...@@ -89,9 +74,6 @@ html_use_modindex = False
# If false, no index is generated. # If false, no index is generated.
html_use_index = False html_use_index = False
# Output file base name for HTML help builder.
htmlhelp_basename = 'Setuptoolsdoc'
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
...@@ -109,60 +91,60 @@ link_files = { ...@@ -109,60 +91,60 @@ link_files = {
), ),
replace=[ replace=[
dict( dict(
pattern=r"(Issue )?#(?P<issue>\d+)", pattern=r'(Issue )?#(?P<issue>\d+)',
url='{GH}/pypa/setuptools/issues/{issue}', url='{package_url}/issues/{issue}',
), ),
dict( 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}', url='{BB}/pypa/setuptools/pull-request/{bb_pull_request}',
), ),
dict( dict(
pattern=r"Distribute #(?P<distribute>\d+)", pattern=r'Distribute #(?P<distribute>\d+)',
url='{BB}/tarek/distribute/issue/{distribute}', url='{BB}/tarek/distribute/issue/{distribute}',
), ),
dict( dict(
pattern=r"Buildout #(?P<buildout>\d+)", pattern=r'Buildout #(?P<buildout>\d+)',
url='{GH}/buildout/buildout/issues/{buildout}', url='{GH}/buildout/buildout/issues/{buildout}',
), ),
dict( 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}', url='http://bugs.python.org/setuptools/issue{old_setuptools}',
), ),
dict( dict(
pattern=r"Jython #(?P<jython>\d+)", pattern=r'Jython #(?P<jython>\d+)',
url='http://bugs.jython.org/issue{jython}', url='http://bugs.jython.org/issue{jython}',
), ),
dict( dict(
pattern=r"Python #(?P<python>\d+)", pattern=r'Python #(?P<python>\d+)',
url='http://bugs.python.org/issue{python}', url='http://bugs.python.org/issue{python}',
), ),
dict( dict(
pattern=r"Interop #(?P<interop>\d+)", pattern=r'Interop #(?P<interop>\d+)',
url='{GH}/pypa/interoperability-peps/issues/{interop}', url='{GH}/pypa/interoperability-peps/issues/{interop}',
), ),
dict( dict(
pattern=r"Pip #(?P<pip>\d+)", pattern=r'Pip #(?P<pip>\d+)',
url='{GH}/pypa/pip/issues/{pip}', url='{GH}/pypa/pip/issues/{pip}',
), ),
dict( dict(
pattern=r"Packaging #(?P<packaging>\d+)", pattern=r'Packaging #(?P<packaging>\d+)',
url='{GH}/pypa/packaging/issues/{packaging}', url='{GH}/pypa/packaging/issues/{packaging}',
), ),
dict( 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', url='{GH}/pypa/packaging/blob/{packaging_ver}/CHANGELOG.rst',
), ),
dict( 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}/', url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
), ),
dict( 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}', url='{GH}/jaraco/setuptools_svn/issues/{setuptools_svn}',
), ),
dict( dict(
pattern=r"^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n", pattern=r'^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n',
with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n", with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n',
), ),
], ],
), ),
......
rst.linker>=1.6.1 sphinx
sphinx>=1.4 rst.linker>=1.9
jaraco.packaging>=3.2
setuptools>=34
...@@ -1176,6 +1176,8 @@ Distributing a ``setuptools``-based project ...@@ -1176,6 +1176,8 @@ Distributing a ``setuptools``-based project
Using ``setuptools``... Without bundling it! 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 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 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`` 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. ...@@ -2277,6 +2279,11 @@ New in 20.1: Added keyring support.
Configuring setup() using setup.cfg files 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`) ``Setuptools`` allows using configuration files (usually `setup.cfg`)
to define package’s metadata and other options which are normally supplied to define package’s metadata and other options which are normally supplied
to ``setup()`` function. to ``setup()`` function.
...@@ -2425,6 +2432,7 @@ zip_safe bool ...@@ -2425,6 +2432,7 @@ zip_safe bool
setup_requires list-semi setup_requires list-semi
install_requires list-semi install_requires list-semi
extras_require section extras_require section
python_requires str
entry_points file:, section entry_points file:, section
use_2to3 bool use_2to3 bool
use_2to3_fixers list-comma use_2to3_fixers list-comma
...@@ -2440,6 +2448,7 @@ package_dir dict ...@@ -2440,6 +2448,7 @@ package_dir dict
package_data section package_data section
exclude_package_data section exclude_package_data section
namespace_packages list-comma namespace_packages list-comma
py_modules list-comma
======================= ===== ======================= =====
.. note:: .. note::
......
...@@ -593,7 +593,7 @@ class TestParsing: ...@@ -593,7 +593,7 @@ class TestParsing:
[Requirement('Twis-Ted>=1.2-1')] [Requirement('Twis-Ted>=1.2-1')]
) )
assert ( 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')] [Requirement('Twisted>=1.2,<2.0')]
) )
......
[bumpversion] [bumpversion]
current_version = 34.3.0 current_version = 35.0.2
commit = True commit = True
tag = True tag = True
......
...@@ -15,8 +15,12 @@ here = os.path.dirname(__file__) ...@@ -15,8 +15,12 @@ here = os.path.dirname(__file__)
def require_metadata(): def require_metadata():
"Prevent improper installs without necessary metadata. See #659" "Prevent improper installs without necessary metadata. See #659"
if not os.path.exists('setuptools.egg-info'): egg_info_dir = os.path.join(here, 'setuptools.egg-info')
msg = "Cannot build setuptools without metadata. Run bootstrap.py" if not os.path.exists(egg_info_dir):
msg = (
"Cannot build setuptools without metadata. "
"Run `bootstrap.py`."
)
raise RuntimeError(msg) raise RuntimeError(msg)
...@@ -85,7 +89,7 @@ def pypi_link(pkg_filename): ...@@ -85,7 +89,7 @@ def pypi_link(pkg_filename):
setup_params = dict( setup_params = dict(
name="setuptools", name="setuptools",
version="34.3.0", version="35.0.2",
description="Easily download, build, install, upgrade, and uninstall " description="Easily download, build, install, upgrade, and uninstall "
"Python packages", "Python packages",
author="Python Packaging Authority", author="Python Packaging Authority",
......
...@@ -474,8 +474,7 @@ class easy_install(Command): ...@@ -474,8 +474,7 @@ class easy_install(Command):
else: else:
self.pth_file = None self.pth_file = None
PYTHONPATH = os.environ.get('PYTHONPATH', '').split(os.pathsep) if instdir not in map(normalize_path, _pythonpath()):
if instdir not in map(normalize_path, filter(None, PYTHONPATH)):
# only PYTHONPATH dirs need a site.py, so pretend it's there # only PYTHONPATH dirs need a site.py, so pretend it's there
self.sitepy_installed = True self.sitepy_installed = True
elif self.multi_version and not os.path.exists(pth_file): elif self.multi_version and not os.path.exists(pth_file):
...@@ -1348,10 +1347,21 @@ class easy_install(Command): ...@@ -1348,10 +1347,21 @@ class easy_install(Command):
setattr(self, attr, val) setattr(self, attr, val)
def _pythonpath():
items = os.environ.get('PYTHONPATH', '').split(os.pathsep)
return filter(None, items)
def get_site_dirs(): def get_site_dirs():
# return a list of 'site' dirs """
sitedirs = [_f for _f in os.environ.get('PYTHONPATH', Return a list of 'site' dirs
'').split(os.pathsep) if _f] """
sitedirs = []
# start with PYTHONPATH
sitedirs.extend(_pythonpath())
prefixes = [sys.prefix] prefixes = [sys.prefix]
if sys.exec_prefix != sys.prefix: if sys.exec_prefix != sys.prefix:
prefixes.append(sys.exec_prefix) prefixes.append(sys.exec_prefix)
...@@ -1675,7 +1685,7 @@ def _first_line_re(): ...@@ -1675,7 +1685,7 @@ def _first_line_re():
def auto_chmod(func, arg, exc): 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) chmod(arg, stat.S_IWRITE)
return func(arg) return func(arg)
et, ev, _ = sys.exc_info() et, ev, _ = sys.exc_info()
...@@ -2013,7 +2023,7 @@ class ScriptWriter(object): ...@@ -2013,7 +2023,7 @@ class ScriptWriter(object):
gui apps. gui apps.
""" """
template = textwrap.dedent(""" template = textwrap.dedent(r"""
# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
__requires__ = %(spec)r __requires__ = %(spec)r
import re import re
......
...@@ -112,7 +112,8 @@ def translate_pattern(glob): ...@@ -112,7 +112,8 @@ def translate_pattern(glob):
if not last_chunk: if not last_chunk:
pat += sep 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): class egg_info(Command):
......
...@@ -67,7 +67,7 @@ class test(Command): ...@@ -67,7 +67,7 @@ class test(Command):
user_options = [ user_options = [
('test-module=', 'm', "Run 'test_suite' in specified module"), ('test-module=', 'm', "Run 'test_suite' in specified module"),
('test-suite=', 's', ('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"), ('test-runner=', 'r', "Test runner to use"),
] ]
......
...@@ -77,9 +77,8 @@ class upload_docs(upload): ...@@ -77,9 +77,8 @@ class upload_docs(upload):
self.mkpath(self.target_dir) # just in case self.mkpath(self.target_dir) # just in case
for root, dirs, files in os.walk(self.target_dir): for root, dirs, files in os.walk(self.target_dir):
if root == self.target_dir and not files: if root == self.target_dir and not files:
raise DistutilsOptionError( tmpl = "no files found in upload directory '%s'"
"no files found in upload directory '%s'" raise DistutilsOptionError(tmpl % self.target_dir)
% self.target_dir)
for name in files: for name in files:
full = os.path.join(root, name) full = os.path.join(root, name)
relative = root[len(self.target_dir):].lstrip(os.path.sep) relative = root[len(self.target_dir):].lstrip(os.path.sep)
...@@ -138,7 +137,7 @@ class upload_docs(upload): ...@@ -138,7 +137,7 @@ class upload_docs(upload):
part_groups = map(builder, data.items()) part_groups = map(builder, data.items())
parts = itertools.chain.from_iterable(part_groups) parts = itertools.chain.from_iterable(part_groups)
body_items = itertools.chain(parts, end_items) 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 return b''.join(body_items), content_type
def upload_file(self, filename): def upload_file(self, filename):
...@@ -159,8 +158,8 @@ class upload_docs(upload): ...@@ -159,8 +158,8 @@ class upload_docs(upload):
body, ct = self._build_multipart(data) body, ct = self._build_multipart(data)
self.announce("Submitting documentation to %s" % (self.repository), msg = "Submitting documentation to %s" % (self.repository)
log.INFO) self.announce(msg, log.INFO)
# build the Request # build the Request
# We can't use urllib2 since we need to send the Basic # We can't use urllib2 since we need to send the Basic
...@@ -191,16 +190,16 @@ class upload_docs(upload): ...@@ -191,16 +190,16 @@ class upload_docs(upload):
r = conn.getresponse() r = conn.getresponse()
if r.status == 200: if r.status == 200:
self.announce('Server response (%s): %s' % (r.status, r.reason), msg = 'Server response (%s): %s' % (r.status, r.reason)
log.INFO) self.announce(msg, log.INFO)
elif r.status == 301: elif r.status == 301:
location = r.getheader('Location') location = r.getheader('Location')
if location is None: if location is None:
location = 'https://pythonhosted.org/%s/' % meta.get_name() location = 'https://pythonhosted.org/%s/' % meta.get_name()
self.announce('Upload successful. Visit %s' % location, msg = 'Upload successful. Visit %s' % location
log.INFO) self.announce(msg, log.INFO)
else: else:
self.announce('Upload failed (%s): %s' % (r.status, r.reason), msg = 'Upload failed (%s): %s' % (r.status, r.reason)
log.ERROR) self.announce(msg, log.ERROR)
if self.show_response: if self.show_response:
print('-' * 75, r.read(), '-' * 75) print('-' * 75, r.read(), '-' * 75)
...@@ -462,6 +462,7 @@ class ConfigOptionsHandler(ConfigHandler): ...@@ -462,6 +462,7 @@ class ConfigOptionsHandler(ConfigHandler):
'tests_require': parse_list_semicolon, 'tests_require': parse_list_semicolon,
'packages': self._parse_packages, 'packages': self._parse_packages,
'entry_points': self._parse_file, 'entry_points': self._parse_file,
'py_modules': parse_list,
} }
def _parse_packages(self, value): def _parse_packages(self, value):
......
...@@ -16,6 +16,9 @@ from setuptools.extern import six ...@@ -16,6 +16,9 @@ from setuptools.extern import six
from setuptools.extern.six.moves import map from setuptools.extern.six.moves import map
from pkg_resources.extern import packaging 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.depends import Require
from setuptools import windows_support from setuptools import windows_support
from setuptools.monkey import get_unpatched from setuptools.monkey import get_unpatched
...@@ -165,7 +168,7 @@ def check_specifier(dist, attr, value): ...@@ -165,7 +168,7 @@ def check_specifier(dist, attr, value):
packaging.specifiers.SpecifierSet(value) packaging.specifiers.SpecifierSet(value)
except packaging.specifiers.InvalidSpecifier as error: except packaging.specifiers.InvalidSpecifier as error:
tmpl = ( tmpl = (
"{attr!r} must be a string or list of strings " "{attr!r} must be a string "
"containing valid version specifiers; {error}" "containing valid version specifiers; {error}"
) )
raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) raise DistutilsSetupError(tmpl.format(attr=attr, error=error))
...@@ -352,6 +355,8 @@ class Distribution(Distribution_parse_config_files, _Distribution): ...@@ -352,6 +355,8 @@ class Distribution(Distribution_parse_config_files, _Distribution):
_Distribution.parse_config_files(self, filenames=filenames) _Distribution.parse_config_files(self, filenames=filenames)
parse_configuration(self, self.command_options) parse_configuration(self, self.command_options)
if getattr(self, 'python_requires', None):
self.metadata.python_requires = self.python_requires
def parse_command_line(self): def parse_command_line(self):
"""Process features after parsing command line options""" """Process features after parsing command line options"""
......
...@@ -21,6 +21,20 @@ if you think you need this functionality. ...@@ -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): def get_unpatched(item):
lookup = ( lookup = (
get_unpatched_class if isinstance(item, six.class_types) else get_unpatched_class if isinstance(item, six.class_types) else
...@@ -38,7 +52,7 @@ def get_unpatched_class(cls): ...@@ -38,7 +52,7 @@ def get_unpatched_class(cls):
""" """
external_bases = ( external_bases = (
cls cls
for cls in inspect.getmro(cls) for cls in _get_mro(cls)
if not cls.__module__.startswith('setuptools') if not cls.__module__.startswith('setuptools')
) )
base = next(external_bases) base = next(external_bases)
......
This diff is collapsed.
...@@ -34,8 +34,8 @@ EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$') ...@@ -34,8 +34,8 @@ EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$')
HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I) HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I)
# this is here to fix emacs' cruddy broken syntax highlighting # this is here to fix emacs' cruddy broken syntax highlighting
PYPI_MD5 = re.compile( PYPI_MD5 = re.compile(
'<a href="([^"#]+)">([^<]+)</a>\n\s+\\(<a (?:title="MD5 hash"\n\s+)' '<a href="([^"#]+)">([^<]+)</a>\n\\s+\\(<a (?:title="MD5 hash"\n\\s+)'
'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)' 'href="[^?]+\\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)'
) )
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match
EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split() EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
...@@ -161,7 +161,7 @@ def interpret_distro_name( ...@@ -161,7 +161,7 @@ def interpret_distro_name(
# versions in distribution archive names (sdist and bdist). # versions in distribution archive names (sdist and bdist).
parts = basename.split('-') 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 # it is a bdist_dumb, not an sdist -- bail out
return return
...@@ -205,7 +205,7 @@ def unique_values(func): ...@@ -205,7 +205,7 @@ def unique_values(func):
return wrapper 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 # this line is here to fix emacs' cruddy broken syntax highlighting
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
Compatibility Support for Python 2.7 and earlier Compatibility Support for Python 2.7 and earlier
""" """
import sys
import platform import platform
import six
def get_all_headers(message, key): def get_all_headers(message, key):
""" """
...@@ -13,15 +14,14 @@ def get_all_headers(message, key): ...@@ -13,15 +14,14 @@ def get_all_headers(message, key):
return message.get_all(key) return message.get_all(key)
if sys.version_info < (3,): if six.PY2:
def get_all_headers(message, key): def get_all_headers(message, key):
return message.getheaders(key) return message.getheaders(key)
linux_py2_ascii = ( linux_py2_ascii = (
platform.system() == 'Linux' and platform.system() == 'Linux' and
sys.version_info < (3,) six.PY2
) )
rmtree_safe = str if linux_py2_ascii else lambda x: x rmtree_safe = str if linux_py2_ascii else lambda x: x
......
...@@ -7,6 +7,7 @@ import itertools ...@@ -7,6 +7,7 @@ import itertools
import re import re
import contextlib import contextlib
import pickle import pickle
import textwrap
from setuptools.extern import six from setuptools.extern import six
from setuptools.extern.six.moves import builtins, map from setuptools.extern.six.moves import builtins, map
...@@ -215,7 +216,7 @@ def _needs_hiding(mod_name): ...@@ -215,7 +216,7 @@ def _needs_hiding(mod_name):
>>> _needs_hiding('Cython') >>> _needs_hiding('Cython')
True True
""" """
pattern = re.compile('(setuptools|pkg_resources|distutils|Cython)(\.|$)') pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)')
return bool(pattern.match(mod_name)) return bool(pattern.match(mod_name))
...@@ -248,11 +249,9 @@ def run_setup(setup_script, args): ...@@ -248,11 +249,9 @@ def run_setup(setup_script, args):
setup_script.encode(sys.getfilesystemencoding()) setup_script.encode(sys.getfilesystemencoding())
) )
def runner(): with DirectorySandbox(setup_dir):
ns = dict(__file__=dunder_file, __name__='__main__') ns = dict(__file__=dunder_file, __name__='__main__')
_execfile(setup_script, ns) _execfile(setup_script, ns)
DirectorySandbox(setup_dir).run(runner)
except SystemExit as v: except SystemExit as v:
if v.args and v.args[0]: if v.args and v.args[0]:
raise raise
...@@ -274,22 +273,25 @@ class AbstractSandbox: ...@@ -274,22 +273,25 @@ class AbstractSandbox:
for name in self._attrs: for name in self._attrs:
setattr(os, name, getattr(source, name)) setattr(os, name, getattr(source, name))
def run(self, func): def __enter__(self):
"""Run 'func' under os sandboxing"""
try:
self._copy(self) self._copy(self)
if _file: if _file:
builtins.file = self._file builtins.file = self._file
builtins.open = self._open builtins.open = self._open
self._active = True self._active = True
return func()
finally: def __exit__(self, exc_type, exc_value, traceback):
self._active = False self._active = False
if _file: if _file:
builtins.file = _file builtins.file = _file
builtins.open = _open builtins.open = _open
self._copy(_os) self._copy(_os)
def run(self, func):
"""Run 'func' under os sandboxing"""
with self:
return func()
def _mk_dual_path_wrapper(name): def _mk_dual_path_wrapper(name):
original = getattr(_os, name) original = getattr(_os, name)
...@@ -391,7 +393,7 @@ class DirectorySandbox(AbstractSandbox): ...@@ -391,7 +393,7 @@ class DirectorySandbox(AbstractSandbox):
_exception_patterns = [ _exception_patterns = [
# Allow lib2to3 to attempt to save a pickled grammar object (#121) # Allow lib2to3 to attempt to save a pickled grammar object (#121)
'.*lib2to3.*\.pickle$', r'.*lib2to3.*\.pickle$',
] ]
"exempt writing to paths that match the pattern" "exempt writing to paths that match the pattern"
...@@ -476,16 +478,18 @@ WRITE_FLAGS = functools.reduce( ...@@ -476,16 +478,18 @@ WRITE_FLAGS = functools.reduce(
class SandboxViolation(DistutilsError): class SandboxViolation(DistutilsError):
"""A setup script attempted to modify the filesystem outside the sandbox""" """A setup script attempted to modify the filesystem outside the sandbox"""
def __str__(self): tmpl = textwrap.dedent("""
return """SandboxViolation: %s%r %s SandboxViolation: {cmd}{args!r} {kwargs}
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 The package setup script has attempted to modify files on your system
support alternate installation locations even if you run its setup that are not within the EasyInstall build area, and has been aborted.
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
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())
...@@ -41,4 +41,4 @@ class Test: ...@@ -41,4 +41,4 @@ class Test:
# let's see if we got our egg link at the right place # let's see if we got our egg link at the right place
[content] = os.listdir('dist') [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)
...@@ -312,6 +312,8 @@ class TestOptions: ...@@ -312,6 +312,8 @@ class TestOptions:
'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n' 'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
'dependency_links = http://some.com/here/1, ' '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'
) )
with get_dist(tmpdir) as dist: with get_dist(tmpdir) as dist:
assert dist.zip_safe assert dist.zip_safe
...@@ -340,6 +342,8 @@ class TestOptions: ...@@ -340,6 +342,8 @@ class TestOptions:
'there' 'there'
]) ])
assert dist.tests_require == ['mock==0.7.2', 'pytest'] 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): def test_multiline(self, tmpdir):
fake_env( fake_env(
......
...@@ -65,7 +65,7 @@ class TestEasyInstallTest: ...@@ -65,7 +65,7 @@ class TestEasyInstallTest:
def test_get_script_args(self): def test_get_script_args(self):
header = ei.CommandSpec.best().from_environment().as_header() header = ei.CommandSpec.best().from_environment().as_header()
expected = header + DALS(""" expected = header + DALS(r"""
# EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name' # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name'
__requires__ = 'spec' __requires__ = 'spec'
import re import re
......
...@@ -98,3 +98,68 @@ def test_pbr(install_context): ...@@ -98,3 +98,68 @@ def test_pbr(install_context):
def test_python_novaclient(install_context): def test_python_novaclient(install_context):
_install_one('python-novaclient', install_context, _install_one('python-novaclient', install_context,
'novaclient', 'base.py') '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])
...@@ -6,6 +6,7 @@ import os ...@@ -6,6 +6,7 @@ import os
import shutil import shutil
import sys import sys
import tempfile import tempfile
import itertools
from distutils import log from distutils import log
from distutils.errors import DistutilsTemplateError from distutils.errors import DistutilsTemplateError
...@@ -65,32 +66,94 @@ default_files = frozenset(map(make_local_path, [ ...@@ -65,32 +66,94 @@ default_files = frozenset(map(make_local_path, [
])) ]))
def get_pattern(glob): translate_specs = [
return translate_pattern(make_local_path(glob)).pattern ('foo', ['foo'], ['bar', 'foobar']),
('foo/bar', ['foo/bar'], ['foo/bar/baz', './foo/bar', 'foo']),
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)')
# Glob matching # Glob matching
assert get_pattern('*.txt') == l(r'[^\/]*\.txt\Z(?ms)') ('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']),
assert get_pattern('dir/*.txt') == l(r'dir\/[^\/]*\.txt\Z(?ms)') ('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']),
assert get_pattern('*/*.py') == l(r'[^\/]*\/[^\/]*\.py\Z(?ms)') ('*/*.py', ['bin/start.py'], []),
assert get_pattern('docs/page-?.txt') \ ('docs/page-?.txt', ['docs/page-9.txt'], ['docs/page-10.txt']),
== l(r'docs\/page\-[^\/]\.txt\Z(?ms)')
# Globstars change what they mean depending upon where they are # 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)') 'foo/**/bar',
assert get_pattern(l('**')) == r'.*\Z(?ms)' ['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 # 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)' 'pre[one]post',
assert get_pattern('[]one].txt') == r'[\]one]\.txt\Z(?ms)' ['preopost', 'prenpost', 'preepost'],
assert get_pattern('foo[!]one]bar') == r'foo[^\]one]bar\Z(?ms)' ['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): class TempDirTestCase(object):
......
...@@ -7,13 +7,12 @@ import pytest ...@@ -7,13 +7,12 @@ import pytest
import pkg_resources import pkg_resources
import setuptools.sandbox import setuptools.sandbox
from setuptools.sandbox import DirectorySandbox
class TestSandbox: class TestSandbox:
def test_devnull(self, tmpdir): def test_devnull(self, tmpdir):
sandbox = DirectorySandbox(str(tmpdir)) with setuptools.sandbox.DirectorySandbox(str(tmpdir)):
sandbox.run(self._file_writer(os.devnull)) self._file_writer(os.devnull)
@staticmethod @staticmethod
def _file_writer(path): def _file_writer(path):
...@@ -116,13 +115,17 @@ class TestExceptionSaver: ...@@ -116,13 +115,17 @@ class TestExceptionSaver:
with open('/etc/foo', 'w'): with open('/etc/foo', 'w'):
pass pass
sandbox = DirectorySandbox(str(tmpdir))
with pytest.raises(setuptools.sandbox.SandboxViolation) as caught: with pytest.raises(setuptools.sandbox.SandboxViolation) as caught:
with setuptools.sandbox.save_modules(): with setuptools.sandbox.save_modules():
setuptools.sandbox.hide_setuptools() setuptools.sandbox.hide_setuptools()
sandbox.run(write_file) with setuptools.sandbox.DirectorySandbox(str(tmpdir)):
write_file()
cmd, args, kwargs = caught.value.args cmd, args, kwargs = caught.value.args
assert cmd == 'open' assert cmd == 'open'
assert args == ('/etc/foo', 'w') assert args == ('/etc/foo', 'w')
assert kwargs == {} assert kwargs == {}
msg = str(caught.value)
assert 'open' in msg
assert "('/etc/foo', 'w')" in msg
...@@ -64,6 +64,8 @@ class TestUploadDocsTest: ...@@ -64,6 +64,8 @@ class TestUploadDocsTest:
) )
body, content_type = upload_docs._build_multipart(data) body, content_type = upload_docs._build_multipart(data)
assert 'form-data' in content_type assert 'form-data' in content_type
assert "b'" not in content_type
assert 'b"' not in content_type
assert isinstance(body, bytes) assert isinstance(body, bytes)
assert b'foo' in body assert b'foo' in body
assert b'content' in body assert b'content' in body
pytest-flake8 pytest-flake8
pytest>=3.0.2 pytest>=3.0.2
setuptools[ssl] # pinned to 1.2 as temporary workaround for #1038
backports.unittest_mock>=1.2 backports.unittest_mock>=1.2,<1.3
# 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] [testenv]
deps=-rtests/requirements.txt deps=-rtests/requirements.txt
passenv=APPDATA USERPROFILE HOMEDRIVE HOMEPATH windir APPVEYOR passenv=APPDATA USERPROFILE HOMEDRIVE HOMEPATH windir APPVEYOR
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment