aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/_imp.py73
-rw-r--r--setuptools/_vendor/vendored.txt2
-rw-r--r--setuptools/command/__init__.py3
-rw-r--r--setuptools/command/bdist_egg.py2
-rw-r--r--setuptools/command/easy_install.py9
-rw-r--r--setuptools/command/register.py22
-rw-r--r--setuptools/command/test.py10
-rw-r--r--setuptools/command/upload.py195
-rw-r--r--setuptools/depends.py48
-rw-r--r--setuptools/errors.py16
-rw-r--r--setuptools/msvc.py1022
-rw-r--r--setuptools/package_index.py2
-rw-r--r--setuptools/py27compat.py32
-rw-r--r--setuptools/py34compat.py13
-rw-r--r--setuptools/tests/test_bdist_egg.py2
-rw-r--r--setuptools/tests/test_config.py2
-rw-r--r--setuptools/tests/test_integration.py2
-rw-r--r--setuptools/tests/test_register.py43
-rw-r--r--setuptools/tests/test_test.py60
-rw-r--r--setuptools/tests/test_upload.py211
-rw-r--r--setuptools/tests/test_wheel.py28
-rw-r--r--setuptools/wheel.py21
22 files changed, 1009 insertions, 809 deletions
diff --git a/setuptools/_imp.py b/setuptools/_imp.py
new file mode 100644
index 00000000..a3cce9b2
--- /dev/null
+++ b/setuptools/_imp.py
@@ -0,0 +1,73 @@
+"""
+Re-implementation of find_module and get_frozen_object
+from the deprecated imp module.
+"""
+
+import os
+import importlib.util
+import importlib.machinery
+
+from .py34compat import module_from_spec
+
+
+PY_SOURCE = 1
+PY_COMPILED = 2
+C_EXTENSION = 3
+C_BUILTIN = 6
+PY_FROZEN = 7
+
+
+def find_module(module, paths=None):
+ """Just like 'imp.find_module()', but with package support"""
+ spec = importlib.util.find_spec(module, paths)
+ if spec is None:
+ raise ImportError("Can't find %s" % module)
+ if not spec.has_location and hasattr(spec, 'submodule_search_locations'):
+ spec = importlib.util.spec_from_loader('__init__.py', spec.loader)
+
+ kind = -1
+ file = None
+ static = isinstance(spec.loader, type)
+ if spec.origin == 'frozen' or static and issubclass(
+ spec.loader, importlib.machinery.FrozenImporter):
+ kind = PY_FROZEN
+ path = None # imp compabilty
+ suffix = mode = '' # imp compability
+ elif spec.origin == 'built-in' or static and issubclass(
+ spec.loader, importlib.machinery.BuiltinImporter):
+ kind = C_BUILTIN
+ path = None # imp compabilty
+ suffix = mode = '' # imp compability
+ elif spec.has_location:
+ path = spec.origin
+ suffix = os.path.splitext(path)[1]
+ mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb'
+
+ if suffix in importlib.machinery.SOURCE_SUFFIXES:
+ kind = PY_SOURCE
+ elif suffix in importlib.machinery.BYTECODE_SUFFIXES:
+ kind = PY_COMPILED
+ elif suffix in importlib.machinery.EXTENSION_SUFFIXES:
+ kind = C_EXTENSION
+
+ if kind in {PY_SOURCE, PY_COMPILED}:
+ file = open(path, mode)
+ else:
+ path = None
+ suffix = mode = ''
+
+ return file, path, (suffix, mode, kind)
+
+
+def get_frozen_object(module, paths=None):
+ spec = importlib.util.find_spec(module, paths)
+ if not spec:
+ raise ImportError("Can't find %s" % module)
+ return spec.loader.get_code(module)
+
+
+def get_module(module, paths, info):
+ spec = importlib.util.find_spec(module, paths)
+ if not spec:
+ raise ImportError("Can't find %s" % module)
+ return module_from_spec(spec)
diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt
index 379aae56..5731b424 100644
--- a/setuptools/_vendor/vendored.txt
+++ b/setuptools/_vendor/vendored.txt
@@ -1,4 +1,4 @@
packaging==16.8
pyparsing==2.2.1
six==1.10.0
-ordered-set
+ordered-set==3.1.1
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
index fe619e2e..743f5588 100644
--- a/setuptools/command/__init__.py
+++ b/setuptools/command/__init__.py
@@ -2,8 +2,7 @@ __all__ = [
'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
- 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib',
- 'dist_info',
+ 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info',
]
from distutils.command.bdist import bdist
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 9f8df917..98470f17 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -284,7 +284,7 @@ class bdist_egg(Command):
"or refer to a module" % (ep,)
)
- pyver = sys.version[:3]
+ pyver = '{}.{}'.format(*sys.version_info)
pkg = ep.module_name
full = '.'.join(ep.attrs)
base = ep.attrs[0]
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 06c98271..545c3c44 100644
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -241,7 +241,7 @@ class easy_install(Command):
"""
Render the Setuptools version and installation details, then exit.
"""
- ver = sys.version[:3]
+ ver = '{}.{}'.format(*sys.version_info)
dist = get_distribution('setuptools')
tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'
print(tmpl.format(**locals()))
@@ -1180,8 +1180,7 @@ class easy_install(Command):
# to the setup.cfg file.
ei_opts = self.distribution.get_option_dict('easy_install').copy()
fetch_directives = (
- 'find_links', 'site_dirs', 'index_url', 'optimize',
- 'site_dirs', 'allow_hosts',
+ 'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts',
)
fetch_options = {}
for key, val in ei_opts.items():
@@ -1412,7 +1411,7 @@ def get_site_dirs():
os.path.join(
prefix,
"lib",
- "python" + sys.version[:3],
+ "python{}.{}".format(*sys.version_info),
"site-packages",
),
os.path.join(prefix, "lib", "site-python"),
@@ -1433,7 +1432,7 @@ def get_site_dirs():
home,
'Library',
'Python',
- sys.version[:3],
+ '{}.{}'.format(*sys.version_info),
'site-packages',
)
sitedirs.append(home_sp)
diff --git a/setuptools/command/register.py b/setuptools/command/register.py
index 98bc0156..b8266b9a 100644
--- a/setuptools/command/register.py
+++ b/setuptools/command/register.py
@@ -1,18 +1,18 @@
from distutils import log
import distutils.command.register as orig
+from setuptools.errors import RemovedCommandError
+
class register(orig.register):
- __doc__ = orig.register.__doc__
+ """Formerly used to register packages on PyPI."""
def run(self):
- try:
- # Make sure that we are using valid current name/version info
- self.run_command('egg_info')
- orig.register.run(self)
- finally:
- self.announce(
- "WARNING: Registering is deprecated, use twine to "
- "upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
+ msg = (
+ "The register command has been removed, use twine to upload "
+ + "instead (https://pypi.org/p/twine)"
+ )
+
+ self.announce("ERROR: " + msg, log.ERROR)
+
+ raise RemovedCommandError(msg)
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index 973e4eb2..c148b38d 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -74,7 +74,7 @@ class NonDataProperty:
class test(Command):
"""Command to run unit tests after in-place build"""
- description = "run unit tests after in-place build"
+ description = "run unit tests after in-place build (deprecated)"
user_options = [
('test-module=', 'm', "Run 'test_suite' in specified module"),
@@ -214,6 +214,14 @@ class test(Command):
return itertools.chain(ir_d, tr_d, er_d)
def run(self):
+ self.announce(
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox.",
+ log.WARN,
+ )
+
installed_dists = self.install_dists(self.distribution)
cmd = ' '.join(self._argv)
diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py
index 6db8888b..ec7f81e2 100644
--- a/setuptools/command/upload.py
+++ b/setuptools/command/upload.py
@@ -1,196 +1,17 @@
-import io
-import os
-import hashlib
-import getpass
-
-from base64 import standard_b64encode
-
from distutils import log
from distutils.command import upload as orig
-from distutils.spawn import spawn
-
-from distutils.errors import DistutilsError
-from setuptools.extern.six.moves.urllib.request import urlopen, Request
-from setuptools.extern.six.moves.urllib.error import HTTPError
-from setuptools.extern.six.moves.urllib.parse import urlparse
+from setuptools.errors import RemovedCommandError
class upload(orig.upload):
- """
- Override default upload behavior to obtain password
- in a variety of different ways.
- """
- def run(self):
- try:
- orig.upload.run(self)
- finally:
- self.announce(
- "WARNING: Uploading via this command is deprecated, use twine "
- "to upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
+ """Formerly used to upload packages to PyPI."""
- def finalize_options(self):
- orig.upload.finalize_options(self)
- self.username = (
- self.username or
- getpass.getuser()
- )
- # Attempt to obtain password. Short circuit evaluation at the first
- # sign of success.
- self.password = (
- self.password or
- self._load_password_from_keyring() or
- self._prompt_for_password()
+ def run(self):
+ msg = (
+ "The upload command has been removed, use twine to upload "
+ + "instead (https://pypi.org/p/twine)"
)
- def upload_file(self, command, pyversion, filename):
- # Makes sure the repository URL is compliant
- schema, netloc, url, params, query, fragments = \
- urlparse(self.repository)
- if params or query or fragments:
- raise AssertionError("Incompatible url %s" % self.repository)
-
- if schema not in ('http', 'https'):
- raise AssertionError("unsupported schema " + schema)
-
- # Sign if requested
- if self.sign:
- gpg_args = ["gpg", "--detach-sign", "-a", filename]
- if self.identity:
- gpg_args[2:2] = ["--local-user", self.identity]
- spawn(gpg_args,
- dry_run=self.dry_run)
-
- # Fill in the data - send all the meta-data in case we need to
- # register a new release
- with open(filename, 'rb') as f:
- content = f.read()
-
- meta = self.distribution.metadata
-
- data = {
- # action
- ':action': 'file_upload',
- 'protocol_version': '1',
-
- # identify release
- 'name': meta.get_name(),
- 'version': meta.get_version(),
-
- # file content
- 'content': (os.path.basename(filename), content),
- 'filetype': command,
- 'pyversion': pyversion,
- 'md5_digest': hashlib.md5(content).hexdigest(),
-
- # additional meta-data
- 'metadata_version': str(meta.get_metadata_version()),
- 'summary': meta.get_description(),
- 'home_page': meta.get_url(),
- 'author': meta.get_contact(),
- 'author_email': meta.get_contact_email(),
- 'license': meta.get_licence(),
- 'description': meta.get_long_description(),
- 'keywords': meta.get_keywords(),
- 'platform': meta.get_platforms(),
- 'classifiers': meta.get_classifiers(),
- 'download_url': meta.get_download_url(),
- # PEP 314
- 'provides': meta.get_provides(),
- 'requires': meta.get_requires(),
- 'obsoletes': meta.get_obsoletes(),
- }
-
- data['comment'] = ''
-
- if self.sign:
- data['gpg_signature'] = (os.path.basename(filename) + ".asc",
- open(filename+".asc", "rb").read())
-
- # set up the authentication
- user_pass = (self.username + ":" + self.password).encode('ascii')
- # The exact encoding of the authentication string is debated.
- # Anyway PyPI only accepts ascii for both username or password.
- auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
-
- # Build up the MIME payload for the POST data
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- sep_boundary = b'\r\n--' + boundary.encode('ascii')
- end_boundary = sep_boundary + b'--\r\n'
- body = io.BytesIO()
- for key, value in data.items():
- title = '\r\nContent-Disposition: form-data; name="%s"' % key
- # handle multiple entries for the same name
- if not isinstance(value, list):
- value = [value]
- for value in value:
- if type(value) is tuple:
- title += '; filename="%s"' % value[0]
- value = value[1]
- else:
- value = str(value).encode('utf-8')
- body.write(sep_boundary)
- body.write(title.encode('utf-8'))
- body.write(b"\r\n\r\n")
- body.write(value)
- body.write(end_boundary)
- body = body.getvalue()
-
- msg = "Submitting %s to %s" % (filename, self.repository)
- self.announce(msg, log.INFO)
-
- # build the Request
- headers = {
- 'Content-type': 'multipart/form-data; boundary=%s' % boundary,
- 'Content-length': str(len(body)),
- 'Authorization': auth,
- }
-
- request = Request(self.repository, data=body,
- headers=headers)
- # send the data
- try:
- result = urlopen(request)
- status = result.getcode()
- reason = result.msg
- except HTTPError as e:
- status = e.code
- reason = e.msg
- except OSError as e:
- self.announce(str(e), log.ERROR)
- raise
-
- if status == 200:
- self.announce('Server response (%s): %s' % (status, reason),
- log.INFO)
- if self.show_response:
- text = getattr(self, '_read_pypi_response',
- lambda x: None)(result)
- if text is not None:
- msg = '\n'.join(('-' * 75, text, '-' * 75))
- self.announce(msg, log.INFO)
- else:
- msg = 'Upload failed (%s): %s' % (status, reason)
- self.announce(msg, log.ERROR)
- raise DistutilsError(msg)
-
- def _load_password_from_keyring(self):
- """
- Attempt to load password from keyring. Suppress Exceptions.
- """
- try:
- keyring = __import__('keyring')
- return keyring.get_password(self.repository, self.username)
- except Exception:
- pass
-
- def _prompt_for_password(self):
- """
- Prompt for a password on the tty. Suppress Exceptions.
- """
- try:
- return getpass.getpass()
- except (Exception, KeyboardInterrupt):
- pass
+ self.announce("ERROR: " + msg, log.ERROR)
+ raise RemovedCommandError(msg)
diff --git a/setuptools/depends.py b/setuptools/depends.py
index 45e7052d..a37675cb 100644
--- a/setuptools/depends.py
+++ b/setuptools/depends.py
@@ -1,11 +1,13 @@
import sys
-import imp
import marshal
+import contextlib
from distutils.version import StrictVersion
-from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN
from .py33compat import Bytecode
+from .py27compat import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
+from . import py27compat
+
__all__ = [
'Require', 'find_module', 'get_module_constant', 'extract_constant'
@@ -15,7 +17,8 @@ __all__ = [
class Require:
"""A prerequisite to building or installing a distribution"""
- def __init__(self, name, requested_version, module, homepage='',
+ def __init__(
+ self, name, requested_version, module, homepage='',
attribute=None, format=None):
if format is None and requested_version is not None:
@@ -79,23 +82,15 @@ class Require:
return self.version_ok(version)
-def find_module(module, paths=None):
- """Just like 'imp.find_module()', but with package support"""
-
- parts = module.split('.')
-
- while parts:
- part = parts.pop(0)
- f, path, (suffix, mode, kind) = info = imp.find_module(part, paths)
-
- if kind == PKG_DIRECTORY:
- parts = parts or ['__init__']
- paths = [path]
-
- elif parts:
- raise ImportError("Can't find %r in %s" % (parts, module))
+def maybe_close(f):
+ @contextlib.contextmanager
+ def empty():
+ yield
+ return
+ if not f:
+ return empty()
- return info
+ return contextlib.closing(f)
def get_module_constant(module, symbol, default=-1, paths=None):
@@ -106,28 +101,23 @@ def get_module_constant(module, symbol, default=-1, paths=None):
constant. Otherwise, return 'default'."""
try:
- f, path, (suffix, mode, kind) = find_module(module, paths)
+ f, path, (suffix, mode, kind) = info = find_module(module, paths)
except ImportError:
# Module doesn't exist
return None
- try:
+ with maybe_close(f):
if kind == PY_COMPILED:
f.read(8) # skip magic & date
code = marshal.load(f)
elif kind == PY_FROZEN:
- code = imp.get_frozen_object(module)
+ code = py27compat.get_frozen_object(module, paths)
elif kind == PY_SOURCE:
code = compile(f.read(), path, 'exec')
else:
# Not something we can parse; we'll have to import it. :(
- if module not in sys.modules:
- imp.load_module(module, f, path, (suffix, mode, kind))
- return getattr(sys.modules[module], symbol, None)
-
- finally:
- if f:
- f.close()
+ imported = py27compat.get_module(module, paths, info)
+ return getattr(imported, symbol, None)
return extract_constant(code, symbol, default)
diff --git a/setuptools/errors.py b/setuptools/errors.py
new file mode 100644
index 00000000..2701747f
--- /dev/null
+++ b/setuptools/errors.py
@@ -0,0 +1,16 @@
+"""setuptools.errors
+
+Provides exceptions used by setuptools modules.
+"""
+
+from distutils.errors import DistutilsError
+
+
+class RemovedCommandError(DistutilsError, RuntimeError):
+ """Error used for commands that have been removed in setuptools.
+
+ Since ``setuptools`` is built on ``distutils``, simply removing a command
+ from ``setuptools`` will make the behavior fall back to ``distutils``; this
+ error is raised if a command exists in ``distutils`` but has been actively
+ removed in ``setuptools``.
+ """
diff --git a/setuptools/msvc.py b/setuptools/msvc.py
index b9c472f1..2ffe1c81 100644
--- a/setuptools/msvc.py
+++ b/setuptools/msvc.py
@@ -11,13 +11,18 @@ Microsoft Visual C++ 9.0:
Microsoft Visual C++ 10.0:
Microsoft Windows SDK 7.1 (x86, x64, ia64)
-Microsoft Visual C++ 14.0:
+Microsoft Visual C++ 14.X:
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)
+ Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)
+
+This may also support compilers shipped with compatible Visual Studio versions.
"""
-import os
+import json
+from io import open
+from os import listdir, pathsep
+from os.path import join, isfile, isdir, dirname
import sys
import platform
import itertools
@@ -30,12 +35,9 @@ from .monkey import get_unpatched
if platform.system() == 'Windows':
from setuptools.extern.six.moves import winreg
- safe_env = os.environ
+ from os import environ
else:
- """
- Mock winreg and environ so the module can be imported
- on this platform.
- """
+ # Mock winreg and environ so the module can be imported on this platform.
class winreg:
HKEY_USERS = None
@@ -43,7 +45,7 @@ else:
HKEY_LOCAL_MACHINE = None
HKEY_CLASSES_ROOT = None
- safe_env = dict()
+ environ = dict()
_msvc9_suppress_errors = (
# msvc9compiler isn't available on some platforms
@@ -63,15 +65,13 @@ except _msvc9_suppress_errors:
def msvc9_find_vcvarsall(version):
"""
Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
- compiler build for Python (VCForPython). Fall back to original behavior
- when the standalone compiler is not available.
+ compiler build for Python
+ (VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
- Redirect the path of "vcvarsall.bat".
+ Fall back to original behavior when the standalone compiler is not
+ available.
- Known supported compilers
- -------------------------
- Microsoft Visual C++ 9.0:
- Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
+ Redirect the path of "vcvarsall.bat".
Parameters
----------
@@ -80,24 +80,25 @@ def msvc9_find_vcvarsall(version):
Return
------
- vcvarsall.bat path: str
+ str
+ vcvarsall.bat path
"""
- VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
- key = VC_BASE % ('', version)
+ vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
+ key = vc_base % ('', version)
try:
# Per-user installs register the compiler path here
productdir = Reg.get_value(key, "installdir")
except KeyError:
try:
# All-user installs on a 64-bit system register here
- key = VC_BASE % ('Wow6432Node\\', version)
+ key = vc_base % ('Wow6432Node\\', version)
productdir = Reg.get_value(key, "installdir")
except KeyError:
productdir = None
if productdir:
- vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat")
- if os.path.isfile(vcvarsall):
+ vcvarsall = join(productdir, "vcvarsall.bat")
+ if isfile(vcvarsall):
return vcvarsall
return get_unpatched(msvc9_find_vcvarsall)(version)
@@ -106,20 +107,10 @@ def msvc9_find_vcvarsall(version):
def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
"""
Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
- compilers.
+ Microsoft Visual C++ 9.0 and 10.0 compilers.
Set environment without use of "vcvarsall.bat".
- Known supported compilers
- -------------------------
- Microsoft Visual C++ 9.0:
- 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)
-
Parameters
----------
ver: float
@@ -129,9 +120,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
Return
------
- environment: dict
+ dict
+ environment
"""
- # Try to get environement from vcvarsall.bat (Classical way)
+ # Try to get environment from vcvarsall.bat (Classical way)
try:
orig = get_unpatched(msvc9_query_vcvarsall)
return orig(ver, arch, *args, **kwargs)
@@ -153,17 +145,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
def msvc14_get_vc_env(plat_spec):
"""
Patched "distutils._msvccompiler._get_vc_env" for support extra
- compilers.
+ Microsoft Visual C++ 14.X compilers.
Set environment without use of "vcvarsall.bat".
- Known supported compilers
- -------------------------
- 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
----------
plat_spec: str
@@ -171,7 +156,8 @@ def msvc14_get_vc_env(plat_spec):
Return
------
- environment: dict
+ dict
+ environment
"""
# Try to get environment from vcvarsall.bat (Classical way)
try:
@@ -217,9 +203,9 @@ def _augment_exception(exc, version, arch=''):
if version == 9.0:
if arch.lower().find('ia64') > -1:
# For VC++ 9.0, if IA64 support is needed, redirect user
- # to Windows SDK 7.0
- message += ' Get it with "Microsoft Windows SDK 7.0": '
- message += msdownload % 3138
+ # to Windows SDK 7.0.
+ # Note: No download link available from Microsoft.
+ message += ' Get it with "Microsoft Windows SDK 7.0"'
else:
# For VC++ 9.0 redirect user to Vc++ for Python 2.7 :
# This redirection link is maintained by Microsoft.
@@ -230,8 +216,8 @@ def _augment_exception(exc, version, arch=''):
message += ' Get it with "Microsoft Windows SDK 7.1": '
message += msdownload % 8279
elif version >= 14.0:
- # For VC++ 14.0 Redirect user to Visual C++ Build Tools
- message += (' Get it with "Microsoft Visual C++ Build Tools": '
+ # For VC++ 14.X Redirect user to latest Visual C++ Build Tools
+ message += (' Get it with "Build Tools for Visual Studio": '
r'https://visualstudio.microsoft.com/downloads/')
exc.args = (message, )
@@ -239,26 +225,50 @@ def _augment_exception(exc, version, arch=''):
class PlatformInfo:
"""
- Current and Target Architectures informations.
+ Current and Target Architectures information.
Parameters
----------
arch: str
Target architecture.
"""
- current_cpu = safe_env.get('processor_architecture', '').lower()
+ current_cpu = environ.get('processor_architecture', '').lower()
def __init__(self, arch):
self.arch = arch.lower().replace('x64', 'amd64')
@property
def target_cpu(self):
+ """
+ Return Target CPU architecture.
+
+ Return
+ ------
+ str
+ Target CPU
+ """
return self.arch[self.arch.find('_') + 1:]
def target_is_x86(self):
+ """
+ Return True if target CPU is x86 32 bits..
+
+ Return
+ ------
+ bool
+ CPU is x86 32 bits
+ """
return self.target_cpu == 'x86'
def current_is_x86(self):
+ """
+ Return True if current CPU is x86 32 bits..
+
+ Return
+ ------
+ bool
+ CPU is x86 32 bits
+ """
return self.current_cpu == 'x86'
def current_dir(self, hidex86=False, x64=False):
@@ -274,8 +284,8 @@ class PlatformInfo:
Return
------
- subfolder: str
- '\target', or '' (see hidex86 parameter)
+ str
+ subfolder: '\target', or '' (see hidex86 parameter)
"""
return (
'' if (self.current_cpu == 'x86' and hidex86) else
@@ -296,8 +306,8 @@ class PlatformInfo:
Return
------
- subfolder: str
- '\current', or '' (see hidex86 parameter)
+ str
+ subfolder: '\current', or '' (see hidex86 parameter)
"""
return (
'' if (self.target_cpu == 'x86' and hidex86) else
@@ -312,13 +322,13 @@ class PlatformInfo:
Parameters
----------
forcex86: bool
- Use 'x86' as current architecture even if current acritecture is
+ Use 'x86' as current architecture even if current architecture is
not x86.
Return
------
- subfolder: str
- '' if target architecture is current architecture,
+ str
+ subfolder: '' if target architecture is current architecture,
'\current_target' if not.
"""
current = 'x86' if forcex86 else self.current_cpu
@@ -330,7 +340,7 @@ class PlatformInfo:
class RegistryInfo:
"""
- Microsoft Visual Studio related registry informations.
+ Microsoft Visual Studio related registry information.
Parameters
----------
@@ -349,6 +359,11 @@ class RegistryInfo:
def visualstudio(self):
"""
Microsoft Visual Studio root registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return 'VisualStudio'
@@ -356,27 +371,47 @@ class RegistryInfo:
def sxs(self):
"""
Microsoft Visual Studio SxS registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.visualstudio, 'SxS')
+ return join(self.visualstudio, 'SxS')
@property
def vc(self):
"""
Microsoft Visual C++ VC7 registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.sxs, 'VC7')
+ return join(self.sxs, 'VC7')
@property
def vs(self):
"""
Microsoft Visual Studio VS7 registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.sxs, 'VS7')
+ return join(self.sxs, 'VS7')
@property
def vc_for_python(self):
"""
Microsoft Visual C++ for Python registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return r'DevDiv\VCForPython'
@@ -384,6 +419,11 @@ class RegistryInfo:
def microsoft_sdk(self):
"""
Microsoft SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return 'Microsoft SDKs'
@@ -391,20 +431,35 @@ class RegistryInfo:
def windows_sdk(self):
"""
Microsoft Windows/Platform SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.microsoft_sdk, 'Windows')
+ return join(self.microsoft_sdk, 'Windows')
@property
def netfx_sdk(self):
"""
Microsoft .NET Framework SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.microsoft_sdk, 'NETFXSDK')
+ return join(self.microsoft_sdk, 'NETFXSDK')
@property
def windows_kits_roots(self):
"""
Microsoft Windows Kits Roots registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return r'Windows Kits\Installed Roots'
@@ -421,10 +476,11 @@ class RegistryInfo:
Return
------
- str: value
+ str
+ Registry key
"""
node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
- return os.path.join('Software', node64, 'Microsoft', key)
+ return join('Software', node64, 'Microsoft', key)
def lookup(self, key, name):
"""
@@ -439,18 +495,19 @@ class RegistryInfo:
Return
------
- str: value
+ str
+ value
"""
- KEY_READ = winreg.KEY_READ
+ key_read = winreg.KEY_READ
openkey = winreg.OpenKey
ms = self.microsoft
for hkey in self.HKEYS:
try:
- bkey = openkey(hkey, ms(key), 0, KEY_READ)
+ bkey = openkey(hkey, ms(key), 0, key_read)
except (OSError, IOError):
if not self.pi.current_is_x86():
try:
- bkey = openkey(hkey, ms(key, True), 0, KEY_READ)
+ bkey = openkey(hkey, ms(key, True), 0, key_read)
except (OSError, IOError):
continue
else:
@@ -463,7 +520,7 @@ class RegistryInfo:
class SystemInfo:
"""
- Microsoft Windows and Visual Studio related system inormations.
+ Microsoft Windows and Visual Studio related system information.
Parameters
----------
@@ -474,30 +531,52 @@ class SystemInfo:
"""
# Variables and properties in this class use originals CamelCase variables
- # names from Microsoft source files for more easy comparaison.
- WinDir = safe_env.get('WinDir', '')
- ProgramFiles = safe_env.get('ProgramFiles', '')
- ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles)
+ # names from Microsoft source files for more easy comparison.
+ WinDir = environ.get('WinDir', '')
+ ProgramFiles = environ.get('ProgramFiles', '')
+ ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
def __init__(self, registry_info, vc_ver=None):
self.ri = registry_info
self.pi = self.ri.pi
- 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)
+ self.known_vs_paths = self.find_programdata_vs_vers()
+
+ # Except for VS15+, VC version is aligned with VS version
+ self.vs_ver = self.vc_ver = (
+ vc_ver or self._find_latest_available_vs_ver())
+
+ def _find_latest_available_vs_ver(self):
+ """
+ Find the latest VC version
+
+ Return
+ ------
+ float
+ version
+ """
+ reg_vc_vers = self.find_reg_vs_vers()
+
+ if not (reg_vc_vers or self.known_vs_paths):
+ raise distutils.errors.DistutilsPlatformError(
+ 'No Microsoft Visual C++ version found')
+
+ vc_vers = set(reg_vc_vers)
+ vc_vers.update(self.known_vs_paths)
+ return sorted(vc_vers)[-1]
- def find_available_vc_vers(self):
+ def find_reg_vs_vers(self):
"""
- Find all available Microsoft Visual C++ versions.
+ Find Microsoft Visual Studio versions available in registry.
+
+ Return
+ ------
+ list of float
+ Versions
"""
ms = self.ri.microsoft
vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
- vc_vers = []
+ vs_vers = []
for hkey in self.ri.HKEYS:
for key in vckeys:
try:
@@ -508,49 +587,108 @@ class SystemInfo:
for i in range(values):
try:
ver = float(winreg.EnumValue(bkey, i)[0])
- if ver not in vc_vers:
- vc_vers.append(ver)
+ if ver not in vs_vers:
+ vs_vers.append(ver)
except ValueError:
pass
for i in range(subkeys):
try:
ver = float(winreg.EnumKey(bkey, i))
- if ver not in vc_vers:
- vc_vers.append(ver)
+ if ver not in vs_vers:
+ vs_vers.append(ver)
except ValueError:
pass
- return sorted(vc_vers)
+ return sorted(vs_vers)
+
+ def find_programdata_vs_vers(self):
+ r"""
+ Find Visual studio 2017+ versions from information in
+ "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
+
+ Return
+ ------
+ dict
+ float version as key, path as value.
+ """
+ vs_versions = {}
+ instances_dir = \
+ r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
+
+ try:
+ hashed_names = listdir(instances_dir)
+
+ except (OSError, IOError):
+ # Directory not exists with all Visual Studio versions
+ return vs_versions
+
+ for name in hashed_names:
+ try:
+ # Get VS installation path from "state.json" file
+ state_path = join(instances_dir, name, 'state.json')
+ with open(state_path, 'rt', encoding='utf-8') as state_file:
+ state = json.load(state_file)
+ vs_path = state['installationPath']
+
+ # Raises OSError if this VS installation does not contain VC
+ listdir(join(vs_path, r'VC\Tools\MSVC'))
+
+ # Store version and path
+ vs_versions[self._as_float_version(
+ state['installationVersion'])] = vs_path
+
+ except (OSError, IOError, KeyError):
+ # Skip if "state.json" file is missing or bad format
+ continue
+
+ return vs_versions
+
+ @staticmethod
+ def _as_float_version(version):
+ """
+ Return a string version as a simplified float version (major.minor)
+
+ Parameters
+ ----------
+ version: str
+ Version.
+
+ Return
+ ------
+ float
+ version
+ """
+ return float('.'.join(version.split('.')[:2]))
@property
def VSInstallDir(self):
"""
Microsoft Visual Studio directory.
+
+ Return
+ ------
+ str
+ path
"""
# Default path
- name = 'Microsoft Visual Studio %0.1f' % self.vc_ver
- default = os.path.join(self.ProgramFilesx86, name)
+ default = join(self.ProgramFilesx86,
+ 'Microsoft Visual Studio %0.1f' % self.vs_ver)
# Try to get path from registry, if fail use default path
- return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default
+ return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default
@property
def VCInstallDir(self):
"""
Microsoft Visual C++ directory.
- """
- 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)
- python_vc = self.ri.lookup(reg_path, 'installdir')
- default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc
- # Try to get path from registry, if fail use default path
- path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc
+ Return
+ ------
+ str
+ path
+ """
+ path = self._guess_vc() or self._guess_vc_legacy()
- if not os.path.isdir(path):
+ if not isdir(path):
msg = 'Microsoft Visual C++ directory not found'
raise distutils.errors.DistutilsPlatformError(msg)
@@ -558,186 +696,256 @@ class SystemInfo:
def _guess_vc(self):
"""
- Locate Visual C for 2017
+ Locate Visual C++ for VS2017+.
+
+ Return
+ ------
+ str
+ path
"""
- if self.vc_ver <= 14.0:
- return
+ if self.vs_ver <= 14.0:
+ return ''
+
+ try:
+ # First search in known VS paths
+ vs_dir = self.known_vs_paths[self.vs_ver]
+ except KeyError:
+ # Else, search with path from registry
+ vs_dir = self.VSInstallDir
+
+ guess_vc = join(vs_dir, r'VC\Tools\MSVC')
- 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)
+ # Update the VC version with real one instead of VS version
+ vc_ver = listdir(guess_vc)[-1]
+ self.vc_ver = self._as_float_version(vc_ver)
+ return join(guess_vc, vc_ver)
except (OSError, IOError, IndexError):
- pass
+ return ''
def _guess_vc_legacy(self):
"""
- Locate Visual C for versions prior to 2017
+ Locate Visual C++ for versions prior to 2017.
+
+ Return
+ ------
+ str
+ path
"""
- default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver
- return os.path.join(self.ProgramFilesx86, default)
+ default = join(self.ProgramFilesx86,
+ r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver)
+
+ # Try to get "VC++ for Python" path from registry as default path
+ reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver)
+ python_vc = self.ri.lookup(reg_path, 'installdir')
+ default_vc = join(python_vc, 'VC') if python_vc else default
+
+ # Try to get path from registry, if fail use default path
+ return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc
@property
def WindowsSdkVersion(self):
"""
Microsoft 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:
- return ('7.1', '7.0a')
- elif self.vc_ver == 11.0:
- return ('8.0', '8.0a')
- elif self.vc_ver == 12.0:
- return ('8.1', '8.1a')
- elif self.vc_ver >= 14.0:
- return ('10.0', '8.1')
+
+ Return
+ ------
+ tuple of str
+ versions
+ """
+ if self.vs_ver <= 9.0:
+ return '7.0', '6.1', '6.0a'
+ elif self.vs_ver == 10.0:
+ return '7.1', '7.0a'
+ elif self.vs_ver == 11.0:
+ return '8.0', '8.0a'
+ elif self.vs_ver == 12.0:
+ return '8.1', '8.1a'
+ elif self.vs_ver >= 14.0:
+ return '10.0', '8.1'
@property
def WindowsSdkLastVersion(self):
"""
- Microsoft Windows SDK last version
+ Microsoft Windows SDK last version.
+
+ Return
+ ------
+ str
+ version
"""
- return self._use_last_dir_name(os.path.join(
- self.WindowsSdkDir, 'lib'))
+ return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
@property
def WindowsSdkDir(self):
"""
Microsoft Windows SDK directory.
+
+ Return
+ ------
+ str
+ path
"""
sdkdir = ''
for ver in self.WindowsSdkVersion:
# Try to get it from registry
- loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver)
+ loc = join(self.ri.windows_sdk, 'v%s' % ver)
sdkdir = self.ri.lookup(loc, 'installationfolder')
if sdkdir:
break
- if not sdkdir or not os.path.isdir(sdkdir):
+ if not sdkdir or not isdir(sdkdir):
# Try to get "VC++ for Python" version from registry
- path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
+ path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
install_base = self.ri.lookup(path, 'installdir')
if install_base:
- sdkdir = os.path.join(install_base, 'WinSDK')
- if not sdkdir or not os.path.isdir(sdkdir):
+ sdkdir = join(install_base, 'WinSDK')
+ if not sdkdir or not isdir(sdkdir):
# If fail, use default new path
for ver in self.WindowsSdkVersion:
intver = ver[:ver.rfind('.')]
- path = r'Microsoft SDKs\Windows Kits\%s' % (intver)
- d = os.path.join(self.ProgramFiles, path)
- if os.path.isdir(d):
+ path = r'Microsoft SDKs\Windows Kits\%s' % intver
+ d = join(self.ProgramFiles, path)
+ if isdir(d):
sdkdir = d
- if not sdkdir or not os.path.isdir(sdkdir):
+ if not sdkdir or not isdir(sdkdir):
# If fail, use default old path
for ver in self.WindowsSdkVersion:
path = r'Microsoft SDKs\Windows\v%s' % ver
- d = os.path.join(self.ProgramFiles, path)
- if os.path.isdir(d):
+ d = join(self.ProgramFiles, path)
+ if isdir(d):
sdkdir = d
if not sdkdir:
# If fail, use Platform SDK
- sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK')
+ sdkdir = join(self.VCInstallDir, 'PlatformSDK')
return sdkdir
@property
def WindowsSDKExecutablePath(self):
"""
Microsoft Windows SDK executable directory.
+
+ Return
+ ------
+ str
+ path
"""
# Find WinSDK NetFx Tools registry dir name
- if self.vc_ver <= 11.0:
+ if self.vs_ver <= 11.0:
netfxver = 35
arch = ''
else:
netfxver = 40
- hidex86 = True if self.vc_ver <= 12.0 else False
+ hidex86 = True if self.vs_ver <= 12.0 else False
arch = self.pi.current_dir(x64=True, hidex86=hidex86)
fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-'))
- # liste all possibles registry paths
+ # list all possibles registry paths
regpaths = []
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
for ver in self.NetFxSdkVersion:
- regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)]
+ regpaths += [join(self.ri.netfx_sdk, ver, fx)]
for ver in self.WindowsSdkVersion:
- regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
+ regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
# Return installation folder from the more recent path
for path in regpaths:
execpath = self.ri.lookup(path, 'installationfolder')
if execpath:
- break
- return execpath
+ return execpath
@property
def FSharpInstallDir(self):
"""
Microsoft Visual F# directory.
+
+ Return
+ ------
+ str
+ path
"""
- path = r'%0.1f\Setup\F#' % self.vc_ver
- path = os.path.join(self.ri.visualstudio, path)
+ path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver)
return self.ri.lookup(path, 'productdir') or ''
@property
def UniversalCRTSdkDir(self):
"""
Microsoft Universal CRT SDK directory.
+
+ Return
+ ------
+ str
+ path
"""
# Set Kit Roots versions for specified MSVC++ version
- if self.vc_ver >= 14.0:
- vers = ('10', '81')
- else:
- vers = ()
+ vers = ('10', '81') if self.vs_ver >= 14.0 else ()
# Find path of the more recent Kit
for ver in vers:
sdkdir = self.ri.lookup(self.ri.windows_kits_roots,
'kitsroot%s' % ver)
if sdkdir:
- break
- return sdkdir or ''
+ return sdkdir or ''
@property
def UniversalCRTSdkLastVersion(self):
"""
- Microsoft Universal C Runtime SDK last version
+ Microsoft Universal C Runtime SDK last version.
+
+ Return
+ ------
+ str
+ version
"""
- return self._use_last_dir_name(os.path.join(
- self.UniversalCRTSdkDir, 'lib'))
+ return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib'))
@property
def NetFxSdkVersion(self):
"""
Microsoft .NET Framework SDK versions.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
- # Set FxSdk versions for specified MSVC++ version
- if self.vc_ver >= 14.0:
- return ('4.6.1', '4.6')
- else:
- return ()
+ # Set FxSdk versions for specified VS version
+ return (('4.7.2', '4.7.1', '4.7',
+ '4.6.2', '4.6.1', '4.6',
+ '4.5.2', '4.5.1', '4.5')
+ if self.vs_ver >= 14.0 else ())
@property
def NetFxSdkDir(self):
"""
Microsoft .NET Framework SDK directory.
+
+ Return
+ ------
+ str
+ path
"""
+ sdkdir = ''
for ver in self.NetFxSdkVersion:
- loc = os.path.join(self.ri.netfx_sdk, ver)
+ loc = join(self.ri.netfx_sdk, ver)
sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
if sdkdir:
break
- return sdkdir or ''
+ return sdkdir
@property
def FrameworkDir32(self):
"""
Microsoft .NET Framework 32bit directory.
+
+ Return
+ ------
+ str
+ path
"""
# Default path
- guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework')
+ guess_fw = join(self.WinDir, r'Microsoft.NET\Framework')
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
@@ -746,9 +954,14 @@ class SystemInfo:
def FrameworkDir64(self):
"""
Microsoft .NET Framework 64bit directory.
+
+ Return
+ ------
+ str
+ path
"""
# Default path
- guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64')
+ guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64')
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
@@ -757,6 +970,11 @@ class SystemInfo:
def FrameworkVersion32(self):
"""
Microsoft .NET Framework 32bit versions.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
return self._find_dot_net_versions(32)
@@ -764,6 +982,11 @@ class SystemInfo:
def FrameworkVersion64(self):
"""
Microsoft .NET Framework 64bit versions.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
return self._find_dot_net_versions(64)
@@ -775,6 +998,11 @@ class SystemInfo:
----------
bits: int
Platform number of bits: 32 or 64.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
# Find actual .NET version in registry
reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits)
@@ -782,18 +1010,17 @@ class SystemInfo:
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:
- frameworkver = (ver, 'v4.0')
- elif self.vc_ver >= 10.0:
- frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver,
- 'v3.5')
- elif self.vc_ver == 9.0:
- frameworkver = ('v3.5', 'v2.0.50727')
- if self.vc_ver == 8.0:
- frameworkver = ('v3.0', 'v2.0.50727')
- return frameworkver
-
- def _use_last_dir_name(self, path, prefix=''):
+ if self.vs_ver >= 12.0:
+ return ver, 'v4.0'
+ elif self.vs_ver >= 10.0:
+ return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'
+ elif self.vs_ver == 9.0:
+ return 'v3.5', 'v2.0.50727'
+ elif self.vs_ver == 8.0:
+ return 'v3.0', 'v2.0.50727'
+
+ @staticmethod
+ def _use_last_dir_name(path, prefix=''):
"""
Return name of the last dir in path or '' if no dir found.
@@ -802,12 +1029,17 @@ class SystemInfo:
path: str
Use dirs in this path
prefix: str
- Use only dirs startings by this prefix
+ Use only dirs starting by this prefix
+
+ Return
+ ------
+ str
+ name
"""
matching_dirs = (
dir_name
- for dir_name in reversed(os.listdir(path))
- if os.path.isdir(os.path.join(path, dir_name)) and
+ for dir_name in reversed(listdir(path))
+ if isdir(join(path, dir_name)) and
dir_name.startswith(prefix)
)
return next(matching_dirs, None) or ''
@@ -818,7 +1050,7 @@ class EnvironmentInfo:
Return environment variables for specified Microsoft Visual C++ version
and platform : Lib, Include, Path and libpath.
- This function is compatible with Microsoft Visual C++ 9.0 to 14.0.
+ This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
Script created by analysing Microsoft environment configuration files like
"vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
@@ -835,7 +1067,7 @@ class EnvironmentInfo:
"""
# Variables and properties in this class use originals CamelCase variables
- # names from Microsoft source files for more easy comparaison.
+ # names from Microsoft source files for more easy comparison.
def __init__(self, arch, vc_ver=None, vc_min_ver=0):
self.pi = PlatformInfo(arch)
@@ -847,204 +1079,254 @@ class EnvironmentInfo:
raise distutils.errors.DistutilsPlatformError(err)
@property
+ def vs_ver(self):
+ """
+ Microsoft Visual Studio.
+
+ Return
+ ------
+ float
+ version
+ """
+ return self.si.vs_ver
+
+ @property
def vc_ver(self):
"""
Microsoft Visual C++ version.
+
+ Return
+ ------
+ float
+ version
"""
return self.si.vc_ver
@property
def VSTools(self):
"""
- Microsoft Visual Studio Tools
+ Microsoft Visual Studio Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
paths = [r'Common7\IDE', r'Common7\Tools']
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
paths += [r'Team Tools\Performance Tools']
paths += [r'Team Tools\Performance Tools%s' % arch_subdir]
- return [os.path.join(self.si.VSInstallDir, path) for path in paths]
+ return [join(self.si.VSInstallDir, path) for path in paths]
@property
def VCIncludes(self):
"""
- Microsoft Visual C++ & Microsoft Foundation Class Includes
+ Microsoft Visual C++ & Microsoft Foundation Class Includes.
+
+ Return
+ ------
+ list of str
+ paths
"""
- return [os.path.join(self.si.VCInstallDir, 'Include'),
- os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')]
+ return [join(self.si.VCInstallDir, 'Include'),
+ join(self.si.VCInstallDir, r'ATLMFC\Include')]
@property
def VCLibraries(self):
"""
- Microsoft Visual C++ & Microsoft Foundation Class Libraries
+ Microsoft Visual C++ & Microsoft Foundation Class Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver >= 15.0:
+ if self.vs_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:
+ if self.vs_ver >= 14.0:
paths += [r'Lib\store%s' % arch_subdir]
- return [os.path.join(self.si.VCInstallDir, path) for path in paths]
+ return [join(self.si.VCInstallDir, path) for path in paths]
@property
def VCStoreRefs(self):
"""
- Microsoft Visual C++ store references Libraries
+ Microsoft Visual C++ store references Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0:
+ if self.vs_ver < 14.0:
return []
- return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')]
+ return [join(self.si.VCInstallDir, r'Lib\store\references')]
@property
def VCTools(self):
"""
- Microsoft Visual C++ Tools
+ Microsoft Visual C++ Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
si = self.si
- tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
+ tools = [join(si.VCInstallDir, 'VCPackages')]
- forcex86 = True if self.vc_ver <= 10.0 else False
+ forcex86 = True if self.vs_ver <= 10.0 else False
arch_subdir = self.pi.cross_dir(forcex86)
if arch_subdir:
- tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
+ tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
- if self.vc_ver == 14.0:
+ if self.vs_ver == 14.0:
path = 'Bin%s' % self.pi.current_dir(hidex86=True)
- tools += [os.path.join(si.VCInstallDir, path)]
+ tools += [join(si.VCInstallDir, path)]
- elif self.vc_ver >= 15.0:
+ elif self.vs_ver >= 15.0:
host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else
r'bin\HostX64%s')
- tools += [os.path.join(
+ tools += [join(
si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))]
if self.pi.current_cpu != self.pi.target_cpu:
- tools += [os.path.join(
+ tools += [join(
si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))]
else:
- tools += [os.path.join(si.VCInstallDir, 'Bin')]
+ tools += [join(si.VCInstallDir, 'Bin')]
return tools
@property
def OSLibraries(self):
"""
- Microsoft Windows SDK Libraries
+ Microsoft Windows SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver <= 10.0:
+ if self.vs_ver <= 10.0:
arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
- return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
+ return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
else:
arch_subdir = self.pi.target_dir(x64=True)
- lib = os.path.join(self.si.WindowsSdkDir, 'lib')
+ lib = join(self.si.WindowsSdkDir, 'lib')
libver = self._sdk_subdir
- return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))]
+ return [join(lib, '%sum%s' % (libver , arch_subdir))]
@property
def OSIncludes(self):
"""
- Microsoft Windows SDK Include
+ Microsoft Windows SDK Include.
+
+ Return
+ ------
+ list of str
+ paths
"""
- include = os.path.join(self.si.WindowsSdkDir, 'include')
+ include = join(self.si.WindowsSdkDir, 'include')
- if self.vc_ver <= 10.0:
- return [include, os.path.join(include, 'gl')]
+ if self.vs_ver <= 10.0:
+ return [include, join(include, 'gl')]
else:
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
sdkver = self._sdk_subdir
else:
sdkver = ''
- return [os.path.join(include, '%sshared' % sdkver),
- os.path.join(include, '%sum' % sdkver),
- os.path.join(include, '%swinrt' % sdkver)]
+ return [join(include, '%sshared' % sdkver),
+ join(include, '%sum' % sdkver),
+ join(include, '%swinrt' % sdkver)]
@property
def OSLibpath(self):
"""
- Microsoft Windows SDK Libraries Paths
+ Microsoft Windows SDK Libraries Paths.
+
+ Return
+ ------
+ list of str
+ paths
"""
- ref = os.path.join(self.si.WindowsSdkDir, 'References')
+ ref = join(self.si.WindowsSdkDir, 'References')
libpath = []
- if self.vc_ver <= 9.0:
+ if self.vs_ver <= 9.0:
libpath += self.OSLibraries
- if self.vc_ver >= 11.0:
- libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
+ if self.vs_ver >= 11.0:
+ libpath += [join(ref, r'CommonConfiguration\Neutral')]
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
libpath += [
ref,
- os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
- os.path.join(
- ref,
- 'Windows.Foundation.UniversalApiContract',
- '1.0.0.0',
- ),
- os.path.join(
- ref,
- 'Windows.Foundation.FoundationContract',
- '1.0.0.0',
- ),
- os.path.join(
- ref,
- 'Windows.Networking.Connectivity.WwanContract',
- '1.0.0.0',
- ),
- os.path.join(
- self.si.WindowsSdkDir,
- 'ExtensionSDKs',
- 'Microsoft.VCLibs',
- '%0.1f' % self.vc_ver,
- 'References',
- 'CommonConfiguration',
- 'neutral',
- ),
+ join(self.si.WindowsSdkDir, 'UnionMetadata'),
+ join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
+ join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
+ join(ref,'Windows.Networking.Connectivity.WwanContract',
+ '1.0.0.0'),
+ join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs',
+ '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration',
+ 'neutral'),
]
return libpath
@property
def SdkTools(self):
"""
- Microsoft Windows SDK Tools
+ Microsoft Windows SDK Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
return list(self._sdk_tools())
def _sdk_tools(self):
"""
- Microsoft Windows SDK Tools paths generator
+ Microsoft Windows SDK Tools paths generator.
+
+ Return
+ ------
+ generator of str
+ paths
"""
- 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 self.vs_ver < 15.0:
+ bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
+ yield 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
- yield os.path.join(self.si.WindowsSdkDir, path)
+ yield join(self.si.WindowsSdkDir, path)
- if self.vc_ver == 10.0 or self.vc_ver == 11.0:
+ if self.vs_ver in (10.0, 11.0):
if self.pi.target_is_x86():
arch_subdir = ''
else:
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir
- yield os.path.join(self.si.WindowsSdkDir, path)
+ yield join(self.si.WindowsSdkDir, path)
- elif self.vc_ver >= 15.0:
- path = os.path.join(self.si.WindowsSdkDir, 'Bin')
+ elif self.vs_ver >= 15.0:
+ 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))
+ yield join(path, '%s%s' % (sdkver, arch_subdir))
if self.si.WindowsSDKExecutablePath:
yield self.si.WindowsSDKExecutablePath
@@ -1052,7 +1334,12 @@ class EnvironmentInfo:
@property
def _sdk_subdir(self):
"""
- Microsoft Windows SDK version subdir
+ Microsoft Windows SDK version subdir.
+
+ Return
+ ------
+ str
+ subdir
"""
ucrtver = self.si.WindowsSdkLastVersion
return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1060,22 +1347,32 @@ class EnvironmentInfo:
@property
def SdkSetup(self):
"""
- Microsoft Windows SDK Setup
+ Microsoft Windows SDK Setup.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver > 9.0:
+ if self.vs_ver > 9.0:
return []
- return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
+ return [join(self.si.WindowsSdkDir, 'Setup')]
@property
def FxTools(self):
"""
- Microsoft .NET Framework Tools
+ Microsoft .NET Framework Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
pi = self.pi
si = self.si
- if self.vc_ver <= 10.0:
+ if self.vs_ver <= 10.0:
include32 = True
include64 = not pi.target_is_x86() and not pi.current_is_x86()
else:
@@ -1084,102 +1381,142 @@ class EnvironmentInfo:
tools = []
if include32:
- tools += [os.path.join(si.FrameworkDir32, ver)
+ tools += [join(si.FrameworkDir32, ver)
for ver in si.FrameworkVersion32]
if include64:
- tools += [os.path.join(si.FrameworkDir64, ver)
+ tools += [join(si.FrameworkDir64, ver)
for ver in si.FrameworkVersion64]
return tools
@property
def NetFxSDKLibraries(self):
"""
- Microsoft .Net Framework SDK Libraries
+ Microsoft .Net Framework SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
+ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
return []
arch_subdir = self.pi.target_dir(x64=True)
- return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
+ return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
@property
def NetFxSDKIncludes(self):
"""
- Microsoft .Net Framework SDK Includes
+ Microsoft .Net Framework SDK Includes.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
+ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
return []
- return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
+ return [join(self.si.NetFxSdkDir, r'include\um')]
@property
def VsTDb(self):
"""
- Microsoft Visual Studio Team System Database
+ Microsoft Visual Studio Team System Database.
+
+ Return
+ ------
+ list of str
+ paths
"""
- return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
+ return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
@property
def MSBuild(self):
"""
- Microsoft Build Engine
+ Microsoft Build Engine.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 12.0:
+ if self.vs_ver < 12.0:
return []
- elif self.vc_ver < 15.0:
+ elif self.vs_ver < 15.0:
base_path = self.si.ProgramFilesx86
arch_subdir = self.pi.current_dir(hidex86=True)
else:
base_path = self.si.VSInstallDir
arch_subdir = ''
- path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir)
- build = [os.path.join(base_path, path)]
+ path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir)
+ build = [join(base_path, path)]
- if self.vc_ver >= 15.0:
+ if self.vs_ver >= 15.0:
# Add Roslyn C# & Visual Basic Compiler
- build += [os.path.join(base_path, path, 'Roslyn')]
+ build += [join(base_path, path, 'Roslyn')]
return build
@property
def HTMLHelpWorkshop(self):
"""
- Microsoft HTML Help Workshop
+ Microsoft HTML Help Workshop.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 11.0:
+ if self.vs_ver < 11.0:
return []
- return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
+ return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
@property
def UCRTLibraries(self):
"""
- Microsoft Universal C Runtime SDK Libraries
+ Microsoft Universal C Runtime SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0:
+ if self.vs_ver < 14.0:
return []
arch_subdir = self.pi.target_dir(x64=True)
- lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib')
+ lib = join(self.si.UniversalCRTSdkDir, 'lib')
ucrtver = self._ucrt_subdir
- return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
+ return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
@property
def UCRTIncludes(self):
"""
- Microsoft Universal C Runtime SDK Include
+ Microsoft Universal C Runtime SDK Include.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0:
+ if self.vs_ver < 14.0:
return []
- include = os.path.join(self.si.UniversalCRTSdkDir, 'include')
- return [os.path.join(include, '%sucrt' % self._ucrt_subdir)]
+ include = join(self.si.UniversalCRTSdkDir, 'include')
+ return [join(include, '%sucrt' % self._ucrt_subdir)]
@property
def _ucrt_subdir(self):
"""
- Microsoft Universal C Runtime SDK version subdir
+ Microsoft Universal C Runtime SDK version subdir.
+
+ Return
+ ------
+ str
+ subdir
"""
ucrtver = self.si.UniversalCRTSdkLastVersion
return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1187,31 +1524,52 @@ class EnvironmentInfo:
@property
def FSharp(self):
"""
- Microsoft Visual F#
+ Microsoft Visual F#.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 11.0 and self.vc_ver > 12.0:
+ if 11.0 > self.vs_ver > 12.0:
return []
- return self.si.FSharpInstallDir
+ return [self.si.FSharpInstallDir]
@property
def VCRuntimeRedist(self):
"""
- Microsoft Visual C++ runtime redistribuable dll
- """
- arch_subdir = self.pi.target_dir(x64=True)
- 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
+ Microsoft Visual C++ runtime redistributable dll.
- vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver)
- return os.path.join(redist_path, vcruntime)
+ Return
+ ------
+ str
+ path
+ """
+ vcruntime = 'vcruntime%d0.dll' % self.vc_ver
+ arch_subdir = self.pi.target_dir(x64=True).strip('\\')
+
+ # Installation prefixes candidates
+ prefixes = []
+ tools_path = self.si.VCInstallDir
+ redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist'))
+ if isdir(redist_path):
+ # Redist version may not be exactly the same as tools
+ redist_path = join(redist_path, listdir(redist_path)[-1])
+ prefixes += [redist_path, join(redist_path, 'onecore')]
+
+ prefixes += [join(tools_path, 'redist')] # VS14 legacy path
+
+ # CRT directory
+ crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10),
+ # Sometime store in directory with VS version instead of VC
+ 'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10))
+
+ # vcruntime path
+ for prefix, crt_dir in itertools.product(prefixes, crt_dirs):
+ path = join(prefix, arch_subdir, crt_dir, vcruntime)
+ if isfile(path):
+ return path
def return_env(self, exists=True):
"""
@@ -1221,6 +1579,11 @@ class EnvironmentInfo:
----------
exists: bool
It True, only return existing paths.
+
+ Return
+ ------
+ dict
+ environment
"""
env = dict(
include=self._build_paths('include',
@@ -1254,7 +1617,7 @@ class EnvironmentInfo:
self.FSharp],
exists),
)
- if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist):
+ if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist):
env['py_vcruntime_redist'] = self.VCRuntimeRedist
return env
@@ -1265,20 +1628,35 @@ class EnvironmentInfo:
unique, extant, directories from those paths and from
the environment variable. Raise an error if no paths
are resolved.
+
+ Parameters
+ ----------
+ name: str
+ Environment variable name
+ spec_path_lists: list of str
+ Paths
+ exists: bool
+ It True, only return existing paths.
+
+ Return
+ ------
+ str
+ Pathsep-separated paths
"""
# flatten spec_path_lists
spec_paths = itertools.chain.from_iterable(spec_path_lists)
- env_paths = safe_env.get(name, '').split(os.pathsep)
+ env_paths = environ.get(name, '').split(pathsep)
paths = itertools.chain(spec_paths, env_paths)
- extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
+ extant_paths = list(filter(isdir, paths)) if exists else paths
if not extant_paths:
msg = "%s environment variable is empty" % name.upper()
raise distutils.errors.DistutilsPlatformError(msg)
unique_paths = self._unique_everseen(extant_paths)
- return os.pathsep.join(unique_paths)
+ return pathsep.join(unique_paths)
# from Python docs
- def _unique_everseen(self, iterable, key=None):
+ @staticmethod
+ def _unique_everseen(iterable, key=None):
"""
List unique elements, preserving order.
Remember all elements ever seen.
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index 6b06f2ca..f419d471 100644
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -46,7 +46,7 @@ __all__ = [
_SOCKET_TIMEOUT = 15
_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}"
-user_agent = _tmpl.format(py_major=sys.version[:3], setuptools=setuptools)
+user_agent = _tmpl.format(py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools)
def parse_requirement_arg(spec):
diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py
index 2985011b..1d57360f 100644
--- a/setuptools/py27compat.py
+++ b/setuptools/py27compat.py
@@ -2,6 +2,7 @@
Compatibility Support for Python 2.7 and earlier
"""
+import sys
import platform
from setuptools.extern import six
@@ -26,3 +27,34 @@ linux_py2_ascii = (
rmtree_safe = str if linux_py2_ascii else lambda x: x
"""Workaround for http://bugs.python.org/issue24672"""
+
+
+try:
+ from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
+ from ._imp import get_frozen_object, get_module
+except ImportError:
+ import imp
+ from imp import PY_COMPILED, PY_FROZEN, PY_SOURCE # noqa
+
+ def find_module(module, paths=None):
+ """Just like 'imp.find_module()', but with package support"""
+ parts = module.split('.')
+ while parts:
+ part = parts.pop(0)
+ f, path, (suffix, mode, kind) = info = imp.find_module(part, paths)
+
+ if kind == imp.PKG_DIRECTORY:
+ parts = parts or ['__init__']
+ paths = [path]
+
+ elif parts:
+ raise ImportError("Can't find %r in %s" % (parts, module))
+
+ return info
+
+ def get_frozen_object(module, paths):
+ return imp.get_frozen_object(module)
+
+ def get_module(module, paths, info):
+ imp.load_module(module, *info)
+ return sys.modules[module]
diff --git a/setuptools/py34compat.py b/setuptools/py34compat.py
new file mode 100644
index 00000000..3ad91722
--- /dev/null
+++ b/setuptools/py34compat.py
@@ -0,0 +1,13 @@
+import importlib
+
+try:
+ import importlib.util
+except ImportError:
+ pass
+
+
+try:
+ module_from_spec = importlib.util.module_from_spec
+except AttributeError:
+ def module_from_spec(spec):
+ return spec.loader.load_module(spec.name)
diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
index 54742aa6..fb5b90b1 100644
--- a/setuptools/tests/test_bdist_egg.py
+++ b/setuptools/tests/test_bdist_egg.py
@@ -42,7 +42,7 @@ class Test:
# let's see if we got our egg link at the right place
[content] = os.listdir('dist')
- assert re.match(r'foo-0.0.0-py[23].\d.egg$', content)
+ assert re.match(r'foo-0.0.0-py[23].\d+.egg$', content)
@pytest.mark.xfail(
os.environ.get('PYTHONDONTWRITEBYTECODE'),
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 1b94a586..69d8d00d 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -1,4 +1,4 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import contextlib
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 1c0b2b18..f1a27f8b 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -64,7 +64,7 @@ def install_context(request, tmpdir, monkeypatch):
monkeypatch.setattr('site.USER_BASE', user_base.strpath)
monkeypatch.setattr('site.USER_SITE', user_site.strpath)
monkeypatch.setattr('sys.path', sys.path + [install_dir.strpath])
- monkeypatch.setenv('PYTHONPATH', os.path.pathsep.join(sys.path))
+ monkeypatch.setenv(str('PYTHONPATH'), str(os.path.pathsep.join(sys.path)))
# Set up the command for performing the installation.
dist = Distribution()
diff --git a/setuptools/tests/test_register.py b/setuptools/tests/test_register.py
index 96114595..98605806 100644
--- a/setuptools/tests/test_register.py
+++ b/setuptools/tests/test_register.py
@@ -1,43 +1,22 @@
-import mock
-from distutils import log
-
-import pytest
-
from setuptools.command.register import register
from setuptools.dist import Distribution
+from setuptools.errors import RemovedCommandError
+try:
+ from unittest import mock
+except ImportError:
+ import mock
-class TestRegisterTest:
- def test_warns_deprecation(self):
- dist = Distribution()
-
- cmd = register(dist)
- cmd.run_command = mock.Mock()
- cmd.send_metadata = mock.Mock()
- cmd.announce = mock.Mock()
-
- cmd.run()
+import pytest
- cmd.announce.assert_called_with(
- "WARNING: Registering is deprecated, use twine to upload instead "
- "(https://pypi.org/p/twine/)",
- log.WARN
- )
- def test_warns_deprecation_when_raising(self):
+class TestRegister:
+ def test_register_exception(self):
+ """Ensure that the register command has been properly removed."""
dist = Distribution()
+ dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
cmd = register(dist)
- cmd.run_command = mock.Mock()
- cmd.send_metadata = mock.Mock()
- cmd.send_metadata.side_effect = Exception
- cmd.announce = mock.Mock()
- with pytest.raises(Exception):
+ with pytest.raises(RemovedCommandError):
cmd.run()
-
- cmd.announce.assert_called_with(
- "WARNING: Registering is deprecated, use twine to upload instead "
- "(https://pypi.org/p/twine/)",
- log.WARN
- )
diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py
index faaa6ba9..6242a018 100644
--- a/setuptools/tests/test_test.py
+++ b/setuptools/tests/test_test.py
@@ -1,7 +1,8 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import mock
from distutils import log
import os
@@ -85,9 +86,7 @@ def test_test(capfd):
dist.script_name = 'setup.py'
cmd = test(dist)
cmd.ensure_finalized()
- # The test runner calls sys.exit
- with contexts.suppress_exceptions(SystemExit):
- cmd.run()
+ cmd.run()
out, err = capfd.readouterr()
assert out == 'Foo\n'
@@ -119,8 +118,55 @@ def test_tests_are_run_once(capfd):
dist.script_name = 'setup.py'
cmd = test(dist)
cmd.ensure_finalized()
- # The test runner calls sys.exit
- with contexts.suppress_exceptions(SystemExit):
- cmd.run()
+ cmd.run()
out, err = capfd.readouterr()
assert out == 'Foo\n'
+
+
+@pytest.mark.usefixtures('sample_test')
+def test_warns_deprecation(capfd):
+ params = dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True
+ )
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ cmd.announce = mock.Mock()
+ cmd.run()
+ capfd.readouterr()
+ msg = (
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox."
+ )
+ cmd.announce.assert_any_call(msg, log.WARN)
+
+
+@pytest.mark.usefixtures('sample_test')
+def test_deprecation_stderr(capfd):
+ params = dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True
+ )
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ cmd.run()
+ out, err = capfd.readouterr()
+ msg = (
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox.\n"
+ )
+ assert msg in err
diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py
index 320c6959..7586cb26 100644
--- a/setuptools/tests/test_upload.py
+++ b/setuptools/tests/test_upload.py
@@ -1,213 +1,22 @@
-import mock
-import os
-import re
-
-from distutils import log
-from distutils.errors import DistutilsError
-
-import pytest
-
from setuptools.command.upload import upload
from setuptools.dist import Distribution
-from setuptools.extern import six
-
-
-def _parse_upload_body(body):
- boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- entries = []
- name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"')
-
- for entry in body.split(boundary):
- pair = entry.split(u'\r\n\r\n')
- if not len(pair) == 2:
- continue
-
- key, value = map(six.text_type.strip, pair)
- m = name_re.match(key)
- if m is not None:
- key = m.group(1)
-
- entries.append((key, value))
-
- return entries
-
-
-@pytest.fixture
-def patched_upload(tmpdir):
- class Fix:
- def __init__(self, cmd, urlopen):
- self.cmd = cmd
- self.urlopen = urlopen
-
- def __iter__(self):
- return iter((self.cmd, self.urlopen))
-
- def get_uploaded_metadata(self):
- request = self.urlopen.call_args_list[0][0][0]
- body = request.data.decode('utf-8')
- entries = dict(_parse_upload_body(body))
-
- return entries
+from setuptools.errors import RemovedCommandError
- class ResponseMock(mock.Mock):
- def getheader(self, name, default=None):
- """Mocked getheader method for response object"""
- return {
- 'content-type': 'text/plain; charset=utf-8',
- }.get(name.lower(), default)
+try:
+ from unittest import mock
+except ImportError:
+ import mock
- with mock.patch('setuptools.command.upload.urlopen') as urlopen:
- urlopen.return_value = ResponseMock()
- urlopen.return_value.getcode.return_value = 200
- urlopen.return_value.read.return_value = b''
-
- content = os.path.join(str(tmpdir), "content_data")
-
- with open(content, 'w') as f:
- f.write("Some content")
-
- dist = Distribution()
- dist.dist_files = [('sdist', '3.7.0', content)]
-
- cmd = upload(dist)
- cmd.announce = mock.Mock()
- cmd.username = 'user'
- cmd.password = 'hunter2'
-
- yield Fix(cmd, urlopen)
-
-
-class TestUploadTest:
- def test_upload_metadata(self, patched_upload):
- cmd, patch = patched_upload
-
- # Set the metadata version to 2.1
- cmd.distribution.metadata.metadata_version = '2.1'
-
- # Run the command
- cmd.ensure_finalized()
- cmd.run()
-
- # Make sure we did the upload
- patch.assert_called_once()
-
- # Make sure the metadata version is correct in the headers
- entries = patched_upload.get_uploaded_metadata()
- assert entries['metadata_version'] == '2.1'
-
- def test_warns_deprecation(self):
- dist = Distribution()
- dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
-
- cmd = upload(dist)
- cmd.upload_file = mock.Mock()
- cmd.announce = mock.Mock()
-
- cmd.run()
+import pytest
- cmd.announce.assert_called_once_with(
- "WARNING: Uploading via this command is deprecated, use twine to "
- "upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
- def test_warns_deprecation_when_raising(self):
+class TestUpload:
+ def test_upload_exception(self):
+ """Ensure that the register command has been properly removed."""
dist = Distribution()
dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
cmd = upload(dist)
- cmd.upload_file = mock.Mock()
- cmd.upload_file.side_effect = Exception
- cmd.announce = mock.Mock()
-
- with pytest.raises(Exception):
- cmd.run()
-
- cmd.announce.assert_called_once_with(
- "WARNING: Uploading via this command is deprecated, use twine to "
- "upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
-
- @pytest.mark.parametrize('url', [
- 'https://example.com/a;parameter', # Has parameters
- 'https://example.com/a?query', # Has query
- 'https://example.com/a#fragment', # Has fragment
- 'ftp://example.com', # Invalid scheme
-
- ])
- def test_upload_file_invalid_url(self, url, patched_upload):
- patched_upload.urlopen.side_effect = Exception("Should not be reached")
-
- cmd = patched_upload.cmd
- cmd.repository = url
-
- cmd.ensure_finalized()
- with pytest.raises(AssertionError):
- cmd.run()
-
- def test_upload_file_http_error(self, patched_upload):
- patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError(
- 'https://example.com',
- 404,
- 'File not found',
- None,
- None
- )
-
- cmd = patched_upload.cmd
- cmd.ensure_finalized()
- with pytest.raises(DistutilsError):
+ with pytest.raises(RemovedCommandError):
cmd.run()
-
- cmd.announce.assert_any_call(
- 'Upload failed (404): File not found',
- log.ERROR)
-
- def test_upload_file_os_error(self, patched_upload):
- patched_upload.urlopen.side_effect = OSError("Invalid")
-
- cmd = patched_upload.cmd
- cmd.ensure_finalized()
-
- with pytest.raises(OSError):
- cmd.run()
-
- cmd.announce.assert_any_call('Invalid', log.ERROR)
-
- @mock.patch('setuptools.command.upload.spawn')
- def test_upload_file_gpg(self, spawn, patched_upload):
- cmd, urlopen = patched_upload
-
- cmd.sign = True
- cmd.identity = "Alice"
- cmd.dry_run = True
- content_fname = cmd.distribution.dist_files[0][2]
- signed_file = content_fname + '.asc'
-
- with open(signed_file, 'wb') as f:
- f.write("signed-data".encode('utf-8'))
-
- cmd.ensure_finalized()
- cmd.run()
-
- # Make sure that GPG was called
- spawn.assert_called_once_with([
- "gpg", "--detach-sign", "--local-user", "Alice", "-a",
- content_fname
- ], dry_run=True)
-
- # Read the 'signed' data that was transmitted
- entries = patched_upload.get_uploaded_metadata()
- assert entries['gpg_signature'] == 'signed-data'
-
- def test_show_response_no_error(self, patched_upload):
- # This test is just that show_response doesn't throw an error
- # It is not really important what the printed response looks like
- # in a deprecated command, but we don't want to introduce new
- # errors when importing this function from distutils
-
- patched_upload.cmd.show_response = True
- patched_upload.cmd.ensure_finalized()
- patched_upload.cmd.run()
diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py
index e85a4a7e..d50816c2 100644
--- a/setuptools/tests/test_wheel.py
+++ b/setuptools/tests/test_wheel.py
@@ -451,6 +451,34 @@ WHEEL_INSTALL_TESTS = (
),
dict(
+ id='empty_namespace_package',
+ file_defs={
+ 'foobar': {
+ '__init__.py': "__import__('pkg_resources').declare_namespace(__name__)",
+ },
+ },
+ setup_kwargs=dict(
+ namespace_packages=['foobar'],
+ packages=['foobar'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': [
+ 'foo-1.0-py{py_version}-nspkg.pth',
+ {'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'namespace_packages.txt',
+ 'top_level.txt',
+ ]},
+ {'foobar': [
+ '__init__.py',
+ ]},
+ ]
+ }),
+ ),
+
+ dict(
id='data_in_package',
file_defs={
'foo': {
diff --git a/setuptools/wheel.py b/setuptools/wheel.py
index e11f0a1d..22eec05e 100644
--- a/setuptools/wheel.py
+++ b/setuptools/wheel.py
@@ -1,6 +1,7 @@
"""Wheels support."""
from distutils.util import get_platform
+from distutils import log
import email
import itertools
import os
@@ -162,11 +163,17 @@ class Wheel:
extras_require=extras_require,
),
)
- write_requirements(
- setup_dist.get_command_obj('egg_info'),
- None,
- os.path.join(egg_info, 'requires.txt'),
- )
+ # Temporarily disable info traces.
+ log_threshold = log._global_log.threshold
+ log.set_threshold(log.WARN)
+ try:
+ write_requirements(
+ setup_dist.get_command_obj('egg_info'),
+ None,
+ os.path.join(egg_info, 'requires.txt'),
+ )
+ finally:
+ log.set_threshold(log_threshold)
@staticmethod
def _move_data_entries(destination_eggdir, dist_data):
@@ -206,6 +213,8 @@ class Wheel:
for mod in namespace_packages:
mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
mod_init = os.path.join(mod_dir, '__init__.py')
- if os.path.exists(mod_dir) and not os.path.exists(mod_init):
+ if not os.path.exists(mod_dir):
+ os.mkdir(mod_dir)
+ if not os.path.exists(mod_init):
with open(mod_init, 'w') as fp:
fp.write(NAMESPACE_PACKAGE_INIT)