diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2017-05-30 19:39:58 -0400 |
---|---|---|
committer | Jason R. Coombs <jaraco@jaraco.com> | 2017-05-30 19:39:58 -0400 |
commit | fcdf12ee7fdf43c9dded5b68232a0fb3376d4858 (patch) | |
tree | 29baaad1490dcce705ccc5f32c6d9d9240e1d10d | |
parent | 3d0cc355fb5e8012cb8c72f0e25042a5a44f31d6 (diff) | |
parent | 4dc2c76b62a5071dfacf434555dfa8ec2be0b433 (diff) | |
download | external_python_setuptools-fcdf12ee7fdf43c9dded5b68232a0fb3376d4858.tar.gz external_python_setuptools-fcdf12ee7fdf43c9dded5b68232a0fb3376d4858.tar.bz2 external_python_setuptools-fcdf12ee7fdf43c9dded5b68232a0fb3376d4858.zip |
Merge branch 'master' into feature/re-vendor-sadface
29 files changed, 565 insertions, 214 deletions
@@ -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')] ) @@ -1,5 +1,5 @@ [bumpversion] -current_version = 34.3.0 +current_version = 35.0.2 commit = True tag = True @@ -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&digest=([0-9a-f]{32})">md5</a>\\)' + '<a href="([^"#]+)">([^<]+)</a>\n\\s+\\(<a (?:title="MD5 hash"\n\\s+)' + 'href="[^?]+\\?:action=show_md5&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 @@ -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 |