diff --git a/libs/common/bin/beet.exe b/libs/common/bin/easy_install-3.7.exe similarity index 99% rename from libs/common/bin/beet.exe rename to libs/common/bin/easy_install-3.7.exe index 42738327..b32abd20 100644 Binary files a/libs/common/bin/beet.exe and b/libs/common/bin/easy_install-3.7.exe differ diff --git a/libs/common/bin/chardetect.exe b/libs/common/bin/easy_install.exe similarity index 99% rename from libs/common/bin/chardetect.exe rename to libs/common/bin/easy_install.exe index fcb8a3ac..b32abd20 100644 Binary files a/libs/common/bin/chardetect.exe and b/libs/common/bin/easy_install.exe differ diff --git a/libs/common/bin/guessit.exe b/libs/common/bin/guessit.exe deleted file mode 100644 index c8d7abb2..00000000 Binary files a/libs/common/bin/guessit.exe and /dev/null differ diff --git a/libs/common/bin/mid3cp.exe b/libs/common/bin/mid3cp.exe deleted file mode 100644 index fe24f111..00000000 Binary files a/libs/common/bin/mid3cp.exe and /dev/null differ diff --git a/libs/common/bin/mid3iconv.exe b/libs/common/bin/mid3iconv.exe deleted file mode 100644 index 7f67db01..00000000 Binary files a/libs/common/bin/mid3iconv.exe and /dev/null differ diff --git a/libs/common/bin/mid3v2.exe b/libs/common/bin/mid3v2.exe deleted file mode 100644 index 0faee10a..00000000 Binary files a/libs/common/bin/mid3v2.exe and /dev/null differ diff --git a/libs/common/bin/moggsplit.exe b/libs/common/bin/moggsplit.exe deleted file mode 100644 index ca7be52f..00000000 Binary files a/libs/common/bin/moggsplit.exe and /dev/null differ diff --git a/libs/common/bin/mutagen-inspect.exe b/libs/common/bin/mutagen-inspect.exe deleted file mode 100644 index eaf4de02..00000000 Binary files a/libs/common/bin/mutagen-inspect.exe and /dev/null differ diff --git a/libs/common/bin/mutagen-pony.exe b/libs/common/bin/mutagen-pony.exe deleted file mode 100644 index 9d4f4e07..00000000 Binary files a/libs/common/bin/mutagen-pony.exe and /dev/null differ diff --git a/libs/common/bin/unidecode.exe b/libs/common/bin/unidecode.exe deleted file mode 100644 index a488e3d5..00000000 Binary files a/libs/common/bin/unidecode.exe and /dev/null differ diff --git a/libs/common/pkg_resources/__init__.py b/libs/common/pkg_resources/__init__.py index 6ca68daa..2f5aa64a 100644 --- a/libs/common/pkg_resources/__init__.py +++ b/libs/common/pkg_resources/__init__.py @@ -39,6 +39,8 @@ import tempfile import textwrap import itertools import inspect +import ntpath +import posixpath from pkgutil import get_importer try: @@ -86,8 +88,8 @@ __import__('pkg_resources.extern.packaging.markers') __metaclass__ = type -if (3, 0) < sys.version_info < (3, 4): - raise RuntimeError("Python 3.4 or later is required") +if (3, 0) < sys.version_info < (3, 5): + raise RuntimeError("Python 3.5 or later is required") if six.PY2: # Those builtin exceptions are only defined in Python 3 @@ -331,7 +333,7 @@ class UnknownExtra(ResolutionError): _provider_factories = {} -PY_MAJOR = sys.version[:3] +PY_MAJOR = '{}.{}'.format(*sys.version_info) EGG_DIST = 3 BINARY_DIST = 2 SOURCE_DIST = 1 @@ -1401,14 +1403,30 @@ class NullProvider: def has_resource(self, resource_name): return self._has(self._fn(self.module_path, resource_name)) + def _get_metadata_path(self, name): + return self._fn(self.egg_info, name) + def has_metadata(self, name): - return self.egg_info and self._has(self._fn(self.egg_info, name)) + if not self.egg_info: + return self.egg_info + + path = self._get_metadata_path(name) + return self._has(path) def get_metadata(self, name): if not self.egg_info: return "" - value = self._get(self._fn(self.egg_info, name)) - return value.decode('utf-8') if six.PY3 else value + path = self._get_metadata_path(name) + value = self._get(path) + if six.PY2: + return value + try: + return value.decode('utf-8') + except UnicodeDecodeError as exc: + # Include the path in the error message to simplify + # troubleshooting, and without changing the exception type. + exc.reason += ' in {} file at path: {}'.format(name, path) + raise def get_metadata_lines(self, name): return yield_lines(self.get_metadata(name)) @@ -1466,10 +1484,86 @@ class NullProvider: ) def _fn(self, base, resource_name): + self._validate_resource_path(resource_name) if resource_name: return os.path.join(base, *resource_name.split('/')) return base + @staticmethod + def _validate_resource_path(path): + """ + Validate the resource paths according to the docs. + https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access + + >>> warned = getfixture('recwarn') + >>> warnings.simplefilter('always') + >>> vrp = NullProvider._validate_resource_path + >>> vrp('foo/bar.txt') + >>> bool(warned) + False + >>> vrp('../foo/bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('/foo/bar.txt') + >>> bool(warned) + True + >>> vrp('foo/../../bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('foo/f../bar.txt') + >>> bool(warned) + False + + Windows path separators are straight-up disallowed. + >>> vrp(r'\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + >>> vrp(r'C:\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + Blank values are allowed + + >>> vrp('') + >>> bool(warned) + False + + Non-string values are not. + + >>> vrp(None) + Traceback (most recent call last): + ... + AttributeError: ... + """ + invalid = ( + os.path.pardir in path.split(posixpath.sep) or + posixpath.isabs(path) or + ntpath.isabs(path) + ) + if not invalid: + return + + msg = "Use of .. or absolute path in a resource path is not allowed." + + # Aggressively disallow Windows absolute paths + if ntpath.isabs(path) and not posixpath.isabs(path): + raise ValueError(msg) + + # for compatibility, warn; in future + # raise ValueError(msg) + warnings.warn( + msg[:-1] + " and will raise exceptions in a future release.", + DeprecationWarning, + stacklevel=4, + ) + def _get(self, path): if hasattr(self.loader, 'get_data'): return self.loader.get_data(path) @@ -1790,6 +1884,9 @@ class FileMetadata(EmptyProvider): def __init__(self, path): self.path = path + def _get_metadata_path(self, name): + return self.path + def has_metadata(self, name): return name == 'PKG-INFO' and os.path.isfile(self.path) @@ -1888,7 +1985,7 @@ def find_eggs_in_zip(importer, path_item, only=False): if only: # don't yield nested distros return - for subitem in metadata.resource_listdir('/'): + for subitem in metadata.resource_listdir(''): if _is_egg_path(subitem): subpath = os.path.join(path_item, subitem) dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) @@ -2583,10 +2680,14 @@ class Distribution: try: return self._version except AttributeError: - version = _version_from_file(self._get_metadata(self.PKG_INFO)) + version = self._get_version() if version is None: - tmpl = "Missing 'Version:' header and/or %s file" - raise ValueError(tmpl % self.PKG_INFO, self) + path = self._get_metadata_path_for_display(self.PKG_INFO) + msg = ( + "Missing 'Version:' header and/or {} file at path: {}" + ).format(self.PKG_INFO, path) + raise ValueError(msg, self) + return version @property @@ -2644,11 +2745,34 @@ class Distribution: ) return deps + def _get_metadata_path_for_display(self, name): + """ + Return the path to the given metadata file, if available. + """ + try: + # We need to access _get_metadata_path() on the provider object + # directly rather than through this class's __getattr__() + # since _get_metadata_path() is marked private. + path = self._provider._get_metadata_path(name) + + # Handle exceptions e.g. in case the distribution's metadata + # provider doesn't support _get_metadata_path(). + except Exception: + return '[could not detect]' + + return path + def _get_metadata(self, name): if self.has_metadata(name): for line in self.get_metadata_lines(name): yield line + def _get_version(self): + lines = self._get_metadata(self.PKG_INFO) + version = _version_from_file(lines) + + return version + def activate(self, path=None, replace=False): """Ensure distribution is importable on `path` (default=sys.path)""" if path is None: @@ -2867,7 +2991,7 @@ class EggInfoDistribution(Distribution): take an extra step and try to get the version number from the metadata file itself instead of the filename. """ - md_version = _version_from_file(self._get_metadata(self.PKG_INFO)) + md_version = self._get_version() if md_version: self._version = md_version return self @@ -2985,6 +3109,7 @@ class Requirement(packaging.requirements.Requirement): self.extras = tuple(map(safe_extra, self.extras)) self.hashCmp = ( self.key, + self.url, self.specifier, frozenset(self.extras), str(self.marker) if self.marker else None, diff --git a/libs/common/setuptools/__init__.py b/libs/common/setuptools/__init__.py index e438036a..9d8ae1ed 100644 --- a/libs/common/setuptools/__init__.py +++ b/libs/common/setuptools/__init__.py @@ -5,12 +5,14 @@ import sys import functools import distutils.core import distutils.filelist +import re +from distutils.errors import DistutilsOptionError from distutils.util import convert_path from fnmatch import fnmatchcase from ._deprecation_warning import SetuptoolsDeprecationWarning -from setuptools.extern.six import PY3 +from setuptools.extern.six import PY3, string_types from setuptools.extern.six.moves import filter, map import setuptools.version @@ -127,10 +129,27 @@ if PY3: def _install_setup_requires(attrs): # Note: do not use `setuptools.Distribution` directly, as # our PEP 517 backend patch `distutils.core.Distribution`. - dist = distutils.core.Distribution(dict( - (k, v) for k, v in attrs.items() - if k in ('dependency_links', 'setup_requires') - )) + class MinimalDistribution(distutils.core.Distribution): + """ + A minimal version of a distribution for supporting the + fetch_build_eggs interface. + """ + def __init__(self, attrs): + _incl = 'dependency_links', 'setup_requires' + filtered = { + k: attrs[k] + for k in set(_incl) & set(attrs) + } + distutils.core.Distribution.__init__(self, filtered) + + def finalize_options(self): + """ + Disable finalize_options to avoid building the working set. + Ref #2158. + """ + + dist = MinimalDistribution(attrs) + # Honor setup.cfg's options. dist.parse_config_files(ignore_option_errors=True) if dist.setup_requires: @@ -161,6 +180,37 @@ class Command(_Command): _Command.__init__(self, dist) vars(self).update(kw) + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, string_types): + raise DistutilsOptionError("'%s' must be a %s (got `%s`)" + % (option, what, val)) + return val + + def ensure_string_list(self, option): + r"""Ensure that 'option' is a list of strings. If 'option' is + currently a string, we split it either on /,\s*/ or /\s+/, so + "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become + ["foo", "bar", "baz"]. + """ + val = getattr(self, option) + if val is None: + return + elif isinstance(val, string_types): + setattr(self, option, re.split(r',\s*|\s+', val)) + else: + if isinstance(val, list): + ok = all(isinstance(v, string_types) for v in val) + else: + ok = False + if not ok: + raise DistutilsOptionError( + "'%s' must be a list of strings (got %r)" + % (option, val)) + def reinitialize_command(self, command, reinit_subcommands=0, **kw): cmd = _Command.reinitialize_command(self, command, reinit_subcommands) vars(cmd).update(kw) diff --git a/libs/common/setuptools/_imp.py b/libs/common/setuptools/_imp.py new file mode 100644 index 00000000..a3cce9b2 --- /dev/null +++ b/libs/common/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/libs/common/setuptools/_vendor/ordered_set.py b/libs/common/setuptools/_vendor/ordered_set.py new file mode 100644 index 00000000..14876000 --- /dev/null +++ b/libs/common/setuptools/_vendor/ordered_set.py @@ -0,0 +1,488 @@ +""" +An OrderedSet is a custom MutableSet that remembers its order, so that every +entry has an index that can be looked up. + +Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, +and released under the MIT license. +""" +import itertools as it +from collections import deque + +try: + # Python 3 + from collections.abc import MutableSet, Sequence +except ImportError: + # Python 2.7 + from collections import MutableSet, Sequence + +SLICE_ALL = slice(None) +__version__ = "3.1" + + +def is_iterable(obj): + """ + Are we being asked to look up a list of things, instead of a single thing? + We check for the `__iter__` attribute so that this can cover types that + don't have to be known by this module, such as NumPy arrays. + + Strings, however, should be considered as atomic values to look up, not + iterables. The same goes for tuples, since they are immutable and therefore + valid entries. + + We don't need to check for the Python 2 `unicode` type, because it doesn't + have an `__iter__` attribute anyway. + """ + return ( + hasattr(obj, "__iter__") + and not isinstance(obj, str) + and not isinstance(obj, tuple) + ) + + +class OrderedSet(MutableSet, Sequence): + """ + An OrderedSet is a custom MutableSet that remembers its order, so that + every entry has an index that can be looked up. + + Example: + >>> OrderedSet([1, 1, 2, 3, 2]) + OrderedSet([1, 2, 3]) + """ + + def __init__(self, iterable=None): + self.items = [] + self.map = {} + if iterable is not None: + self |= iterable + + def __len__(self): + """ + Returns the number of unique elements in the ordered set + + Example: + >>> len(OrderedSet([])) + 0 + >>> len(OrderedSet([1, 2])) + 2 + """ + return len(self.items) + + def __getitem__(self, index): + """ + Get the item at a given index. + + If `index` is a slice, you will get back that slice of items, as a + new OrderedSet. + + If `index` is a list or a similar iterable, you'll get a list of + items corresponding to those indices. This is similar to NumPy's + "fancy indexing". The result is not an OrderedSet because you may ask + for duplicate indices, and the number of elements returned should be + the number of elements asked for. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset[1] + 2 + """ + if isinstance(index, slice) and index == SLICE_ALL: + return self.copy() + elif is_iterable(index): + return [self.items[i] for i in index] + elif hasattr(index, "__index__") or isinstance(index, slice): + result = self.items[index] + if isinstance(result, list): + return self.__class__(result) + else: + return result + else: + raise TypeError("Don't know how to index an OrderedSet by %r" % index) + + def copy(self): + """ + Return a shallow copy of this object. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> other = this.copy() + >>> this == other + True + >>> this is other + False + """ + return self.__class__(self) + + def __getstate__(self): + if len(self) == 0: + # The state can't be an empty list. + # We need to return a truthy value, or else __setstate__ won't be run. + # + # This could have been done more gracefully by always putting the state + # in a tuple, but this way is backwards- and forwards- compatible with + # previous versions of OrderedSet. + return (None,) + else: + return list(self) + + def __setstate__(self, state): + if state == (None,): + self.__init__([]) + else: + self.__init__(state) + + def __contains__(self, key): + """ + Test if the item is in this ordered set + + Example: + >>> 1 in OrderedSet([1, 3, 2]) + True + >>> 5 in OrderedSet([1, 3, 2]) + False + """ + return key in self.map + + def add(self, key): + """ + Add `key` as an item to this OrderedSet, then return its index. + + If `key` is already in the OrderedSet, return the index it already + had. + + Example: + >>> oset = OrderedSet() + >>> oset.append(3) + 0 + >>> print(oset) + OrderedSet([3]) + """ + if key not in self.map: + self.map[key] = len(self.items) + self.items.append(key) + return self.map[key] + + append = add + + def update(self, sequence): + """ + Update the set with the given iterable sequence, then return the index + of the last element inserted. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.update([3, 1, 5, 1, 4]) + 4 + >>> print(oset) + OrderedSet([1, 2, 3, 5, 4]) + """ + item_index = None + try: + for item in sequence: + item_index = self.add(item) + except TypeError: + raise ValueError( + "Argument needs to be an iterable, got %s" % type(sequence) + ) + return item_index + + def index(self, key): + """ + Get the index of a given entry, raising an IndexError if it's not + present. + + `key` can be an iterable of entries that is not a string, in which case + this returns a list of indices. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.index(2) + 1 + """ + if is_iterable(key): + return [self.index(subkey) for subkey in key] + return self.map[key] + + # Provide some compatibility with pd.Index + get_loc = index + get_indexer = index + + def pop(self): + """ + Remove and return the last element from the set. + + Raises KeyError if the set is empty. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.pop() + 3 + """ + if not self.items: + raise KeyError("Set is empty") + + elem = self.items[-1] + del self.items[-1] + del self.map[elem] + return elem + + def discard(self, key): + """ + Remove an element. Do not raise an exception if absent. + + The MutableSet mixin uses this to implement the .remove() method, which + *does* raise an error when asked to remove a non-existent item. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + """ + if key in self: + i = self.map[key] + del self.items[i] + del self.map[key] + for k, v in self.map.items(): + if v >= i: + self.map[k] = v - 1 + + def clear(self): + """ + Remove all items from this OrderedSet. + """ + del self.items[:] + self.map.clear() + + def __iter__(self): + """ + Example: + >>> list(iter(OrderedSet([1, 2, 3]))) + [1, 2, 3] + """ + return iter(self.items) + + def __reversed__(self): + """ + Example: + >>> list(reversed(OrderedSet([1, 2, 3]))) + [3, 2, 1] + """ + return reversed(self.items) + + def __repr__(self): + if not self: + return "%s()" % (self.__class__.__name__,) + return "%s(%r)" % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + """ + Returns true if the containers have the same items. If `other` is a + Sequence, then order is checked, otherwise it is ignored. + + Example: + >>> oset = OrderedSet([1, 3, 2]) + >>> oset == [1, 3, 2] + True + >>> oset == [1, 2, 3] + False + >>> oset == [2, 3] + False + >>> oset == OrderedSet([3, 2, 1]) + False + """ + # In Python 2 deque is not a Sequence, so treat it as one for + # consistent behavior with Python 3. + if isinstance(other, (Sequence, deque)): + # Check that this OrderedSet contains the same elements, in the + # same order, as the other object. + return list(self) == list(other) + try: + other_as_set = set(other) + except TypeError: + # If `other` can't be converted into a set, it's not equal. + return False + else: + return set(self) == other_as_set + + def union(self, *sets): + """ + Combines all unique items. + Each items order is defined by its first appearance. + + Example: + >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0]) + >>> print(oset) + OrderedSet([3, 1, 4, 5, 2, 0]) + >>> oset.union([8, 9]) + OrderedSet([3, 1, 4, 5, 2, 0, 8, 9]) + >>> oset | {10} + OrderedSet([3, 1, 4, 5, 2, 0, 10]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + containers = map(list, it.chain([self], sets)) + items = it.chain.from_iterable(containers) + return cls(items) + + def __and__(self, other): + # the parent implementation of this is backwards + return self.intersection(other) + + def intersection(self, *sets): + """ + Returns elements in common between all sets. Order is defined only + by the first set. + + Example: + >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3]) + >>> print(oset) + OrderedSet([1, 2, 3]) + >>> oset.intersection([2, 4, 5], [1, 2, 3, 4]) + OrderedSet([2]) + >>> oset.intersection() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + if sets: + common = set.intersection(*map(set, sets)) + items = (item for item in self if item in common) + else: + items = self + return cls(items) + + def difference(self, *sets): + """ + Returns all elements that are in this set but not the others. + + Example: + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2])) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3])) + OrderedSet([1]) + >>> OrderedSet([1, 2, 3]) - OrderedSet([2]) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ + if sets: + other = set.union(*map(set, sets)) + items = (item for item in self if item not in other) + else: + items = self + return cls(items) + + def issubset(self, other): + """ + Report whether another set contains this set. + + Example: + >>> OrderedSet([1, 2, 3]).issubset({1, 2}) + False + >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4}) + True + >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5}) + False + """ + if len(self) > len(other): # Fast check for obvious cases + return False + return all(item in other for item in self) + + def issuperset(self, other): + """ + Report whether this set contains another set. + + Example: + >>> OrderedSet([1, 2]).issuperset([1, 2, 3]) + False + >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3}) + True + >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3}) + False + """ + if len(self) < len(other): # Fast check for obvious cases + return False + return all(item in self for item in other) + + def symmetric_difference(self, other): + """ + Return the symmetric difference of two OrderedSets as a new set. + That is, the new set will contain all elements that are in exactly + one of the sets. + + Their order will be preserved, with elements from `self` preceding + elements from `other`. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference(other) + OrderedSet([4, 5, 9, 2]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + diff1 = cls(self).difference(other) + diff2 = cls(other).difference(self) + return diff1.union(diff2) + + def _update_items(self, items): + """ + Replace the 'items' list of this OrderedSet with a new one, updating + self.map accordingly. + """ + self.items = items + self.map = {item: idx for (idx, item) in enumerate(items)} + + def difference_update(self, *sets): + """ + Update this OrderedSet to remove items from one or more other sets. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> this.difference_update(OrderedSet([2, 4])) + >>> print(this) + OrderedSet([1, 3]) + + >>> this = OrderedSet([1, 2, 3, 4, 5]) + >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6])) + >>> print(this) + OrderedSet([3, 5]) + """ + items_to_remove = set() + for other in sets: + items_to_remove |= set(other) + self._update_items([item for item in self.items if item not in items_to_remove]) + + def intersection_update(self, other): + """ + Update this OrderedSet to keep only items in another set, preserving + their order in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.intersection_update(other) + >>> print(this) + OrderedSet([1, 3, 7]) + """ + other = set(other) + self._update_items([item for item in self.items if item in other]) + + def symmetric_difference_update(self, other): + """ + Update this OrderedSet to remove items from another set, then + add items from the other set that were not present in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference_update(other) + >>> print(this) + OrderedSet([4, 5, 9, 2]) + """ + items_to_add = [item for item in other if item not in self] + items_to_remove = set(other) + self._update_items( + [item for item in self.items if item not in items_to_remove] + items_to_add + ) diff --git a/libs/common/setuptools/_vendor/packaging/__about__.py b/libs/common/setuptools/_vendor/packaging/__about__.py index 95d330ef..dc95138d 100644 --- a/libs/common/setuptools/_vendor/packaging/__about__.py +++ b/libs/common/setuptools/_vendor/packaging/__about__.py @@ -4,18 +4,24 @@ from __future__ import absolute_import, division, print_function __all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", ] __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "16.8" +__version__ = "19.2" __author__ = "Donald Stufft and individual contributors" __email__ = "donald@stufft.io" __license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2014-2016 %s" % __author__ +__copyright__ = "Copyright 2014-2019 %s" % __author__ diff --git a/libs/common/setuptools/_vendor/packaging/__init__.py b/libs/common/setuptools/_vendor/packaging/__init__.py index 5ee62202..a0cf67df 100644 --- a/libs/common/setuptools/_vendor/packaging/__init__.py +++ b/libs/common/setuptools/_vendor/packaging/__init__.py @@ -4,11 +4,23 @@ from __future__ import absolute_import, division, print_function from .__about__ import ( - __author__, __copyright__, __email__, __license__, __summary__, __title__, - __uri__, __version__ + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, ) __all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", ] diff --git a/libs/common/setuptools/_vendor/packaging/_compat.py b/libs/common/setuptools/_vendor/packaging/_compat.py index 210bb80b..25da473c 100644 --- a/libs/common/setuptools/_vendor/packaging/_compat.py +++ b/libs/common/setuptools/_vendor/packaging/_compat.py @@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3 # flake8: noqa if PY3: - string_types = str, + string_types = (str,) else: - string_types = basestring, + string_types = (basestring,) def with_metaclass(meta, *bases): @@ -27,4 +27,5 @@ def with_metaclass(meta, *bases): class metaclass(meta): def __new__(cls, name, this_bases, d): return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) + + return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/libs/common/setuptools/_vendor/packaging/_structures.py b/libs/common/setuptools/_vendor/packaging/_structures.py index ccc27861..68dcca63 100644 --- a/libs/common/setuptools/_vendor/packaging/_structures.py +++ b/libs/common/setuptools/_vendor/packaging/_structures.py @@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function class Infinity(object): - def __repr__(self): return "Infinity" @@ -33,11 +32,11 @@ class Infinity(object): def __neg__(self): return NegativeInfinity + Infinity = Infinity() class NegativeInfinity(object): - def __repr__(self): return "-Infinity" @@ -65,4 +64,5 @@ class NegativeInfinity(object): def __neg__(self): return Infinity + NegativeInfinity = NegativeInfinity() diff --git a/libs/common/setuptools/_vendor/packaging/markers.py b/libs/common/setuptools/_vendor/packaging/markers.py index 031332a3..4bdfdb24 100644 --- a/libs/common/setuptools/_vendor/packaging/markers.py +++ b/libs/common/setuptools/_vendor/packaging/markers.py @@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier __all__ = [ - "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", - "Marker", "default_environment", + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", ] @@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError): class Node(object): - def __init__(self, value): self.value = value @@ -57,62 +59,52 @@ class Node(object): class Variable(Node): - def serialize(self): return str(self) class Value(Node): - def serialize(self): return '"{0}"'.format(self) class Op(Node): - def serialize(self): return str(self) VARIABLE = ( - L("implementation_version") | - L("platform_python_implementation") | - L("implementation_name") | - L("python_full_version") | - L("platform_release") | - L("platform_version") | - L("platform_machine") | - L("platform_system") | - L("python_version") | - L("sys_platform") | - L("os_name") | - L("os.name") | # PEP-345 - L("sys.platform") | # PEP-345 - L("platform.version") | # PEP-345 - L("platform.machine") | # PEP-345 - L("platform.python_implementation") | # PEP-345 - L("python_implementation") | # undocumented setuptools legacy - L("extra") + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # PEP-345 + | L("extra") # undocumented setuptools legacy ) ALIASES = { - 'os.name': 'os_name', - 'sys.platform': 'sys_platform', - 'platform.version': 'platform_version', - 'platform.machine': 'platform_machine', - 'platform.python_implementation': 'platform_python_implementation', - 'python_implementation': 'platform_python_implementation' + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", } VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) VERSION_CMP = ( - L("===") | - L("==") | - L(">=") | - L("<=") | - L("!=") | - L("~=") | - L(">") | - L("<") + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") ) MARKER_OP = VERSION_CMP | L("not in") | L("in") @@ -152,8 +144,11 @@ def _format_marker(marker, first=True): # where the single item is itself it's own list. In that case we want skip # the rest of this function so that we don't get extraneous () on the # outside. - if (isinstance(marker, list) and len(marker) == 1 and - isinstance(marker[0], (list, tuple))): + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): return _format_marker(marker[0]) if isinstance(marker, list): @@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment): def format_full_version(info): - version = '{0.major}.{0.minor}.{0.micro}'.format(info) + version = "{0.major}.{0.minor}.{0.micro}".format(info) kind = info.releaselevel - if kind != 'final': + if kind != "final": version += kind[0] + str(info.serial) return version def default_environment(): - if hasattr(sys, 'implementation'): + if hasattr(sys, "implementation"): iver = format_full_version(sys.implementation.version) implementation_name = sys.implementation.name else: - iver = '0' - implementation_name = '' + iver = "0" + implementation_name = "" return { "implementation_name": implementation_name, @@ -264,19 +259,19 @@ def default_environment(): "platform_version": platform.version(), "python_full_version": platform.python_version(), "platform_python_implementation": platform.python_implementation(), - "python_version": platform.python_version()[:3], + "python_version": ".".join(platform.python_version_tuple()[:2]), "sys_platform": sys.platform, } class Marker(object): - def __init__(self, marker): try: self._markers = _coerce_parse_result(MARKER.parseString(marker)) except ParseException as e: err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( - marker, marker[e.loc:e.loc + 8]) + marker, marker[e.loc : e.loc + 8] + ) raise InvalidMarker(err_str) def __str__(self): diff --git a/libs/common/setuptools/_vendor/packaging/requirements.py b/libs/common/setuptools/_vendor/packaging/requirements.py index 5b493416..8a0c2cb9 100644 --- a/libs/common/setuptools/_vendor/packaging/requirements.py +++ b/libs/common/setuptools/_vendor/packaging/requirements.py @@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) NAME = IDENTIFIER("name") EXTRA = IDENTIFIER -URI = Regex(r'[^ ]+')("url") -URL = (AT + URI) +URI = Regex(r"[^ ]+")("url") +URL = AT + URI EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") @@ -48,28 +48,31 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY -VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), - joinString=",", adjacent=False)("_raw_spec") +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") _VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) -_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") MARKER_EXPR.setParseAction( - lambda s, l, t: Marker(s[t._original_start:t._original_end]) + lambda s, l, t: Marker(s[t._original_start : t._original_end]) ) -MARKER_SEPERATOR = SEMICOLON -MARKER = MARKER_SEPERATOR + MARKER_EXPR +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) URL_AND_MARKER = URL + Optional(MARKER) -NAMED_REQUIREMENT = \ - NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# setuptools.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") class Requirement(object): @@ -90,15 +93,21 @@ class Requirement(object): req = REQUIREMENT.parseString(requirement_string) except ParseException as e: raise InvalidRequirement( - "Invalid requirement, parse error at \"{0!r}\"".format( - requirement_string[e.loc:e.loc + 8])) + 'Parse error at "{0!r}": {1}'.format( + requirement_string[e.loc : e.loc + 8], e.msg + ) + ) self.name = req.name if req.url: parsed_url = urlparse.urlparse(req.url) - if not (parsed_url.scheme and parsed_url.netloc) or ( - not parsed_url.scheme and not parsed_url.netloc): - raise InvalidRequirement("Invalid URL given") + if parsed_url.scheme == "file": + if urlparse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement("Invalid URL: {0}".format(req.url)) self.url = req.url else: self.url = None @@ -117,6 +126,8 @@ class Requirement(object): if self.url: parts.append("@ {0}".format(self.url)) + if self.marker: + parts.append(" ") if self.marker: parts.append("; {0}".format(self.marker)) diff --git a/libs/common/setuptools/_vendor/packaging/specifiers.py b/libs/common/setuptools/_vendor/packaging/specifiers.py index 7f5a76cf..743576a0 100644 --- a/libs/common/setuptools/_vendor/packaging/specifiers.py +++ b/libs/common/setuptools/_vendor/packaging/specifiers.py @@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError): class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): - @abc.abstractmethod def __str__(self): """ @@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier): if not match: raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) - self._spec = ( - match.group("operator").strip(), - match.group("version").strip(), - ) + self._spec = (match.group("operator").strip(), match.group("version").strip()) # Store whether or not this Specifier should accept prereleases self._prereleases = prereleases @@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier): else "" ) - return "<{0}({1!r}{2})>".format( - self.__class__.__name__, - str(self), - pre, - ) + return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) def __str__(self): return "{0}{1}".format(*self._spec) @@ -194,11 +186,12 @@ class _IndividualSpecifier(BaseSpecifier): # If our version is a prerelease, and we were not set to allow # prereleases, then we'll store it for later incase nothing # else matches this specifier. - if (parsed_version.is_prerelease and not - (prereleases or self.prereleases)): + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): found_prereleases.append(version) # Either this is not a prerelease, or we should have been - # accepting prereleases from the begining. + # accepting prereleases from the beginning. else: yielded = True yield version @@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier): class LegacySpecifier(_IndividualSpecifier): - _regex_str = ( - r""" + _regex_str = r""" (?P(==|!=|<=|>=|<|>)) \s* (?P @@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier): # them, and a comma since it's a version separator. ) """ - ) - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) _operators = { "==": "equal", @@ -269,13 +259,13 @@ def _require_version_compare(fn): if not isinstance(prospective, Version): return False return fn(self, prospective, spec) + return wrapped class Specifier(_IndividualSpecifier): - _regex_str = ( - r""" + _regex_str = r""" (?P(~=|==|!=|<=|>=|<|>|===)) (?P (?: @@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier): ) ) """ - ) - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) _operators = { "~=": "compatible", @@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier): prefix = ".".join( list( itertools.takewhile( - lambda x: (not x.startswith("post") and not - x.startswith("dev")), + lambda x: (not x.startswith("post") and not x.startswith("dev")), _version_split(spec), ) )[:-1] @@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier): # Add the prefix notation to the end of our string prefix += ".*" - return (self._get_operator(">=")(prospective, spec) and - self._get_operator("==")(prospective, prefix)) + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) @_require_version_compare def _compare_equal(self, prospective, spec): @@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier): # Shorten the prospective version to be the same length as the spec # so that we can determine if the specifier is a prefix of the # prospective version or not. - prospective = prospective[:len(spec)] + prospective = prospective[: len(spec)] # Pad out our two sides with zeros so that they both equal the same # length. @@ -503,7 +491,7 @@ class Specifier(_IndividualSpecifier): return False # Ensure that we do not allow a local version of the version mentioned - # in the specifier, which is techincally greater than, to match. + # in the specifier, which is technically greater than, to match. if prospective.local is not None: if Version(prospective.base_version) == Version(spec.base_version): return False @@ -567,27 +555,17 @@ def _pad_version(left, right): right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) # Get the rest of our versions - left_split.append(left[len(left_split[0]):]) - right_split.append(right[len(right_split[0]):]) + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) # Insert our padding - left_split.insert( - 1, - ["0"] * max(0, len(right_split[0]) - len(left_split[0])), - ) - right_split.insert( - 1, - ["0"] * max(0, len(left_split[0]) - len(right_split[0])), - ) + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) - return ( - list(itertools.chain(*left_split)), - list(itertools.chain(*right_split)), - ) + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) class SpecifierSet(BaseSpecifier): - def __init__(self, specifiers="", prereleases=None): # Split on , to break each indidivual specifier into it's own item, and # strip each item to remove leading/trailing whitespace. @@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier): # given version is contained within all of them. # Note: This use of all() here means that an empty set of specifiers # will always return True, this is an explicit design decision. - return all( - s.contains(item, prereleases=prereleases) - for s in self._specs - ) + return all(s.contains(item, prereleases=prereleases) for s in self._specs) def filter(self, iterable, prereleases=None): # Determine if we're forcing a prerelease or not, if we're not forcing diff --git a/libs/common/setuptools/_vendor/packaging/tags.py b/libs/common/setuptools/_vendor/packaging/tags.py new file mode 100644 index 00000000..ec9942f0 --- /dev/null +++ b/libs/common/setuptools/_vendor/packaging/tags.py @@ -0,0 +1,404 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import + +import distutils.util + +try: + from importlib.machinery import EXTENSION_SUFFIXES +except ImportError: # pragma: no cover + import imp + + EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] + del imp +import platform +import re +import sys +import sysconfig +import warnings + + +INTERPRETER_SHORT_NAMES = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag(object): + + __slots__ = ["_interpreter", "_abi", "_platform"] + + def __init__(self, interpreter, abi, platform): + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + + @property + def interpreter(self): + return self._interpreter + + @property + def abi(self): + return self._abi + + @property + def platform(self): + return self._platform + + def __eq__(self, other): + return ( + (self.platform == other.platform) + and (self.abi == other.abi) + and (self.interpreter == other.interpreter) + ) + + def __hash__(self): + return hash((self._interpreter, self._abi, self._platform)) + + def __str__(self): + return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) + + def __repr__(self): + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag): + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _normalize_string(string): + return string.replace(".", "_").replace("-", "_") + + +def _cpython_interpreter(py_version): + # TODO: Is using py_version_nodot for interpreter version critical? + return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1]) + + +def _cpython_abis(py_version): + abis = [] + version = "{}{}".format(*py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = sysconfig.get_config_var("Py_DEBUG") + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC") + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = sysconfig.get_config_var("Py_UNICODE_SIZE") + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append("cp{version}".format(version=version)) + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def _cpython_tags(py_version, interpreter, abis, platforms): + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): + yield tag + # PEP 384 was first implemented in Python 3.2. + for minor_version in range(py_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{major}{minor}".format( + major=py_version[0], minor=minor_version + ) + yield Tag(interpreter, "abi3", platform_) + + +def _pypy_interpreter(): + return "pp{py_major}{pypy_major}{pypy_minor}".format( + py_major=sys.version_info[0], + pypy_major=sys.pypy_version_info.major, + pypy_minor=sys.pypy_version_info.minor, + ) + + +def _generic_abi(): + abi = sysconfig.get_config_var("SOABI") + if abi: + return _normalize_string(abi) + else: + return "none" + + +def _pypy_tags(py_version, interpreter, abi, platforms): + for tag in (Tag(interpreter, abi, platform) for platform in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform) for platform in platforms): + yield tag + + +def _generic_tags(interpreter, py_version, abi, platforms): + for tag in (Tag(interpreter, abi, platform) for platform in platforms): + yield tag + if abi != "none": + tags = (Tag(interpreter, "none", platform_) for platform_ in platforms) + for tag in tags: + yield tag + + +def _py_interpreter_range(py_version): + """ + Yield Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all following versions up to 'end'. + """ + yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) + yield "py{major}".format(major=py_version[0]) + for minor in range(py_version[1] - 1, -1, -1): + yield "py{major}{minor}".format(major=py_version[0], minor=minor) + + +def _independent_tags(interpreter, py_version, platforms): + """ + Return the sequence of tags that are consistent across implementations. + + The tags consist of: + - py*-none- + - -none-any + - py*-none-any + """ + for version in _py_interpreter_range(py_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(py_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version, cpu_arch): + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + formats.append("universal") + return formats + + +def _mac_platforms(version=None, arch=None): + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = tuple(map(int, version_str.split(".")[:2])) + if arch is None: + arch = _mac_arch(cpu_arch) + platforms = [] + for minor_version in range(version[1], -1, -1): + compat_version = version[0], minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + platforms.append( + "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + ) + return platforms + + +# From PEP 513. +def _is_manylinux_compatible(name, glibc_version): + # Check for presence of _manylinux module. + try: + import _manylinux + + return bool(getattr(_manylinux, name + "_compatible")) + except (ImportError, AttributeError): + # Fall through to heuristic check below. + pass + + return _have_compatible_glibc(*glibc_version) + + +def _glibc_version_string(): + # Returns glibc version string, or None if not using glibc. + import ctypes + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + process_namespace = ctypes.CDLL(None) + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# Separated out from have_compatible_glibc for easier unit testing. +def _check_glibc_version(version_str, required_major, minimum_minor): + # Parse string and check against requested version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return False + return ( + int(m.group("major")) == required_major + and int(m.group("minor")) >= minimum_minor + ) + + +def _have_compatible_glibc(required_major, minimum_minor): + version_str = _glibc_version_string() + if version_str is None: + return False + return _check_glibc_version(version_str, required_major, minimum_minor) + + +def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): + linux = _normalize_string(distutils.util.get_platform()) + if linux == "linux_x86_64" and is_32bit: + linux = "linux_i686" + manylinux_support = ( + ("manylinux2014", (2, 17)), # CentOS 7 w/ glibc 2.17 (PEP 599) + ("manylinux2010", (2, 12)), # CentOS 6 w/ glibc 2.12 (PEP 571) + ("manylinux1", (2, 5)), # CentOS 5 w/ glibc 2.5 (PEP 513) + ) + manylinux_support_iter = iter(manylinux_support) + for name, glibc_version in manylinux_support_iter: + if _is_manylinux_compatible(name, glibc_version): + platforms = [linux.replace("linux", name)] + break + else: + platforms = [] + # Support for a later manylinux implies support for an earlier version. + platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter] + platforms.append(linux) + return platforms + + +def _generic_platforms(): + platform = _normalize_string(distutils.util.get_platform()) + return [platform] + + +def _interpreter_name(): + name = platform.python_implementation().lower() + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def _generic_interpreter(name, py_version): + version = sysconfig.get_config_var("py_version_nodot") + if not version: + version = "".join(map(str, py_version[:2])) + return "{name}{version}".format(name=name, version=version) + + +def sys_tags(): + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + py_version = sys.version_info[:2] + interpreter_name = _interpreter_name() + if platform.system() == "Darwin": + platforms = _mac_platforms() + elif platform.system() == "Linux": + platforms = _linux_platforms() + else: + platforms = _generic_platforms() + + if interpreter_name == "cp": + interpreter = _cpython_interpreter(py_version) + abis = _cpython_abis(py_version) + for tag in _cpython_tags(py_version, interpreter, abis, platforms): + yield tag + elif interpreter_name == "pp": + interpreter = _pypy_interpreter() + abi = _generic_abi() + for tag in _pypy_tags(py_version, interpreter, abi, platforms): + yield tag + else: + interpreter = _generic_interpreter(interpreter_name, py_version) + abi = _generic_abi() + for tag in _generic_tags(interpreter, py_version, abi, platforms): + yield tag + for tag in _independent_tags(interpreter, py_version, platforms): + yield tag diff --git a/libs/common/setuptools/_vendor/packaging/utils.py b/libs/common/setuptools/_vendor/packaging/utils.py index 942387ce..88418786 100644 --- a/libs/common/setuptools/_vendor/packaging/utils.py +++ b/libs/common/setuptools/_vendor/packaging/utils.py @@ -5,6 +5,8 @@ from __future__ import absolute_import, division, print_function import re +from .version import InvalidVersion, Version + _canonicalize_regex = re.compile(r"[-_.]+") @@ -12,3 +14,44 @@ _canonicalize_regex = re.compile(r"[-_.]+") def canonicalize_name(name): # This is taken from PEP 503. return _canonicalize_regex.sub("-", name).lower() + + +def canonicalize_version(version): + """ + This is very similar to Version.__str__, but has one subtle differences + with the way it handles the release segment. + """ + + try: + version = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + + parts = [] + + # Epoch + if version.epoch != 0: + parts.append("{0}!".format(version.epoch)) + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + + # Pre-release + if version.pre is not None: + parts.append("".join(str(x) for x in version.pre)) + + # Post-release + if version.post is not None: + parts.append(".post{0}".format(version.post)) + + # Development release + if version.dev is not None: + parts.append(".dev{0}".format(version.dev)) + + # Local version segment + if version.local is not None: + parts.append("+{0}".format(version.local)) + + return "".join(parts) diff --git a/libs/common/setuptools/_vendor/packaging/version.py b/libs/common/setuptools/_vendor/packaging/version.py index 83b5ee8c..95157a1f 100644 --- a/libs/common/setuptools/_vendor/packaging/version.py +++ b/libs/common/setuptools/_vendor/packaging/version.py @@ -10,14 +10,11 @@ import re from ._structures import Infinity -__all__ = [ - "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN" -] +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] _Version = collections.namedtuple( - "_Version", - ["epoch", "release", "dev", "pre", "post", "local"], + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] ) @@ -40,7 +37,6 @@ class InvalidVersion(ValueError): class _BaseVersion(object): - def __hash__(self): return hash(self._key) @@ -70,7 +66,6 @@ class _BaseVersion(object): class LegacyVersion(_BaseVersion): - def __init__(self, version): self._version = str(version) self._key = _legacy_cmpkey(self._version) @@ -89,6 +84,26 @@ class LegacyVersion(_BaseVersion): def base_version(self): return self._version + @property + def epoch(self): + return -1 + + @property + def release(self): + return None + + @property + def pre(self): + return None + + @property + def post(self): + return None + + @property + def dev(self): + return None + @property def local(self): return None @@ -101,13 +116,19 @@ class LegacyVersion(_BaseVersion): def is_postrelease(self): return False + @property + def is_devrelease(self): + return False -_legacy_version_component_re = re.compile( - r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, -) + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) _legacy_version_replacement_map = { - "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", } @@ -154,6 +175,7 @@ def _legacy_cmpkey(version): return epoch, parts + # Deliberately not anchored to the start and end of the string, to make it # easier for 3rd party code to reuse VERSION_PATTERN = r""" @@ -190,10 +212,7 @@ VERSION_PATTERN = r""" class Version(_BaseVersion): - _regex = re.compile( - r"^\s*" + VERSION_PATTERN + r"\s*$", - re.VERBOSE | re.IGNORECASE, - ) + _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) def __init__(self, version): # Validate the version and parse it into pieces @@ -205,18 +224,11 @@ class Version(_BaseVersion): self._version = _Version( epoch=int(match.group("epoch")) if match.group("epoch") else 0, release=tuple(int(i) for i in match.group("release").split(".")), - pre=_parse_letter_version( - match.group("pre_l"), - match.group("pre_n"), - ), + pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")), post=_parse_letter_version( - match.group("post_l"), - match.group("post_n1") or match.group("post_n2"), - ), - dev=_parse_letter_version( - match.group("dev_l"), - match.group("dev_n"), + match.group("post_l"), match.group("post_n1") or match.group("post_n2") ), + dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), local=_parse_local_version(match.group("local")), ) @@ -237,32 +249,57 @@ class Version(_BaseVersion): parts = [] # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) + if self.epoch != 0: + parts.append("{0}!".format(self.epoch)) # Release segment - parts.append(".".join(str(x) for x in self._version.release)) + parts.append(".".join(str(x) for x in self.release)) # Pre-release - if self._version.pre is not None: - parts.append("".join(str(x) for x in self._version.pre)) + if self.pre is not None: + parts.append("".join(str(x) for x in self.pre)) # Post-release - if self._version.post is not None: - parts.append(".post{0}".format(self._version.post[1])) + if self.post is not None: + parts.append(".post{0}".format(self.post)) # Development release - if self._version.dev is not None: - parts.append(".dev{0}".format(self._version.dev[1])) + if self.dev is not None: + parts.append(".dev{0}".format(self.dev)) # Local version segment - if self._version.local is not None: - parts.append( - "+{0}".format(".".join(str(x) for x in self._version.local)) - ) + if self.local is not None: + parts.append("+{0}".format(self.local)) return "".join(parts) + @property + def epoch(self): + return self._version.epoch + + @property + def release(self): + return self._version.release + + @property + def pre(self): + return self._version.pre + + @property + def post(self): + return self._version.post[1] if self._version.post else None + + @property + def dev(self): + return self._version.dev[1] if self._version.dev else None + + @property + def local(self): + if self._version.local: + return ".".join(str(x) for x in self._version.local) + else: + return None + @property def public(self): return str(self).split("+", 1)[0] @@ -272,27 +309,25 @@ class Version(_BaseVersion): parts = [] # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) + if self.epoch != 0: + parts.append("{0}!".format(self.epoch)) # Release segment - parts.append(".".join(str(x) for x in self._version.release)) + parts.append(".".join(str(x) for x in self.release)) return "".join(parts) - @property - def local(self): - version_string = str(self) - if "+" in version_string: - return version_string.split("+", 1)[1] - @property def is_prerelease(self): - return bool(self._version.dev or self._version.pre) + return self.dev is not None or self.pre is not None @property def is_postrelease(self): - return bool(self._version.post) + return self.post is not None + + @property + def is_devrelease(self): + return self.dev is not None def _parse_letter_version(letter, number): @@ -326,7 +361,7 @@ def _parse_letter_version(letter, number): return letter, int(number) -_local_version_seperators = re.compile(r"[\._-]") +_local_version_separators = re.compile(r"[\._-]") def _parse_local_version(local): @@ -336,7 +371,7 @@ def _parse_local_version(local): if local is not None: return tuple( part.lower() if not part.isdigit() else int(part) - for part in _local_version_seperators.split(local) + for part in _local_version_separators.split(local) ) @@ -347,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local): # re-reverse it back into the correct order and make it a tuple and use # that for our sorting key. release = tuple( - reversed(list( - itertools.dropwhile( - lambda x: x == 0, - reversed(release), - ) - )) + reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))) ) # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. @@ -385,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local): # - Numeric segments sort numerically # - Shorter versions sort before longer versions when the prefixes # match exactly - local = tuple( - (i, "") if isinstance(i, int) else (-Infinity, i) - for i in local - ) + local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local) return epoch, release, pre, post, dev, local diff --git a/libs/common/setuptools/build_meta.py b/libs/common/setuptools/build_meta.py index 463d3757..eb9e815e 100644 --- a/libs/common/setuptools/build_meta.py +++ b/libs/common/setuptools/build_meta.py @@ -26,6 +26,7 @@ bug reports or API stability): Again, this is not a formal definition! Just a "taste" of the module. """ +import io import os import sys import tokenize @@ -34,7 +35,18 @@ import contextlib import setuptools import distutils +from setuptools.py31compat import TemporaryDirectory +from pkg_resources import parse_requirements +from pkg_resources.py31compat import makedirs + +__all__ = ['get_requires_for_build_sdist', + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'build_sdist', + '__legacy__', + 'SetupRequirementsError'] class SetupRequirementsError(BaseException): def __init__(self, specifiers): @@ -43,7 +55,9 @@ class SetupRequirementsError(BaseException): class Distribution(setuptools.dist.Distribution): def fetch_build_eggs(self, specifiers): - raise SetupRequirementsError(specifiers) + specifier_list = list(map(str, parse_requirements(specifiers))) + + raise SetupRequirementsError(specifier_list) @classmethod @contextlib.contextmanager @@ -74,109 +88,177 @@ def _to_str(s): return s -def _run_setup(setup_script='setup.py'): - # Note that we can reuse our build directory between calls - # Correctness comes first, then optimization later - __file__ = setup_script - __name__ = '__main__' - f = getattr(tokenize, 'open', open)(__file__) - code = f.read().replace('\\r\\n', '\\n') - f.close() - exec(compile(code, __file__, 'exec'), locals()) - - -def _fix_config(config_settings): - config_settings = config_settings or {} - config_settings.setdefault('--global-option', []) - return config_settings - - -def _get_build_requires(config_settings, requirements): - config_settings = _fix_config(config_settings) - - sys.argv = sys.argv[:1] + ['egg_info'] + \ - config_settings["--global-option"] - try: - with Distribution.patch(): - _run_setup() - except SetupRequirementsError as e: - requirements += e.specifiers - - return requirements - - def _get_immediate_subdirectories(a_dir): return [name for name in os.listdir(a_dir) if os.path.isdir(os.path.join(a_dir, name))] -def get_requires_for_build_wheel(config_settings=None): - config_settings = _fix_config(config_settings) - return _get_build_requires(config_settings, requirements=['wheel']) +def _file_with_extension(directory, extension): + matching = ( + f for f in os.listdir(directory) + if f.endswith(extension) + ) + file, = matching + return file -def get_requires_for_build_sdist(config_settings=None): - config_settings = _fix_config(config_settings) - return _get_build_requires(config_settings, requirements=[]) +def _open_setup_script(setup_script): + if not os.path.exists(setup_script): + # Supply a default setup.py + return io.StringIO(u"from setuptools import setup; setup()") + + return getattr(tokenize, 'open', open)(setup_script) -def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): - sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', _to_str(metadata_directory)] - _run_setup() +class _BuildMetaBackend(object): - dist_info_directory = metadata_directory - while True: - dist_infos = [f for f in os.listdir(dist_info_directory) - if f.endswith('.dist-info')] + def _fix_config(self, config_settings): + config_settings = config_settings or {} + config_settings.setdefault('--global-option', []) + return config_settings - if len(dist_infos) == 0 and \ - len(_get_immediate_subdirectories(dist_info_directory)) == 1: - dist_info_directory = os.path.join( - dist_info_directory, os.listdir(dist_info_directory)[0]) - continue + def _get_build_requires(self, config_settings, requirements): + config_settings = self._fix_config(config_settings) - assert len(dist_infos) == 1 - break + sys.argv = sys.argv[:1] + ['egg_info'] + \ + config_settings["--global-option"] + try: + with Distribution.patch(): + self.run_setup() + except SetupRequirementsError as e: + requirements += e.specifiers - # PEP 517 requires that the .dist-info directory be placed in the - # metadata_directory. To comply, we MUST copy the directory to the root - if dist_info_directory != metadata_directory: - shutil.move( - os.path.join(dist_info_directory, dist_infos[0]), - metadata_directory) - shutil.rmtree(dist_info_directory, ignore_errors=True) + return requirements - return dist_infos[0] + def run_setup(self, setup_script='setup.py'): + # Note that we can reuse our build directory between calls + # Correctness comes first, then optimization later + __file__ = setup_script + __name__ = '__main__' + + with _open_setup_script(__file__) as f: + code = f.read().replace(r'\r\n', r'\n') + + exec(compile(code, __file__, 'exec'), locals()) + + def get_requires_for_build_wheel(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires(config_settings, requirements=['wheel']) + + def get_requires_for_build_sdist(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires(config_settings, requirements=[]) + + def prepare_metadata_for_build_wheel(self, metadata_directory, + config_settings=None): + sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', + _to_str(metadata_directory)] + self.run_setup() + + dist_info_directory = metadata_directory + while True: + dist_infos = [f for f in os.listdir(dist_info_directory) + if f.endswith('.dist-info')] + + if (len(dist_infos) == 0 and + len(_get_immediate_subdirectories(dist_info_directory)) == 1): + + dist_info_directory = os.path.join( + dist_info_directory, os.listdir(dist_info_directory)[0]) + continue + + assert len(dist_infos) == 1 + break + + # PEP 517 requires that the .dist-info directory be placed in the + # metadata_directory. To comply, we MUST copy the directory to the root + if dist_info_directory != metadata_directory: + shutil.move( + os.path.join(dist_info_directory, dist_infos[0]), + metadata_directory) + shutil.rmtree(dist_info_directory, ignore_errors=True) + + return dist_infos[0] + + def _build_with_temp_dir(self, setup_command, result_extension, + result_directory, config_settings): + config_settings = self._fix_config(config_settings) + result_directory = os.path.abspath(result_directory) + + # Build in a temporary directory, then copy to the target. + makedirs(result_directory, exist_ok=True) + with TemporaryDirectory(dir=result_directory) as tmp_dist_dir: + sys.argv = (sys.argv[:1] + setup_command + + ['--dist-dir', tmp_dist_dir] + + config_settings["--global-option"]) + self.run_setup() + + result_basename = _file_with_extension(tmp_dist_dir, result_extension) + result_path = os.path.join(result_directory, result_basename) + if os.path.exists(result_path): + # os.rename will fail overwriting on non-Unix. + os.remove(result_path) + os.rename(os.path.join(tmp_dist_dir, result_basename), result_path) + + return result_basename -def build_wheel(wheel_directory, config_settings=None, - metadata_directory=None): - config_settings = _fix_config(config_settings) - wheel_directory = os.path.abspath(wheel_directory) - sys.argv = sys.argv[:1] + ['bdist_wheel'] + \ - config_settings["--global-option"] - _run_setup() - if wheel_directory != 'dist': - shutil.rmtree(wheel_directory) - shutil.copytree('dist', wheel_directory) + def build_wheel(self, wheel_directory, config_settings=None, + metadata_directory=None): + return self._build_with_temp_dir(['bdist_wheel'], '.whl', + wheel_directory, config_settings) - wheels = [f for f in os.listdir(wheel_directory) - if f.endswith('.whl')] - - assert len(wheels) == 1 - return wheels[0] + def build_sdist(self, sdist_directory, config_settings=None): + return self._build_with_temp_dir(['sdist', '--formats', 'gztar'], + '.tar.gz', sdist_directory, + config_settings) -def build_sdist(sdist_directory, config_settings=None): - config_settings = _fix_config(config_settings) - sdist_directory = os.path.abspath(sdist_directory) - sys.argv = sys.argv[:1] + ['sdist'] + \ - config_settings["--global-option"] + \ - ["--dist-dir", sdist_directory] - _run_setup() +class _BuildMetaLegacyBackend(_BuildMetaBackend): + """Compatibility backend for setuptools - sdists = [f for f in os.listdir(sdist_directory) - if f.endswith('.tar.gz')] + This is a version of setuptools.build_meta that endeavors to maintain backwards + compatibility with pre-PEP 517 modes of invocation. It exists as a temporary + bridge between the old packaging mechanism and the new packaging mechanism, + and will eventually be removed. + """ + def run_setup(self, setup_script='setup.py'): + # In order to maintain compatibility with scripts assuming that + # the setup.py script is in a directory on the PYTHONPATH, inject + # '' into sys.path. (pypa/setuptools#1642) + sys_path = list(sys.path) # Save the original path - assert len(sdists) == 1 - return sdists[0] + script_dir = os.path.dirname(os.path.abspath(setup_script)) + if script_dir not in sys.path: + sys.path.insert(0, script_dir) + + # Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to + # get the directory of the source code. They expect it to refer to the + # setup.py script. + sys_argv_0 = sys.argv[0] + sys.argv[0] = setup_script + + try: + super(_BuildMetaLegacyBackend, + self).run_setup(setup_script=setup_script) + finally: + # While PEP 517 frontends should be calling each hook in a fresh + # subprocess according to the standard (and thus it should not be + # strictly necessary to restore the old sys.path), we'll restore + # the original path so that the path manipulation does not persist + # within the hook after run_setup is called. + sys.path[:] = sys_path + sys.argv[0] = sys_argv_0 + +# The primary backend +_BACKEND = _BuildMetaBackend() + +get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel +get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist +prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel +build_wheel = _BACKEND.build_wheel +build_sdist = _BACKEND.build_sdist + + +# The legacy backend +__legacy__ = _BuildMetaLegacyBackend() diff --git a/libs/common/setuptools/command/__init__.py b/libs/common/setuptools/command/__init__.py index fe619e2e..743f5588 100644 --- a/libs/common/setuptools/command/__init__.py +++ b/libs/common/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/libs/common/setuptools/command/bdist_egg.py b/libs/common/setuptools/command/bdist_egg.py index 9f8df917..98470f17 100644 --- a/libs/common/setuptools/command/bdist_egg.py +++ b/libs/common/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/libs/common/setuptools/command/build_ext.py b/libs/common/setuptools/command/build_ext.py index 60a8a32f..1b51e040 100644 --- a/libs/common/setuptools/command/build_ext.py +++ b/libs/common/setuptools/command/build_ext.py @@ -1,7 +1,6 @@ import os import sys import itertools -import imp from distutils.command.build_ext import build_ext as _du_build_ext from distutils.file_util import copy_file from distutils.ccompiler import new_compiler @@ -12,6 +11,13 @@ from distutils import log from setuptools.extension import Library from setuptools.extern import six +if six.PY2: + import imp + + EXTENSION_SUFFIXES = [s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION] +else: + from importlib.machinery import EXTENSION_SUFFIXES + try: # Attempt to use Cython for building extensions, if available from Cython.Distutils.build_ext import build_ext as _build_ext @@ -64,7 +70,7 @@ if_dl = lambda s: s if have_rtld else '' def get_abi3_suffix(): """Return the file extension for an abi3-compliant Extension()""" - for suffix, _, _ in (s for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION): + for suffix in EXTENSION_SUFFIXES: if '.abi3' in suffix: # Unix return suffix elif suffix == '.pyd': # Windows @@ -107,7 +113,7 @@ class build_ext(_build_ext): if fullname in self.ext_map: ext = self.ext_map[fullname] use_abi3 = ( - six.PY3 + not six.PY2 and getattr(ext, 'py_limited_api') and get_abi3_suffix() ) diff --git a/libs/common/setuptools/command/develop.py b/libs/common/setuptools/command/develop.py index fdc9fc43..b5619246 100644 --- a/libs/common/setuptools/command/develop.py +++ b/libs/common/setuptools/command/develop.py @@ -7,7 +7,7 @@ import io from setuptools.extern import six -from pkg_resources import Distribution, PathMetadata, normalize_path +import pkg_resources from setuptools.command.easy_install import easy_install from setuptools import namespaces import setuptools @@ -65,9 +65,9 @@ class develop(namespaces.DevelopInstaller, easy_install): if self.egg_path is None: self.egg_path = os.path.abspath(ei.egg_base) - target = normalize_path(self.egg_base) - egg_path = normalize_path(os.path.join(self.install_dir, - self.egg_path)) + target = pkg_resources.normalize_path(self.egg_base) + egg_path = pkg_resources.normalize_path( + os.path.join(self.install_dir, self.egg_path)) if egg_path != target: raise DistutilsOptionError( "--egg-path must be a relative path from the install" @@ -75,9 +75,9 @@ class develop(namespaces.DevelopInstaller, easy_install): ) # Make a distribution for the package's source - self.dist = Distribution( + self.dist = pkg_resources.Distribution( target, - PathMetadata(target, os.path.abspath(ei.egg_info)), + pkg_resources.PathMetadata(target, os.path.abspath(ei.egg_info)), project_name=ei.egg_name ) @@ -97,24 +97,25 @@ class develop(namespaces.DevelopInstaller, easy_install): path_to_setup = egg_base.replace(os.sep, '/').rstrip('/') if path_to_setup != os.curdir: path_to_setup = '../' * (path_to_setup.count('/') + 1) - resolved = normalize_path( + resolved = pkg_resources.normalize_path( os.path.join(install_dir, egg_path, path_to_setup) ) - if resolved != normalize_path(os.curdir): + if resolved != pkg_resources.normalize_path(os.curdir): raise DistutilsOptionError( "Can't get a consistent path to setup script from" - " installation directory", resolved, normalize_path(os.curdir)) + " installation directory", resolved, + pkg_resources.normalize_path(os.curdir)) return path_to_setup def install_for_development(self): - if six.PY3 and getattr(self.distribution, 'use_2to3', False): + if not six.PY2 and getattr(self.distribution, 'use_2to3', False): # If we run 2to3 we can not do this inplace: # Ensure metadata is up-to-date self.reinitialize_command('build_py', inplace=0) self.run_command('build_py') bpy_cmd = self.get_finalized_command("build_py") - build_path = normalize_path(bpy_cmd.build_lib) + build_path = pkg_resources.normalize_path(bpy_cmd.build_lib) # Build extensions self.reinitialize_command('egg_info', egg_base=build_path) @@ -128,7 +129,8 @@ class develop(namespaces.DevelopInstaller, easy_install): self.egg_path = build_path self.dist.location = build_path # XXX - self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info) + self.dist._provider = pkg_resources.PathMetadata( + build_path, ei_cmd.egg_info) else: # Without 2to3 inplace works fine: self.run_command('egg_info') @@ -200,6 +202,7 @@ class VersionlessRequirement: name as the 'requirement' so that scripts will work across multiple versions. + >>> from pkg_resources import Distribution >>> dist = Distribution(project_name='foo', version='1.0') >>> str(dist.as_requirement()) 'foo==1.0' diff --git a/libs/common/setuptools/command/easy_install.py b/libs/common/setuptools/command/easy_install.py index 06c98271..426301d6 100644 --- a/libs/common/setuptools/command/easy_install.py +++ b/libs/common/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())) @@ -410,7 +410,13 @@ class easy_install(Command): ] self._expand_attrs(dirs) - def run(self): + def run(self, show_deprecation=True): + if show_deprecation: + self.announce( + "WARNING: The easy_install command is deprecated " + "and will be removed in a future version." + , log.WARN, + ) if self.verbose != self.distribution.verbose: log.set_verbosity(self.verbose) try: @@ -1180,8 +1186,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 +1417,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 +1438,7 @@ def get_site_dirs(): home, 'Library', 'Python', - sys.version[:3], + '{}.{}'.format(*sys.version_info), 'site-packages', ) sitedirs.append(home_sp) @@ -1562,7 +1567,7 @@ def get_exe_prefixes(exe_filename): continue if parts[0].upper() in ('PURELIB', 'PLATLIB'): contents = z.read(name) - if six.PY3: + if not six.PY2: contents = contents.decode() for pth in yield_lines(contents): pth = pth.strip().replace('\\', '/') diff --git a/libs/common/setuptools/command/egg_info.py b/libs/common/setuptools/command/egg_info.py index d9fe3da3..a5c5a2fc 100644 --- a/libs/common/setuptools/command/egg_info.py +++ b/libs/common/setuptools/command/egg_info.py @@ -266,7 +266,7 @@ class egg_info(InfoCommon, Command): to the file. """ log.info("writing %s to %s", what, filename) - if six.PY3: + if not six.PY2: data = data.encode("utf-8") if not self.dry_run: f = open(filename, 'wb') @@ -568,6 +568,7 @@ class manifest_maker(sdist): def add_defaults(self): sdist.add_defaults(self) + self.check_license() self.filelist.append(self.template) self.filelist.append(self.manifest) rcfiles = list(walk_revctrl()) diff --git a/libs/common/setuptools/command/install.py b/libs/common/setuptools/command/install.py index 31a5ddb5..72b9a3e4 100644 --- a/libs/common/setuptools/command/install.py +++ b/libs/common/setuptools/command/install.py @@ -114,7 +114,7 @@ class install(orig.install): args.insert(0, setuptools.bootstrap_install_from) cmd.args = args - cmd.run() + cmd.run(show_deprecation=False) setuptools.bootstrap_install_from = None diff --git a/libs/common/setuptools/command/install_lib.py b/libs/common/setuptools/command/install_lib.py index 2b31c3e3..07d65933 100644 --- a/libs/common/setuptools/command/install_lib.py +++ b/libs/common/setuptools/command/install_lib.py @@ -1,5 +1,5 @@ import os -import imp +import sys from itertools import product, starmap import distutils.command.install_lib as orig @@ -74,10 +74,10 @@ class install_lib(orig.install_lib): yield '__init__.pyc' yield '__init__.pyo' - if not hasattr(imp, 'get_tag'): + if not hasattr(sys, 'implementation'): return - base = os.path.join('__pycache__', '__init__.' + imp.get_tag()) + base = os.path.join('__pycache__', '__init__.' + sys.implementation.cache_tag) yield base + '.pyc' yield base + '.pyo' yield base + '.opt-1.pyc' diff --git a/libs/common/setuptools/command/register.py b/libs/common/setuptools/command/register.py index 98bc0156..b8266b9a 100644 --- a/libs/common/setuptools/command/register.py +++ b/libs/common/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/libs/common/setuptools/command/sdist.py b/libs/common/setuptools/command/sdist.py index bcfae4d8..8c3438ea 100644 --- a/libs/common/setuptools/command/sdist.py +++ b/libs/common/setuptools/command/sdist.py @@ -5,7 +5,7 @@ import sys import io import contextlib -from setuptools.extern import six +from setuptools.extern import six, ordered_set from .py36compat import sdist_add_defaults @@ -121,19 +121,40 @@ class sdist(sdist_add_defaults, orig.sdist): if has_leaky_handle: read_template = __read_template_hack + def _add_defaults_optional(self): + if six.PY2: + sdist_add_defaults._add_defaults_optional(self) + else: + super()._add_defaults_optional() + if os.path.isfile('pyproject.toml'): + self.filelist.append('pyproject.toml') + def _add_defaults_python(self): """getting python files""" if self.distribution.has_pure_modules(): build_py = self.get_finalized_command('build_py') self.filelist.extend(build_py.get_source_files()) - # This functionality is incompatible with include_package_data, and - # will in fact create an infinite recursion if include_package_data - # is True. Use of include_package_data will imply that - # distutils-style automatic handling of package_data is disabled - if not self.distribution.include_package_data: - for _, src_dir, _, filenames in build_py.data_files: - self.filelist.extend([os.path.join(src_dir, filename) - for filename in filenames]) + self._add_data_files(self._safe_data_files(build_py)) + + def _safe_data_files(self, build_py): + """ + Extracting data_files from build_py is known to cause + infinite recursion errors when `include_package_data` + is enabled, so suppress it in that case. + """ + if self.distribution.include_package_data: + return () + return build_py.data_files + + def _add_data_files(self, data_files): + """ + Add data files as found in build_py.data_files. + """ + self.filelist.extend( + os.path.join(src_dir, name) + for _, src_dir, _, filenames in data_files + for name in filenames + ) def _add_defaults_data_files(self): try: @@ -186,7 +207,7 @@ class sdist(sdist_add_defaults, orig.sdist): manifest = open(self.manifest, 'rb') for line in manifest: # The manifest must contain UTF-8. See #303. - if six.PY3: + if not six.PY2: try: line = line.decode('UTF-8') except UnicodeDecodeError: @@ -198,3 +219,34 @@ class sdist(sdist_add_defaults, orig.sdist): continue self.filelist.append(line) manifest.close() + + def check_license(self): + """Checks if license_file' or 'license_files' is configured and adds any + valid paths to 'self.filelist'. + """ + + files = ordered_set.OrderedSet() + + opts = self.distribution.get_option_dict('metadata') + + # ignore the source of the value + _, license_file = opts.get('license_file', (None, None)) + + if license_file is None: + log.debug("'license_file' option was not specified") + else: + files.add(license_file) + + try: + files.update(self.distribution.metadata.license_files) + except TypeError: + log.warn("warning: 'license_files' option is malformed") + + for f in files: + if not os.path.exists(f): + log.warn( + "warning: Failed to find the configured license file '%s'", + f) + files.remove(f) + + self.filelist.extend(files) diff --git a/libs/common/setuptools/command/test.py b/libs/common/setuptools/command/test.py index dde0118c..f6470e9c 100644 --- a/libs/common/setuptools/command/test.py +++ b/libs/common/setuptools/command/test.py @@ -15,6 +15,7 @@ from pkg_resources import (resource_listdir, resource_exists, normalize_path, working_set, _namespace_packages, evaluate_marker, add_activation_listener, require, EntryPoint) from setuptools import Command +from .build_py import _unique_everseen __metaclass__ = type @@ -73,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"), @@ -128,7 +129,7 @@ class test(Command): @contextlib.contextmanager def project_on_sys_path(self, include_dists=[]): - with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False) + with_2to3 = not six.PY2 and getattr(self.distribution, 'use_2to3', False) if with_2to3: # If we run 2to3 we can not do this inplace: @@ -186,7 +187,7 @@ class test(Command): orig_pythonpath = os.environ.get('PYTHONPATH', nothing) current_pythonpath = os.environ.get('PYTHONPATH', '') try: - prefix = os.pathsep.join(paths) + prefix = os.pathsep.join(_unique_everseen(paths)) to_join = filter(None, [prefix, current_pythonpath]) new_path = os.pathsep.join(to_join) if new_path: @@ -213,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) @@ -231,7 +240,7 @@ class test(Command): # Purge modules under test from sys.modules. The test loader will # re-import them from the build location. Required when 2to3 is used # with namespace packages. - if six.PY3 and getattr(self.distribution, 'use_2to3', False): + if not six.PY2 and getattr(self.distribution, 'use_2to3', False): module = self.test_suite.split('.')[0] if module in _namespace_packages: del_modules = [] diff --git a/libs/common/setuptools/command/upload.py b/libs/common/setuptools/command/upload.py index dd17f7a9..ec7f81e2 100644 --- a/libs/common/setuptools/command/upload.py +++ b/libs/common/setuptools/command/upload.py @@ -1,196 +1,17 @@ -import io -import os -import hashlib -import getpass -import platform - -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.errors import RemovedCommandError -from setuptools.extern.six.moves.urllib.request import urlopen, Request -from setuptools.extern.six.moves.urllib.error import HTTPError -from setuptools.extern.six.moves.urllib.parse import urlparse class upload(orig.upload): - """ - Override default upload behavior to obtain password - in a variety of different ways. - """ + """Formerly used to upload packages to PyPI.""" + 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 - ) - - 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() + 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/libs/common/setuptools/command/upload_docs.py b/libs/common/setuptools/command/upload_docs.py index 07aa564a..130a0cb6 100644 --- a/libs/common/setuptools/command/upload_docs.py +++ b/libs/common/setuptools/command/upload_docs.py @@ -24,7 +24,7 @@ from .upload import upload def _encode(s): - errors = 'surrogateescape' if six.PY3 else 'strict' + errors = 'strict' if six.PY2 else 'surrogateescape' return s.encode('utf-8', errors) @@ -153,7 +153,7 @@ class upload_docs(upload): # set up the authentication credentials = _encode(self.username + ':' + self.password) credentials = standard_b64encode(credentials) - if six.PY3: + if not six.PY2: credentials = credentials.decode('ascii') auth = "Basic " + credentials diff --git a/libs/common/setuptools/config.py b/libs/common/setuptools/config.py index d1ac6734..9b9a0c45 100644 --- a/libs/common/setuptools/config.py +++ b/libs/common/setuptools/config.py @@ -12,6 +12,7 @@ from importlib import import_module from distutils.errors import DistutilsOptionError, DistutilsFileError from setuptools.extern.packaging.version import LegacyVersion, parse +from setuptools.extern.packaging.specifiers import SpecifierSet from setuptools.extern.six import string_types, PY3 @@ -246,6 +247,26 @@ class ConfigHandler: value = value.lower() return value in ('1', 'true', 'yes') + @classmethod + def _exclude_files_parser(cls, key): + """Returns a parser function to make sure field inputs + are not files. + + Parses a value after getting the key so error messages are + more informative. + + :param key: + :rtype: callable + """ + def parser(value): + exclude_directive = 'file:' + if value.startswith(exclude_directive): + raise ValueError( + 'Only strings are accepted for the {0} field, ' + 'files are not accepted'.format(key)) + return value + return parser + @classmethod def _parse_file(cls, value): """Represents value as a string, allowing including text @@ -255,7 +276,6 @@ class ConfigHandler: directory with setup.py. Examples: - file: LICENSE file: README.rst, CHANGELOG.md, src/file.txt :param str value: @@ -394,7 +414,7 @@ class ConfigHandler: section_parser_method = getattr( self, - # Dots in section names are tranlsated into dunderscores. + # Dots in section names are translated into dunderscores. ('parse_section%s' % method_postfix).replace('.', '__'), None) @@ -407,8 +427,8 @@ class ConfigHandler: def _deprecated_config_handler(self, func, msg, warning_class): """ this function will wrap around parameters that are deprecated - - :param msg: deprecation message + + :param msg: deprecation message :param warning_class: class of warning exception to be raised :param func: function to be wrapped around """ @@ -416,7 +436,7 @@ class ConfigHandler: def config_handler(*args, **kwargs): warnings.warn(msg, warning_class) return func(*args, **kwargs) - + return config_handler @@ -449,18 +469,21 @@ class ConfigMetadataHandler(ConfigHandler): parse_list = self._parse_list parse_file = self._parse_file parse_dict = self._parse_dict + exclude_files_parser = self._exclude_files_parser return { 'platforms': parse_list, 'keywords': parse_list, 'provides': parse_list, - 'requires': self._deprecated_config_handler(parse_list, - "The requires parameter is deprecated, please use " + + 'requires': self._deprecated_config_handler( + parse_list, + "The requires parameter is deprecated, please use " "install_requires for runtime dependencies.", DeprecationWarning), 'obsoletes': parse_list, 'classifiers': self._get_parser_compound(parse_file, parse_list), - 'license': parse_file, + 'license': exclude_files_parser('license'), + 'license_files': parse_list, 'description': parse_file, 'long_description': parse_file, 'version': self._parse_version, @@ -533,6 +556,7 @@ class ConfigOptionsHandler(ConfigHandler): 'packages': self._parse_packages, 'entry_points': self._parse_file, 'py_modules': parse_list, + 'python_requires': SpecifierSet, } def _parse_packages(self, value): diff --git a/libs/common/setuptools/depends.py b/libs/common/setuptools/depends.py index 45e7052d..a37675cb 100644 --- a/libs/common/setuptools/depends.py +++ b/libs/common/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""" +def maybe_close(f): + @contextlib.contextmanager + def empty(): + yield + return + if not f: + return empty() - 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)) - - 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/libs/common/setuptools/dist.py b/libs/common/setuptools/dist.py index 7062ae8d..f6453a08 100644 --- a/libs/common/setuptools/dist.py +++ b/libs/common/setuptools/dist.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- __all__ = ['Distribution'] +import io +import sys import re import os import warnings @@ -9,9 +11,11 @@ import distutils.log import distutils.core import distutils.cmd import distutils.dist +from distutils.util import strtobool +from distutils.debug import DEBUG +from distutils.fancy_getopt import translate_longopt import itertools - from collections import defaultdict from email import message_from_file @@ -23,6 +27,7 @@ from distutils.version import StrictVersion from setuptools.extern import six from setuptools.extern import packaging +from setuptools.extern import ordered_set from setuptools.extern.six.moves import map, filter, filterfalse from . import SetuptoolsDeprecationWarning @@ -32,7 +37,6 @@ from setuptools import windows_support from setuptools.monkey import get_unpatched from setuptools.config import parse_configuration import pkg_resources -from .py36compat import Distribution_parse_config_files __import__('setuptools.extern.packaging.specifiers') __import__('setuptools.extern.packaging.version') @@ -51,7 +55,8 @@ def get_metadata_version(self): mv = StrictVersion('2.1') elif (self.maintainer is not None or self.maintainer_email is not None or - getattr(self, 'python_requires', None) is not None): + getattr(self, 'python_requires', None) is not None or + self.project_urls): mv = StrictVersion('1.2') elif (self.provides or self.requires or self.obsoletes or self.classifiers or self.download_url): @@ -130,7 +135,6 @@ def write_pkg_file(self, file): def write_field(key, value): file.write("%s: %s\n" % (key, value)) - write_field('Metadata-Version', str(version)) write_field('Name', self.get_name()) write_field('Version', self.get_version()) @@ -210,8 +214,12 @@ def check_importable(dist, attr, value): def assert_string_list(dist, attr, value): - """Verify that value is a string list or None""" + """Verify that value is a string list""" try: + # verify that value is a list or tuple to exclude unordered + # or single-use iterables + assert isinstance(value, (list, tuple)) + # verify that elements of value are strings assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError): raise DistutilsSetupError( @@ -304,20 +312,17 @@ def check_test_suite(dist, attr, value): def check_package_data(dist, attr, value): """Verify that value is a dictionary of package names to glob lists""" - if isinstance(value, dict): - for k, v in value.items(): - if not isinstance(k, str): - break - try: - iter(v) - except TypeError: - break - else: - return - raise DistutilsSetupError( - attr + " must be a dictionary mapping package names to lists of " - "wildcard patterns" - ) + if not isinstance(value, dict): + raise DistutilsSetupError( + "{!r} must be a dictionary mapping package names to lists of " + "string wildcard patterns".format(attr)) + for k, v in value.items(): + if not isinstance(k, six.string_types): + raise DistutilsSetupError( + "keys of {!r} dict must be strings (got {!r})" + .format(attr, k) + ) + assert_string_list(dist, 'values of {!r} dict'.format(attr), v) def check_packages(dist, attr, value): @@ -332,7 +337,7 @@ def check_packages(dist, attr, value): _Distribution = get_unpatched(distutils.core.Distribution) -class Distribution(Distribution_parse_config_files, _Distribution): +class Distribution(_Distribution): """Distribution with support for features, tests, and package data This is an enhanced version of 'distutils.dist.Distribution' that @@ -403,7 +408,8 @@ class Distribution(Distribution_parse_config_files, _Distribution): _DISTUTILS_UNSUPPORTED_METADATA = { 'long_description_content_type': None, 'project_urls': dict, - 'provides_extras': set, + 'provides_extras': ordered_set.OrderedSet, + 'license_files': ordered_set.OrderedSet, } _patched_dist = None @@ -556,12 +562,141 @@ class Distribution(Distribution_parse_config_files, _Distribution): req.marker = None return req + def _parse_config_files(self, filenames=None): + """ + Adapted from distutils.dist.Distribution.parse_config_files, + this method provides the same functionality in subtly-improved + ways. + """ + from setuptools.extern.six.moves.configparser import ConfigParser + + # Ignore install directory options if we have a venv + if not six.PY2 and sys.prefix != sys.base_prefix: + ignore_options = [ + 'install-base', 'install-platbase', 'install-lib', + 'install-platlib', 'install-purelib', 'install-headers', + 'install-scripts', 'install-data', 'prefix', 'exec-prefix', + 'home', 'user', 'root'] + else: + ignore_options = [] + + ignore_options = frozenset(ignore_options) + + if filenames is None: + filenames = self.find_config_files() + + if DEBUG: + self.announce("Distribution.parse_config_files():") + + parser = ConfigParser() + for filename in filenames: + with io.open(filename, encoding='utf-8') as reader: + if DEBUG: + self.announce(" reading {filename}".format(**locals())) + (parser.readfp if six.PY2 else parser.read_file)(reader) + for section in parser.sections(): + options = parser.options(section) + opt_dict = self.get_option_dict(section) + + for opt in options: + if opt != '__name__' and opt not in ignore_options: + val = self._try_str(parser.get(section, opt)) + opt = opt.replace('-', '_') + opt_dict[opt] = (filename, val) + + # Make the ConfigParser forget everything (so we retain + # the original filenames that options come from) + parser.__init__() + + # If there was a "global" section in the config file, use it + # to set Distribution options. + + if 'global' in self.command_options: + for (opt, (src, val)) in self.command_options['global'].items(): + alias = self.negative_opt.get(opt) + try: + if alias: + setattr(self, alias, not strtobool(val)) + elif opt in ('verbose', 'dry_run'): # ugh! + setattr(self, opt, strtobool(val)) + else: + setattr(self, opt, val) + except ValueError as msg: + raise DistutilsOptionError(msg) + + @staticmethod + def _try_str(val): + """ + On Python 2, much of distutils relies on string values being of + type 'str' (bytes) and not unicode text. If the value can be safely + encoded to bytes using the default encoding, prefer that. + + Why the default encoding? Because that value can be implicitly + decoded back to text if needed. + + Ref #1653 + """ + if not six.PY2: + return val + try: + return val.encode() + except UnicodeEncodeError: + pass + return val + + def _set_command_options(self, command_obj, option_dict=None): + """ + Set the options for 'command_obj' from 'option_dict'. Basically + this means copying elements of a dictionary ('option_dict') to + attributes of an instance ('command'). + + 'command_obj' must be a Command instance. If 'option_dict' is not + supplied, uses the standard option dictionary for this command + (from 'self.command_options'). + + (Adopted from distutils.dist.Distribution._set_command_options) + """ + command_name = command_obj.get_command_name() + if option_dict is None: + option_dict = self.get_option_dict(command_name) + + if DEBUG: + self.announce(" setting options for '%s' command:" % command_name) + for (option, (source, value)) in option_dict.items(): + if DEBUG: + self.announce(" %s = %s (from %s)" % (option, value, + source)) + try: + bool_opts = [translate_longopt(o) + for o in command_obj.boolean_options] + except AttributeError: + bool_opts = [] + try: + neg_opt = command_obj.negative_opt + except AttributeError: + neg_opt = {} + + try: + is_string = isinstance(value, six.string_types) + if option in neg_opt and is_string: + setattr(command_obj, neg_opt[option], not strtobool(value)) + elif option in bool_opts and is_string: + setattr(command_obj, option, strtobool(value)) + elif hasattr(command_obj, option): + setattr(command_obj, option, value) + else: + raise DistutilsOptionError( + "error in %s: command '%s' has no such option '%s'" + % (source, command_name, option)) + except ValueError as msg: + raise DistutilsOptionError(msg) + def parse_config_files(self, filenames=None, ignore_option_errors=False): """Parses configuration files from various levels and loads configuration. """ - _Distribution.parse_config_files(self, filenames=filenames) + self._parse_config_files(filenames=filenames) parse_configuration(self, self.command_options, ignore_option_errors=ignore_option_errors) @@ -590,15 +725,28 @@ class Distribution(Distribution_parse_config_files, _Distribution): return resolved_dists def finalize_options(self): - _Distribution.finalize_options(self) - if self.features: - self._set_global_opts_from_features() + """ + Allow plugins to apply arbitrary operations to the + distribution. Each hook may optionally define a 'order' + to influence the order of execution. Smaller numbers + go first and the default is 0. + """ + group = 'setuptools.finalize_distribution_options' + def by_order(hook): + return getattr(hook, 'order', 0) + eps = map(lambda e: e.load(), pkg_resources.iter_entry_points(group)) + for ep in sorted(eps, key=by_order): + ep(self) + + def _finalize_setup_keywords(self): for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): value = getattr(self, ep.name, None) if value is not None: ep.require(installer=self.fetch_build_egg) ep.load()(self, ep.name, value) + + def _finalize_2to3_doctests(self): if getattr(self, 'convert_2to3_doctests', None): # XXX may convert to set here when we can rely on set being builtin self.convert_2to3_doctests = [ @@ -625,36 +773,15 @@ class Distribution(Distribution_parse_config_files, _Distribution): def fetch_build_egg(self, req): """Fetch an egg needed for building""" - from setuptools.command.easy_install import easy_install - dist = self.__class__({'script_args': ['easy_install']}) - opts = dist.get_option_dict('easy_install') - opts.clear() - opts.update( - (k, v) - for k, v in self.get_option_dict('easy_install').items() - if k in ( - # don't use any other settings - 'find_links', 'site_dirs', 'index_url', - 'optimize', 'site_dirs', 'allow_hosts', - )) - if self.dependency_links: - links = self.dependency_links[:] - if 'find_links' in opts: - links = opts['find_links'][1] + links - opts['find_links'] = ('setup', links) - install_dir = self.get_egg_cache_dir() - cmd = easy_install( - dist, args=["x"], install_dir=install_dir, - exclude_scripts=True, - always_copy=False, build_directory=None, editable=False, - upgrade=False, multi_version=True, no_report=True, user=False - ) - cmd.ensure_finalized() - return cmd.easy_install(req) + from setuptools.installer import fetch_build_egg + return fetch_build_egg(self, req) - def _set_global_opts_from_features(self): + def _finalize_feature_opts(self): """Add --with-X/--without-X options based on optional features""" + if not self.features: + return + go = [] no = self.negative_opt.copy() @@ -747,7 +874,7 @@ class Distribution(Distribution_parse_config_files, _Distribution): def include(self, **attrs): """Add items to distribution that are named in keyword arguments - For example, 'dist.exclude(py_modules=["x"])' would add 'x' to + For example, 'dist.include(py_modules=["x"])' would add 'x' to the distribution's 'py_modules' attribute, if it was not already there. @@ -965,7 +1092,6 @@ class Distribution(Distribution_parse_config_files, _Distribution): return _Distribution.handle_display_options(self, option_order) # Stdout may be StringIO (e.g. in tests) - import io if not isinstance(sys.stdout, io.TextIOWrapper): return _Distribution.handle_display_options(self, option_order) @@ -1144,4 +1270,5 @@ class Feature: class DistDeprecationWarning(SetuptoolsDeprecationWarning): - """Class for warning about deprecations in dist in setuptools. Not ignored by default, unlike DeprecationWarning.""" + """Class for warning about deprecations in dist in + setuptools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/libs/common/setuptools/errors.py b/libs/common/setuptools/errors.py new file mode 100644 index 00000000..2701747f --- /dev/null +++ b/libs/common/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/libs/common/setuptools/extern/__init__.py b/libs/common/setuptools/extern/__init__.py index cb2fa329..e8c616f9 100644 --- a/libs/common/setuptools/extern/__init__.py +++ b/libs/common/setuptools/extern/__init__.py @@ -69,5 +69,5 @@ class VendorImporter: sys.meta_path.append(self) -names = 'six', 'packaging', 'pyparsing', +names = 'six', 'packaging', 'pyparsing', 'ordered_set', VendorImporter(__name__, names, 'setuptools._vendor').install() diff --git a/libs/common/setuptools/glibc.py b/libs/common/setuptools/glibc.py deleted file mode 100644 index a134591c..00000000 --- a/libs/common/setuptools/glibc.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file originally from pip: -# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/utils/glibc.py -from __future__ import absolute_import - -import ctypes -import re -import warnings - - -def glibc_version_string(): - "Returns glibc version string, or None if not using glibc." - - # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen - # manpage says, "If filename is NULL, then the returned handle is for the - # main program". This way we can let the linker do the work to figure out - # which libc our process is actually using. - process_namespace = ctypes.CDLL(None) - try: - gnu_get_libc_version = process_namespace.gnu_get_libc_version - except AttributeError: - # Symbol doesn't exist -> therefore, we are not linked to - # glibc. - return None - - # Call gnu_get_libc_version, which returns a string like "2.5" - gnu_get_libc_version.restype = ctypes.c_char_p - version_str = gnu_get_libc_version() - # py2 / py3 compatibility: - if not isinstance(version_str, str): - version_str = version_str.decode("ascii") - - return version_str - - -# Separated out from have_compatible_glibc for easier unit testing -def check_glibc_version(version_str, required_major, minimum_minor): - # Parse string and check against requested version. - # - # We use a regexp instead of str.split because we want to discard any - # random junk that might come after the minor version -- this might happen - # in patched/forked versions of glibc (e.g. Linaro's version of glibc - # uses version strings like "2.20-2014.11"). See gh-3588. - m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) - if not m: - warnings.warn("Expected glibc version with 2 components major.minor," - " got: %s" % version_str, RuntimeWarning) - return False - return (int(m.group("major")) == required_major and - int(m.group("minor")) >= minimum_minor) - - -def have_compatible_glibc(required_major, minimum_minor): - version_str = glibc_version_string() - if version_str is None: - return False - return check_glibc_version(version_str, required_major, minimum_minor) - - -# platform.libc_ver regularly returns completely nonsensical glibc -# versions. E.g. on my computer, platform says: -# -# ~$ python2.7 -c 'import platform; print(platform.libc_ver())' -# ('glibc', '2.7') -# ~$ python3.5 -c 'import platform; print(platform.libc_ver())' -# ('glibc', '2.9') -# -# But the truth is: -# -# ~$ ldd --version -# ldd (Debian GLIBC 2.22-11) 2.22 -# -# This is unfortunate, because it means that the linehaul data on libc -# versions that was generated by pip 8.1.2 and earlier is useless and -# misleading. Solution: instead of using platform, use our code that actually -# works. -def libc_ver(): - """Try to determine the glibc version - - Returns a tuple of strings (lib, version) which default to empty strings - in case the lookup fails. - """ - glibc_version = glibc_version_string() - if glibc_version is None: - return ("", "") - else: - return ("glibc", glibc_version) diff --git a/libs/common/setuptools/installer.py b/libs/common/setuptools/installer.py new file mode 100644 index 00000000..9f8be2ef --- /dev/null +++ b/libs/common/setuptools/installer.py @@ -0,0 +1,150 @@ +import glob +import os +import subprocess +import sys +from distutils import log +from distutils.errors import DistutilsError + +import pkg_resources +from setuptools.command.easy_install import easy_install +from setuptools.extern import six +from setuptools.wheel import Wheel + +from .py31compat import TemporaryDirectory + + +def _fixup_find_links(find_links): + """Ensure find-links option end-up being a list of strings.""" + if isinstance(find_links, six.string_types): + return find_links.split() + assert isinstance(find_links, (tuple, list)) + return find_links + + +def _legacy_fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Legacy path using EasyInstall. + """ + tmp_dist = dist.__class__({'script_args': ['easy_install']}) + opts = tmp_dist.get_option_dict('easy_install') + opts.clear() + opts.update( + (k, v) + for k, v in dist.get_option_dict('easy_install').items() + if k in ( + # don't use any other settings + 'find_links', 'site_dirs', 'index_url', + 'optimize', 'site_dirs', 'allow_hosts', + )) + if dist.dependency_links: + links = dist.dependency_links[:] + if 'find_links' in opts: + links = _fixup_find_links(opts['find_links'][1]) + links + opts['find_links'] = ('setup', links) + install_dir = dist.get_egg_cache_dir() + cmd = easy_install( + tmp_dist, args=["x"], install_dir=install_dir, + exclude_scripts=True, + always_copy=False, build_directory=None, editable=False, + upgrade=False, multi_version=True, no_report=True, user=False + ) + cmd.ensure_finalized() + return cmd.easy_install(req) + + +def fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Use pip/wheel to fetch/build a wheel.""" + # Check pip is available. + try: + pkg_resources.get_distribution('pip') + except pkg_resources.DistributionNotFound: + dist.announce( + 'WARNING: The pip package is not available, falling back ' + 'to EasyInstall for handling setup_requires/test_requires; ' + 'this is deprecated and will be removed in a future version.' + , log.WARN + ) + return _legacy_fetch_build_egg(dist, req) + # Warn if wheel is not. + try: + pkg_resources.get_distribution('wheel') + except pkg_resources.DistributionNotFound: + dist.announce('WARNING: The wheel package is not available.', log.WARN) + # Ignore environment markers; if supplied, it is required. + req = strip_marker(req) + # Take easy_install options into account, but do not override relevant + # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll + # take precedence. + opts = dist.get_option_dict('easy_install') + if 'allow_hosts' in opts: + raise DistutilsError('the `allow-hosts` option is not supported ' + 'when using pip to install requirements.') + if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ: + quiet = False + else: + quiet = True + if 'PIP_INDEX_URL' in os.environ: + index_url = None + elif 'index_url' in opts: + index_url = opts['index_url'][1] + else: + index_url = None + if 'find_links' in opts: + find_links = _fixup_find_links(opts['find_links'][1])[:] + else: + find_links = [] + if dist.dependency_links: + find_links.extend(dist.dependency_links) + eggs_dir = os.path.realpath(dist.get_egg_cache_dir()) + environment = pkg_resources.Environment() + for egg_dist in pkg_resources.find_distributions(eggs_dir): + if egg_dist in req and environment.can_add(egg_dist): + return egg_dist + with TemporaryDirectory() as tmpdir: + cmd = [ + sys.executable, '-m', 'pip', + '--disable-pip-version-check', + 'wheel', '--no-deps', + '-w', tmpdir, + ] + if quiet: + cmd.append('--quiet') + if index_url is not None: + cmd.extend(('--index-url', index_url)) + if find_links is not None: + for link in find_links: + cmd.extend(('--find-links', link)) + # If requirement is a PEP 508 direct URL, directly pass + # the URL to pip, as `req @ url` does not work on the + # command line. + if req.url: + cmd.append(req.url) + else: + cmd.append(str(req)) + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError as e: + raise DistutilsError(str(e)) + wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0]) + dist_location = os.path.join(eggs_dir, wheel.egg_name()) + wheel.install_as_egg(dist_location) + dist_metadata = pkg_resources.PathMetadata( + dist_location, os.path.join(dist_location, 'EGG-INFO')) + dist = pkg_resources.Distribution.from_filename( + dist_location, metadata=dist_metadata) + return dist + + +def strip_marker(req): + """ + Return a new requirement without the environment marker to avoid + calling pip with something like `babel; extra == "i18n"`, which + would always be ignored. + """ + # create a copy to avoid mutating the input + req = pkg_resources.Requirement.parse(str(req)) + req.marker = None + return req diff --git a/libs/common/setuptools/msvc.py b/libs/common/setuptools/msvc.py index b9c472f1..2ffe1c81 100644 --- a/libs/common/setuptools/msvc.py +++ b/libs/common/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,16 +65,14 @@ 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). + + Fall back to original behavior when the standalone compiler is not + available. Redirect the path of "vcvarsall.bat". - Known supported compilers - ------------------------- - Microsoft Visual C++ 9.0: - Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) - Parameters ---------- version: float @@ -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() - def find_available_vc_vers(self): + # 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 all available Microsoft Visual C++ versions. + 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_reg_vs_vers(self): + """ + 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. + + Return + ------ + str + path """ - self.VSInstallDir + path = self._guess_vc() or self._guess_vc_legacy() - 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 - - 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 - """ - if self.vc_ver <= 14.0: - return + Locate Visual C++ for VS2017+. + + Return + ------ + str + path + """ + 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. + + Return + ------ + tuple of str + versions """ - 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') + 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 + 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' - def _use_last_dir_name(self, path, prefix=''): + @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) @@ -846,205 +1078,255 @@ class EnvironmentInfo: err = 'No suitable Microsoft Visual C++ version found' 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 - """ - include = os.path.join(self.si.WindowsSdkDir, 'include') + Microsoft Windows SDK Include. - if self.vc_ver <= 10.0: - return [include, os.path.join(include, 'gl')] + Return + ------ + list of str + paths + """ + include = join(self.si.WindowsSdkDir, 'include') + + 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 + Microsoft Visual C++ runtime redistributable dll. + + Return + ------ + str + path """ - 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' + vcruntime = 'vcruntime%d0.dll' % self.vc_ver + arch_subdir = self.pi.target_dir(x64=True).strip('\\') - # Visual Studio 2017 is still Visual C++ 14.0 - dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver + # 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')] - vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver) - return os.path.join(redist_path, vcruntime) + 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/libs/common/setuptools/package_index.py b/libs/common/setuptools/package_index.py index 1608b91a..f419d471 100644 --- a/libs/common/setuptools/package_index.py +++ b/libs/common/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): @@ -856,7 +856,7 @@ class PackageIndex(Environment): scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) if not netloc and path.startswith('//') and '/' in path[2:]: netloc, path = path[2:].split('/', 1) - auth, host = urllib.parse.splituser(netloc) + auth, host = _splituser(netloc) if auth: if ':' in auth: user, pw = auth.split(':', 1) @@ -897,7 +897,7 @@ class PackageIndex(Environment): if rev is not None: self.info("Checking out %s", rev) - os.system("(cd %s && git checkout --quiet %s)" % ( + os.system("git -C %s checkout --quiet %s" % ( filename, rev, )) @@ -913,7 +913,7 @@ class PackageIndex(Environment): if rev is not None: self.info("Updating to %s", rev) - os.system("(cd %s && hg up -C -r %s -q)" % ( + os.system("hg --cwd %s up -C -r %s -q" % ( filename, rev, )) @@ -1047,7 +1047,8 @@ class PyPIConfig(configparser.RawConfigParser): def open_with_auth(url, opener=urllib.request.urlopen): """Open a urllib2 request, handling HTTP authentication""" - scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url) + parsed = urllib.parse.urlparse(url) + scheme, netloc, path, params, query, frag = parsed # Double scheme does not raise on Mac OS X as revealed by a # failing test. We would expect "nonnumeric port". Refs #20. @@ -1055,7 +1056,7 @@ def open_with_auth(url, opener=urllib.request.urlopen): raise http_client.InvalidURL("nonnumeric port: ''") if scheme in ('http', 'https'): - auth, host = urllib.parse.splituser(netloc) + auth, address = _splituser(netloc) else: auth = None @@ -1068,7 +1069,7 @@ def open_with_auth(url, opener=urllib.request.urlopen): if auth: auth = "Basic " + _encode_auth(auth) - parts = scheme, host, path, params, query, frag + parts = scheme, address, path, params, query, frag new_url = urllib.parse.urlunparse(parts) request = urllib.request.Request(new_url) request.add_header("Authorization", auth) @@ -1082,13 +1083,20 @@ def open_with_auth(url, opener=urllib.request.urlopen): # Put authentication info back into request URL if same host, # so that links found on the page will work s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url) - if s2 == scheme and h2 == host: + if s2 == scheme and h2 == address: parts = s2, netloc, path2, param2, query2, frag2 fp.url = urllib.parse.urlunparse(parts) return fp +# copy of urllib.parse._splituser from Python 3.8 +def _splituser(host): + """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" + user, delim, host = host.rpartition('@') + return (user if delim else None), host + + # adding a timeout to avoid freezing package_index open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth) diff --git a/libs/common/setuptools/pep425tags.py b/libs/common/setuptools/pep425tags.py deleted file mode 100644 index 8bf4277d..00000000 --- a/libs/common/setuptools/pep425tags.py +++ /dev/null @@ -1,319 +0,0 @@ -# This file originally from pip: -# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/pep425tags.py -"""Generate and work with PEP 425 Compatibility Tags.""" -from __future__ import absolute_import - -import distutils.util -from distutils import log -import platform -import re -import sys -import sysconfig -import warnings -from collections import OrderedDict - -from .extern import six - -from . import glibc - -_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') - - -def get_config_var(var): - try: - return sysconfig.get_config_var(var) - except IOError as e: # Issue #1074 - warnings.warn("{}".format(e), RuntimeWarning) - return None - - -def get_abbr_impl(): - """Return abbreviated implementation name.""" - if hasattr(sys, 'pypy_version_info'): - pyimpl = 'pp' - elif sys.platform.startswith('java'): - pyimpl = 'jy' - elif sys.platform == 'cli': - pyimpl = 'ip' - else: - pyimpl = 'cp' - return pyimpl - - -def get_impl_ver(): - """Return implementation version.""" - impl_ver = get_config_var("py_version_nodot") - if not impl_ver or get_abbr_impl() == 'pp': - impl_ver = ''.join(map(str, get_impl_version_info())) - return impl_ver - - -def get_impl_version_info(): - """Return sys.version_info-like tuple for use in decrementing the minor - version.""" - if get_abbr_impl() == 'pp': - # as per https://github.com/pypa/pip/issues/2882 - return (sys.version_info[0], sys.pypy_version_info.major, - sys.pypy_version_info.minor) - else: - return sys.version_info[0], sys.version_info[1] - - -def get_impl_tag(): - """ - Returns the Tag for this specific implementation. - """ - return "{}{}".format(get_abbr_impl(), get_impl_ver()) - - -def get_flag(var, fallback, expected=True, warn=True): - """Use a fallback method for determining SOABI flags if the needed config - var is unset or unavailable.""" - val = get_config_var(var) - if val is None: - if warn: - log.debug("Config variable '%s' is unset, Python ABI tag may " - "be incorrect", var) - return fallback() - return val == expected - - -def get_abi_tag(): - """Return the ABI tag based on SOABI (if available) or emulate SOABI - (CPython 2, PyPy).""" - soabi = get_config_var('SOABI') - impl = get_abbr_impl() - if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'): - d = '' - m = '' - u = '' - if get_flag('Py_DEBUG', - lambda: hasattr(sys, 'gettotalrefcount'), - warn=(impl == 'cp')): - d = 'd' - if get_flag('WITH_PYMALLOC', - lambda: impl == 'cp', - warn=(impl == 'cp')): - m = 'm' - if get_flag('Py_UNICODE_SIZE', - lambda: sys.maxunicode == 0x10ffff, - expected=4, - warn=(impl == 'cp' and - six.PY2)) \ - and six.PY2: - u = 'u' - abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) - elif soabi and soabi.startswith('cpython-'): - abi = 'cp' + soabi.split('-')[1] - elif soabi: - abi = soabi.replace('.', '_').replace('-', '_') - else: - abi = None - return abi - - -def _is_running_32bit(): - return sys.maxsize == 2147483647 - - -def get_platform(): - """Return our platform name 'win32', 'linux_x86_64'""" - if sys.platform == 'darwin': - # distutils.util.get_platform() returns the release based on the value - # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may - # be significantly older than the user's current machine. - release, _, machine = platform.mac_ver() - split_ver = release.split('.') - - if machine == "x86_64" and _is_running_32bit(): - machine = "i386" - elif machine == "ppc64" and _is_running_32bit(): - machine = "ppc" - - return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine) - - # XXX remove distutils dependency - result = distutils.util.get_platform().replace('.', '_').replace('-', '_') - if result == "linux_x86_64" and _is_running_32bit(): - # 32 bit Python program (running on a 64 bit Linux): pip should only - # install and run 32 bit compiled extensions in that case. - result = "linux_i686" - - return result - - -def is_manylinux1_compatible(): - # Only Linux, and only x86-64 / i686 - if get_platform() not in {"linux_x86_64", "linux_i686"}: - return False - - # Check for presence of _manylinux module - try: - import _manylinux - return bool(_manylinux.manylinux1_compatible) - except (ImportError, AttributeError): - # Fall through to heuristic check below - pass - - # Check glibc version. CentOS 5 uses glibc 2.5. - return glibc.have_compatible_glibc(2, 5) - - -def get_darwin_arches(major, minor, machine): - """Return a list of supported arches (including group arches) for - the given major, minor and machine architecture of an macOS machine. - """ - arches = [] - - def _supports_arch(major, minor, arch): - # Looking at the application support for macOS versions in the chart - # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears - # our timeline looks roughly like: - # - # 10.0 - Introduces ppc support. - # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64 - # and x86_64 support is CLI only, and cannot be used for GUI - # applications. - # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications. - # 10.6 - Drops support for ppc64 - # 10.7 - Drops support for ppc - # - # Given that we do not know if we're installing a CLI or a GUI - # application, we must be conservative and assume it might be a GUI - # application and behave as if ppc64 and x86_64 support did not occur - # until 10.5. - # - # Note: The above information is taken from the "Application support" - # column in the chart not the "Processor support" since I believe - # that we care about what instruction sets an application can use - # not which processors the OS supports. - if arch == 'ppc': - return (major, minor) <= (10, 5) - if arch == 'ppc64': - return (major, minor) == (10, 5) - if arch == 'i386': - return (major, minor) >= (10, 4) - if arch == 'x86_64': - return (major, minor) >= (10, 5) - if arch in groups: - for garch in groups[arch]: - if _supports_arch(major, minor, garch): - return True - return False - - groups = OrderedDict([ - ("fat", ("i386", "ppc")), - ("intel", ("x86_64", "i386")), - ("fat64", ("x86_64", "ppc64")), - ("fat32", ("x86_64", "i386", "ppc")), - ]) - - if _supports_arch(major, minor, machine): - arches.append(machine) - - for garch in groups: - if machine in groups[garch] and _supports_arch(major, minor, garch): - arches.append(garch) - - arches.append('universal') - - return arches - - -def get_supported(versions=None, noarch=False, platform=None, - impl=None, abi=None): - """Return a list of supported tags for each version specified in - `versions`. - - :param versions: a list of string versions, of the form ["33", "32"], - or None. The first version will be assumed to support our ABI. - :param platform: specify the exact platform you want valid - tags for, or None. If None, use the local system platform. - :param impl: specify the exact implementation you want valid - tags for, or None. If None, use the local interpreter impl. - :param abi: specify the exact abi you want valid - tags for, or None. If None, use the local interpreter abi. - """ - supported = [] - - # Versions must be given with respect to the preference - if versions is None: - versions = [] - version_info = get_impl_version_info() - major = version_info[:-1] - # Support all previous minor Python versions. - for minor in range(version_info[-1], -1, -1): - versions.append(''.join(map(str, major + (minor,)))) - - impl = impl or get_abbr_impl() - - abis = [] - - abi = abi or get_abi_tag() - if abi: - abis[0:0] = [abi] - - abi3s = set() - import imp - for suffix in imp.get_suffixes(): - if suffix[0].startswith('.abi'): - abi3s.add(suffix[0].split('.', 2)[1]) - - abis.extend(sorted(list(abi3s))) - - abis.append('none') - - if not noarch: - arch = platform or get_platform() - if arch.startswith('macosx'): - # support macosx-10.6-intel on macosx-10.9-x86_64 - match = _osx_arch_pat.match(arch) - if match: - name, major, minor, actual_arch = match.groups() - tpl = '{}_{}_%i_%s'.format(name, major) - arches = [] - for m in reversed(range(int(minor) + 1)): - for a in get_darwin_arches(int(major), m, actual_arch): - arches.append(tpl % (m, a)) - else: - # arch pattern didn't match (?!) - arches = [arch] - elif platform is None and is_manylinux1_compatible(): - arches = [arch.replace('linux', 'manylinux1'), arch] - else: - arches = [arch] - - # Current version, current API (built specifically for our Python): - for abi in abis: - for arch in arches: - supported.append(('%s%s' % (impl, versions[0]), abi, arch)) - - # abi3 modules compatible with older version of Python - for version in versions[1:]: - # abi3 was introduced in Python 3.2 - if version in {'31', '30'}: - break - for abi in abi3s: # empty set if not Python 3 - for arch in arches: - supported.append(("%s%s" % (impl, version), abi, arch)) - - # Has binaries, does not use the Python API: - for arch in arches: - supported.append(('py%s' % (versions[0][0]), 'none', arch)) - - # No abi / arch, but requires our implementation: - supported.append(('%s%s' % (impl, versions[0]), 'none', 'any')) - # Tagged specifically as being cross-version compatible - # (with just the major version specified) - supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) - - # No abi / arch, generic Python - for i, version in enumerate(versions): - supported.append(('py%s' % (version,), 'none', 'any')) - if i == 0: - supported.append(('py%s' % (version[0]), 'none', 'any')) - - return supported - - -implementation_tag = get_impl_tag() diff --git a/libs/common/setuptools/py27compat.py b/libs/common/setuptools/py27compat.py index 2985011b..1d57360f 100644 --- a/libs/common/setuptools/py27compat.py +++ b/libs/common/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/libs/common/setuptools/py31compat.py b/libs/common/setuptools/py31compat.py index 1a0705ec..e1da7ee2 100644 --- a/libs/common/setuptools/py31compat.py +++ b/libs/common/setuptools/py31compat.py @@ -17,9 +17,9 @@ except ImportError: errors on deletion. """ - def __init__(self): + def __init__(self, **kwargs): self.name = None # Handle mkdtemp raising an exception - self.name = tempfile.mkdtemp() + self.name = tempfile.mkdtemp(**kwargs) def __enter__(self): return self.name diff --git a/libs/common/setuptools/py33compat.py b/libs/common/setuptools/py33compat.py index 87cf5398..cb694436 100644 --- a/libs/common/setuptools/py33compat.py +++ b/libs/common/setuptools/py33compat.py @@ -52,4 +52,8 @@ class Bytecode_compat: Bytecode = getattr(dis, 'Bytecode', Bytecode_compat) -unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape) +unescape = getattr(html, 'unescape', None) +if unescape is None: + # HTMLParser.unescape is deprecated since Python 3.4, and will be removed + # from 3.9. + unescape = html_parser.HTMLParser().unescape diff --git a/libs/common/setuptools/py34compat.py b/libs/common/setuptools/py34compat.py new file mode 100644 index 00000000..3ad91722 --- /dev/null +++ b/libs/common/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/libs/common/setuptools/py36compat.py b/libs/common/setuptools/py36compat.py deleted file mode 100644 index f5279696..00000000 --- a/libs/common/setuptools/py36compat.py +++ /dev/null @@ -1,82 +0,0 @@ -import sys -from distutils.errors import DistutilsOptionError -from distutils.util import strtobool -from distutils.debug import DEBUG - - -class Distribution_parse_config_files: - """ - Mix-in providing forward-compatibility for functionality to be - included by default on Python 3.7. - - Do not edit the code in this class except to update functionality - as implemented in distutils. - """ - def parse_config_files(self, filenames=None): - from configparser import ConfigParser - - # Ignore install directory options if we have a venv - if sys.prefix != sys.base_prefix: - ignore_options = [ - 'install-base', 'install-platbase', 'install-lib', - 'install-platlib', 'install-purelib', 'install-headers', - 'install-scripts', 'install-data', 'prefix', 'exec-prefix', - 'home', 'user', 'root'] - else: - ignore_options = [] - - ignore_options = frozenset(ignore_options) - - if filenames is None: - filenames = self.find_config_files() - - if DEBUG: - self.announce("Distribution.parse_config_files():") - - parser = ConfigParser(interpolation=None) - for filename in filenames: - if DEBUG: - self.announce(" reading %s" % filename) - parser.read(filename) - for section in parser.sections(): - options = parser.options(section) - opt_dict = self.get_option_dict(section) - - for opt in options: - if opt != '__name__' and opt not in ignore_options: - val = parser.get(section,opt) - opt = opt.replace('-', '_') - opt_dict[opt] = (filename, val) - - # Make the ConfigParser forget everything (so we retain - # the original filenames that options come from) - parser.__init__() - - # If there was a "global" section in the config file, use it - # to set Distribution options. - - if 'global' in self.command_options: - for (opt, (src, val)) in self.command_options['global'].items(): - alias = self.negative_opt.get(opt) - try: - if alias: - setattr(self, alias, not strtobool(val)) - elif opt in ('verbose', 'dry_run'): # ugh! - setattr(self, opt, strtobool(val)) - else: - setattr(self, opt, val) - except ValueError as msg: - raise DistutilsOptionError(msg) - - -if sys.version_info < (3,): - # Python 2 behavior is sufficient - class Distribution_parse_config_files: - pass - - -if False: - # When updated behavior is available upstream, - # disable override here. - class Distribution_parse_config_files: - pass diff --git a/libs/common/setuptools/ssl_support.py b/libs/common/setuptools/ssl_support.py index 6362f1f4..226db694 100644 --- a/libs/common/setuptools/ssl_support.py +++ b/libs/common/setuptools/ssl_support.py @@ -59,7 +59,7 @@ if not match_hostname: def _dnsname_match(dn, hostname, max_wildcards=1): """Matching according to RFC 6125, section 6.4.3 - http://tools.ietf.org/html/rfc6125#section-6.4.3 + https://tools.ietf.org/html/rfc6125#section-6.4.3 """ pats = [] if not dn: diff --git a/libs/common/setuptools/wheel.py b/libs/common/setuptools/wheel.py index 95a794a8..025aaa82 100644 --- a/libs/common/setuptools/wheel.py +++ b/libs/common/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 @@ -8,11 +9,12 @@ import posixpath import re import zipfile -from pkg_resources import Distribution, PathMetadata, parse_version +import pkg_resources +import setuptools +from pkg_resources import parse_version +from setuptools.extern.packaging.tags import sys_tags from setuptools.extern.packaging.utils import canonicalize_name from setuptools.extern.six import PY3 -from setuptools import Distribution as SetuptoolsDistribution -from setuptools import pep425tags from setuptools.command.egg_info import write_requirements @@ -75,11 +77,11 @@ class Wheel: def is_compatible(self): '''Is the wheel is compatible with the current platform?''' - supported_tags = pep425tags.get_supported() + supported_tags = set((t.interpreter, t.abi, t.platform) for t in sys_tags()) return next((True for t in self.tags() if t in supported_tags), False) def egg_name(self): - return Distribution( + return pkg_resources.Distribution( project_name=self.project_name, version=self.version, platform=(None if self.platform == 'any' else get_platform()), ).egg_name() + '.egg' @@ -130,9 +132,9 @@ class Wheel: zf.extractall(destination_eggdir) # Convert metadata. dist_info = os.path.join(destination_eggdir, dist_info) - dist = Distribution.from_location( + dist = pkg_resources.Distribution.from_location( destination_eggdir, dist_info, - metadata=PathMetadata(destination_eggdir, dist_info), + metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info), ) # Note: Evaluate and strip markers now, @@ -155,17 +157,23 @@ class Wheel: os.path.join(egg_info, 'METADATA'), os.path.join(egg_info, 'PKG-INFO'), ) - setup_dist = SetuptoolsDistribution( + setup_dist = setuptools.Distribution( attrs=dict( install_requires=install_requires, extras_require=extras_require, ), ) - 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): @@ -205,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)