demo + utils venv
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
"""Extensions to the 'distutils' for large or complex distributions"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import functools
|
||||
import distutils.core
|
||||
import distutils.filelist
|
||||
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.moves import filter, map
|
||||
|
||||
import setuptools.version
|
||||
from setuptools.extension import Extension
|
||||
from setuptools.dist import Distribution, Feature
|
||||
from setuptools.depends import Require
|
||||
from . import monkey
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
__all__ = [
|
||||
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
|
||||
'SetuptoolsDeprecationWarning',
|
||||
'find_packages'
|
||||
]
|
||||
|
||||
if PY3:
|
||||
__all__.append('find_namespace_packages')
|
||||
|
||||
__version__ = setuptools.version.__version__
|
||||
|
||||
bootstrap_install_from = None
|
||||
|
||||
# If we run 2to3 on .py files, should we also convert docstrings?
|
||||
# Default: yes; assume that we can detect doctests reliably
|
||||
run_2to3_on_doctests = True
|
||||
# Standard package names for fixer packages
|
||||
lib2to3_fixer_packages = ['lib2to3.fixes']
|
||||
|
||||
|
||||
class PackageFinder:
|
||||
"""
|
||||
Generate a list of all Python packages found within a directory
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def find(cls, where='.', exclude=(), include=('*',)):
|
||||
"""Return a list all Python packages found within directory 'where'
|
||||
|
||||
'where' is the root directory which will be searched for packages. It
|
||||
should be supplied as a "cross-platform" (i.e. URL-style) path; it will
|
||||
be converted to the appropriate local path syntax.
|
||||
|
||||
'exclude' is a sequence of package names to exclude; '*' can be used
|
||||
as a wildcard in the names, such that 'foo.*' will exclude all
|
||||
subpackages of 'foo' (but not 'foo' itself).
|
||||
|
||||
'include' is a sequence of package names to include. If it's
|
||||
specified, only the named packages will be included. If it's not
|
||||
specified, all found packages will be included. 'include' can contain
|
||||
shell style wildcard patterns just like 'exclude'.
|
||||
"""
|
||||
|
||||
return list(cls._find_packages_iter(
|
||||
convert_path(where),
|
||||
cls._build_filter('ez_setup', '*__pycache__', *exclude),
|
||||
cls._build_filter(*include)))
|
||||
|
||||
@classmethod
|
||||
def _find_packages_iter(cls, where, exclude, include):
|
||||
"""
|
||||
All the packages found in 'where' that pass the 'include' filter, but
|
||||
not the 'exclude' filter.
|
||||
"""
|
||||
for root, dirs, files in os.walk(where, followlinks=True):
|
||||
# Copy dirs to iterate over it, then empty dirs.
|
||||
all_dirs = dirs[:]
|
||||
dirs[:] = []
|
||||
|
||||
for dir in all_dirs:
|
||||
full_path = os.path.join(root, dir)
|
||||
rel_path = os.path.relpath(full_path, where)
|
||||
package = rel_path.replace(os.path.sep, '.')
|
||||
|
||||
# Skip directory trees that are not valid packages
|
||||
if ('.' in dir or not cls._looks_like_package(full_path)):
|
||||
continue
|
||||
|
||||
# Should this package be included?
|
||||
if include(package) and not exclude(package):
|
||||
yield package
|
||||
|
||||
# Keep searching subdirectories, as there may be more packages
|
||||
# down there, even if the parent was excluded.
|
||||
dirs.append(dir)
|
||||
|
||||
@staticmethod
|
||||
def _looks_like_package(path):
|
||||
"""Does a directory look like a package?"""
|
||||
return os.path.isfile(os.path.join(path, '__init__.py'))
|
||||
|
||||
@staticmethod
|
||||
def _build_filter(*patterns):
|
||||
"""
|
||||
Given a list of patterns, return a callable that will be true only if
|
||||
the input matches at least one of the patterns.
|
||||
"""
|
||||
return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns)
|
||||
|
||||
|
||||
class PEP420PackageFinder(PackageFinder):
|
||||
@staticmethod
|
||||
def _looks_like_package(path):
|
||||
return True
|
||||
|
||||
|
||||
find_packages = PackageFinder.find
|
||||
|
||||
if PY3:
|
||||
find_namespace_packages = PEP420PackageFinder.find
|
||||
|
||||
|
||||
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')
|
||||
))
|
||||
# Honor setup.cfg's options.
|
||||
dist.parse_config_files(ignore_option_errors=True)
|
||||
if dist.setup_requires:
|
||||
dist.fetch_build_eggs(dist.setup_requires)
|
||||
|
||||
|
||||
def setup(**attrs):
|
||||
# Make sure we have any requirements needed to interpret 'attrs'.
|
||||
_install_setup_requires(attrs)
|
||||
return distutils.core.setup(**attrs)
|
||||
|
||||
setup.__doc__ = distutils.core.setup.__doc__
|
||||
|
||||
|
||||
_Command = monkey.get_unpatched(distutils.core.Command)
|
||||
|
||||
|
||||
class Command(_Command):
|
||||
__doc__ = _Command.__doc__
|
||||
|
||||
command_consumes_arguments = False
|
||||
|
||||
def __init__(self, dist, **kw):
|
||||
"""
|
||||
Construct the command for dist, updating
|
||||
vars(self) with any keyword parameters.
|
||||
"""
|
||||
_Command.__init__(self, dist)
|
||||
vars(self).update(kw)
|
||||
|
||||
def reinitialize_command(self, command, reinit_subcommands=0, **kw):
|
||||
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
|
||||
vars(cmd).update(kw)
|
||||
return cmd
|
||||
|
||||
|
||||
def _find_all_simple(path):
|
||||
"""
|
||||
Find all files under 'path'
|
||||
"""
|
||||
results = (
|
||||
os.path.join(base, file)
|
||||
for base, dirs, files in os.walk(path, followlinks=True)
|
||||
for file in files
|
||||
)
|
||||
return filter(os.path.isfile, results)
|
||||
|
||||
|
||||
def findall(dir=os.curdir):
|
||||
"""
|
||||
Find all files under 'dir' and return the list of full filenames.
|
||||
Unless dir is '.', return full filenames with dir prepended.
|
||||
"""
|
||||
files = _find_all_simple(dir)
|
||||
if dir == os.curdir:
|
||||
make_rel = functools.partial(os.path.relpath, start=dir)
|
||||
files = map(make_rel, files)
|
||||
return list(files)
|
||||
|
||||
|
||||
# Apply monkey patches
|
||||
monkey.patch_all()
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
class SetuptoolsDeprecationWarning(Warning):
|
||||
"""
|
||||
Base class for warning deprecations in ``setuptools``
|
||||
|
||||
This class is not derived from ``DeprecationWarning``, and as such is
|
||||
visible by default.
|
||||
"""
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
# 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, division, print_function
|
||||
|
||||
__all__ = [
|
||||
"__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"
|
||||
|
||||
__author__ = "Donald Stufft and individual contributors"
|
||||
__email__ = "donald@stufft.io"
|
||||
|
||||
__license__ = "BSD or Apache License, Version 2.0"
|
||||
__copyright__ = "Copyright 2014-2016 %s" % __author__
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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, division, print_function
|
||||
|
||||
from .__about__ import (
|
||||
__author__, __copyright__, __email__, __license__, __summary__, __title__,
|
||||
__uri__, __version__
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"__title__", "__summary__", "__uri__", "__version__", "__author__",
|
||||
"__email__", "__license__", "__copyright__",
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
# 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, division, print_function
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
else:
|
||||
string_types = basestring,
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""
|
||||
Create a base class with a metaclass.
|
||||
"""
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta):
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
@@ -0,0 +1,68 @@
|
||||
# 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, division, print_function
|
||||
|
||||
|
||||
class Infinity(object):
|
||||
|
||||
def __repr__(self):
|
||||
return "Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
return False
|
||||
|
||||
def __le__(self, other):
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
return True
|
||||
|
||||
def __ge__(self, other):
|
||||
return True
|
||||
|
||||
def __neg__(self):
|
||||
return NegativeInfinity
|
||||
|
||||
Infinity = Infinity()
|
||||
|
||||
|
||||
class NegativeInfinity(object):
|
||||
|
||||
def __repr__(self):
|
||||
return "-Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
return True
|
||||
|
||||
def __le__(self, other):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
return False
|
||||
|
||||
def __ge__(self, other):
|
||||
return False
|
||||
|
||||
def __neg__(self):
|
||||
return Infinity
|
||||
|
||||
NegativeInfinity = NegativeInfinity()
|
||||
@@ -0,0 +1,301 @@
|
||||
# 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, division, print_function
|
||||
|
||||
import operator
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from setuptools.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd
|
||||
from setuptools.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString
|
||||
from setuptools.extern.pyparsing import Literal as L # noqa
|
||||
|
||||
from ._compat import string_types
|
||||
from .specifiers import Specifier, InvalidSpecifier
|
||||
|
||||
|
||||
__all__ = [
|
||||
"InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName",
|
||||
"Marker", "default_environment",
|
||||
]
|
||||
|
||||
|
||||
class InvalidMarker(ValueError):
|
||||
"""
|
||||
An invalid marker was found, users should refer to PEP 508.
|
||||
"""
|
||||
|
||||
|
||||
class UndefinedComparison(ValueError):
|
||||
"""
|
||||
An invalid operation was attempted on a value that doesn't support it.
|
||||
"""
|
||||
|
||||
|
||||
class UndefinedEnvironmentName(ValueError):
|
||||
"""
|
||||
A name was attempted to be used that does not exist inside of the
|
||||
environment.
|
||||
"""
|
||||
|
||||
|
||||
class Node(object):
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
|
||||
|
||||
def serialize(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
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")
|
||||
)
|
||||
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'
|
||||
}
|
||||
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
|
||||
|
||||
VERSION_CMP = (
|
||||
L("===") |
|
||||
L("==") |
|
||||
L(">=") |
|
||||
L("<=") |
|
||||
L("!=") |
|
||||
L("~=") |
|
||||
L(">") |
|
||||
L("<")
|
||||
)
|
||||
|
||||
MARKER_OP = VERSION_CMP | L("not in") | L("in")
|
||||
MARKER_OP.setParseAction(lambda s, l, t: Op(t[0]))
|
||||
|
||||
MARKER_VALUE = QuotedString("'") | QuotedString('"')
|
||||
MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0]))
|
||||
|
||||
BOOLOP = L("and") | L("or")
|
||||
|
||||
MARKER_VAR = VARIABLE | MARKER_VALUE
|
||||
|
||||
MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR)
|
||||
MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0]))
|
||||
|
||||
LPAREN = L("(").suppress()
|
||||
RPAREN = L(")").suppress()
|
||||
|
||||
MARKER_EXPR = Forward()
|
||||
MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN)
|
||||
MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
|
||||
|
||||
MARKER = stringStart + MARKER_EXPR + stringEnd
|
||||
|
||||
|
||||
def _coerce_parse_result(results):
|
||||
if isinstance(results, ParseResults):
|
||||
return [_coerce_parse_result(i) for i in results]
|
||||
else:
|
||||
return results
|
||||
|
||||
|
||||
def _format_marker(marker, first=True):
|
||||
assert isinstance(marker, (list, tuple, string_types))
|
||||
|
||||
# Sometimes we have a structure like [[...]] which is a single item list
|
||||
# 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))):
|
||||
return _format_marker(marker[0])
|
||||
|
||||
if isinstance(marker, list):
|
||||
inner = (_format_marker(m, first=False) for m in marker)
|
||||
if first:
|
||||
return " ".join(inner)
|
||||
else:
|
||||
return "(" + " ".join(inner) + ")"
|
||||
elif isinstance(marker, tuple):
|
||||
return " ".join([m.serialize() for m in marker])
|
||||
else:
|
||||
return marker
|
||||
|
||||
|
||||
_operators = {
|
||||
"in": lambda lhs, rhs: lhs in rhs,
|
||||
"not in": lambda lhs, rhs: lhs not in rhs,
|
||||
"<": operator.lt,
|
||||
"<=": operator.le,
|
||||
"==": operator.eq,
|
||||
"!=": operator.ne,
|
||||
">=": operator.ge,
|
||||
">": operator.gt,
|
||||
}
|
||||
|
||||
|
||||
def _eval_op(lhs, op, rhs):
|
||||
try:
|
||||
spec = Specifier("".join([op.serialize(), rhs]))
|
||||
except InvalidSpecifier:
|
||||
pass
|
||||
else:
|
||||
return spec.contains(lhs)
|
||||
|
||||
oper = _operators.get(op.serialize())
|
||||
if oper is None:
|
||||
raise UndefinedComparison(
|
||||
"Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs)
|
||||
)
|
||||
|
||||
return oper(lhs, rhs)
|
||||
|
||||
|
||||
_undefined = object()
|
||||
|
||||
|
||||
def _get_env(environment, name):
|
||||
value = environment.get(name, _undefined)
|
||||
|
||||
if value is _undefined:
|
||||
raise UndefinedEnvironmentName(
|
||||
"{0!r} does not exist in evaluation environment.".format(name)
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _evaluate_markers(markers, environment):
|
||||
groups = [[]]
|
||||
|
||||
for marker in markers:
|
||||
assert isinstance(marker, (list, tuple, string_types))
|
||||
|
||||
if isinstance(marker, list):
|
||||
groups[-1].append(_evaluate_markers(marker, environment))
|
||||
elif isinstance(marker, tuple):
|
||||
lhs, op, rhs = marker
|
||||
|
||||
if isinstance(lhs, Variable):
|
||||
lhs_value = _get_env(environment, lhs.value)
|
||||
rhs_value = rhs.value
|
||||
else:
|
||||
lhs_value = lhs.value
|
||||
rhs_value = _get_env(environment, rhs.value)
|
||||
|
||||
groups[-1].append(_eval_op(lhs_value, op, rhs_value))
|
||||
else:
|
||||
assert marker in ["and", "or"]
|
||||
if marker == "or":
|
||||
groups.append([])
|
||||
|
||||
return any(all(item) for item in groups)
|
||||
|
||||
|
||||
def format_full_version(info):
|
||||
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
|
||||
kind = info.releaselevel
|
||||
if kind != 'final':
|
||||
version += kind[0] + str(info.serial)
|
||||
return version
|
||||
|
||||
|
||||
def default_environment():
|
||||
if hasattr(sys, 'implementation'):
|
||||
iver = format_full_version(sys.implementation.version)
|
||||
implementation_name = sys.implementation.name
|
||||
else:
|
||||
iver = '0'
|
||||
implementation_name = ''
|
||||
|
||||
return {
|
||||
"implementation_name": implementation_name,
|
||||
"implementation_version": iver,
|
||||
"os_name": os.name,
|
||||
"platform_machine": platform.machine(),
|
||||
"platform_release": platform.release(),
|
||||
"platform_system": platform.system(),
|
||||
"platform_version": platform.version(),
|
||||
"python_full_version": platform.python_version(),
|
||||
"platform_python_implementation": platform.python_implementation(),
|
||||
"python_version": platform.python_version()[:3],
|
||||
"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])
|
||||
raise InvalidMarker(err_str)
|
||||
|
||||
def __str__(self):
|
||||
return _format_marker(self._markers)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Marker({0!r})>".format(str(self))
|
||||
|
||||
def evaluate(self, environment=None):
|
||||
"""Evaluate a marker.
|
||||
|
||||
Return the boolean from evaluating the given marker against the
|
||||
environment. environment is an optional argument to override all or
|
||||
part of the determined environment.
|
||||
|
||||
The environment is determined from the current Python process.
|
||||
"""
|
||||
current_environment = default_environment()
|
||||
if environment is not None:
|
||||
current_environment.update(environment)
|
||||
|
||||
return _evaluate_markers(self._markers, current_environment)
|
||||
@@ -0,0 +1,127 @@
|
||||
# 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, division, print_function
|
||||
|
||||
import string
|
||||
import re
|
||||
|
||||
from setuptools.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException
|
||||
from setuptools.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine
|
||||
from setuptools.extern.pyparsing import Literal as L # noqa
|
||||
from setuptools.extern.six.moves.urllib import parse as urlparse
|
||||
|
||||
from .markers import MARKER_EXPR, Marker
|
||||
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
|
||||
|
||||
|
||||
class InvalidRequirement(ValueError):
|
||||
"""
|
||||
An invalid requirement was found, users should refer to PEP 508.
|
||||
"""
|
||||
|
||||
|
||||
ALPHANUM = Word(string.ascii_letters + string.digits)
|
||||
|
||||
LBRACKET = L("[").suppress()
|
||||
RBRACKET = L("]").suppress()
|
||||
LPAREN = L("(").suppress()
|
||||
RPAREN = L(")").suppress()
|
||||
COMMA = L(",").suppress()
|
||||
SEMICOLON = L(";").suppress()
|
||||
AT = L("@").suppress()
|
||||
|
||||
PUNCTUATION = Word("-_.")
|
||||
IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM)
|
||||
IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
|
||||
|
||||
NAME = IDENTIFIER("name")
|
||||
EXTRA = IDENTIFIER
|
||||
|
||||
URI = Regex(r'[^ ]+')("url")
|
||||
URL = (AT + URI)
|
||||
|
||||
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
|
||||
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
|
||||
|
||||
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_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
|
||||
_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])
|
||||
)
|
||||
MARKER_SEPERATOR = SEMICOLON
|
||||
MARKER = MARKER_SEPERATOR + 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)
|
||||
|
||||
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
|
||||
|
||||
|
||||
class Requirement(object):
|
||||
"""Parse a requirement.
|
||||
|
||||
Parse a given requirement string into its parts, such as name, specifier,
|
||||
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
|
||||
string.
|
||||
"""
|
||||
|
||||
# TODO: Can we test whether something is contained within a requirement?
|
||||
# If so how do we do that? Do we need to test against the _name_ of
|
||||
# the thing as well as the version? What about the markers?
|
||||
# TODO: Can we normalize the name and extra name?
|
||||
|
||||
def __init__(self, requirement_string):
|
||||
try:
|
||||
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]))
|
||||
|
||||
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")
|
||||
self.url = req.url
|
||||
else:
|
||||
self.url = None
|
||||
self.extras = set(req.extras.asList() if req.extras else [])
|
||||
self.specifier = SpecifierSet(req.specifier)
|
||||
self.marker = req.marker if req.marker else None
|
||||
|
||||
def __str__(self):
|
||||
parts = [self.name]
|
||||
|
||||
if self.extras:
|
||||
parts.append("[{0}]".format(",".join(sorted(self.extras))))
|
||||
|
||||
if self.specifier:
|
||||
parts.append(str(self.specifier))
|
||||
|
||||
if self.url:
|
||||
parts.append("@ {0}".format(self.url))
|
||||
|
||||
if self.marker:
|
||||
parts.append("; {0}".format(self.marker))
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Requirement({0!r})>".format(str(self))
|
||||
@@ -0,0 +1,774 @@
|
||||
# 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, division, print_function
|
||||
|
||||
import abc
|
||||
import functools
|
||||
import itertools
|
||||
import re
|
||||
|
||||
from ._compat import string_types, with_metaclass
|
||||
from .version import Version, LegacyVersion, parse
|
||||
|
||||
|
||||
class InvalidSpecifier(ValueError):
|
||||
"""
|
||||
An invalid specifier was found, users should refer to PEP 440.
|
||||
"""
|
||||
|
||||
|
||||
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
|
||||
|
||||
@abc.abstractmethod
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns the str representation of this Specifier like object. This
|
||||
should be representative of the Specifier itself.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __hash__(self):
|
||||
"""
|
||||
Returns a hash value for this Specifier like object.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Returns a boolean representing whether or not the two Specifier like
|
||||
objects are equal.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
Returns a boolean representing whether or not the two Specifier like
|
||||
objects are not equal.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def prereleases(self):
|
||||
"""
|
||||
Returns whether or not pre-releases as a whole are allowed by this
|
||||
specifier.
|
||||
"""
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
"""
|
||||
Sets whether or not pre-releases as a whole are allowed by this
|
||||
specifier.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def contains(self, item, prereleases=None):
|
||||
"""
|
||||
Determines if the given item is contained within this specifier.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def filter(self, iterable, prereleases=None):
|
||||
"""
|
||||
Takes an iterable of items and filters them so that only items which
|
||||
are contained within this specifier are allowed in it.
|
||||
"""
|
||||
|
||||
|
||||
class _IndividualSpecifier(BaseSpecifier):
|
||||
|
||||
_operators = {}
|
||||
|
||||
def __init__(self, spec="", prereleases=None):
|
||||
match = self._regex.search(spec)
|
||||
if not match:
|
||||
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
|
||||
|
||||
self._spec = (
|
||||
match.group("operator").strip(),
|
||||
match.group("version").strip(),
|
||||
)
|
||||
|
||||
# Store whether or not this Specifier should accept prereleases
|
||||
self._prereleases = prereleases
|
||||
|
||||
def __repr__(self):
|
||||
pre = (
|
||||
", prereleases={0!r}".format(self.prereleases)
|
||||
if self._prereleases is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
return "<{0}({1!r}{2})>".format(
|
||||
self.__class__.__name__,
|
||||
str(self),
|
||||
pre,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "{0}{1}".format(*self._spec)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._spec)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
try:
|
||||
other = self.__class__(other)
|
||||
except InvalidSpecifier:
|
||||
return NotImplemented
|
||||
elif not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self._spec == other._spec
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
try:
|
||||
other = self.__class__(other)
|
||||
except InvalidSpecifier:
|
||||
return NotImplemented
|
||||
elif not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self._spec != other._spec
|
||||
|
||||
def _get_operator(self, op):
|
||||
return getattr(self, "_compare_{0}".format(self._operators[op]))
|
||||
|
||||
def _coerce_version(self, version):
|
||||
if not isinstance(version, (LegacyVersion, Version)):
|
||||
version = parse(version)
|
||||
return version
|
||||
|
||||
@property
|
||||
def operator(self):
|
||||
return self._spec[0]
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._spec[1]
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
return self._prereleases
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
self._prereleases = value
|
||||
|
||||
def __contains__(self, item):
|
||||
return self.contains(item)
|
||||
|
||||
def contains(self, item, prereleases=None):
|
||||
# Determine if prereleases are to be allowed or not.
|
||||
if prereleases is None:
|
||||
prereleases = self.prereleases
|
||||
|
||||
# Normalize item to a Version or LegacyVersion, this allows us to have
|
||||
# a shortcut for ``"2.0" in Specifier(">=2")
|
||||
item = self._coerce_version(item)
|
||||
|
||||
# Determine if we should be supporting prereleases in this specifier
|
||||
# or not, if we do not support prereleases than we can short circuit
|
||||
# logic if this version is a prereleases.
|
||||
if item.is_prerelease and not prereleases:
|
||||
return False
|
||||
|
||||
# Actually do the comparison to determine if this item is contained
|
||||
# within this Specifier or not.
|
||||
return self._get_operator(self.operator)(item, self.version)
|
||||
|
||||
def filter(self, iterable, prereleases=None):
|
||||
yielded = False
|
||||
found_prereleases = []
|
||||
|
||||
kw = {"prereleases": prereleases if prereleases is not None else True}
|
||||
|
||||
# Attempt to iterate over all the values in the iterable and if any of
|
||||
# them match, yield them.
|
||||
for version in iterable:
|
||||
parsed_version = self._coerce_version(version)
|
||||
|
||||
if self.contains(parsed_version, **kw):
|
||||
# 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)):
|
||||
found_prereleases.append(version)
|
||||
# Either this is not a prerelease, or we should have been
|
||||
# accepting prereleases from the begining.
|
||||
else:
|
||||
yielded = True
|
||||
yield version
|
||||
|
||||
# Now that we've iterated over everything, determine if we've yielded
|
||||
# any values, and if we have not and we have any prereleases stored up
|
||||
# then we will go ahead and yield the prereleases.
|
||||
if not yielded and found_prereleases:
|
||||
for version in found_prereleases:
|
||||
yield version
|
||||
|
||||
|
||||
class LegacySpecifier(_IndividualSpecifier):
|
||||
|
||||
_regex_str = (
|
||||
r"""
|
||||
(?P<operator>(==|!=|<=|>=|<|>))
|
||||
\s*
|
||||
(?P<version>
|
||||
[^,;\s)]* # Since this is a "legacy" specifier, and the version
|
||||
# string can be just about anything, we match everything
|
||||
# except for whitespace, a semi-colon for marker support,
|
||||
# a closing paren since versions can be enclosed in
|
||||
# them, and a comma since it's a version separator.
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
_regex = re.compile(
|
||||
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
_operators = {
|
||||
"==": "equal",
|
||||
"!=": "not_equal",
|
||||
"<=": "less_than_equal",
|
||||
">=": "greater_than_equal",
|
||||
"<": "less_than",
|
||||
">": "greater_than",
|
||||
}
|
||||
|
||||
def _coerce_version(self, version):
|
||||
if not isinstance(version, LegacyVersion):
|
||||
version = LegacyVersion(str(version))
|
||||
return version
|
||||
|
||||
def _compare_equal(self, prospective, spec):
|
||||
return prospective == self._coerce_version(spec)
|
||||
|
||||
def _compare_not_equal(self, prospective, spec):
|
||||
return prospective != self._coerce_version(spec)
|
||||
|
||||
def _compare_less_than_equal(self, prospective, spec):
|
||||
return prospective <= self._coerce_version(spec)
|
||||
|
||||
def _compare_greater_than_equal(self, prospective, spec):
|
||||
return prospective >= self._coerce_version(spec)
|
||||
|
||||
def _compare_less_than(self, prospective, spec):
|
||||
return prospective < self._coerce_version(spec)
|
||||
|
||||
def _compare_greater_than(self, prospective, spec):
|
||||
return prospective > self._coerce_version(spec)
|
||||
|
||||
|
||||
def _require_version_compare(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapped(self, prospective, spec):
|
||||
if not isinstance(prospective, Version):
|
||||
return False
|
||||
return fn(self, prospective, spec)
|
||||
return wrapped
|
||||
|
||||
|
||||
class Specifier(_IndividualSpecifier):
|
||||
|
||||
_regex_str = (
|
||||
r"""
|
||||
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
|
||||
(?P<version>
|
||||
(?:
|
||||
# The identity operators allow for an escape hatch that will
|
||||
# do an exact string match of the version you wish to install.
|
||||
# This will not be parsed by PEP 440 and we cannot determine
|
||||
# any semantic meaning from it. This operator is discouraged
|
||||
# but included entirely as an escape hatch.
|
||||
(?<====) # Only match for the identity operator
|
||||
\s*
|
||||
[^\s]* # We just match everything, except for whitespace
|
||||
# since we are only testing for strict identity.
|
||||
)
|
||||
|
|
||||
(?:
|
||||
# The (non)equality operators allow for wild card and local
|
||||
# versions to be specified so we have to define these two
|
||||
# operators separately to enable that.
|
||||
(?<===|!=) # Only match for equals and not equals
|
||||
|
||||
\s*
|
||||
v?
|
||||
(?:[0-9]+!)? # epoch
|
||||
[0-9]+(?:\.[0-9]+)* # release
|
||||
(?: # pre release
|
||||
[-_\.]?
|
||||
(a|b|c|rc|alpha|beta|pre|preview)
|
||||
[-_\.]?
|
||||
[0-9]*
|
||||
)?
|
||||
(?: # post release
|
||||
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
||||
)?
|
||||
|
||||
# You cannot use a wild card and a dev or local version
|
||||
# together so group them with a | and make them optional.
|
||||
(?:
|
||||
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
||||
(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
|
||||
|
|
||||
\.\* # Wild card syntax of .*
|
||||
)?
|
||||
)
|
||||
|
|
||||
(?:
|
||||
# The compatible operator requires at least two digits in the
|
||||
# release segment.
|
||||
(?<=~=) # Only match for the compatible operator
|
||||
|
||||
\s*
|
||||
v?
|
||||
(?:[0-9]+!)? # epoch
|
||||
[0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
|
||||
(?: # pre release
|
||||
[-_\.]?
|
||||
(a|b|c|rc|alpha|beta|pre|preview)
|
||||
[-_\.]?
|
||||
[0-9]*
|
||||
)?
|
||||
(?: # post release
|
||||
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
||||
)?
|
||||
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
||||
)
|
||||
|
|
||||
(?:
|
||||
# All other operators only allow a sub set of what the
|
||||
# (non)equality operators do. Specifically they do not allow
|
||||
# local versions to be specified nor do they allow the prefix
|
||||
# matching wild cards.
|
||||
(?<!==|!=|~=) # We have special cases for these
|
||||
# operators so we want to make sure they
|
||||
# don't match here.
|
||||
|
||||
\s*
|
||||
v?
|
||||
(?:[0-9]+!)? # epoch
|
||||
[0-9]+(?:\.[0-9]+)* # release
|
||||
(?: # pre release
|
||||
[-_\.]?
|
||||
(a|b|c|rc|alpha|beta|pre|preview)
|
||||
[-_\.]?
|
||||
[0-9]*
|
||||
)?
|
||||
(?: # post release
|
||||
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
||||
)?
|
||||
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
||||
)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
_regex = re.compile(
|
||||
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
_operators = {
|
||||
"~=": "compatible",
|
||||
"==": "equal",
|
||||
"!=": "not_equal",
|
||||
"<=": "less_than_equal",
|
||||
">=": "greater_than_equal",
|
||||
"<": "less_than",
|
||||
">": "greater_than",
|
||||
"===": "arbitrary",
|
||||
}
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_compatible(self, prospective, spec):
|
||||
# Compatible releases have an equivalent combination of >= and ==. That
|
||||
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
|
||||
# implement this in terms of the other specifiers instead of
|
||||
# implementing it ourselves. The only thing we need to do is construct
|
||||
# the other specifiers.
|
||||
|
||||
# We want everything but the last item in the version, but we want to
|
||||
# ignore post and dev releases and we want to treat the pre-release as
|
||||
# it's own separate segment.
|
||||
prefix = ".".join(
|
||||
list(
|
||||
itertools.takewhile(
|
||||
lambda x: (not x.startswith("post") and not
|
||||
x.startswith("dev")),
|
||||
_version_split(spec),
|
||||
)
|
||||
)[:-1]
|
||||
)
|
||||
|
||||
# Add the prefix notation to the end of our string
|
||||
prefix += ".*"
|
||||
|
||||
return (self._get_operator(">=")(prospective, spec) and
|
||||
self._get_operator("==")(prospective, prefix))
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_equal(self, prospective, spec):
|
||||
# We need special logic to handle prefix matching
|
||||
if spec.endswith(".*"):
|
||||
# In the case of prefix matching we want to ignore local segment.
|
||||
prospective = Version(prospective.public)
|
||||
# Split the spec out by dots, and pretend that there is an implicit
|
||||
# dot in between a release segment and a pre-release segment.
|
||||
spec = _version_split(spec[:-2]) # Remove the trailing .*
|
||||
|
||||
# Split the prospective version out by dots, and pretend that there
|
||||
# is an implicit dot in between a release segment and a pre-release
|
||||
# segment.
|
||||
prospective = _version_split(str(prospective))
|
||||
|
||||
# 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)]
|
||||
|
||||
# Pad out our two sides with zeros so that they both equal the same
|
||||
# length.
|
||||
spec, prospective = _pad_version(spec, prospective)
|
||||
else:
|
||||
# Convert our spec string into a Version
|
||||
spec = Version(spec)
|
||||
|
||||
# If the specifier does not have a local segment, then we want to
|
||||
# act as if the prospective version also does not have a local
|
||||
# segment.
|
||||
if not spec.local:
|
||||
prospective = Version(prospective.public)
|
||||
|
||||
return prospective == spec
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_not_equal(self, prospective, spec):
|
||||
return not self._compare_equal(prospective, spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_less_than_equal(self, prospective, spec):
|
||||
return prospective <= Version(spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_greater_than_equal(self, prospective, spec):
|
||||
return prospective >= Version(spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_less_than(self, prospective, spec):
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
spec = Version(spec)
|
||||
|
||||
# Check to see if the prospective version is less than the spec
|
||||
# version. If it's not we can short circuit and just return False now
|
||||
# instead of doing extra unneeded work.
|
||||
if not prospective < spec:
|
||||
return False
|
||||
|
||||
# This special case is here so that, unless the specifier itself
|
||||
# includes is a pre-release version, that we do not accept pre-release
|
||||
# versions for the version mentioned in the specifier (e.g. <3.1 should
|
||||
# not match 3.1.dev0, but should match 3.0.dev0).
|
||||
if not spec.is_prerelease and prospective.is_prerelease:
|
||||
if Version(prospective.base_version) == Version(spec.base_version):
|
||||
return False
|
||||
|
||||
# If we've gotten to here, it means that prospective version is both
|
||||
# less than the spec version *and* it's not a pre-release of the same
|
||||
# version in the spec.
|
||||
return True
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_greater_than(self, prospective, spec):
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
spec = Version(spec)
|
||||
|
||||
# Check to see if the prospective version is greater than the spec
|
||||
# version. If it's not we can short circuit and just return False now
|
||||
# instead of doing extra unneeded work.
|
||||
if not prospective > spec:
|
||||
return False
|
||||
|
||||
# This special case is here so that, unless the specifier itself
|
||||
# includes is a post-release version, that we do not accept
|
||||
# post-release versions for the version mentioned in the specifier
|
||||
# (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
|
||||
if not spec.is_postrelease and prospective.is_postrelease:
|
||||
if Version(prospective.base_version) == Version(spec.base_version):
|
||||
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.
|
||||
if prospective.local is not None:
|
||||
if Version(prospective.base_version) == Version(spec.base_version):
|
||||
return False
|
||||
|
||||
# If we've gotten to here, it means that prospective version is both
|
||||
# greater than the spec version *and* it's not a pre-release of the
|
||||
# same version in the spec.
|
||||
return True
|
||||
|
||||
def _compare_arbitrary(self, prospective, spec):
|
||||
return str(prospective).lower() == str(spec).lower()
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
# If there is an explicit prereleases set for this, then we'll just
|
||||
# blindly use that.
|
||||
if self._prereleases is not None:
|
||||
return self._prereleases
|
||||
|
||||
# Look at all of our specifiers and determine if they are inclusive
|
||||
# operators, and if they are if they are including an explicit
|
||||
# prerelease.
|
||||
operator, version = self._spec
|
||||
if operator in ["==", ">=", "<=", "~=", "==="]:
|
||||
# The == specifier can include a trailing .*, if it does we
|
||||
# want to remove before parsing.
|
||||
if operator == "==" and version.endswith(".*"):
|
||||
version = version[:-2]
|
||||
|
||||
# Parse the version, and if it is a pre-release than this
|
||||
# specifier allows pre-releases.
|
||||
if parse(version).is_prerelease:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
self._prereleases = value
|
||||
|
||||
|
||||
_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
|
||||
|
||||
|
||||
def _version_split(version):
|
||||
result = []
|
||||
for item in version.split("."):
|
||||
match = _prefix_regex.search(item)
|
||||
if match:
|
||||
result.extend(match.groups())
|
||||
else:
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def _pad_version(left, right):
|
||||
left_split, right_split = [], []
|
||||
|
||||
# Get the release segment of our versions
|
||||
left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
|
||||
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]):])
|
||||
|
||||
# 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])),
|
||||
)
|
||||
|
||||
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.
|
||||
specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
|
||||
|
||||
# Parsed each individual specifier, attempting first to make it a
|
||||
# Specifier and falling back to a LegacySpecifier.
|
||||
parsed = set()
|
||||
for specifier in specifiers:
|
||||
try:
|
||||
parsed.add(Specifier(specifier))
|
||||
except InvalidSpecifier:
|
||||
parsed.add(LegacySpecifier(specifier))
|
||||
|
||||
# Turn our parsed specifiers into a frozen set and save them for later.
|
||||
self._specs = frozenset(parsed)
|
||||
|
||||
# Store our prereleases value so we can use it later to determine if
|
||||
# we accept prereleases or not.
|
||||
self._prereleases = prereleases
|
||||
|
||||
def __repr__(self):
|
||||
pre = (
|
||||
", prereleases={0!r}".format(self.prereleases)
|
||||
if self._prereleases is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
|
||||
|
||||
def __str__(self):
|
||||
return ",".join(sorted(str(s) for s in self._specs))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._specs)
|
||||
|
||||
def __and__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
other = SpecifierSet(other)
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
specifier = SpecifierSet()
|
||||
specifier._specs = frozenset(self._specs | other._specs)
|
||||
|
||||
if self._prereleases is None and other._prereleases is not None:
|
||||
specifier._prereleases = other._prereleases
|
||||
elif self._prereleases is not None and other._prereleases is None:
|
||||
specifier._prereleases = self._prereleases
|
||||
elif self._prereleases == other._prereleases:
|
||||
specifier._prereleases = self._prereleases
|
||||
else:
|
||||
raise ValueError(
|
||||
"Cannot combine SpecifierSets with True and False prerelease "
|
||||
"overrides."
|
||||
)
|
||||
|
||||
return specifier
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
other = SpecifierSet(other)
|
||||
elif isinstance(other, _IndividualSpecifier):
|
||||
other = SpecifierSet(str(other))
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
return self._specs == other._specs
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, string_types):
|
||||
other = SpecifierSet(other)
|
||||
elif isinstance(other, _IndividualSpecifier):
|
||||
other = SpecifierSet(str(other))
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
return self._specs != other._specs
|
||||
|
||||
def __len__(self):
|
||||
return len(self._specs)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._specs)
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
# If we have been given an explicit prerelease modifier, then we'll
|
||||
# pass that through here.
|
||||
if self._prereleases is not None:
|
||||
return self._prereleases
|
||||
|
||||
# If we don't have any specifiers, and we don't have a forced value,
|
||||
# then we'll just return None since we don't know if this should have
|
||||
# pre-releases or not.
|
||||
if not self._specs:
|
||||
return None
|
||||
|
||||
# Otherwise we'll see if any of the given specifiers accept
|
||||
# prereleases, if any of them do we'll return True, otherwise False.
|
||||
return any(s.prereleases for s in self._specs)
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
self._prereleases = value
|
||||
|
||||
def __contains__(self, item):
|
||||
return self.contains(item)
|
||||
|
||||
def contains(self, item, prereleases=None):
|
||||
# Ensure that our item is a Version or LegacyVersion instance.
|
||||
if not isinstance(item, (LegacyVersion, Version)):
|
||||
item = parse(item)
|
||||
|
||||
# Determine if we're forcing a prerelease or not, if we're not forcing
|
||||
# one for this particular filter call, then we'll use whatever the
|
||||
# SpecifierSet thinks for whether or not we should support prereleases.
|
||||
if prereleases is None:
|
||||
prereleases = self.prereleases
|
||||
|
||||
# We can determine if we're going to allow pre-releases by looking to
|
||||
# see if any of the underlying items supports them. If none of them do
|
||||
# and this item is a pre-release then we do not allow it and we can
|
||||
# short circuit that here.
|
||||
# Note: This means that 1.0.dev1 would not be contained in something
|
||||
# like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
|
||||
if not prereleases and item.is_prerelease:
|
||||
return False
|
||||
|
||||
# We simply dispatch to the underlying specs here to make sure that the
|
||||
# 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
|
||||
)
|
||||
|
||||
def filter(self, iterable, prereleases=None):
|
||||
# Determine if we're forcing a prerelease or not, if we're not forcing
|
||||
# one for this particular filter call, then we'll use whatever the
|
||||
# SpecifierSet thinks for whether or not we should support prereleases.
|
||||
if prereleases is None:
|
||||
prereleases = self.prereleases
|
||||
|
||||
# If we have any specifiers, then we want to wrap our iterable in the
|
||||
# filter method for each one, this will act as a logical AND amongst
|
||||
# each specifier.
|
||||
if self._specs:
|
||||
for spec in self._specs:
|
||||
iterable = spec.filter(iterable, prereleases=bool(prereleases))
|
||||
return iterable
|
||||
# If we do not have any specifiers, then we need to have a rough filter
|
||||
# which will filter out any pre-releases, unless there are no final
|
||||
# releases, and which will filter out LegacyVersion in general.
|
||||
else:
|
||||
filtered = []
|
||||
found_prereleases = []
|
||||
|
||||
for item in iterable:
|
||||
# Ensure that we some kind of Version class for this item.
|
||||
if not isinstance(item, (LegacyVersion, Version)):
|
||||
parsed_version = parse(item)
|
||||
else:
|
||||
parsed_version = item
|
||||
|
||||
# Filter out any item which is parsed as a LegacyVersion
|
||||
if isinstance(parsed_version, LegacyVersion):
|
||||
continue
|
||||
|
||||
# Store any item which is a pre-release for later unless we've
|
||||
# already found a final version or we are accepting prereleases
|
||||
if parsed_version.is_prerelease and not prereleases:
|
||||
if not filtered:
|
||||
found_prereleases.append(item)
|
||||
else:
|
||||
filtered.append(item)
|
||||
|
||||
# If we've found no items except for pre-releases, then we'll go
|
||||
# ahead and use the pre-releases
|
||||
if not filtered and found_prereleases and prereleases is None:
|
||||
return found_prereleases
|
||||
|
||||
return filtered
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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, division, print_function
|
||||
|
||||
import re
|
||||
|
||||
|
||||
_canonicalize_regex = re.compile(r"[-_.]+")
|
||||
|
||||
|
||||
def canonicalize_name(name):
|
||||
# This is taken from PEP 503.
|
||||
return _canonicalize_regex.sub("-", name).lower()
|
||||
@@ -0,0 +1,393 @@
|
||||
# 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, division, print_function
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import re
|
||||
|
||||
from ._structures import Infinity
|
||||
|
||||
|
||||
__all__ = [
|
||||
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
|
||||
]
|
||||
|
||||
|
||||
_Version = collections.namedtuple(
|
||||
"_Version",
|
||||
["epoch", "release", "dev", "pre", "post", "local"],
|
||||
)
|
||||
|
||||
|
||||
def parse(version):
|
||||
"""
|
||||
Parse the given version string and return either a :class:`Version` object
|
||||
or a :class:`LegacyVersion` object depending on if the given version is
|
||||
a valid PEP 440 version or a legacy version.
|
||||
"""
|
||||
try:
|
||||
return Version(version)
|
||||
except InvalidVersion:
|
||||
return LegacyVersion(version)
|
||||
|
||||
|
||||
class InvalidVersion(ValueError):
|
||||
"""
|
||||
An invalid version was found, users should refer to PEP 440.
|
||||
"""
|
||||
|
||||
|
||||
class _BaseVersion(object):
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._key)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._compare(other, lambda s, o: s < o)
|
||||
|
||||
def __le__(self, other):
|
||||
return self._compare(other, lambda s, o: s <= o)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._compare(other, lambda s, o: s == o)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self._compare(other, lambda s, o: s >= o)
|
||||
|
||||
def __gt__(self, other):
|
||||
return self._compare(other, lambda s, o: s > o)
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._compare(other, lambda s, o: s != o)
|
||||
|
||||
def _compare(self, other, method):
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return method(self._key, other._key)
|
||||
|
||||
|
||||
class LegacyVersion(_BaseVersion):
|
||||
|
||||
def __init__(self, version):
|
||||
self._version = str(version)
|
||||
self._key = _legacy_cmpkey(self._version)
|
||||
|
||||
def __str__(self):
|
||||
return self._version
|
||||
|
||||
def __repr__(self):
|
||||
return "<LegacyVersion({0})>".format(repr(str(self)))
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
return False
|
||||
|
||||
|
||||
_legacy_version_component_re = re.compile(
|
||||
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
|
||||
)
|
||||
|
||||
_legacy_version_replacement_map = {
|
||||
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
|
||||
}
|
||||
|
||||
|
||||
def _parse_version_parts(s):
|
||||
for part in _legacy_version_component_re.split(s):
|
||||
part = _legacy_version_replacement_map.get(part, part)
|
||||
|
||||
if not part or part == ".":
|
||||
continue
|
||||
|
||||
if part[:1] in "0123456789":
|
||||
# pad for numeric comparison
|
||||
yield part.zfill(8)
|
||||
else:
|
||||
yield "*" + part
|
||||
|
||||
# ensure that alpha/beta/candidate are before final
|
||||
yield "*final"
|
||||
|
||||
|
||||
def _legacy_cmpkey(version):
|
||||
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
|
||||
# greater than or equal to 0. This will effectively put the LegacyVersion,
|
||||
# which uses the defacto standard originally implemented by setuptools,
|
||||
# as before all PEP 440 versions.
|
||||
epoch = -1
|
||||
|
||||
# This scheme is taken from pkg_resources.parse_version setuptools prior to
|
||||
# it's adoption of the packaging library.
|
||||
parts = []
|
||||
for part in _parse_version_parts(version.lower()):
|
||||
if part.startswith("*"):
|
||||
# remove "-" before a prerelease tag
|
||||
if part < "*final":
|
||||
while parts and parts[-1] == "*final-":
|
||||
parts.pop()
|
||||
|
||||
# remove trailing zeros from each series of numeric parts
|
||||
while parts and parts[-1] == "00000000":
|
||||
parts.pop()
|
||||
|
||||
parts.append(part)
|
||||
parts = tuple(parts)
|
||||
|
||||
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"""
|
||||
v?
|
||||
(?:
|
||||
(?:(?P<epoch>[0-9]+)!)? # epoch
|
||||
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
||||
(?P<pre> # pre-release
|
||||
[-_\.]?
|
||||
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
||||
[-_\.]?
|
||||
(?P<pre_n>[0-9]+)?
|
||||
)?
|
||||
(?P<post> # post release
|
||||
(?:-(?P<post_n1>[0-9]+))
|
||||
|
|
||||
(?:
|
||||
[-_\.]?
|
||||
(?P<post_l>post|rev|r)
|
||||
[-_\.]?
|
||||
(?P<post_n2>[0-9]+)?
|
||||
)
|
||||
)?
|
||||
(?P<dev> # dev release
|
||||
[-_\.]?
|
||||
(?P<dev_l>dev)
|
||||
[-_\.]?
|
||||
(?P<dev_n>[0-9]+)?
|
||||
)?
|
||||
)
|
||||
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
||||
"""
|
||||
|
||||
|
||||
class Version(_BaseVersion):
|
||||
|
||||
_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
|
||||
match = self._regex.search(version)
|
||||
if not match:
|
||||
raise InvalidVersion("Invalid version: '{0}'".format(version))
|
||||
|
||||
# Store the parsed out pieces of the version
|
||||
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"),
|
||||
),
|
||||
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"),
|
||||
),
|
||||
local=_parse_local_version(match.group("local")),
|
||||
)
|
||||
|
||||
# Generate a key which will be used for sorting
|
||||
self._key = _cmpkey(
|
||||
self._version.epoch,
|
||||
self._version.release,
|
||||
self._version.pre,
|
||||
self._version.post,
|
||||
self._version.dev,
|
||||
self._version.local,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Version({0})>".format(repr(str(self)))
|
||||
|
||||
def __str__(self):
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self._version.epoch != 0:
|
||||
parts.append("{0}!".format(self._version.epoch))
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self._version.release))
|
||||
|
||||
# Pre-release
|
||||
if self._version.pre is not None:
|
||||
parts.append("".join(str(x) for x in self._version.pre))
|
||||
|
||||
# Post-release
|
||||
if self._version.post is not None:
|
||||
parts.append(".post{0}".format(self._version.post[1]))
|
||||
|
||||
# Development release
|
||||
if self._version.dev is not None:
|
||||
parts.append(".dev{0}".format(self._version.dev[1]))
|
||||
|
||||
# Local version segment
|
||||
if self._version.local is not None:
|
||||
parts.append(
|
||||
"+{0}".format(".".join(str(x) for x in self._version.local))
|
||||
)
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
return str(self).split("+", 1)[0]
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self._version.epoch != 0:
|
||||
parts.append("{0}!".format(self._version.epoch))
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self._version.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)
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
return bool(self._version.post)
|
||||
|
||||
|
||||
def _parse_letter_version(letter, number):
|
||||
if letter:
|
||||
# We consider there to be an implicit 0 in a pre-release if there is
|
||||
# not a numeral associated with it.
|
||||
if number is None:
|
||||
number = 0
|
||||
|
||||
# We normalize any letters to their lower case form
|
||||
letter = letter.lower()
|
||||
|
||||
# We consider some words to be alternate spellings of other words and
|
||||
# in those cases we want to normalize the spellings to our preferred
|
||||
# spelling.
|
||||
if letter == "alpha":
|
||||
letter = "a"
|
||||
elif letter == "beta":
|
||||
letter = "b"
|
||||
elif letter in ["c", "pre", "preview"]:
|
||||
letter = "rc"
|
||||
elif letter in ["rev", "r"]:
|
||||
letter = "post"
|
||||
|
||||
return letter, int(number)
|
||||
if not letter and number:
|
||||
# We assume if we are given a number, but we are not given a letter
|
||||
# then this is using the implicit post release syntax (e.g. 1.0-1)
|
||||
letter = "post"
|
||||
|
||||
return letter, int(number)
|
||||
|
||||
|
||||
_local_version_seperators = re.compile(r"[\._-]")
|
||||
|
||||
|
||||
def _parse_local_version(local):
|
||||
"""
|
||||
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
||||
"""
|
||||
if local is not None:
|
||||
return tuple(
|
||||
part.lower() if not part.isdigit() else int(part)
|
||||
for part in _local_version_seperators.split(local)
|
||||
)
|
||||
|
||||
|
||||
def _cmpkey(epoch, release, pre, post, dev, local):
|
||||
# When we compare a release version, we want to compare it with all of the
|
||||
# trailing zeros removed. So we'll use a reverse the list, drop all the now
|
||||
# leading zeros until we come to something non zero, then take the rest
|
||||
# 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),
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
|
||||
# We'll do this by abusing the pre segment, but we _only_ want to do this
|
||||
# if there is not a pre or a post segment. If we have one of those then
|
||||
# the normal sorting rules will handle this case correctly.
|
||||
if pre is None and post is None and dev is not None:
|
||||
pre = -Infinity
|
||||
# Versions without a pre-release (except as noted above) should sort after
|
||||
# those with one.
|
||||
elif pre is None:
|
||||
pre = Infinity
|
||||
|
||||
# Versions without a post segment should sort before those with one.
|
||||
if post is None:
|
||||
post = -Infinity
|
||||
|
||||
# Versions without a development segment should sort after those with one.
|
||||
if dev is None:
|
||||
dev = Infinity
|
||||
|
||||
if local is None:
|
||||
# Versions without a local segment should sort before those with one.
|
||||
local = -Infinity
|
||||
else:
|
||||
# Versions with a local segment need that segment parsed to implement
|
||||
# the sorting rules in PEP440.
|
||||
# - Alpha numeric segments sort before numeric segments
|
||||
# - Alpha numeric segments sort lexicographically
|
||||
# - 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
|
||||
)
|
||||
|
||||
return epoch, release, pre, post, dev, local
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,868 @@
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.10.0"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY34 = sys.version_info[0:2] >= (3, 4)
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
integer_types = int,
|
||||
class_types = type,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
|
||||
MAXSIZE = sys.maxsize
|
||||
else:
|
||||
string_types = basestring,
|
||||
integer_types = (int, long)
|
||||
class_types = (type, types.ClassType)
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
if sys.platform.startswith("java"):
|
||||
# Jython always uses 32 bits.
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
func.__doc__ = doc
|
||||
|
||||
|
||||
def _import_module(name):
|
||||
"""Import module, returning the module after the last dot."""
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class _LazyDescr(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
setattr(obj, self.name, result) # Invokes __set__.
|
||||
try:
|
||||
# This is a bit ugly, but it avoids running this again by
|
||||
# removing this descriptor.
|
||||
delattr(obj.__class__, self.name)
|
||||
except AttributeError:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
class MovedModule(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old, new=None):
|
||||
super(MovedModule, self).__init__(name)
|
||||
if PY3:
|
||||
if new is None:
|
||||
new = name
|
||||
self.mod = new
|
||||
else:
|
||||
self.mod = old
|
||||
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
_module = self._resolve()
|
||||
value = getattr(_module, attr)
|
||||
setattr(self, attr, value)
|
||||
return value
|
||||
|
||||
|
||||
class _LazyModule(types.ModuleType):
|
||||
|
||||
def __init__(self, name):
|
||||
super(_LazyModule, self).__init__(name)
|
||||
self.__doc__ = self.__class__.__doc__
|
||||
|
||||
def __dir__(self):
|
||||
attrs = ["__doc__", "__name__"]
|
||||
attrs += [attr.name for attr in self._moved_attributes]
|
||||
return attrs
|
||||
|
||||
# Subclasses should override this
|
||||
_moved_attributes = []
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
||||
super(MovedAttribute, self).__init__(name)
|
||||
if PY3:
|
||||
if new_mod is None:
|
||||
new_mod = name
|
||||
self.mod = new_mod
|
||||
if new_attr is None:
|
||||
if old_attr is None:
|
||||
new_attr = name
|
||||
else:
|
||||
new_attr = old_attr
|
||||
self.attr = new_attr
|
||||
else:
|
||||
self.mod = old_mod
|
||||
if old_attr is None:
|
||||
old_attr = name
|
||||
self.attr = old_attr
|
||||
|
||||
def _resolve(self):
|
||||
module = _import_module(self.mod)
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
class _SixMetaPathImporter(object):
|
||||
|
||||
"""
|
||||
A meta path importer to import six.moves and its submodules.
|
||||
|
||||
This class implements a PEP302 finder and loader. It should be compatible
|
||||
with Python 2.5 and all existing versions of Python3
|
||||
"""
|
||||
|
||||
def __init__(self, six_module_name):
|
||||
self.name = six_module_name
|
||||
self.known_modules = {}
|
||||
|
||||
def _add_module(self, mod, *fullnames):
|
||||
for fullname in fullnames:
|
||||
self.known_modules[self.name + "." + fullname] = mod
|
||||
|
||||
def _get_module(self, fullname):
|
||||
return self.known_modules[self.name + "." + fullname]
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if fullname in self.known_modules:
|
||||
return self
|
||||
return None
|
||||
|
||||
def __get_module(self, fullname):
|
||||
try:
|
||||
return self.known_modules[fullname]
|
||||
except KeyError:
|
||||
raise ImportError("This loader does not know module " + fullname)
|
||||
|
||||
def load_module(self, fullname):
|
||||
try:
|
||||
# in case of a reload
|
||||
return sys.modules[fullname]
|
||||
except KeyError:
|
||||
pass
|
||||
mod = self.__get_module(fullname)
|
||||
if isinstance(mod, MovedModule):
|
||||
mod = mod._resolve()
|
||||
else:
|
||||
mod.__loader__ = self
|
||||
sys.modules[fullname] = mod
|
||||
return mod
|
||||
|
||||
def is_package(self, fullname):
|
||||
"""
|
||||
Return true, if the named module is a package.
|
||||
|
||||
We need this method to get correct spec objects with
|
||||
Python 3.4 (see PEP451)
|
||||
"""
|
||||
return hasattr(self.__get_module(fullname), "__path__")
|
||||
|
||||
def get_code(self, fullname):
|
||||
"""Return None
|
||||
|
||||
Required, if is_package is implemented"""
|
||||
self.__get_module(fullname) # eventually raises ImportError
|
||||
return None
|
||||
get_source = get_code # same as get_code
|
||||
|
||||
_importer = _SixMetaPathImporter(__name__)
|
||||
|
||||
|
||||
class _MovedItems(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects"""
|
||||
__path__ = [] # mark as package
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
|
||||
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
|
||||
MovedAttribute("intern", "__builtin__", "sys"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
|
||||
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
|
||||
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("UserDict", "UserDict", "collections"),
|
||||
MovedAttribute("UserList", "UserList", "collections"),
|
||||
MovedAttribute("UserString", "UserString", "collections"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
|
||||
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
||||
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
|
||||
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
MovedModule("cPickle", "cPickle", "pickle"),
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("_thread", "thread", "_thread"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
"tkinter.colorchooser"),
|
||||
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
||||
"tkinter.commondialog"),
|
||||
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
|
||||
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
|
||||
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
|
||||
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
|
||||
]
|
||||
# Add windows specific modules.
|
||||
if sys.platform == "win32":
|
||||
_moved_attributes += [
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
if isinstance(attr, MovedModule):
|
||||
_importer._add_module(attr, "moves." + attr.name)
|
||||
del attr
|
||||
|
||||
_MovedItems._moved_attributes = _moved_attributes
|
||||
|
||||
moves = _MovedItems(__name__ + ".moves")
|
||||
_importer._add_module(moves, "moves")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_parse(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_parse"""
|
||||
|
||||
|
||||
_urllib_parse_moved_attributes = [
|
||||
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("quote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splittag", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splituser", "urllib", "urllib.parse"),
|
||||
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
|
||||
]
|
||||
for attr in _urllib_parse_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_parse, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
|
||||
"moves.urllib_parse", "moves.urllib.parse")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_error(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_error"""
|
||||
|
||||
|
||||
_urllib_error_moved_attributes = [
|
||||
MovedAttribute("URLError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
|
||||
]
|
||||
for attr in _urllib_error_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_error, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
|
||||
"moves.urllib_error", "moves.urllib.error")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_request(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_request"""
|
||||
|
||||
|
||||
_urllib_request_moved_attributes = [
|
||||
MovedAttribute("urlopen", "urllib2", "urllib.request"),
|
||||
MovedAttribute("install_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("build_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("pathname2url", "urllib", "urllib.request"),
|
||||
MovedAttribute("url2pathname", "urllib", "urllib.request"),
|
||||
MovedAttribute("getproxies", "urllib", "urllib.request"),
|
||||
MovedAttribute("Request", "urllib2", "urllib.request"),
|
||||
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
|
||||
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
|
||||
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
||||
]
|
||||
for attr in _urllib_request_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
|
||||
"moves.urllib_request", "moves.urllib.request")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_response(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_response"""
|
||||
|
||||
|
||||
_urllib_response_moved_attributes = [
|
||||
MovedAttribute("addbase", "urllib", "urllib.response"),
|
||||
MovedAttribute("addclosehook", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfo", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfourl", "urllib", "urllib.response"),
|
||||
]
|
||||
for attr in _urllib_response_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_response, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
|
||||
"moves.urllib_response", "moves.urllib.response")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_robotparser(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
|
||||
|
||||
|
||||
_urllib_robotparser_moved_attributes = [
|
||||
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
|
||||
]
|
||||
for attr in _urllib_robotparser_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
|
||||
"moves.urllib_robotparser", "moves.urllib.robotparser")
|
||||
|
||||
|
||||
class Module_six_moves_urllib(types.ModuleType):
|
||||
|
||||
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
|
||||
__path__ = [] # mark as package
|
||||
parse = _importer._get_module("moves.urllib_parse")
|
||||
error = _importer._get_module("moves.urllib_error")
|
||||
request = _importer._get_module("moves.urllib_request")
|
||||
response = _importer._get_module("moves.urllib_response")
|
||||
robotparser = _importer._get_module("moves.urllib_robotparser")
|
||||
|
||||
def __dir__(self):
|
||||
return ['parse', 'error', 'request', 'response', 'robotparser']
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
|
||||
"moves.urllib")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
"""Add an item to six.moves."""
|
||||
setattr(_MovedItems, move.name, move)
|
||||
|
||||
|
||||
def remove_move(name):
|
||||
"""Remove item from six.moves."""
|
||||
try:
|
||||
delattr(_MovedItems, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
del moves.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such move, %r" % (name,))
|
||||
|
||||
|
||||
if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_closure = "__closure__"
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
_func_globals = "__globals__"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_closure = "func_closure"
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
_func_globals = "func_globals"
|
||||
|
||||
|
||||
try:
|
||||
advance_iterator = next
|
||||
except NameError:
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
next = advance_iterator
|
||||
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
create_bound_method = types.MethodType
|
||||
|
||||
def create_unbound_method(func, cls):
|
||||
return func
|
||||
|
||||
Iterator = object
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
def create_bound_method(func, obj):
|
||||
return types.MethodType(func, obj, obj.__class__)
|
||||
|
||||
def create_unbound_method(func, cls):
|
||||
return types.MethodType(func, None, cls)
|
||||
|
||||
class Iterator(object):
|
||||
|
||||
def next(self):
|
||||
return type(self).__next__(self)
|
||||
|
||||
callable = callable
|
||||
_add_doc(get_unbound_function,
|
||||
"""Get the function out of a possibly unbound function""")
|
||||
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_closure = operator.attrgetter(_func_closure)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
get_function_globals = operator.attrgetter(_func_globals)
|
||||
|
||||
|
||||
if PY3:
|
||||
def iterkeys(d, **kw):
|
||||
return iter(d.keys(**kw))
|
||||
|
||||
def itervalues(d, **kw):
|
||||
return iter(d.values(**kw))
|
||||
|
||||
def iteritems(d, **kw):
|
||||
return iter(d.items(**kw))
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return iter(d.lists(**kw))
|
||||
|
||||
viewkeys = operator.methodcaller("keys")
|
||||
|
||||
viewvalues = operator.methodcaller("values")
|
||||
|
||||
viewitems = operator.methodcaller("items")
|
||||
else:
|
||||
def iterkeys(d, **kw):
|
||||
return d.iterkeys(**kw)
|
||||
|
||||
def itervalues(d, **kw):
|
||||
return d.itervalues(**kw)
|
||||
|
||||
def iteritems(d, **kw):
|
||||
return d.iteritems(**kw)
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return d.iterlists(**kw)
|
||||
|
||||
viewkeys = operator.methodcaller("viewkeys")
|
||||
|
||||
viewvalues = operator.methodcaller("viewvalues")
|
||||
|
||||
viewitems = operator.methodcaller("viewitems")
|
||||
|
||||
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
|
||||
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
|
||||
_add_doc(iteritems,
|
||||
"Return an iterator over the (key, value) pairs of a dictionary.")
|
||||
_add_doc(iterlists,
|
||||
"Return an iterator over the (key, [values]) pairs of a dictionary.")
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
|
||||
def u(s):
|
||||
return s
|
||||
unichr = chr
|
||||
import struct
|
||||
int2byte = struct.Struct(">B").pack
|
||||
del struct
|
||||
byte2int = operator.itemgetter(0)
|
||||
indexbytes = operator.getitem
|
||||
iterbytes = iter
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
_assertCountEqual = "assertCountEqual"
|
||||
if sys.version_info[1] <= 1:
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
else:
|
||||
_assertRaisesRegex = "assertRaisesRegex"
|
||||
_assertRegex = "assertRegex"
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
# Workaround for standalone backslash
|
||||
|
||||
def u(s):
|
||||
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
|
||||
unichr = unichr
|
||||
int2byte = chr
|
||||
|
||||
def byte2int(bs):
|
||||
return ord(bs[0])
|
||||
|
||||
def indexbytes(buf, i):
|
||||
return ord(buf[i])
|
||||
iterbytes = functools.partial(itertools.imap, ord)
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_assertCountEqual = "assertItemsEqual"
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
def assertCountEqual(self, *args, **kwargs):
|
||||
return getattr(self, _assertCountEqual)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRaisesRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
if PY3:
|
||||
exec_ = getattr(moves.builtins, "exec")
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
else:
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
"""Execute code in a namespace."""
|
||||
if _globs_ is None:
|
||||
frame = sys._getframe(1)
|
||||
_globs_ = frame.f_globals
|
||||
if _locs_ is None:
|
||||
_locs_ = frame.f_locals
|
||||
del frame
|
||||
elif _locs_ is None:
|
||||
_locs_ = _globs_
|
||||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
""")
|
||||
|
||||
|
||||
if sys.version_info[:2] == (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
if from_value is None:
|
||||
raise value
|
||||
raise value from from_value
|
||||
""")
|
||||
elif sys.version_info[:2] > (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
raise value from from_value
|
||||
""")
|
||||
else:
|
||||
def raise_from(value, from_value):
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(moves.builtins, "print", None)
|
||||
if print_ is None:
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function for Python 2.4 and 2.5."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
# If the file has an encoding, encode unicode with it.
|
||||
if (isinstance(fp, file) and
|
||||
isinstance(data, unicode) and
|
||||
fp.encoding is not None):
|
||||
errors = getattr(fp, "errors", None)
|
||||
if errors is None:
|
||||
errors = "strict"
|
||||
data = data.encode(fp.encoding, errors)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
if sep is not None:
|
||||
if isinstance(sep, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(sep, str):
|
||||
raise TypeError("sep must be None or a string")
|
||||
end = kwargs.pop("end", None)
|
||||
if end is not None:
|
||||
if isinstance(end, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(end, str):
|
||||
raise TypeError("end must be None or a string")
|
||||
if kwargs:
|
||||
raise TypeError("invalid keyword arguments to print()")
|
||||
if not want_unicode:
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
want_unicode = True
|
||||
break
|
||||
if want_unicode:
|
||||
newline = unicode("\n")
|
||||
space = unicode(" ")
|
||||
else:
|
||||
newline = "\n"
|
||||
space = " "
|
||||
if sep is None:
|
||||
sep = space
|
||||
if end is None:
|
||||
end = newline
|
||||
for i, arg in enumerate(args):
|
||||
if i:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
if sys.version_info[:2] < (3, 3):
|
||||
_print = print_
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
fp = kwargs.get("file", sys.stdout)
|
||||
flush = kwargs.pop("flush", False)
|
||||
_print(*args, **kwargs)
|
||||
if flush and fp is not None:
|
||||
fp.flush()
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
if sys.version_info[0:2] < (3, 4):
|
||||
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
|
||||
updated=functools.WRAPPER_UPDATES):
|
||||
def wrapper(f):
|
||||
f = functools.wraps(wrapped, assigned, updated)(f)
|
||||
f.__wrapped__ = wrapped
|
||||
return f
|
||||
return wrapper
|
||||
else:
|
||||
wraps = functools.wraps
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""Create a base class with a metaclass."""
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta):
|
||||
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
|
||||
|
||||
def add_metaclass(metaclass):
|
||||
"""Class decorator for creating a class with a metaclass."""
|
||||
def wrapper(cls):
|
||||
orig_vars = cls.__dict__.copy()
|
||||
slots = orig_vars.get('__slots__')
|
||||
if slots is not None:
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for slots_var in slots:
|
||||
orig_vars.pop(slots_var)
|
||||
orig_vars.pop('__dict__', None)
|
||||
orig_vars.pop('__weakref__', None)
|
||||
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||
return wrapper
|
||||
|
||||
|
||||
def python_2_unicode_compatible(klass):
|
||||
"""
|
||||
A decorator that defines __unicode__ and __str__ methods under Python 2.
|
||||
Under Python 3 it does nothing.
|
||||
|
||||
To support Python 2 and 3 with a single code base, define a __str__ method
|
||||
returning text and apply this decorator to the class.
|
||||
"""
|
||||
if PY2:
|
||||
if '__str__' not in klass.__dict__:
|
||||
raise ValueError("@python_2_unicode_compatible cannot be applied "
|
||||
"to %s because it doesn't define __str__()." %
|
||||
klass.__name__)
|
||||
klass.__unicode__ = klass.__str__
|
||||
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
|
||||
return klass
|
||||
|
||||
|
||||
# Complete the moves implementation.
|
||||
# This code is at the end of this module to speed up module loading.
|
||||
# Turn this module into a package.
|
||||
__path__ = [] # required for PEP 302 and PEP 451
|
||||
__package__ = __name__ # see PEP 366 @ReservedAssignment
|
||||
if globals().get("__spec__") is not None:
|
||||
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
|
||||
# Remove other six meta path importers, since they cause problems. This can
|
||||
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
|
||||
# this for some reason.)
|
||||
if sys.meta_path:
|
||||
for i, importer in enumerate(sys.meta_path):
|
||||
# Here's some real nastiness: Another "instance" of the six module might
|
||||
# be floating around. Therefore, we can't use isinstance() to check for
|
||||
# the six meta path importer, since the other six instance will have
|
||||
# inserted an importer with different class.
|
||||
if (type(importer).__name__ == "_SixMetaPathImporter" and
|
||||
importer.name == __name__):
|
||||
del sys.meta_path[i]
|
||||
break
|
||||
del i, importer
|
||||
# Finally, add the importer to the meta path import hook.
|
||||
sys.meta_path.append(_importer)
|
||||
@@ -0,0 +1,173 @@
|
||||
"""Utilities for extracting common archive formats"""
|
||||
|
||||
import zipfile
|
||||
import tarfile
|
||||
import os
|
||||
import shutil
|
||||
import posixpath
|
||||
import contextlib
|
||||
from distutils.errors import DistutilsError
|
||||
|
||||
from pkg_resources import ensure_directory
|
||||
|
||||
__all__ = [
|
||||
"unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter",
|
||||
"UnrecognizedFormat", "extraction_drivers", "unpack_directory",
|
||||
]
|
||||
|
||||
|
||||
class UnrecognizedFormat(DistutilsError):
|
||||
"""Couldn't recognize the archive type"""
|
||||
|
||||
|
||||
def default_filter(src, dst):
|
||||
"""The default progress/filter callback; returns True for all files"""
|
||||
return dst
|
||||
|
||||
|
||||
def unpack_archive(filename, extract_dir, progress_filter=default_filter,
|
||||
drivers=None):
|
||||
"""Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat``
|
||||
|
||||
`progress_filter` is a function taking two arguments: a source path
|
||||
internal to the archive ('/'-separated), and a filesystem path where it
|
||||
will be extracted. The callback must return the desired extract path
|
||||
(which may be the same as the one passed in), or else ``None`` to skip
|
||||
that file or directory. The callback can thus be used to report on the
|
||||
progress of the extraction, as well as to filter the items extracted or
|
||||
alter their extraction paths.
|
||||
|
||||
`drivers`, if supplied, must be a non-empty sequence of functions with the
|
||||
same signature as this function (minus the `drivers` argument), that raise
|
||||
``UnrecognizedFormat`` if they do not support extracting the designated
|
||||
archive type. The `drivers` are tried in sequence until one is found that
|
||||
does not raise an error, or until all are exhausted (in which case
|
||||
``UnrecognizedFormat`` is raised). If you do not supply a sequence of
|
||||
drivers, the module's ``extraction_drivers`` constant will be used, which
|
||||
means that ``unpack_zipfile`` and ``unpack_tarfile`` will be tried, in that
|
||||
order.
|
||||
"""
|
||||
for driver in drivers or extraction_drivers:
|
||||
try:
|
||||
driver(filename, extract_dir, progress_filter)
|
||||
except UnrecognizedFormat:
|
||||
continue
|
||||
else:
|
||||
return
|
||||
else:
|
||||
raise UnrecognizedFormat(
|
||||
"Not a recognized archive type: %s" % filename
|
||||
)
|
||||
|
||||
|
||||
def unpack_directory(filename, extract_dir, progress_filter=default_filter):
|
||||
""""Unpack" a directory, using the same interface as for archives
|
||||
|
||||
Raises ``UnrecognizedFormat`` if `filename` is not a directory
|
||||
"""
|
||||
if not os.path.isdir(filename):
|
||||
raise UnrecognizedFormat("%s is not a directory" % filename)
|
||||
|
||||
paths = {
|
||||
filename: ('', extract_dir),
|
||||
}
|
||||
for base, dirs, files in os.walk(filename):
|
||||
src, dst = paths[base]
|
||||
for d in dirs:
|
||||
paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d)
|
||||
for f in files:
|
||||
target = os.path.join(dst, f)
|
||||
target = progress_filter(src + f, target)
|
||||
if not target:
|
||||
# skip non-files
|
||||
continue
|
||||
ensure_directory(target)
|
||||
f = os.path.join(base, f)
|
||||
shutil.copyfile(f, target)
|
||||
shutil.copystat(f, target)
|
||||
|
||||
|
||||
def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
|
||||
"""Unpack zip `filename` to `extract_dir`
|
||||
|
||||
Raises ``UnrecognizedFormat`` if `filename` is not a zipfile (as determined
|
||||
by ``zipfile.is_zipfile()``). See ``unpack_archive()`` for an explanation
|
||||
of the `progress_filter` argument.
|
||||
"""
|
||||
|
||||
if not zipfile.is_zipfile(filename):
|
||||
raise UnrecognizedFormat("%s is not a zip file" % (filename,))
|
||||
|
||||
with zipfile.ZipFile(filename) as z:
|
||||
for info in z.infolist():
|
||||
name = info.filename
|
||||
|
||||
# don't extract absolute paths or ones with .. in them
|
||||
if name.startswith('/') or '..' in name.split('/'):
|
||||
continue
|
||||
|
||||
target = os.path.join(extract_dir, *name.split('/'))
|
||||
target = progress_filter(name, target)
|
||||
if not target:
|
||||
continue
|
||||
if name.endswith('/'):
|
||||
# directory
|
||||
ensure_directory(target)
|
||||
else:
|
||||
# file
|
||||
ensure_directory(target)
|
||||
data = z.read(info.filename)
|
||||
with open(target, 'wb') as f:
|
||||
f.write(data)
|
||||
unix_attributes = info.external_attr >> 16
|
||||
if unix_attributes:
|
||||
os.chmod(target, unix_attributes)
|
||||
|
||||
|
||||
def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
|
||||
"""Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
|
||||
|
||||
Raises ``UnrecognizedFormat`` if `filename` is not a tarfile (as determined
|
||||
by ``tarfile.open()``). See ``unpack_archive()`` for an explanation
|
||||
of the `progress_filter` argument.
|
||||
"""
|
||||
try:
|
||||
tarobj = tarfile.open(filename)
|
||||
except tarfile.TarError:
|
||||
raise UnrecognizedFormat(
|
||||
"%s is not a compressed or uncompressed tar file" % (filename,)
|
||||
)
|
||||
with contextlib.closing(tarobj):
|
||||
# don't do any chowning!
|
||||
tarobj.chown = lambda *args: None
|
||||
for member in tarobj:
|
||||
name = member.name
|
||||
# don't extract absolute paths or ones with .. in them
|
||||
if not name.startswith('/') and '..' not in name.split('/'):
|
||||
prelim_dst = os.path.join(extract_dir, *name.split('/'))
|
||||
|
||||
# resolve any links and to extract the link targets as normal
|
||||
# files
|
||||
while member is not None and (member.islnk() or member.issym()):
|
||||
linkpath = member.linkname
|
||||
if member.issym():
|
||||
base = posixpath.dirname(member.name)
|
||||
linkpath = posixpath.join(base, linkpath)
|
||||
linkpath = posixpath.normpath(linkpath)
|
||||
member = tarobj._getmember(linkpath)
|
||||
|
||||
if member is not None and (member.isfile() or member.isdir()):
|
||||
final_dst = progress_filter(name, prelim_dst)
|
||||
if final_dst:
|
||||
if final_dst.endswith(os.sep):
|
||||
final_dst = final_dst[:-1]
|
||||
try:
|
||||
# XXX Ugh
|
||||
tarobj._extract_member(member, final_dst)
|
||||
except tarfile.ExtractError:
|
||||
# chown/chmod/mkfifo/mknode/makedev failed
|
||||
pass
|
||||
return True
|
||||
|
||||
|
||||
extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile
|
||||
@@ -0,0 +1,182 @@
|
||||
"""A PEP 517 interface to setuptools
|
||||
|
||||
Previously, when a user or a command line tool (let's call it a "frontend")
|
||||
needed to make a request of setuptools to take a certain action, for
|
||||
example, generating a list of installation requirements, the frontend would
|
||||
would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line.
|
||||
|
||||
PEP 517 defines a different method of interfacing with setuptools. Rather
|
||||
than calling "setup.py" directly, the frontend should:
|
||||
|
||||
1. Set the current directory to the directory with a setup.py file
|
||||
2. Import this module into a safe python interpreter (one in which
|
||||
setuptools can potentially set global variables or crash hard).
|
||||
3. Call one of the functions defined in PEP 517.
|
||||
|
||||
What each function does is defined in PEP 517. However, here is a "casual"
|
||||
definition of the functions (this definition should not be relied on for
|
||||
bug reports or API stability):
|
||||
|
||||
- `build_wheel`: build a wheel in the folder and return the basename
|
||||
- `get_requires_for_build_wheel`: get the `setup_requires` to build
|
||||
- `prepare_metadata_for_build_wheel`: get the `install_requires`
|
||||
- `build_sdist`: build an sdist in the folder and return the basename
|
||||
- `get_requires_for_build_sdist`: get the `setup_requires` to build
|
||||
|
||||
Again, this is not a formal definition! Just a "taste" of the module.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tokenize
|
||||
import shutil
|
||||
import contextlib
|
||||
|
||||
import setuptools
|
||||
import distutils
|
||||
|
||||
|
||||
class SetupRequirementsError(BaseException):
|
||||
def __init__(self, specifiers):
|
||||
self.specifiers = specifiers
|
||||
|
||||
|
||||
class Distribution(setuptools.dist.Distribution):
|
||||
def fetch_build_eggs(self, specifiers):
|
||||
raise SetupRequirementsError(specifiers)
|
||||
|
||||
@classmethod
|
||||
@contextlib.contextmanager
|
||||
def patch(cls):
|
||||
"""
|
||||
Replace
|
||||
distutils.dist.Distribution with this class
|
||||
for the duration of this context.
|
||||
"""
|
||||
orig = distutils.core.Distribution
|
||||
distutils.core.Distribution = cls
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
distutils.core.Distribution = orig
|
||||
|
||||
|
||||
def _to_str(s):
|
||||
"""
|
||||
Convert a filename to a string (on Python 2, explicitly
|
||||
a byte string, not Unicode) as distutils checks for the
|
||||
exact type str.
|
||||
"""
|
||||
if sys.version_info[0] == 2 and not isinstance(s, str):
|
||||
# Assume it's Unicode, as that's what the PEP says
|
||||
# should be provided.
|
||||
return s.encode(sys.getfilesystemencoding())
|
||||
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=['setuptools', 'wheel'])
|
||||
|
||||
|
||||
def get_requires_for_build_sdist(config_settings=None):
|
||||
config_settings = _fix_config(config_settings)
|
||||
return _get_build_requires(config_settings, requirements=['setuptools'])
|
||||
|
||||
|
||||
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()
|
||||
|
||||
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_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)
|
||||
|
||||
wheels = [f for f in os.listdir(wheel_directory)
|
||||
if f.endswith('.whl')]
|
||||
|
||||
assert len(wheels) == 1
|
||||
return wheels[0]
|
||||
|
||||
|
||||
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()
|
||||
|
||||
sdists = [f for f in os.listdir(sdist_directory)
|
||||
if f.endswith('.tar.gz')]
|
||||
|
||||
assert len(sdists) == 1
|
||||
return sdists[0]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
__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',
|
||||
]
|
||||
|
||||
from distutils.command.bdist import bdist
|
||||
import sys
|
||||
|
||||
from setuptools.command import install_scripts
|
||||
|
||||
if 'egg' not in bdist.format_commands:
|
||||
bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
|
||||
bdist.format_commands.append('egg')
|
||||
|
||||
del bdist, sys
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,80 @@
|
||||
from distutils.errors import DistutilsOptionError
|
||||
|
||||
from setuptools.extern.six.moves import map
|
||||
|
||||
from setuptools.command.setopt import edit_config, option_base, config_file
|
||||
|
||||
|
||||
def shquote(arg):
|
||||
"""Quote an argument for later parsing by shlex.split()"""
|
||||
for c in '"', "'", "\\", "#":
|
||||
if c in arg:
|
||||
return repr(arg)
|
||||
if arg.split() != [arg]:
|
||||
return repr(arg)
|
||||
return arg
|
||||
|
||||
|
||||
class alias(option_base):
|
||||
"""Define a shortcut that invokes one or more commands"""
|
||||
|
||||
description = "define a shortcut to invoke one or more commands"
|
||||
command_consumes_arguments = True
|
||||
|
||||
user_options = [
|
||||
('remove', 'r', 'remove (unset) the alias'),
|
||||
] + option_base.user_options
|
||||
|
||||
boolean_options = option_base.boolean_options + ['remove']
|
||||
|
||||
def initialize_options(self):
|
||||
option_base.initialize_options(self)
|
||||
self.args = None
|
||||
self.remove = None
|
||||
|
||||
def finalize_options(self):
|
||||
option_base.finalize_options(self)
|
||||
if self.remove and len(self.args) != 1:
|
||||
raise DistutilsOptionError(
|
||||
"Must specify exactly one argument (the alias name) when "
|
||||
"using --remove"
|
||||
)
|
||||
|
||||
def run(self):
|
||||
aliases = self.distribution.get_option_dict('aliases')
|
||||
|
||||
if not self.args:
|
||||
print("Command Aliases")
|
||||
print("---------------")
|
||||
for alias in aliases:
|
||||
print("setup.py alias", format_alias(alias, aliases))
|
||||
return
|
||||
|
||||
elif len(self.args) == 1:
|
||||
alias, = self.args
|
||||
if self.remove:
|
||||
command = None
|
||||
elif alias in aliases:
|
||||
print("setup.py alias", format_alias(alias, aliases))
|
||||
return
|
||||
else:
|
||||
print("No alias definition found for %r" % alias)
|
||||
return
|
||||
else:
|
||||
alias = self.args[0]
|
||||
command = ' '.join(map(shquote, self.args[1:]))
|
||||
|
||||
edit_config(self.filename, {'aliases': {alias: command}}, self.dry_run)
|
||||
|
||||
|
||||
def format_alias(name, aliases):
|
||||
source, command = aliases[name]
|
||||
if source == config_file('global'):
|
||||
source = '--global-config '
|
||||
elif source == config_file('user'):
|
||||
source = '--user-config '
|
||||
elif source == config_file('local'):
|
||||
source = ''
|
||||
else:
|
||||
source = '--filename=%r' % source
|
||||
return source + name + ' ' + command
|
||||
@@ -0,0 +1,502 @@
|
||||
"""setuptools.command.bdist_egg
|
||||
|
||||
Build .egg distributions"""
|
||||
|
||||
from distutils.errors import DistutilsSetupError
|
||||
from distutils.dir_util import remove_tree, mkpath
|
||||
from distutils import log
|
||||
from types import CodeType
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
import marshal
|
||||
|
||||
from setuptools.extern import six
|
||||
|
||||
from pkg_resources import get_build_platform, Distribution, ensure_directory
|
||||
from pkg_resources import EntryPoint
|
||||
from setuptools.extension import Library
|
||||
from setuptools import Command
|
||||
|
||||
try:
|
||||
# Python 2.7 or >=3.2
|
||||
from sysconfig import get_path, get_python_version
|
||||
|
||||
def _get_purelib():
|
||||
return get_path("purelib")
|
||||
except ImportError:
|
||||
from distutils.sysconfig import get_python_lib, get_python_version
|
||||
|
||||
def _get_purelib():
|
||||
return get_python_lib(False)
|
||||
|
||||
|
||||
def strip_module(filename):
|
||||
if '.' in filename:
|
||||
filename = os.path.splitext(filename)[0]
|
||||
if filename.endswith('module'):
|
||||
filename = filename[:-6]
|
||||
return filename
|
||||
|
||||
|
||||
def sorted_walk(dir):
|
||||
"""Do os.walk in a reproducible way,
|
||||
independent of indeterministic filesystem readdir order
|
||||
"""
|
||||
for base, dirs, files in os.walk(dir):
|
||||
dirs.sort()
|
||||
files.sort()
|
||||
yield base, dirs, files
|
||||
|
||||
|
||||
def write_stub(resource, pyfile):
|
||||
_stub_template = textwrap.dedent("""
|
||||
def __bootstrap__():
|
||||
global __bootstrap__, __loader__, __file__
|
||||
import sys, pkg_resources, imp
|
||||
__file__ = pkg_resources.resource_filename(__name__, %r)
|
||||
__loader__ = None; del __bootstrap__, __loader__
|
||||
imp.load_dynamic(__name__,__file__)
|
||||
__bootstrap__()
|
||||
""").lstrip()
|
||||
with open(pyfile, 'w') as f:
|
||||
f.write(_stub_template % resource)
|
||||
|
||||
|
||||
class bdist_egg(Command):
|
||||
description = "create an \"egg\" distribution"
|
||||
|
||||
user_options = [
|
||||
('bdist-dir=', 'b',
|
||||
"temporary directory for creating the distribution"),
|
||||
('plat-name=', 'p', "platform name to embed in generated filenames "
|
||||
"(default: %s)" % get_build_platform()),
|
||||
('exclude-source-files', None,
|
||||
"remove all .py files from the generated egg"),
|
||||
('keep-temp', 'k',
|
||||
"keep the pseudo-installation tree around after " +
|
||||
"creating the distribution archive"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put final built distributions in"),
|
||||
('skip-build', None,
|
||||
"skip rebuilding everything (for testing/debugging)"),
|
||||
]
|
||||
|
||||
boolean_options = [
|
||||
'keep-temp', 'skip-build', 'exclude-source-files'
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.bdist_dir = None
|
||||
self.plat_name = None
|
||||
self.keep_temp = 0
|
||||
self.dist_dir = None
|
||||
self.skip_build = 0
|
||||
self.egg_output = None
|
||||
self.exclude_source_files = None
|
||||
|
||||
def finalize_options(self):
|
||||
ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info")
|
||||
self.egg_info = ei_cmd.egg_info
|
||||
|
||||
if self.bdist_dir is None:
|
||||
bdist_base = self.get_finalized_command('bdist').bdist_base
|
||||
self.bdist_dir = os.path.join(bdist_base, 'egg')
|
||||
|
||||
if self.plat_name is None:
|
||||
self.plat_name = get_build_platform()
|
||||
|
||||
self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
|
||||
|
||||
if self.egg_output is None:
|
||||
|
||||
# Compute filename of the output egg
|
||||
basename = Distribution(
|
||||
None, None, ei_cmd.egg_name, ei_cmd.egg_version,
|
||||
get_python_version(),
|
||||
self.distribution.has_ext_modules() and self.plat_name
|
||||
).egg_name()
|
||||
|
||||
self.egg_output = os.path.join(self.dist_dir, basename + '.egg')
|
||||
|
||||
def do_install_data(self):
|
||||
# Hack for packages that install data to install's --install-lib
|
||||
self.get_finalized_command('install').install_lib = self.bdist_dir
|
||||
|
||||
site_packages = os.path.normcase(os.path.realpath(_get_purelib()))
|
||||
old, self.distribution.data_files = self.distribution.data_files, []
|
||||
|
||||
for item in old:
|
||||
if isinstance(item, tuple) and len(item) == 2:
|
||||
if os.path.isabs(item[0]):
|
||||
realpath = os.path.realpath(item[0])
|
||||
normalized = os.path.normcase(realpath)
|
||||
if normalized == site_packages or normalized.startswith(
|
||||
site_packages + os.sep
|
||||
):
|
||||
item = realpath[len(site_packages) + 1:], item[1]
|
||||
# XXX else: raise ???
|
||||
self.distribution.data_files.append(item)
|
||||
|
||||
try:
|
||||
log.info("installing package data to %s", self.bdist_dir)
|
||||
self.call_command('install_data', force=0, root=None)
|
||||
finally:
|
||||
self.distribution.data_files = old
|
||||
|
||||
def get_outputs(self):
|
||||
return [self.egg_output]
|
||||
|
||||
def call_command(self, cmdname, **kw):
|
||||
"""Invoke reinitialized command `cmdname` with keyword args"""
|
||||
for dirname in INSTALL_DIRECTORY_ATTRS:
|
||||
kw.setdefault(dirname, self.bdist_dir)
|
||||
kw.setdefault('skip_build', self.skip_build)
|
||||
kw.setdefault('dry_run', self.dry_run)
|
||||
cmd = self.reinitialize_command(cmdname, **kw)
|
||||
self.run_command(cmdname)
|
||||
return cmd
|
||||
|
||||
def run(self):
|
||||
# Generate metadata first
|
||||
self.run_command("egg_info")
|
||||
# We run install_lib before install_data, because some data hacks
|
||||
# pull their data path from the install_lib command.
|
||||
log.info("installing library code to %s", self.bdist_dir)
|
||||
instcmd = self.get_finalized_command('install')
|
||||
old_root = instcmd.root
|
||||
instcmd.root = None
|
||||
if self.distribution.has_c_libraries() and not self.skip_build:
|
||||
self.run_command('build_clib')
|
||||
cmd = self.call_command('install_lib', warn_dir=0)
|
||||
instcmd.root = old_root
|
||||
|
||||
all_outputs, ext_outputs = self.get_ext_outputs()
|
||||
self.stubs = []
|
||||
to_compile = []
|
||||
for (p, ext_name) in enumerate(ext_outputs):
|
||||
filename, ext = os.path.splitext(ext_name)
|
||||
pyfile = os.path.join(self.bdist_dir, strip_module(filename) +
|
||||
'.py')
|
||||
self.stubs.append(pyfile)
|
||||
log.info("creating stub loader for %s", ext_name)
|
||||
if not self.dry_run:
|
||||
write_stub(os.path.basename(ext_name), pyfile)
|
||||
to_compile.append(pyfile)
|
||||
ext_outputs[p] = ext_name.replace(os.sep, '/')
|
||||
|
||||
if to_compile:
|
||||
cmd.byte_compile(to_compile)
|
||||
if self.distribution.data_files:
|
||||
self.do_install_data()
|
||||
|
||||
# Make the EGG-INFO directory
|
||||
archive_root = self.bdist_dir
|
||||
egg_info = os.path.join(archive_root, 'EGG-INFO')
|
||||
self.mkpath(egg_info)
|
||||
if self.distribution.scripts:
|
||||
script_dir = os.path.join(egg_info, 'scripts')
|
||||
log.info("installing scripts to %s", script_dir)
|
||||
self.call_command('install_scripts', install_dir=script_dir,
|
||||
no_ep=1)
|
||||
|
||||
self.copy_metadata_to(egg_info)
|
||||
native_libs = os.path.join(egg_info, "native_libs.txt")
|
||||
if all_outputs:
|
||||
log.info("writing %s", native_libs)
|
||||
if not self.dry_run:
|
||||
ensure_directory(native_libs)
|
||||
libs_file = open(native_libs, 'wt')
|
||||
libs_file.write('\n'.join(all_outputs))
|
||||
libs_file.write('\n')
|
||||
libs_file.close()
|
||||
elif os.path.isfile(native_libs):
|
||||
log.info("removing %s", native_libs)
|
||||
if not self.dry_run:
|
||||
os.unlink(native_libs)
|
||||
|
||||
write_safety_flag(
|
||||
os.path.join(archive_root, 'EGG-INFO'), self.zip_safe()
|
||||
)
|
||||
|
||||
if os.path.exists(os.path.join(self.egg_info, 'depends.txt')):
|
||||
log.warn(
|
||||
"WARNING: 'depends.txt' will not be used by setuptools 0.6!\n"
|
||||
"Use the install_requires/extras_require setup() args instead."
|
||||
)
|
||||
|
||||
if self.exclude_source_files:
|
||||
self.zap_pyfiles()
|
||||
|
||||
# Make the archive
|
||||
make_zipfile(self.egg_output, archive_root, verbose=self.verbose,
|
||||
dry_run=self.dry_run, mode=self.gen_header())
|
||||
if not self.keep_temp:
|
||||
remove_tree(self.bdist_dir, dry_run=self.dry_run)
|
||||
|
||||
# Add to 'Distribution.dist_files' so that the "upload" command works
|
||||
getattr(self.distribution, 'dist_files', []).append(
|
||||
('bdist_egg', get_python_version(), self.egg_output))
|
||||
|
||||
def zap_pyfiles(self):
|
||||
log.info("Removing .py files from temporary directory")
|
||||
for base, dirs, files in walk_egg(self.bdist_dir):
|
||||
for name in files:
|
||||
path = os.path.join(base, name)
|
||||
|
||||
if name.endswith('.py'):
|
||||
log.debug("Deleting %s", path)
|
||||
os.unlink(path)
|
||||
|
||||
if base.endswith('__pycache__'):
|
||||
path_old = path
|
||||
|
||||
pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc'
|
||||
m = re.match(pattern, name)
|
||||
path_new = os.path.join(
|
||||
base, os.pardir, m.group('name') + '.pyc')
|
||||
log.info(
|
||||
"Renaming file from [%s] to [%s]"
|
||||
% (path_old, path_new))
|
||||
try:
|
||||
os.remove(path_new)
|
||||
except OSError:
|
||||
pass
|
||||
os.rename(path_old, path_new)
|
||||
|
||||
def zip_safe(self):
|
||||
safe = getattr(self.distribution, 'zip_safe', None)
|
||||
if safe is not None:
|
||||
return safe
|
||||
log.warn("zip_safe flag not set; analyzing archive contents...")
|
||||
return analyze_egg(self.bdist_dir, self.stubs)
|
||||
|
||||
def gen_header(self):
|
||||
epm = EntryPoint.parse_map(self.distribution.entry_points or '')
|
||||
ep = epm.get('setuptools.installation', {}).get('eggsecutable')
|
||||
if ep is None:
|
||||
return 'w' # not an eggsecutable, do it the usual way.
|
||||
|
||||
if not ep.attrs or ep.extras:
|
||||
raise DistutilsSetupError(
|
||||
"eggsecutable entry point (%r) cannot have 'extras' "
|
||||
"or refer to a module" % (ep,)
|
||||
)
|
||||
|
||||
pyver = sys.version[:3]
|
||||
pkg = ep.module_name
|
||||
full = '.'.join(ep.attrs)
|
||||
base = ep.attrs[0]
|
||||
basename = os.path.basename(self.egg_output)
|
||||
|
||||
header = (
|
||||
"#!/bin/sh\n"
|
||||
'if [ `basename $0` = "%(basename)s" ]\n'
|
||||
'then exec python%(pyver)s -c "'
|
||||
"import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
|
||||
"from %(pkg)s import %(base)s; sys.exit(%(full)s())"
|
||||
'" "$@"\n'
|
||||
'else\n'
|
||||
' echo $0 is not the correct name for this egg file.\n'
|
||||
' echo Please rename it back to %(basename)s and try again.\n'
|
||||
' exec false\n'
|
||||
'fi\n'
|
||||
) % locals()
|
||||
|
||||
if not self.dry_run:
|
||||
mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)
|
||||
f = open(self.egg_output, 'w')
|
||||
f.write(header)
|
||||
f.close()
|
||||
return 'a'
|
||||
|
||||
def copy_metadata_to(self, target_dir):
|
||||
"Copy metadata (egg info) to the target_dir"
|
||||
# normalize the path (so that a forward-slash in egg_info will
|
||||
# match using startswith below)
|
||||
norm_egg_info = os.path.normpath(self.egg_info)
|
||||
prefix = os.path.join(norm_egg_info, '')
|
||||
for path in self.ei_cmd.filelist.files:
|
||||
if path.startswith(prefix):
|
||||
target = os.path.join(target_dir, path[len(prefix):])
|
||||
ensure_directory(target)
|
||||
self.copy_file(path, target)
|
||||
|
||||
def get_ext_outputs(self):
|
||||
"""Get a list of relative paths to C extensions in the output distro"""
|
||||
|
||||
all_outputs = []
|
||||
ext_outputs = []
|
||||
|
||||
paths = {self.bdist_dir: ''}
|
||||
for base, dirs, files in sorted_walk(self.bdist_dir):
|
||||
for filename in files:
|
||||
if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS:
|
||||
all_outputs.append(paths[base] + filename)
|
||||
for filename in dirs:
|
||||
paths[os.path.join(base, filename)] = (paths[base] +
|
||||
filename + '/')
|
||||
|
||||
if self.distribution.has_ext_modules():
|
||||
build_cmd = self.get_finalized_command('build_ext')
|
||||
for ext in build_cmd.extensions:
|
||||
if isinstance(ext, Library):
|
||||
continue
|
||||
fullname = build_cmd.get_ext_fullname(ext.name)
|
||||
filename = build_cmd.get_ext_filename(fullname)
|
||||
if not os.path.basename(filename).startswith('dl-'):
|
||||
if os.path.exists(os.path.join(self.bdist_dir, filename)):
|
||||
ext_outputs.append(filename)
|
||||
|
||||
return all_outputs, ext_outputs
|
||||
|
||||
|
||||
NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split())
|
||||
|
||||
|
||||
def walk_egg(egg_dir):
|
||||
"""Walk an unpacked egg's contents, skipping the metadata directory"""
|
||||
walker = sorted_walk(egg_dir)
|
||||
base, dirs, files = next(walker)
|
||||
if 'EGG-INFO' in dirs:
|
||||
dirs.remove('EGG-INFO')
|
||||
yield base, dirs, files
|
||||
for bdf in walker:
|
||||
yield bdf
|
||||
|
||||
|
||||
def analyze_egg(egg_dir, stubs):
|
||||
# check for existing flag in EGG-INFO
|
||||
for flag, fn in safety_flags.items():
|
||||
if os.path.exists(os.path.join(egg_dir, 'EGG-INFO', fn)):
|
||||
return flag
|
||||
if not can_scan():
|
||||
return False
|
||||
safe = True
|
||||
for base, dirs, files in walk_egg(egg_dir):
|
||||
for name in files:
|
||||
if name.endswith('.py') or name.endswith('.pyw'):
|
||||
continue
|
||||
elif name.endswith('.pyc') or name.endswith('.pyo'):
|
||||
# always scan, even if we already know we're not safe
|
||||
safe = scan_module(egg_dir, base, name, stubs) and safe
|
||||
return safe
|
||||
|
||||
|
||||
def write_safety_flag(egg_dir, safe):
|
||||
# Write or remove zip safety flag file(s)
|
||||
for flag, fn in safety_flags.items():
|
||||
fn = os.path.join(egg_dir, fn)
|
||||
if os.path.exists(fn):
|
||||
if safe is None or bool(safe) != flag:
|
||||
os.unlink(fn)
|
||||
elif safe is not None and bool(safe) == flag:
|
||||
f = open(fn, 'wt')
|
||||
f.write('\n')
|
||||
f.close()
|
||||
|
||||
|
||||
safety_flags = {
|
||||
True: 'zip-safe',
|
||||
False: 'not-zip-safe',
|
||||
}
|
||||
|
||||
|
||||
def scan_module(egg_dir, base, name, stubs):
|
||||
"""Check whether module possibly uses unsafe-for-zipfile stuff"""
|
||||
|
||||
filename = os.path.join(base, name)
|
||||
if filename[:-1] in stubs:
|
||||
return True # Extension module
|
||||
pkg = base[len(egg_dir) + 1:].replace(os.sep, '.')
|
||||
module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0]
|
||||
if six.PY2:
|
||||
skip = 8 # skip magic & date
|
||||
elif sys.version_info < (3, 7):
|
||||
skip = 12 # skip magic & date & file size
|
||||
else:
|
||||
skip = 16 # skip magic & reserved? & date & file size
|
||||
f = open(filename, 'rb')
|
||||
f.read(skip)
|
||||
code = marshal.load(f)
|
||||
f.close()
|
||||
safe = True
|
||||
symbols = dict.fromkeys(iter_symbols(code))
|
||||
for bad in ['__file__', '__path__']:
|
||||
if bad in symbols:
|
||||
log.warn("%s: module references %s", module, bad)
|
||||
safe = False
|
||||
if 'inspect' in symbols:
|
||||
for bad in [
|
||||
'getsource', 'getabsfile', 'getsourcefile', 'getfile'
|
||||
'getsourcelines', 'findsource', 'getcomments', 'getframeinfo',
|
||||
'getinnerframes', 'getouterframes', 'stack', 'trace'
|
||||
]:
|
||||
if bad in symbols:
|
||||
log.warn("%s: module MAY be using inspect.%s", module, bad)
|
||||
safe = False
|
||||
return safe
|
||||
|
||||
|
||||
def iter_symbols(code):
|
||||
"""Yield names and strings used by `code` and its nested code objects"""
|
||||
for name in code.co_names:
|
||||
yield name
|
||||
for const in code.co_consts:
|
||||
if isinstance(const, six.string_types):
|
||||
yield const
|
||||
elif isinstance(const, CodeType):
|
||||
for name in iter_symbols(const):
|
||||
yield name
|
||||
|
||||
|
||||
def can_scan():
|
||||
if not sys.platform.startswith('java') and sys.platform != 'cli':
|
||||
# CPython, PyPy, etc.
|
||||
return True
|
||||
log.warn("Unable to analyze compiled code on this platform.")
|
||||
log.warn("Please ask the author to include a 'zip_safe'"
|
||||
" setting (either True or False) in the package's setup.py")
|
||||
|
||||
|
||||
# Attribute names of options for commands that might need to be convinced to
|
||||
# install to the egg build directory
|
||||
|
||||
INSTALL_DIRECTORY_ATTRS = [
|
||||
'install_lib', 'install_dir', 'install_data', 'install_base'
|
||||
]
|
||||
|
||||
|
||||
def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True,
|
||||
mode='w'):
|
||||
"""Create a zip file from all the files under 'base_dir'. The output
|
||||
zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"
|
||||
Python module (if available) or the InfoZIP "zip" utility (if installed
|
||||
and found on the default search path). If neither tool is available,
|
||||
raises DistutilsExecError. Returns the name of the output zip file.
|
||||
"""
|
||||
import zipfile
|
||||
|
||||
mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
|
||||
log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)
|
||||
|
||||
def visit(z, dirname, names):
|
||||
for name in names:
|
||||
path = os.path.normpath(os.path.join(dirname, name))
|
||||
if os.path.isfile(path):
|
||||
p = path[len(base_dir) + 1:]
|
||||
if not dry_run:
|
||||
z.write(path, p)
|
||||
log.debug("adding '%s'", p)
|
||||
|
||||
compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED
|
||||
if not dry_run:
|
||||
z = zipfile.ZipFile(zip_filename, mode, compression=compression)
|
||||
for dirname, dirs, files in sorted_walk(base_dir):
|
||||
visit(z, dirname, files)
|
||||
z.close()
|
||||
else:
|
||||
for dirname, dirs, files in sorted_walk(base_dir):
|
||||
visit(None, dirname, files)
|
||||
return zip_filename
|
||||
@@ -0,0 +1,43 @@
|
||||
import distutils.command.bdist_rpm as orig
|
||||
|
||||
|
||||
class bdist_rpm(orig.bdist_rpm):
|
||||
"""
|
||||
Override the default bdist_rpm behavior to do the following:
|
||||
|
||||
1. Run egg_info to ensure the name and version are properly calculated.
|
||||
2. Always run 'install' using --single-version-externally-managed to
|
||||
disable eggs in RPM distributions.
|
||||
3. Replace dash with underscore in the version numbers for better RPM
|
||||
compatibility.
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
# ensure distro name is up-to-date
|
||||
self.run_command('egg_info')
|
||||
|
||||
orig.bdist_rpm.run(self)
|
||||
|
||||
def _make_spec_file(self):
|
||||
version = self.distribution.get_version()
|
||||
rpmversion = version.replace('-', '_')
|
||||
spec = orig.bdist_rpm._make_spec_file(self)
|
||||
line23 = '%define version ' + version
|
||||
line24 = '%define version ' + rpmversion
|
||||
spec = [
|
||||
line.replace(
|
||||
"Source0: %{name}-%{version}.tar",
|
||||
"Source0: %{name}-%{unmangled_version}.tar"
|
||||
).replace(
|
||||
"setup.py install ",
|
||||
"setup.py install --single-version-externally-managed "
|
||||
).replace(
|
||||
"%setup",
|
||||
"%setup -n %{name}-%{unmangled_version}"
|
||||
).replace(line23, line24)
|
||||
for line in spec
|
||||
]
|
||||
insert_loc = spec.index(line24) + 1
|
||||
unmangled_version = "%define unmangled_version " + version
|
||||
spec.insert(insert_loc, unmangled_version)
|
||||
return spec
|
||||
@@ -0,0 +1,21 @@
|
||||
import distutils.command.bdist_wininst as orig
|
||||
|
||||
|
||||
class bdist_wininst(orig.bdist_wininst):
|
||||
def reinitialize_command(self, command, reinit_subcommands=0):
|
||||
"""
|
||||
Supplement reinitialize_command to work around
|
||||
http://bugs.python.org/issue20819
|
||||
"""
|
||||
cmd = self.distribution.reinitialize_command(
|
||||
command, reinit_subcommands)
|
||||
if command in ('install', 'install_lib'):
|
||||
cmd.install_lib = None
|
||||
return cmd
|
||||
|
||||
def run(self):
|
||||
self._is_running = True
|
||||
try:
|
||||
orig.bdist_wininst.run(self)
|
||||
finally:
|
||||
self._is_running = False
|
||||
@@ -0,0 +1,98 @@
|
||||
import distutils.command.build_clib as orig
|
||||
from distutils.errors import DistutilsSetupError
|
||||
from distutils import log
|
||||
from setuptools.dep_util import newer_pairwise_group
|
||||
|
||||
|
||||
class build_clib(orig.build_clib):
|
||||
"""
|
||||
Override the default build_clib behaviour to do the following:
|
||||
|
||||
1. Implement a rudimentary timestamp-based dependency system
|
||||
so 'compile()' doesn't run every time.
|
||||
2. Add more keys to the 'build_info' dictionary:
|
||||
* obj_deps - specify dependencies for each object compiled.
|
||||
this should be a dictionary mapping a key
|
||||
with the source filename to a list of
|
||||
dependencies. Use an empty string for global
|
||||
dependencies.
|
||||
* cflags - specify a list of additional flags to pass to
|
||||
the compiler.
|
||||
"""
|
||||
|
||||
def build_libraries(self, libraries):
|
||||
for (lib_name, build_info) in libraries:
|
||||
sources = build_info.get('sources')
|
||||
if sources is None or not isinstance(sources, (list, tuple)):
|
||||
raise DistutilsSetupError(
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'sources' must be present and must be "
|
||||
"a list of source filenames" % lib_name)
|
||||
sources = list(sources)
|
||||
|
||||
log.info("building '%s' library", lib_name)
|
||||
|
||||
# Make sure everything is the correct type.
|
||||
# obj_deps should be a dictionary of keys as sources
|
||||
# and a list/tuple of files that are its dependencies.
|
||||
obj_deps = build_info.get('obj_deps', dict())
|
||||
if not isinstance(obj_deps, dict):
|
||||
raise DistutilsSetupError(
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'obj_deps' must be a dictionary of "
|
||||
"type 'source: list'" % lib_name)
|
||||
dependencies = []
|
||||
|
||||
# Get the global dependencies that are specified by the '' key.
|
||||
# These will go into every source's dependency list.
|
||||
global_deps = obj_deps.get('', list())
|
||||
if not isinstance(global_deps, (list, tuple)):
|
||||
raise DistutilsSetupError(
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'obj_deps' must be a dictionary of "
|
||||
"type 'source: list'" % lib_name)
|
||||
|
||||
# Build the list to be used by newer_pairwise_group
|
||||
# each source will be auto-added to its dependencies.
|
||||
for source in sources:
|
||||
src_deps = [source]
|
||||
src_deps.extend(global_deps)
|
||||
extra_deps = obj_deps.get(source, list())
|
||||
if not isinstance(extra_deps, (list, tuple)):
|
||||
raise DistutilsSetupError(
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'obj_deps' must be a dictionary of "
|
||||
"type 'source: list'" % lib_name)
|
||||
src_deps.extend(extra_deps)
|
||||
dependencies.append(src_deps)
|
||||
|
||||
expected_objects = self.compiler.object_filenames(
|
||||
sources,
|
||||
output_dir=self.build_temp
|
||||
)
|
||||
|
||||
if newer_pairwise_group(dependencies, expected_objects) != ([], []):
|
||||
# First, compile the source code to object files in the library
|
||||
# directory. (This should probably change to putting object
|
||||
# files in a temporary build directory.)
|
||||
macros = build_info.get('macros')
|
||||
include_dirs = build_info.get('include_dirs')
|
||||
cflags = build_info.get('cflags')
|
||||
objects = self.compiler.compile(
|
||||
sources,
|
||||
output_dir=self.build_temp,
|
||||
macros=macros,
|
||||
include_dirs=include_dirs,
|
||||
extra_postargs=cflags,
|
||||
debug=self.debug
|
||||
)
|
||||
|
||||
# Now "link" the object files together into a static library.
|
||||
# (On Unix at least, this isn't really linking -- it just
|
||||
# builds an archive. Whatever.)
|
||||
self.compiler.create_static_lib(
|
||||
expected_objects,
|
||||
lib_name,
|
||||
output_dir=self.build_clib,
|
||||
debug=self.debug
|
||||
)
|
||||
@@ -0,0 +1,321 @@
|
||||
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
|
||||
from distutils.sysconfig import customize_compiler, get_config_var
|
||||
from distutils.errors import DistutilsError
|
||||
from distutils import log
|
||||
|
||||
from setuptools.extension import Library
|
||||
from setuptools.extern import six
|
||||
|
||||
try:
|
||||
# Attempt to use Cython for building extensions, if available
|
||||
from Cython.Distutils.build_ext import build_ext as _build_ext
|
||||
# Additionally, assert that the compiler module will load
|
||||
# also. Ref #1229.
|
||||
__import__('Cython.Compiler.Main')
|
||||
except ImportError:
|
||||
_build_ext = _du_build_ext
|
||||
|
||||
# make sure _config_vars is initialized
|
||||
get_config_var("LDSHARED")
|
||||
from distutils.sysconfig import _config_vars as _CONFIG_VARS
|
||||
|
||||
|
||||
def _customize_compiler_for_shlib(compiler):
|
||||
if sys.platform == "darwin":
|
||||
# building .dylib requires additional compiler flags on OSX; here we
|
||||
# temporarily substitute the pyconfig.h variables so that distutils'
|
||||
# 'customize_compiler' uses them before we build the shared libraries.
|
||||
tmp = _CONFIG_VARS.copy()
|
||||
try:
|
||||
# XXX Help! I don't have any idea whether these are right...
|
||||
_CONFIG_VARS['LDSHARED'] = (
|
||||
"gcc -Wl,-x -dynamiclib -undefined dynamic_lookup")
|
||||
_CONFIG_VARS['CCSHARED'] = " -dynamiclib"
|
||||
_CONFIG_VARS['SO'] = ".dylib"
|
||||
customize_compiler(compiler)
|
||||
finally:
|
||||
_CONFIG_VARS.clear()
|
||||
_CONFIG_VARS.update(tmp)
|
||||
else:
|
||||
customize_compiler(compiler)
|
||||
|
||||
|
||||
have_rtld = False
|
||||
use_stubs = False
|
||||
libtype = 'shared'
|
||||
|
||||
if sys.platform == "darwin":
|
||||
use_stubs = True
|
||||
elif os.name != 'nt':
|
||||
try:
|
||||
import dl
|
||||
use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
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):
|
||||
if '.abi3' in suffix: # Unix
|
||||
return suffix
|
||||
elif suffix == '.pyd': # Windows
|
||||
return suffix
|
||||
|
||||
|
||||
class build_ext(_build_ext):
|
||||
def run(self):
|
||||
"""Build extensions in build directory, then copy if --inplace"""
|
||||
old_inplace, self.inplace = self.inplace, 0
|
||||
_build_ext.run(self)
|
||||
self.inplace = old_inplace
|
||||
if old_inplace:
|
||||
self.copy_extensions_to_source()
|
||||
|
||||
def copy_extensions_to_source(self):
|
||||
build_py = self.get_finalized_command('build_py')
|
||||
for ext in self.extensions:
|
||||
fullname = self.get_ext_fullname(ext.name)
|
||||
filename = self.get_ext_filename(fullname)
|
||||
modpath = fullname.split('.')
|
||||
package = '.'.join(modpath[:-1])
|
||||
package_dir = build_py.get_package_dir(package)
|
||||
dest_filename = os.path.join(package_dir,
|
||||
os.path.basename(filename))
|
||||
src_filename = os.path.join(self.build_lib, filename)
|
||||
|
||||
# Always copy, even if source is older than destination, to ensure
|
||||
# that the right extensions for the current Python/platform are
|
||||
# used.
|
||||
copy_file(
|
||||
src_filename, dest_filename, verbose=self.verbose,
|
||||
dry_run=self.dry_run
|
||||
)
|
||||
if ext._needs_stub:
|
||||
self.write_stub(package_dir or os.curdir, ext, True)
|
||||
|
||||
def get_ext_filename(self, fullname):
|
||||
filename = _build_ext.get_ext_filename(self, fullname)
|
||||
if fullname in self.ext_map:
|
||||
ext = self.ext_map[fullname]
|
||||
use_abi3 = (
|
||||
six.PY3
|
||||
and getattr(ext, 'py_limited_api')
|
||||
and get_abi3_suffix()
|
||||
)
|
||||
if use_abi3:
|
||||
so_ext = get_config_var('EXT_SUFFIX')
|
||||
filename = filename[:-len(so_ext)]
|
||||
filename = filename + get_abi3_suffix()
|
||||
if isinstance(ext, Library):
|
||||
fn, ext = os.path.splitext(filename)
|
||||
return self.shlib_compiler.library_filename(fn, libtype)
|
||||
elif use_stubs and ext._links_to_dynamic:
|
||||
d, fn = os.path.split(filename)
|
||||
return os.path.join(d, 'dl-' + fn)
|
||||
return filename
|
||||
|
||||
def initialize_options(self):
|
||||
_build_ext.initialize_options(self)
|
||||
self.shlib_compiler = None
|
||||
self.shlibs = []
|
||||
self.ext_map = {}
|
||||
|
||||
def finalize_options(self):
|
||||
_build_ext.finalize_options(self)
|
||||
self.extensions = self.extensions or []
|
||||
self.check_extensions_list(self.extensions)
|
||||
self.shlibs = [ext for ext in self.extensions
|
||||
if isinstance(ext, Library)]
|
||||
if self.shlibs:
|
||||
self.setup_shlib_compiler()
|
||||
for ext in self.extensions:
|
||||
ext._full_name = self.get_ext_fullname(ext.name)
|
||||
for ext in self.extensions:
|
||||
fullname = ext._full_name
|
||||
self.ext_map[fullname] = ext
|
||||
|
||||
# distutils 3.1 will also ask for module names
|
||||
# XXX what to do with conflicts?
|
||||
self.ext_map[fullname.split('.')[-1]] = ext
|
||||
|
||||
ltd = self.shlibs and self.links_to_dynamic(ext) or False
|
||||
ns = ltd and use_stubs and not isinstance(ext, Library)
|
||||
ext._links_to_dynamic = ltd
|
||||
ext._needs_stub = ns
|
||||
filename = ext._file_name = self.get_ext_filename(fullname)
|
||||
libdir = os.path.dirname(os.path.join(self.build_lib, filename))
|
||||
if ltd and libdir not in ext.library_dirs:
|
||||
ext.library_dirs.append(libdir)
|
||||
if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs:
|
||||
ext.runtime_library_dirs.append(os.curdir)
|
||||
|
||||
def setup_shlib_compiler(self):
|
||||
compiler = self.shlib_compiler = new_compiler(
|
||||
compiler=self.compiler, dry_run=self.dry_run, force=self.force
|
||||
)
|
||||
_customize_compiler_for_shlib(compiler)
|
||||
|
||||
if self.include_dirs is not None:
|
||||
compiler.set_include_dirs(self.include_dirs)
|
||||
if self.define is not None:
|
||||
# 'define' option is a list of (name,value) tuples
|
||||
for (name, value) in self.define:
|
||||
compiler.define_macro(name, value)
|
||||
if self.undef is not None:
|
||||
for macro in self.undef:
|
||||
compiler.undefine_macro(macro)
|
||||
if self.libraries is not None:
|
||||
compiler.set_libraries(self.libraries)
|
||||
if self.library_dirs is not None:
|
||||
compiler.set_library_dirs(self.library_dirs)
|
||||
if self.rpath is not None:
|
||||
compiler.set_runtime_library_dirs(self.rpath)
|
||||
if self.link_objects is not None:
|
||||
compiler.set_link_objects(self.link_objects)
|
||||
|
||||
# hack so distutils' build_extension() builds a library instead
|
||||
compiler.link_shared_object = link_shared_object.__get__(compiler)
|
||||
|
||||
def get_export_symbols(self, ext):
|
||||
if isinstance(ext, Library):
|
||||
return ext.export_symbols
|
||||
return _build_ext.get_export_symbols(self, ext)
|
||||
|
||||
def build_extension(self, ext):
|
||||
ext._convert_pyx_sources_to_lang()
|
||||
_compiler = self.compiler
|
||||
try:
|
||||
if isinstance(ext, Library):
|
||||
self.compiler = self.shlib_compiler
|
||||
_build_ext.build_extension(self, ext)
|
||||
if ext._needs_stub:
|
||||
cmd = self.get_finalized_command('build_py').build_lib
|
||||
self.write_stub(cmd, ext)
|
||||
finally:
|
||||
self.compiler = _compiler
|
||||
|
||||
def links_to_dynamic(self, ext):
|
||||
"""Return true if 'ext' links to a dynamic lib in the same package"""
|
||||
# XXX this should check to ensure the lib is actually being built
|
||||
# XXX as dynamic, and not just using a locally-found version or a
|
||||
# XXX static-compiled version
|
||||
libnames = dict.fromkeys([lib._full_name for lib in self.shlibs])
|
||||
pkg = '.'.join(ext._full_name.split('.')[:-1] + [''])
|
||||
return any(pkg + libname in libnames for libname in ext.libraries)
|
||||
|
||||
def get_outputs(self):
|
||||
return _build_ext.get_outputs(self) + self.__get_stubs_outputs()
|
||||
|
||||
def __get_stubs_outputs(self):
|
||||
# assemble the base name for each extension that needs a stub
|
||||
ns_ext_bases = (
|
||||
os.path.join(self.build_lib, *ext._full_name.split('.'))
|
||||
for ext in self.extensions
|
||||
if ext._needs_stub
|
||||
)
|
||||
# pair each base with the extension
|
||||
pairs = itertools.product(ns_ext_bases, self.__get_output_extensions())
|
||||
return list(base + fnext for base, fnext in pairs)
|
||||
|
||||
def __get_output_extensions(self):
|
||||
yield '.py'
|
||||
yield '.pyc'
|
||||
if self.get_finalized_command('build_py').optimize:
|
||||
yield '.pyo'
|
||||
|
||||
def write_stub(self, output_dir, ext, compile=False):
|
||||
log.info("writing stub loader for %s to %s", ext._full_name,
|
||||
output_dir)
|
||||
stub_file = (os.path.join(output_dir, *ext._full_name.split('.')) +
|
||||
'.py')
|
||||
if compile and os.path.exists(stub_file):
|
||||
raise DistutilsError(stub_file + " already exists! Please delete.")
|
||||
if not self.dry_run:
|
||||
f = open(stub_file, 'w')
|
||||
f.write(
|
||||
'\n'.join([
|
||||
"def __bootstrap__():",
|
||||
" global __bootstrap__, __file__, __loader__",
|
||||
" import sys, os, pkg_resources, imp" + if_dl(", dl"),
|
||||
" __file__ = pkg_resources.resource_filename"
|
||||
"(__name__,%r)"
|
||||
% os.path.basename(ext._file_name),
|
||||
" del __bootstrap__",
|
||||
" if '__loader__' in globals():",
|
||||
" del __loader__",
|
||||
if_dl(" old_flags = sys.getdlopenflags()"),
|
||||
" old_dir = os.getcwd()",
|
||||
" try:",
|
||||
" os.chdir(os.path.dirname(__file__))",
|
||||
if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"),
|
||||
" imp.load_dynamic(__name__,__file__)",
|
||||
" finally:",
|
||||
if_dl(" sys.setdlopenflags(old_flags)"),
|
||||
" os.chdir(old_dir)",
|
||||
"__bootstrap__()",
|
||||
"" # terminal \n
|
||||
])
|
||||
)
|
||||
f.close()
|
||||
if compile:
|
||||
from distutils.util import byte_compile
|
||||
|
||||
byte_compile([stub_file], optimize=0,
|
||||
force=True, dry_run=self.dry_run)
|
||||
optimize = self.get_finalized_command('install_lib').optimize
|
||||
if optimize > 0:
|
||||
byte_compile([stub_file], optimize=optimize,
|
||||
force=True, dry_run=self.dry_run)
|
||||
if os.path.exists(stub_file) and not self.dry_run:
|
||||
os.unlink(stub_file)
|
||||
|
||||
|
||||
if use_stubs or os.name == 'nt':
|
||||
# Build shared libraries
|
||||
#
|
||||
def link_shared_object(
|
||||
self, objects, output_libname, output_dir=None, libraries=None,
|
||||
library_dirs=None, runtime_library_dirs=None, export_symbols=None,
|
||||
debug=0, extra_preargs=None, extra_postargs=None, build_temp=None,
|
||||
target_lang=None):
|
||||
self.link(
|
||||
self.SHARED_LIBRARY, objects, output_libname,
|
||||
output_dir, libraries, library_dirs, runtime_library_dirs,
|
||||
export_symbols, debug, extra_preargs, extra_postargs,
|
||||
build_temp, target_lang
|
||||
)
|
||||
else:
|
||||
# Build static libraries everywhere else
|
||||
libtype = 'static'
|
||||
|
||||
def link_shared_object(
|
||||
self, objects, output_libname, output_dir=None, libraries=None,
|
||||
library_dirs=None, runtime_library_dirs=None, export_symbols=None,
|
||||
debug=0, extra_preargs=None, extra_postargs=None, build_temp=None,
|
||||
target_lang=None):
|
||||
# XXX we need to either disallow these attrs on Library instances,
|
||||
# or warn/abort here if set, or something...
|
||||
# libraries=None, library_dirs=None, runtime_library_dirs=None,
|
||||
# export_symbols=None, extra_preargs=None, extra_postargs=None,
|
||||
# build_temp=None
|
||||
|
||||
assert output_dir is None # distutils build_ext doesn't pass this
|
||||
output_dir, filename = os.path.split(output_libname)
|
||||
basename, ext = os.path.splitext(filename)
|
||||
if self.library_filename("x").startswith('lib'):
|
||||
# strip 'lib' prefix; this is kludgy if some platform uses
|
||||
# a different prefix
|
||||
basename = basename[3:]
|
||||
|
||||
self.create_static_lib(
|
||||
objects, basename, output_dir, debug, target_lang
|
||||
)
|
||||
@@ -0,0 +1,270 @@
|
||||
from glob import glob
|
||||
from distutils.util import convert_path
|
||||
import distutils.command.build_py as orig
|
||||
import os
|
||||
import fnmatch
|
||||
import textwrap
|
||||
import io
|
||||
import distutils.errors
|
||||
import itertools
|
||||
|
||||
from setuptools.extern import six
|
||||
from setuptools.extern.six.moves import map, filter, filterfalse
|
||||
|
||||
try:
|
||||
from setuptools.lib2to3_ex import Mixin2to3
|
||||
except ImportError:
|
||||
|
||||
class Mixin2to3:
|
||||
def run_2to3(self, files, doctests=True):
|
||||
"do nothing"
|
||||
|
||||
|
||||
class build_py(orig.build_py, Mixin2to3):
|
||||
"""Enhanced 'build_py' command that includes data files with packages
|
||||
|
||||
The data files are specified via a 'package_data' argument to 'setup()'.
|
||||
See 'setuptools.dist.Distribution' for more details.
|
||||
|
||||
Also, this version of the 'build_py' command allows you to specify both
|
||||
'py_modules' and 'packages' in the same setup operation.
|
||||
"""
|
||||
|
||||
def finalize_options(self):
|
||||
orig.build_py.finalize_options(self)
|
||||
self.package_data = self.distribution.package_data
|
||||
self.exclude_package_data = (self.distribution.exclude_package_data or
|
||||
{})
|
||||
if 'data_files' in self.__dict__:
|
||||
del self.__dict__['data_files']
|
||||
self.__updated_files = []
|
||||
self.__doctests_2to3 = []
|
||||
|
||||
def run(self):
|
||||
"""Build modules, packages, and copy data files to build directory"""
|
||||
if not self.py_modules and not self.packages:
|
||||
return
|
||||
|
||||
if self.py_modules:
|
||||
self.build_modules()
|
||||
|
||||
if self.packages:
|
||||
self.build_packages()
|
||||
self.build_package_data()
|
||||
|
||||
self.run_2to3(self.__updated_files, False)
|
||||
self.run_2to3(self.__updated_files, True)
|
||||
self.run_2to3(self.__doctests_2to3, True)
|
||||
|
||||
# Only compile actual .py files, using our base class' idea of what our
|
||||
# output files are.
|
||||
self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0))
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"lazily compute data files"
|
||||
if attr == 'data_files':
|
||||
self.data_files = self._get_data_files()
|
||||
return self.data_files
|
||||
return orig.build_py.__getattr__(self, attr)
|
||||
|
||||
def build_module(self, module, module_file, package):
|
||||
if six.PY2 and isinstance(package, six.string_types):
|
||||
# avoid errors on Python 2 when unicode is passed (#190)
|
||||
package = package.split('.')
|
||||
outfile, copied = orig.build_py.build_module(self, module, module_file,
|
||||
package)
|
||||
if copied:
|
||||
self.__updated_files.append(outfile)
|
||||
return outfile, copied
|
||||
|
||||
def _get_data_files(self):
|
||||
"""Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
|
||||
self.analyze_manifest()
|
||||
return list(map(self._get_pkg_data_files, self.packages or ()))
|
||||
|
||||
def _get_pkg_data_files(self, package):
|
||||
# Locate package source directory
|
||||
src_dir = self.get_package_dir(package)
|
||||
|
||||
# Compute package build directory
|
||||
build_dir = os.path.join(*([self.build_lib] + package.split('.')))
|
||||
|
||||
# Strip directory from globbed filenames
|
||||
filenames = [
|
||||
os.path.relpath(file, src_dir)
|
||||
for file in self.find_data_files(package, src_dir)
|
||||
]
|
||||
return package, src_dir, build_dir, filenames
|
||||
|
||||
def find_data_files(self, package, src_dir):
|
||||
"""Return filenames for package's data files in 'src_dir'"""
|
||||
patterns = self._get_platform_patterns(
|
||||
self.package_data,
|
||||
package,
|
||||
src_dir,
|
||||
)
|
||||
globs_expanded = map(glob, patterns)
|
||||
# flatten the expanded globs into an iterable of matches
|
||||
globs_matches = itertools.chain.from_iterable(globs_expanded)
|
||||
glob_files = filter(os.path.isfile, globs_matches)
|
||||
files = itertools.chain(
|
||||
self.manifest_files.get(package, []),
|
||||
glob_files,
|
||||
)
|
||||
return self.exclude_data_files(package, src_dir, files)
|
||||
|
||||
def build_package_data(self):
|
||||
"""Copy data files into build directory"""
|
||||
for package, src_dir, build_dir, filenames in self.data_files:
|
||||
for filename in filenames:
|
||||
target = os.path.join(build_dir, filename)
|
||||
self.mkpath(os.path.dirname(target))
|
||||
srcfile = os.path.join(src_dir, filename)
|
||||
outf, copied = self.copy_file(srcfile, target)
|
||||
srcfile = os.path.abspath(srcfile)
|
||||
if (copied and
|
||||
srcfile in self.distribution.convert_2to3_doctests):
|
||||
self.__doctests_2to3.append(outf)
|
||||
|
||||
def analyze_manifest(self):
|
||||
self.manifest_files = mf = {}
|
||||
if not self.distribution.include_package_data:
|
||||
return
|
||||
src_dirs = {}
|
||||
for package in self.packages or ():
|
||||
# Locate package source directory
|
||||
src_dirs[assert_relative(self.get_package_dir(package))] = package
|
||||
|
||||
self.run_command('egg_info')
|
||||
ei_cmd = self.get_finalized_command('egg_info')
|
||||
for path in ei_cmd.filelist.files:
|
||||
d, f = os.path.split(assert_relative(path))
|
||||
prev = None
|
||||
oldf = f
|
||||
while d and d != prev and d not in src_dirs:
|
||||
prev = d
|
||||
d, df = os.path.split(d)
|
||||
f = os.path.join(df, f)
|
||||
if d in src_dirs:
|
||||
if path.endswith('.py') and f == oldf:
|
||||
continue # it's a module, not data
|
||||
mf.setdefault(src_dirs[d], []).append(path)
|
||||
|
||||
def get_data_files(self):
|
||||
pass # Lazily compute data files in _get_data_files() function.
|
||||
|
||||
def check_package(self, package, package_dir):
|
||||
"""Check namespace packages' __init__ for declare_namespace"""
|
||||
try:
|
||||
return self.packages_checked[package]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
init_py = orig.build_py.check_package(self, package, package_dir)
|
||||
self.packages_checked[package] = init_py
|
||||
|
||||
if not init_py or not self.distribution.namespace_packages:
|
||||
return init_py
|
||||
|
||||
for pkg in self.distribution.namespace_packages:
|
||||
if pkg == package or pkg.startswith(package + '.'):
|
||||
break
|
||||
else:
|
||||
return init_py
|
||||
|
||||
with io.open(init_py, 'rb') as f:
|
||||
contents = f.read()
|
||||
if b'declare_namespace' not in contents:
|
||||
raise distutils.errors.DistutilsError(
|
||||
"Namespace package problem: %s is a namespace package, but "
|
||||
"its\n__init__.py does not call declare_namespace()! Please "
|
||||
'fix it.\n(See the setuptools manual under '
|
||||
'"Namespace Packages" for details.)\n"' % (package,)
|
||||
)
|
||||
return init_py
|
||||
|
||||
def initialize_options(self):
|
||||
self.packages_checked = {}
|
||||
orig.build_py.initialize_options(self)
|
||||
|
||||
def get_package_dir(self, package):
|
||||
res = orig.build_py.get_package_dir(self, package)
|
||||
if self.distribution.src_root is not None:
|
||||
return os.path.join(self.distribution.src_root, res)
|
||||
return res
|
||||
|
||||
def exclude_data_files(self, package, src_dir, files):
|
||||
"""Filter filenames for package's data files in 'src_dir'"""
|
||||
files = list(files)
|
||||
patterns = self._get_platform_patterns(
|
||||
self.exclude_package_data,
|
||||
package,
|
||||
src_dir,
|
||||
)
|
||||
match_groups = (
|
||||
fnmatch.filter(files, pattern)
|
||||
for pattern in patterns
|
||||
)
|
||||
# flatten the groups of matches into an iterable of matches
|
||||
matches = itertools.chain.from_iterable(match_groups)
|
||||
bad = set(matches)
|
||||
keepers = (
|
||||
fn
|
||||
for fn in files
|
||||
if fn not in bad
|
||||
)
|
||||
# ditch dupes
|
||||
return list(_unique_everseen(keepers))
|
||||
|
||||
@staticmethod
|
||||
def _get_platform_patterns(spec, package, src_dir):
|
||||
"""
|
||||
yield platform-specific path patterns (suitable for glob
|
||||
or fn_match) from a glob-based spec (such as
|
||||
self.package_data or self.exclude_package_data)
|
||||
matching package in src_dir.
|
||||
"""
|
||||
raw_patterns = itertools.chain(
|
||||
spec.get('', []),
|
||||
spec.get(package, []),
|
||||
)
|
||||
return (
|
||||
# Each pattern has to be converted to a platform-specific path
|
||||
os.path.join(src_dir, convert_path(pattern))
|
||||
for pattern in raw_patterns
|
||||
)
|
||||
|
||||
|
||||
# from Python docs
|
||||
def _unique_everseen(iterable, key=None):
|
||||
"List unique elements, preserving order. Remember all elements ever seen."
|
||||
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
|
||||
# unique_everseen('ABBCcAD', str.lower) --> A B C D
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
if key is None:
|
||||
for element in filterfalse(seen.__contains__, iterable):
|
||||
seen_add(element)
|
||||
yield element
|
||||
else:
|
||||
for element in iterable:
|
||||
k = key(element)
|
||||
if k not in seen:
|
||||
seen_add(k)
|
||||
yield element
|
||||
|
||||
|
||||
def assert_relative(path):
|
||||
if not os.path.isabs(path):
|
||||
return path
|
||||
from distutils.errors import DistutilsSetupError
|
||||
|
||||
msg = textwrap.dedent("""
|
||||
Error: setup script specifies an absolute path:
|
||||
|
||||
%s
|
||||
|
||||
setup() arguments must *always* be /-separated paths relative to the
|
||||
setup.py directory, *never* absolute paths.
|
||||
""").lstrip() % path
|
||||
raise DistutilsSetupError(msg)
|
||||
@@ -0,0 +1,218 @@
|
||||
from distutils.util import convert_path
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsError, DistutilsOptionError
|
||||
import os
|
||||
import glob
|
||||
import io
|
||||
|
||||
from setuptools.extern import six
|
||||
|
||||
from pkg_resources import Distribution, PathMetadata, normalize_path
|
||||
from setuptools.command.easy_install import easy_install
|
||||
from setuptools import namespaces
|
||||
import setuptools
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class develop(namespaces.DevelopInstaller, easy_install):
|
||||
"""Set up package for development"""
|
||||
|
||||
description = "install package in 'development mode'"
|
||||
|
||||
user_options = easy_install.user_options + [
|
||||
("uninstall", "u", "Uninstall this source package"),
|
||||
("egg-path=", None, "Set the path to be used in the .egg-link file"),
|
||||
]
|
||||
|
||||
boolean_options = easy_install.boolean_options + ['uninstall']
|
||||
|
||||
command_consumes_arguments = False # override base
|
||||
|
||||
def run(self):
|
||||
if self.uninstall:
|
||||
self.multi_version = True
|
||||
self.uninstall_link()
|
||||
self.uninstall_namespaces()
|
||||
else:
|
||||
self.install_for_development()
|
||||
self.warn_deprecated_options()
|
||||
|
||||
def initialize_options(self):
|
||||
self.uninstall = None
|
||||
self.egg_path = None
|
||||
easy_install.initialize_options(self)
|
||||
self.setup_path = None
|
||||
self.always_copy_from = '.' # always copy eggs installed in curdir
|
||||
|
||||
def finalize_options(self):
|
||||
ei = self.get_finalized_command("egg_info")
|
||||
if ei.broken_egg_info:
|
||||
template = "Please rename %r to %r before using 'develop'"
|
||||
args = ei.egg_info, ei.broken_egg_info
|
||||
raise DistutilsError(template % args)
|
||||
self.args = [ei.egg_name]
|
||||
|
||||
easy_install.finalize_options(self)
|
||||
self.expand_basedirs()
|
||||
self.expand_dirs()
|
||||
# pick up setup-dir .egg files only: no .egg-info
|
||||
self.package_index.scan(glob.glob('*.egg'))
|
||||
|
||||
egg_link_fn = ei.egg_name + '.egg-link'
|
||||
self.egg_link = os.path.join(self.install_dir, egg_link_fn)
|
||||
self.egg_base = ei.egg_base
|
||||
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))
|
||||
if egg_path != target:
|
||||
raise DistutilsOptionError(
|
||||
"--egg-path must be a relative path from the install"
|
||||
" directory to " + target
|
||||
)
|
||||
|
||||
# Make a distribution for the package's source
|
||||
self.dist = Distribution(
|
||||
target,
|
||||
PathMetadata(target, os.path.abspath(ei.egg_info)),
|
||||
project_name=ei.egg_name
|
||||
)
|
||||
|
||||
self.setup_path = self._resolve_setup_path(
|
||||
self.egg_base,
|
||||
self.install_dir,
|
||||
self.egg_path,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_setup_path(egg_base, install_dir, egg_path):
|
||||
"""
|
||||
Generate a path from egg_base back to '.' where the
|
||||
setup script resides and ensure that path points to the
|
||||
setup path from $install_dir/$egg_path.
|
||||
"""
|
||||
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(
|
||||
os.path.join(install_dir, egg_path, path_to_setup)
|
||||
)
|
||||
if resolved != normalize_path(os.curdir):
|
||||
raise DistutilsOptionError(
|
||||
"Can't get a consistent path to setup script from"
|
||||
" installation directory", resolved, normalize_path(os.curdir))
|
||||
return path_to_setup
|
||||
|
||||
def install_for_development(self):
|
||||
if six.PY3 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 extensions
|
||||
self.reinitialize_command('egg_info', egg_base=build_path)
|
||||
self.run_command('egg_info')
|
||||
|
||||
self.reinitialize_command('build_ext', inplace=0)
|
||||
self.run_command('build_ext')
|
||||
|
||||
# Fixup egg-link and easy-install.pth
|
||||
ei_cmd = self.get_finalized_command("egg_info")
|
||||
self.egg_path = build_path
|
||||
self.dist.location = build_path
|
||||
# XXX
|
||||
self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info)
|
||||
else:
|
||||
# Without 2to3 inplace works fine:
|
||||
self.run_command('egg_info')
|
||||
|
||||
# Build extensions in-place
|
||||
self.reinitialize_command('build_ext', inplace=1)
|
||||
self.run_command('build_ext')
|
||||
|
||||
self.install_site_py() # ensure that target dir is site-safe
|
||||
if setuptools.bootstrap_install_from:
|
||||
self.easy_install(setuptools.bootstrap_install_from)
|
||||
setuptools.bootstrap_install_from = None
|
||||
|
||||
self.install_namespaces()
|
||||
|
||||
# create an .egg-link in the installation dir, pointing to our egg
|
||||
log.info("Creating %s (link to %s)", self.egg_link, self.egg_base)
|
||||
if not self.dry_run:
|
||||
with open(self.egg_link, "w") as f:
|
||||
f.write(self.egg_path + "\n" + self.setup_path)
|
||||
# postprocess the installed distro, fixing up .pth, installing scripts,
|
||||
# and handling requirements
|
||||
self.process_distribution(None, self.dist, not self.no_deps)
|
||||
|
||||
def uninstall_link(self):
|
||||
if os.path.exists(self.egg_link):
|
||||
log.info("Removing %s (link to %s)", self.egg_link, self.egg_base)
|
||||
egg_link_file = open(self.egg_link)
|
||||
contents = [line.rstrip() for line in egg_link_file]
|
||||
egg_link_file.close()
|
||||
if contents not in ([self.egg_path],
|
||||
[self.egg_path, self.setup_path]):
|
||||
log.warn("Link points to %s: uninstall aborted", contents)
|
||||
return
|
||||
if not self.dry_run:
|
||||
os.unlink(self.egg_link)
|
||||
if not self.dry_run:
|
||||
self.update_pth(self.dist) # remove any .pth link to us
|
||||
if self.distribution.scripts:
|
||||
# XXX should also check for entry point scripts!
|
||||
log.warn("Note: you must uninstall or replace scripts manually!")
|
||||
|
||||
def install_egg_scripts(self, dist):
|
||||
if dist is not self.dist:
|
||||
# Installing a dependency, so fall back to normal behavior
|
||||
return easy_install.install_egg_scripts(self, dist)
|
||||
|
||||
# create wrapper scripts in the script dir, pointing to dist.scripts
|
||||
|
||||
# new-style...
|
||||
self.install_wrapper_scripts(dist)
|
||||
|
||||
# ...and old-style
|
||||
for script_name in self.distribution.scripts or []:
|
||||
script_path = os.path.abspath(convert_path(script_name))
|
||||
script_name = os.path.basename(script_path)
|
||||
with io.open(script_path) as strm:
|
||||
script_text = strm.read()
|
||||
self.install_script(dist, script_name, script_text, script_path)
|
||||
|
||||
def install_wrapper_scripts(self, dist):
|
||||
dist = VersionlessRequirement(dist)
|
||||
return easy_install.install_wrapper_scripts(self, dist)
|
||||
|
||||
|
||||
class VersionlessRequirement:
|
||||
"""
|
||||
Adapt a pkg_resources.Distribution to simply return the project
|
||||
name as the 'requirement' so that scripts will work across
|
||||
multiple versions.
|
||||
|
||||
>>> dist = Distribution(project_name='foo', version='1.0')
|
||||
>>> str(dist.as_requirement())
|
||||
'foo==1.0'
|
||||
>>> adapted_dist = VersionlessRequirement(dist)
|
||||
>>> str(adapted_dist.as_requirement())
|
||||
'foo'
|
||||
"""
|
||||
|
||||
def __init__(self, dist):
|
||||
self.__dist = dist
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__dist, name)
|
||||
|
||||
def as_requirement(self):
|
||||
return self.project_name
|
||||
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Create a dist_info directory
|
||||
As defined in the wheel specification
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from distutils.core import Command
|
||||
from distutils import log
|
||||
|
||||
|
||||
class dist_info(Command):
|
||||
|
||||
description = 'create a .dist-info directory'
|
||||
|
||||
user_options = [
|
||||
('egg-base=', 'e', "directory containing .egg-info directories"
|
||||
" (default: top of the source tree)"),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.egg_base = None
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
egg_info = self.get_finalized_command('egg_info')
|
||||
egg_info.egg_base = self.egg_base
|
||||
egg_info.finalize_options()
|
||||
egg_info.run()
|
||||
dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info'
|
||||
log.info("creating '{}'".format(os.path.abspath(dist_info_dir)))
|
||||
|
||||
bdist_wheel = self.get_finalized_command('bdist_wheel')
|
||||
bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,716 @@
|
||||
"""setuptools.command.egg_info
|
||||
|
||||
Create a distribution's .egg-info directory and contents"""
|
||||
|
||||
from distutils.filelist import FileList as _FileList
|
||||
from distutils.errors import DistutilsInternalError
|
||||
from distutils.util import convert_path
|
||||
from distutils import log
|
||||
import distutils.errors
|
||||
import distutils.filelist
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import io
|
||||
import warnings
|
||||
import time
|
||||
import collections
|
||||
|
||||
from setuptools.extern import six
|
||||
from setuptools.extern.six.moves import map
|
||||
|
||||
from setuptools import Command
|
||||
from setuptools.command.sdist import sdist
|
||||
from setuptools.command.sdist import walk_revctrl
|
||||
from setuptools.command.setopt import edit_config
|
||||
from setuptools.command import bdist_egg
|
||||
from pkg_resources import (
|
||||
parse_requirements, safe_name, parse_version,
|
||||
safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename)
|
||||
import setuptools.unicode_utils as unicode_utils
|
||||
from setuptools.glob import glob
|
||||
|
||||
from setuptools.extern import packaging
|
||||
from setuptools import SetuptoolsDeprecationWarning
|
||||
|
||||
def translate_pattern(glob):
|
||||
"""
|
||||
Translate a file path glob like '*.txt' in to a regular expression.
|
||||
This differs from fnmatch.translate which allows wildcards to match
|
||||
directory separators. It also knows about '**/' which matches any number of
|
||||
directories.
|
||||
"""
|
||||
pat = ''
|
||||
|
||||
# This will split on '/' within [character classes]. This is deliberate.
|
||||
chunks = glob.split(os.path.sep)
|
||||
|
||||
sep = re.escape(os.sep)
|
||||
valid_char = '[^%s]' % (sep,)
|
||||
|
||||
for c, chunk in enumerate(chunks):
|
||||
last_chunk = c == len(chunks) - 1
|
||||
|
||||
# Chunks that are a literal ** are globstars. They match anything.
|
||||
if chunk == '**':
|
||||
if last_chunk:
|
||||
# Match anything if this is the last component
|
||||
pat += '.*'
|
||||
else:
|
||||
# Match '(name/)*'
|
||||
pat += '(?:%s+%s)*' % (valid_char, sep)
|
||||
continue # Break here as the whole path component has been handled
|
||||
|
||||
# Find any special characters in the remainder
|
||||
i = 0
|
||||
chunk_len = len(chunk)
|
||||
while i < chunk_len:
|
||||
char = chunk[i]
|
||||
if char == '*':
|
||||
# Match any number of name characters
|
||||
pat += valid_char + '*'
|
||||
elif char == '?':
|
||||
# Match a name character
|
||||
pat += valid_char
|
||||
elif char == '[':
|
||||
# Character class
|
||||
inner_i = i + 1
|
||||
# Skip initial !/] chars
|
||||
if inner_i < chunk_len and chunk[inner_i] == '!':
|
||||
inner_i = inner_i + 1
|
||||
if inner_i < chunk_len and chunk[inner_i] == ']':
|
||||
inner_i = inner_i + 1
|
||||
|
||||
# Loop till the closing ] is found
|
||||
while inner_i < chunk_len and chunk[inner_i] != ']':
|
||||
inner_i = inner_i + 1
|
||||
|
||||
if inner_i >= chunk_len:
|
||||
# Got to the end of the string without finding a closing ]
|
||||
# Do not treat this as a matching group, but as a literal [
|
||||
pat += re.escape(char)
|
||||
else:
|
||||
# Grab the insides of the [brackets]
|
||||
inner = chunk[i + 1:inner_i]
|
||||
char_class = ''
|
||||
|
||||
# Class negation
|
||||
if inner[0] == '!':
|
||||
char_class = '^'
|
||||
inner = inner[1:]
|
||||
|
||||
char_class += re.escape(inner)
|
||||
pat += '[%s]' % (char_class,)
|
||||
|
||||
# Skip to the end ]
|
||||
i = inner_i
|
||||
else:
|
||||
pat += re.escape(char)
|
||||
i += 1
|
||||
|
||||
# Join each chunk with the dir separator
|
||||
if not last_chunk:
|
||||
pat += sep
|
||||
|
||||
pat += r'\Z'
|
||||
return re.compile(pat, flags=re.MULTILINE|re.DOTALL)
|
||||
|
||||
|
||||
class InfoCommon:
|
||||
tag_build = None
|
||||
tag_date = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return safe_name(self.distribution.get_name())
|
||||
|
||||
def tagged_version(self):
|
||||
version = self.distribution.get_version()
|
||||
# egg_info may be called more than once for a distribution,
|
||||
# in which case the version string already contains all tags.
|
||||
if self.vtags and version.endswith(self.vtags):
|
||||
return safe_version(version)
|
||||
return safe_version(version + self.vtags)
|
||||
|
||||
def tags(self):
|
||||
version = ''
|
||||
if self.tag_build:
|
||||
version += self.tag_build
|
||||
if self.tag_date:
|
||||
version += time.strftime("-%Y%m%d")
|
||||
return version
|
||||
vtags = property(tags)
|
||||
|
||||
|
||||
class egg_info(InfoCommon, Command):
|
||||
description = "create a distribution's .egg-info directory"
|
||||
|
||||
user_options = [
|
||||
('egg-base=', 'e', "directory containing .egg-info directories"
|
||||
" (default: top of the source tree)"),
|
||||
('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
|
||||
('tag-build=', 'b', "Specify explicit tag to add to version number"),
|
||||
('no-date', 'D', "Don't include date stamp [default]"),
|
||||
]
|
||||
|
||||
boolean_options = ['tag-date']
|
||||
negative_opt = {
|
||||
'no-date': 'tag-date',
|
||||
}
|
||||
|
||||
def initialize_options(self):
|
||||
self.egg_base = None
|
||||
self.egg_name = None
|
||||
self.egg_info = None
|
||||
self.egg_version = None
|
||||
self.broken_egg_info = False
|
||||
|
||||
####################################
|
||||
# allow the 'tag_svn_revision' to be detected and
|
||||
# set, supporting sdists built on older Setuptools.
|
||||
@property
|
||||
def tag_svn_revision(self):
|
||||
pass
|
||||
|
||||
@tag_svn_revision.setter
|
||||
def tag_svn_revision(self, value):
|
||||
pass
|
||||
####################################
|
||||
|
||||
def save_version_info(self, filename):
|
||||
"""
|
||||
Materialize the value of date into the
|
||||
build tag. Install build keys in a deterministic order
|
||||
to avoid arbitrary reordering on subsequent builds.
|
||||
"""
|
||||
egg_info = collections.OrderedDict()
|
||||
# follow the order these keys would have been added
|
||||
# when PYTHONHASHSEED=0
|
||||
egg_info['tag_build'] = self.tags()
|
||||
egg_info['tag_date'] = 0
|
||||
edit_config(filename, dict(egg_info=egg_info))
|
||||
|
||||
def finalize_options(self):
|
||||
# Note: we need to capture the current value returned
|
||||
# by `self.tagged_version()`, so we can later update
|
||||
# `self.distribution.metadata.version` without
|
||||
# repercussions.
|
||||
self.egg_name = self.name
|
||||
self.egg_version = self.tagged_version()
|
||||
parsed_version = parse_version(self.egg_version)
|
||||
|
||||
try:
|
||||
is_version = isinstance(parsed_version, packaging.version.Version)
|
||||
spec = (
|
||||
"%s==%s" if is_version else "%s===%s"
|
||||
)
|
||||
list(
|
||||
parse_requirements(spec % (self.egg_name, self.egg_version))
|
||||
)
|
||||
except ValueError:
|
||||
raise distutils.errors.DistutilsOptionError(
|
||||
"Invalid distribution name or version syntax: %s-%s" %
|
||||
(self.egg_name, self.egg_version)
|
||||
)
|
||||
|
||||
if self.egg_base is None:
|
||||
dirs = self.distribution.package_dir
|
||||
self.egg_base = (dirs or {}).get('', os.curdir)
|
||||
|
||||
self.ensure_dirname('egg_base')
|
||||
self.egg_info = to_filename(self.egg_name) + '.egg-info'
|
||||
if self.egg_base != os.curdir:
|
||||
self.egg_info = os.path.join(self.egg_base, self.egg_info)
|
||||
if '-' in self.egg_name:
|
||||
self.check_broken_egg_info()
|
||||
|
||||
# Set package version for the benefit of dumber commands
|
||||
# (e.g. sdist, bdist_wininst, etc.)
|
||||
#
|
||||
self.distribution.metadata.version = self.egg_version
|
||||
|
||||
# If we bootstrapped around the lack of a PKG-INFO, as might be the
|
||||
# case in a fresh checkout, make sure that any special tags get added
|
||||
# to the version info
|
||||
#
|
||||
pd = self.distribution._patched_dist
|
||||
if pd is not None and pd.key == self.egg_name.lower():
|
||||
pd._version = self.egg_version
|
||||
pd._parsed_version = parse_version(self.egg_version)
|
||||
self.distribution._patched_dist = None
|
||||
|
||||
def write_or_delete_file(self, what, filename, data, force=False):
|
||||
"""Write `data` to `filename` or delete if empty
|
||||
|
||||
If `data` is non-empty, this routine is the same as ``write_file()``.
|
||||
If `data` is empty but not ``None``, this is the same as calling
|
||||
``delete_file(filename)`. If `data` is ``None``, then this is a no-op
|
||||
unless `filename` exists, in which case a warning is issued about the
|
||||
orphaned file (if `force` is false), or deleted (if `force` is true).
|
||||
"""
|
||||
if data:
|
||||
self.write_file(what, filename, data)
|
||||
elif os.path.exists(filename):
|
||||
if data is None and not force:
|
||||
log.warn(
|
||||
"%s not set in setup(), but %s exists", what, filename
|
||||
)
|
||||
return
|
||||
else:
|
||||
self.delete_file(filename)
|
||||
|
||||
def write_file(self, what, filename, data):
|
||||
"""Write `data` to `filename` (if not a dry run) after announcing it
|
||||
|
||||
`what` is used in a log message to identify what is being written
|
||||
to the file.
|
||||
"""
|
||||
log.info("writing %s to %s", what, filename)
|
||||
if six.PY3:
|
||||
data = data.encode("utf-8")
|
||||
if not self.dry_run:
|
||||
f = open(filename, 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
def delete_file(self, filename):
|
||||
"""Delete `filename` (if not a dry run) after announcing it"""
|
||||
log.info("deleting %s", filename)
|
||||
if not self.dry_run:
|
||||
os.unlink(filename)
|
||||
|
||||
def run(self):
|
||||
self.mkpath(self.egg_info)
|
||||
os.utime(self.egg_info, None)
|
||||
installer = self.distribution.fetch_build_egg
|
||||
for ep in iter_entry_points('egg_info.writers'):
|
||||
ep.require(installer=installer)
|
||||
writer = ep.resolve()
|
||||
writer(self, ep.name, os.path.join(self.egg_info, ep.name))
|
||||
|
||||
# Get rid of native_libs.txt if it was put there by older bdist_egg
|
||||
nl = os.path.join(self.egg_info, "native_libs.txt")
|
||||
if os.path.exists(nl):
|
||||
self.delete_file(nl)
|
||||
|
||||
self.find_sources()
|
||||
|
||||
def find_sources(self):
|
||||
"""Generate SOURCES.txt manifest file"""
|
||||
manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
|
||||
mm = manifest_maker(self.distribution)
|
||||
mm.manifest = manifest_filename
|
||||
mm.run()
|
||||
self.filelist = mm.filelist
|
||||
|
||||
def check_broken_egg_info(self):
|
||||
bei = self.egg_name + '.egg-info'
|
||||
if self.egg_base != os.curdir:
|
||||
bei = os.path.join(self.egg_base, bei)
|
||||
if os.path.exists(bei):
|
||||
log.warn(
|
||||
"-" * 78 + '\n'
|
||||
"Note: Your current .egg-info directory has a '-' in its name;"
|
||||
'\nthis will not work correctly with "setup.py develop".\n\n'
|
||||
'Please rename %s to %s to correct this problem.\n' + '-' * 78,
|
||||
bei, self.egg_info
|
||||
)
|
||||
self.broken_egg_info = self.egg_info
|
||||
self.egg_info = bei # make it work for now
|
||||
|
||||
|
||||
class FileList(_FileList):
|
||||
# Implementations of the various MANIFEST.in commands
|
||||
|
||||
def process_template_line(self, line):
|
||||
# Parse the line: split it up, make sure the right number of words
|
||||
# is there, and return the relevant words. 'action' is always
|
||||
# defined: it's the first word of the line. Which of the other
|
||||
# three are defined depends on the action; it'll be either
|
||||
# patterns, (dir and patterns), or (dir_pattern).
|
||||
(action, patterns, dir, dir_pattern) = self._parse_template_line(line)
|
||||
|
||||
# OK, now we know that the action is valid and we have the
|
||||
# right number of words on the line for that action -- so we
|
||||
# can proceed with minimal error-checking.
|
||||
if action == 'include':
|
||||
self.debug_print("include " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.include(pattern):
|
||||
log.warn("warning: no files found matching '%s'", pattern)
|
||||
|
||||
elif action == 'exclude':
|
||||
self.debug_print("exclude " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.exclude(pattern):
|
||||
log.warn(("warning: no previously-included files "
|
||||
"found matching '%s'"), pattern)
|
||||
|
||||
elif action == 'global-include':
|
||||
self.debug_print("global-include " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.global_include(pattern):
|
||||
log.warn(("warning: no files found matching '%s' "
|
||||
"anywhere in distribution"), pattern)
|
||||
|
||||
elif action == 'global-exclude':
|
||||
self.debug_print("global-exclude " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.global_exclude(pattern):
|
||||
log.warn(("warning: no previously-included files matching "
|
||||
"'%s' found anywhere in distribution"),
|
||||
pattern)
|
||||
|
||||
elif action == 'recursive-include':
|
||||
self.debug_print("recursive-include %s %s" %
|
||||
(dir, ' '.join(patterns)))
|
||||
for pattern in patterns:
|
||||
if not self.recursive_include(dir, pattern):
|
||||
log.warn(("warning: no files found matching '%s' "
|
||||
"under directory '%s'"),
|
||||
pattern, dir)
|
||||
|
||||
elif action == 'recursive-exclude':
|
||||
self.debug_print("recursive-exclude %s %s" %
|
||||
(dir, ' '.join(patterns)))
|
||||
for pattern in patterns:
|
||||
if not self.recursive_exclude(dir, pattern):
|
||||
log.warn(("warning: no previously-included files matching "
|
||||
"'%s' found under directory '%s'"),
|
||||
pattern, dir)
|
||||
|
||||
elif action == 'graft':
|
||||
self.debug_print("graft " + dir_pattern)
|
||||
if not self.graft(dir_pattern):
|
||||
log.warn("warning: no directories found matching '%s'",
|
||||
dir_pattern)
|
||||
|
||||
elif action == 'prune':
|
||||
self.debug_print("prune " + dir_pattern)
|
||||
if not self.prune(dir_pattern):
|
||||
log.warn(("no previously-included directories found "
|
||||
"matching '%s'"), dir_pattern)
|
||||
|
||||
else:
|
||||
raise DistutilsInternalError(
|
||||
"this cannot happen: invalid action '%s'" % action)
|
||||
|
||||
def _remove_files(self, predicate):
|
||||
"""
|
||||
Remove all files from the file list that match the predicate.
|
||||
Return True if any matching files were removed
|
||||
"""
|
||||
found = False
|
||||
for i in range(len(self.files) - 1, -1, -1):
|
||||
if predicate(self.files[i]):
|
||||
self.debug_print(" removing " + self.files[i])
|
||||
del self.files[i]
|
||||
found = True
|
||||
return found
|
||||
|
||||
def include(self, pattern):
|
||||
"""Include files that match 'pattern'."""
|
||||
found = [f for f in glob(pattern) if not os.path.isdir(f)]
|
||||
self.extend(found)
|
||||
return bool(found)
|
||||
|
||||
def exclude(self, pattern):
|
||||
"""Exclude files that match 'pattern'."""
|
||||
match = translate_pattern(pattern)
|
||||
return self._remove_files(match.match)
|
||||
|
||||
def recursive_include(self, dir, pattern):
|
||||
"""
|
||||
Include all files anywhere in 'dir/' that match the pattern.
|
||||
"""
|
||||
full_pattern = os.path.join(dir, '**', pattern)
|
||||
found = [f for f in glob(full_pattern, recursive=True)
|
||||
if not os.path.isdir(f)]
|
||||
self.extend(found)
|
||||
return bool(found)
|
||||
|
||||
def recursive_exclude(self, dir, pattern):
|
||||
"""
|
||||
Exclude any file anywhere in 'dir/' that match the pattern.
|
||||
"""
|
||||
match = translate_pattern(os.path.join(dir, '**', pattern))
|
||||
return self._remove_files(match.match)
|
||||
|
||||
def graft(self, dir):
|
||||
"""Include all files from 'dir/'."""
|
||||
found = [
|
||||
item
|
||||
for match_dir in glob(dir)
|
||||
for item in distutils.filelist.findall(match_dir)
|
||||
]
|
||||
self.extend(found)
|
||||
return bool(found)
|
||||
|
||||
def prune(self, dir):
|
||||
"""Filter out files from 'dir/'."""
|
||||
match = translate_pattern(os.path.join(dir, '**'))
|
||||
return self._remove_files(match.match)
|
||||
|
||||
def global_include(self, pattern):
|
||||
"""
|
||||
Include all files anywhere in the current directory that match the
|
||||
pattern. This is very inefficient on large file trees.
|
||||
"""
|
||||
if self.allfiles is None:
|
||||
self.findall()
|
||||
match = translate_pattern(os.path.join('**', pattern))
|
||||
found = [f for f in self.allfiles if match.match(f)]
|
||||
self.extend(found)
|
||||
return bool(found)
|
||||
|
||||
def global_exclude(self, pattern):
|
||||
"""
|
||||
Exclude all files anywhere that match the pattern.
|
||||
"""
|
||||
match = translate_pattern(os.path.join('**', pattern))
|
||||
return self._remove_files(match.match)
|
||||
|
||||
def append(self, item):
|
||||
if item.endswith('\r'): # Fix older sdists built on Windows
|
||||
item = item[:-1]
|
||||
path = convert_path(item)
|
||||
|
||||
if self._safe_path(path):
|
||||
self.files.append(path)
|
||||
|
||||
def extend(self, paths):
|
||||
self.files.extend(filter(self._safe_path, paths))
|
||||
|
||||
def _repair(self):
|
||||
"""
|
||||
Replace self.files with only safe paths
|
||||
|
||||
Because some owners of FileList manipulate the underlying
|
||||
``files`` attribute directly, this method must be called to
|
||||
repair those paths.
|
||||
"""
|
||||
self.files = list(filter(self._safe_path, self.files))
|
||||
|
||||
def _safe_path(self, path):
|
||||
enc_warn = "'%s' not %s encodable -- skipping"
|
||||
|
||||
# To avoid accidental trans-codings errors, first to unicode
|
||||
u_path = unicode_utils.filesys_decode(path)
|
||||
if u_path is None:
|
||||
log.warn("'%s' in unexpected encoding -- skipping" % path)
|
||||
return False
|
||||
|
||||
# Must ensure utf-8 encodability
|
||||
utf8_path = unicode_utils.try_encode(u_path, "utf-8")
|
||||
if utf8_path is None:
|
||||
log.warn(enc_warn, path, 'utf-8')
|
||||
return False
|
||||
|
||||
try:
|
||||
# accept is either way checks out
|
||||
if os.path.exists(u_path) or os.path.exists(utf8_path):
|
||||
return True
|
||||
# this will catch any encode errors decoding u_path
|
||||
except UnicodeEncodeError:
|
||||
log.warn(enc_warn, path, sys.getfilesystemencoding())
|
||||
|
||||
|
||||
class manifest_maker(sdist):
|
||||
template = "MANIFEST.in"
|
||||
|
||||
def initialize_options(self):
|
||||
self.use_defaults = 1
|
||||
self.prune = 1
|
||||
self.manifest_only = 1
|
||||
self.force_manifest = 1
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
self.filelist = FileList()
|
||||
if not os.path.exists(self.manifest):
|
||||
self.write_manifest() # it must exist so it'll get in the list
|
||||
self.add_defaults()
|
||||
if os.path.exists(self.template):
|
||||
self.read_template()
|
||||
self.prune_file_list()
|
||||
self.filelist.sort()
|
||||
self.filelist.remove_duplicates()
|
||||
self.write_manifest()
|
||||
|
||||
def _manifest_normalize(self, path):
|
||||
path = unicode_utils.filesys_decode(path)
|
||||
return path.replace(os.sep, '/')
|
||||
|
||||
def write_manifest(self):
|
||||
"""
|
||||
Write the file list in 'self.filelist' to the manifest file
|
||||
named by 'self.manifest'.
|
||||
"""
|
||||
self.filelist._repair()
|
||||
|
||||
# Now _repairs should encodability, but not unicode
|
||||
files = [self._manifest_normalize(f) for f in self.filelist.files]
|
||||
msg = "writing manifest file '%s'" % self.manifest
|
||||
self.execute(write_file, (self.manifest, files), msg)
|
||||
|
||||
def warn(self, msg):
|
||||
if not self._should_suppress_warning(msg):
|
||||
sdist.warn(self, msg)
|
||||
|
||||
@staticmethod
|
||||
def _should_suppress_warning(msg):
|
||||
"""
|
||||
suppress missing-file warnings from sdist
|
||||
"""
|
||||
return re.match(r"standard file .*not found", msg)
|
||||
|
||||
def add_defaults(self):
|
||||
sdist.add_defaults(self)
|
||||
self.filelist.append(self.template)
|
||||
self.filelist.append(self.manifest)
|
||||
rcfiles = list(walk_revctrl())
|
||||
if rcfiles:
|
||||
self.filelist.extend(rcfiles)
|
||||
elif os.path.exists(self.manifest):
|
||||
self.read_manifest()
|
||||
|
||||
if os.path.exists("setup.py"):
|
||||
# setup.py should be included by default, even if it's not
|
||||
# the script called to create the sdist
|
||||
self.filelist.append("setup.py")
|
||||
|
||||
ei_cmd = self.get_finalized_command('egg_info')
|
||||
self.filelist.graft(ei_cmd.egg_info)
|
||||
|
||||
def prune_file_list(self):
|
||||
build = self.get_finalized_command('build')
|
||||
base_dir = self.distribution.get_fullname()
|
||||
self.filelist.prune(build.build_base)
|
||||
self.filelist.prune(base_dir)
|
||||
sep = re.escape(os.sep)
|
||||
self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep,
|
||||
is_regex=1)
|
||||
|
||||
|
||||
def write_file(filename, contents):
|
||||
"""Create a file with the specified name and write 'contents' (a
|
||||
sequence of strings without line terminators) to it.
|
||||
"""
|
||||
contents = "\n".join(contents)
|
||||
|
||||
# assuming the contents has been vetted for utf-8 encoding
|
||||
contents = contents.encode("utf-8")
|
||||
|
||||
with open(filename, "wb") as f: # always write POSIX-style manifest
|
||||
f.write(contents)
|
||||
|
||||
|
||||
def write_pkg_info(cmd, basename, filename):
|
||||
log.info("writing %s", filename)
|
||||
if not cmd.dry_run:
|
||||
metadata = cmd.distribution.metadata
|
||||
metadata.version, oldver = cmd.egg_version, metadata.version
|
||||
metadata.name, oldname = cmd.egg_name, metadata.name
|
||||
|
||||
try:
|
||||
# write unescaped data to PKG-INFO, so older pkg_resources
|
||||
# can still parse it
|
||||
metadata.write_pkg_info(cmd.egg_info)
|
||||
finally:
|
||||
metadata.name, metadata.version = oldname, oldver
|
||||
|
||||
safe = getattr(cmd.distribution, 'zip_safe', None)
|
||||
|
||||
bdist_egg.write_safety_flag(cmd.egg_info, safe)
|
||||
|
||||
|
||||
def warn_depends_obsolete(cmd, basename, filename):
|
||||
if os.path.exists(filename):
|
||||
log.warn(
|
||||
"WARNING: 'depends.txt' is not used by setuptools 0.6!\n"
|
||||
"Use the install_requires/extras_require setup() args instead."
|
||||
)
|
||||
|
||||
|
||||
def _write_requirements(stream, reqs):
|
||||
lines = yield_lines(reqs or ())
|
||||
append_cr = lambda line: line + '\n'
|
||||
lines = map(append_cr, sorted(lines))
|
||||
stream.writelines(lines)
|
||||
|
||||
|
||||
def write_requirements(cmd, basename, filename):
|
||||
dist = cmd.distribution
|
||||
data = six.StringIO()
|
||||
_write_requirements(data, dist.install_requires)
|
||||
extras_require = dist.extras_require or {}
|
||||
for extra in sorted(extras_require):
|
||||
data.write('\n[{extra}]\n'.format(**vars()))
|
||||
_write_requirements(data, extras_require[extra])
|
||||
cmd.write_or_delete_file("requirements", filename, data.getvalue())
|
||||
|
||||
|
||||
def write_setup_requirements(cmd, basename, filename):
|
||||
data = io.StringIO()
|
||||
_write_requirements(data, cmd.distribution.setup_requires)
|
||||
cmd.write_or_delete_file("setup-requirements", filename, data.getvalue())
|
||||
|
||||
|
||||
def write_toplevel_names(cmd, basename, filename):
|
||||
pkgs = dict.fromkeys(
|
||||
[
|
||||
k.split('.', 1)[0]
|
||||
for k in cmd.distribution.iter_distribution_names()
|
||||
]
|
||||
)
|
||||
cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')
|
||||
|
||||
|
||||
def overwrite_arg(cmd, basename, filename):
|
||||
write_arg(cmd, basename, filename, True)
|
||||
|
||||
|
||||
def write_arg(cmd, basename, filename, force=False):
|
||||
argname = os.path.splitext(basename)[0]
|
||||
value = getattr(cmd.distribution, argname, None)
|
||||
if value is not None:
|
||||
value = '\n'.join(value) + '\n'
|
||||
cmd.write_or_delete_file(argname, filename, value, force)
|
||||
|
||||
|
||||
def write_entries(cmd, basename, filename):
|
||||
ep = cmd.distribution.entry_points
|
||||
|
||||
if isinstance(ep, six.string_types) or ep is None:
|
||||
data = ep
|
||||
elif ep is not None:
|
||||
data = []
|
||||
for section, contents in sorted(ep.items()):
|
||||
if not isinstance(contents, six.string_types):
|
||||
contents = EntryPoint.parse_group(section, contents)
|
||||
contents = '\n'.join(sorted(map(str, contents.values())))
|
||||
data.append('[%s]\n%s\n\n' % (section, contents))
|
||||
data = ''.join(data)
|
||||
|
||||
cmd.write_or_delete_file('entry points', filename, data, True)
|
||||
|
||||
|
||||
def get_pkg_info_revision():
|
||||
"""
|
||||
Get a -r### off of PKG-INFO Version in case this is an sdist of
|
||||
a subversion revision.
|
||||
"""
|
||||
warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning)
|
||||
if os.path.exists('PKG-INFO'):
|
||||
with io.open('PKG-INFO') as f:
|
||||
for line in f:
|
||||
match = re.match(r"Version:.*-r(\d+)\s*$", line)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
return 0
|
||||
|
||||
|
||||
class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):
|
||||
"""Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning."""
|
||||
@@ -0,0 +1,125 @@
|
||||
from distutils.errors import DistutilsArgError
|
||||
import inspect
|
||||
import glob
|
||||
import warnings
|
||||
import platform
|
||||
import distutils.command.install as orig
|
||||
|
||||
import setuptools
|
||||
|
||||
# Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for
|
||||
# now. See https://github.com/pypa/setuptools/issues/199/
|
||||
_install = orig.install
|
||||
|
||||
|
||||
class install(orig.install):
|
||||
"""Use easy_install to install the package, w/dependencies"""
|
||||
|
||||
user_options = orig.install.user_options + [
|
||||
('old-and-unmanageable', None, "Try not to use this!"),
|
||||
('single-version-externally-managed', None,
|
||||
"used by system package builders to create 'flat' eggs"),
|
||||
]
|
||||
boolean_options = orig.install.boolean_options + [
|
||||
'old-and-unmanageable', 'single-version-externally-managed',
|
||||
]
|
||||
new_commands = [
|
||||
('install_egg_info', lambda self: True),
|
||||
('install_scripts', lambda self: True),
|
||||
]
|
||||
_nc = dict(new_commands)
|
||||
|
||||
def initialize_options(self):
|
||||
orig.install.initialize_options(self)
|
||||
self.old_and_unmanageable = None
|
||||
self.single_version_externally_managed = None
|
||||
|
||||
def finalize_options(self):
|
||||
orig.install.finalize_options(self)
|
||||
if self.root:
|
||||
self.single_version_externally_managed = True
|
||||
elif self.single_version_externally_managed:
|
||||
if not self.root and not self.record:
|
||||
raise DistutilsArgError(
|
||||
"You must specify --record or --root when building system"
|
||||
" packages"
|
||||
)
|
||||
|
||||
def handle_extra_path(self):
|
||||
if self.root or self.single_version_externally_managed:
|
||||
# explicit backward-compatibility mode, allow extra_path to work
|
||||
return orig.install.handle_extra_path(self)
|
||||
|
||||
# Ignore extra_path when installing an egg (or being run by another
|
||||
# command without --root or --single-version-externally-managed
|
||||
self.path_file = None
|
||||
self.extra_dirs = ''
|
||||
|
||||
def run(self):
|
||||
# Explicit request for old-style install? Just do it
|
||||
if self.old_and_unmanageable or self.single_version_externally_managed:
|
||||
return orig.install.run(self)
|
||||
|
||||
if not self._called_from_setup(inspect.currentframe()):
|
||||
# Run in backward-compatibility mode to support bdist_* commands.
|
||||
orig.install.run(self)
|
||||
else:
|
||||
self.do_egg_install()
|
||||
|
||||
@staticmethod
|
||||
def _called_from_setup(run_frame):
|
||||
"""
|
||||
Attempt to detect whether run() was called from setup() or by another
|
||||
command. If called by setup(), the parent caller will be the
|
||||
'run_command' method in 'distutils.dist', and *its* caller will be
|
||||
the 'run_commands' method. If called any other way, the
|
||||
immediate caller *might* be 'run_command', but it won't have been
|
||||
called by 'run_commands'. Return True in that case or if a call stack
|
||||
is unavailable. Return False otherwise.
|
||||
"""
|
||||
if run_frame is None:
|
||||
msg = "Call stack not available. bdist_* commands may fail."
|
||||
warnings.warn(msg)
|
||||
if platform.python_implementation() == 'IronPython':
|
||||
msg = "For best results, pass -X:Frames to enable call stack."
|
||||
warnings.warn(msg)
|
||||
return True
|
||||
res = inspect.getouterframes(run_frame)[2]
|
||||
caller, = res[:1]
|
||||
info = inspect.getframeinfo(caller)
|
||||
caller_module = caller.f_globals.get('__name__', '')
|
||||
return (
|
||||
caller_module == 'distutils.dist'
|
||||
and info.function == 'run_commands'
|
||||
)
|
||||
|
||||
def do_egg_install(self):
|
||||
|
||||
easy_install = self.distribution.get_command_class('easy_install')
|
||||
|
||||
cmd = easy_install(
|
||||
self.distribution, args="x", root=self.root, record=self.record,
|
||||
)
|
||||
cmd.ensure_finalized() # finalize before bdist_egg munges install cmd
|
||||
cmd.always_copy_from = '.' # make sure local-dir eggs get installed
|
||||
|
||||
# pick up setup-dir .egg files only: no .egg-info
|
||||
cmd.package_index.scan(glob.glob('*.egg'))
|
||||
|
||||
self.run_command('bdist_egg')
|
||||
args = [self.distribution.get_command_obj('bdist_egg').egg_output]
|
||||
|
||||
if setuptools.bootstrap_install_from:
|
||||
# Bootstrap self-installation of setuptools
|
||||
args.insert(0, setuptools.bootstrap_install_from)
|
||||
|
||||
cmd.args = args
|
||||
cmd.run()
|
||||
setuptools.bootstrap_install_from = None
|
||||
|
||||
|
||||
# XXX Python 3.1 doesn't see _nc if this is inside the class
|
||||
install.sub_commands = (
|
||||
[cmd for cmd in orig.install.sub_commands if cmd[0] not in install._nc] +
|
||||
install.new_commands
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
from distutils import log, dir_util
|
||||
import os, sys
|
||||
|
||||
from setuptools import Command
|
||||
from setuptools import namespaces
|
||||
from setuptools.archive_util import unpack_archive
|
||||
import pkg_resources
|
||||
|
||||
|
||||
class install_egg_info(namespaces.Installer, Command):
|
||||
"""Install an .egg-info directory for the package"""
|
||||
|
||||
description = "Install an .egg-info directory for the package"
|
||||
|
||||
user_options = [
|
||||
('install-dir=', 'd', "directory to install to"),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.install_dir = None
|
||||
self.install_layout = None
|
||||
self.prefix_option = None
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('install_lib',
|
||||
('install_dir', 'install_dir'))
|
||||
self.set_undefined_options('install',('install_layout','install_layout'))
|
||||
if sys.hexversion > 0x2060000:
|
||||
self.set_undefined_options('install',('prefix_option','prefix_option'))
|
||||
ei_cmd = self.get_finalized_command("egg_info")
|
||||
basename = pkg_resources.Distribution(
|
||||
None, None, ei_cmd.egg_name, ei_cmd.egg_version
|
||||
).egg_name() + '.egg-info'
|
||||
|
||||
if self.install_layout:
|
||||
if not self.install_layout.lower() in ['deb']:
|
||||
raise DistutilsOptionError("unknown value for --install-layout")
|
||||
self.install_layout = self.install_layout.lower()
|
||||
basename = basename.replace('-py%s' % pkg_resources.PY_MAJOR, '')
|
||||
elif self.prefix_option or 'real_prefix' in sys.__dict__:
|
||||
# don't modify for virtualenv
|
||||
pass
|
||||
else:
|
||||
basename = basename.replace('-py%s' % pkg_resources.PY_MAJOR, '')
|
||||
|
||||
self.source = ei_cmd.egg_info
|
||||
self.target = os.path.join(self.install_dir, basename)
|
||||
self.outputs = []
|
||||
|
||||
def run(self):
|
||||
self.run_command('egg_info')
|
||||
if os.path.isdir(self.target) and not os.path.islink(self.target):
|
||||
dir_util.remove_tree(self.target, dry_run=self.dry_run)
|
||||
elif os.path.exists(self.target):
|
||||
self.execute(os.unlink, (self.target,), "Removing " + self.target)
|
||||
if not self.dry_run:
|
||||
pkg_resources.ensure_directory(self.target)
|
||||
self.execute(
|
||||
self.copytree, (), "Copying %s to %s" % (self.source, self.target)
|
||||
)
|
||||
self.install_namespaces()
|
||||
|
||||
def get_outputs(self):
|
||||
return self.outputs
|
||||
|
||||
def copytree(self):
|
||||
# Copy the .egg-info tree to site-packages
|
||||
def skimmer(src, dst):
|
||||
# filter out source-control directories; note that 'src' is always
|
||||
# a '/'-separated path, regardless of platform. 'dst' is a
|
||||
# platform-specific path.
|
||||
for skip in '.svn/', 'CVS/':
|
||||
if src.startswith(skip) or '/' + skip in src:
|
||||
return None
|
||||
if self.install_layout and self.install_layout in ['deb'] and src.startswith('SOURCES.txt'):
|
||||
log.info("Skipping SOURCES.txt")
|
||||
return None
|
||||
self.outputs.append(dst)
|
||||
log.debug("Copying %s to %s", src, dst)
|
||||
return dst
|
||||
|
||||
unpack_archive(self.source, self.target, skimmer)
|
||||
@@ -0,0 +1,148 @@
|
||||
import os
|
||||
import sys
|
||||
import imp
|
||||
from itertools import product, starmap
|
||||
import distutils.command.install_lib as orig
|
||||
|
||||
|
||||
class install_lib(orig.install_lib):
|
||||
"""Don't add compiled flags to filenames of non-Python files"""
|
||||
|
||||
def initialize_options(self):
|
||||
orig.install_lib.initialize_options(self)
|
||||
self.multiarch = None
|
||||
self.install_layout = None
|
||||
|
||||
def finalize_options(self):
|
||||
orig.install_lib.finalize_options(self)
|
||||
self.set_undefined_options('install',('install_layout','install_layout'))
|
||||
if self.install_layout == 'deb' and sys.version_info[:2] >= (3, 3):
|
||||
import sysconfig
|
||||
self.multiarch = sysconfig.get_config_var('MULTIARCH')
|
||||
|
||||
def run(self):
|
||||
self.build()
|
||||
outfiles = self.install()
|
||||
if outfiles is not None:
|
||||
# always compile, in case we have any extension stubs to deal with
|
||||
self.byte_compile(outfiles)
|
||||
|
||||
def get_exclusions(self):
|
||||
"""
|
||||
Return a collections.Sized collections.Container of paths to be
|
||||
excluded for single_version_externally_managed installations.
|
||||
"""
|
||||
all_packages = (
|
||||
pkg
|
||||
for ns_pkg in self._get_SVEM_NSPs()
|
||||
for pkg in self._all_packages(ns_pkg)
|
||||
)
|
||||
|
||||
excl_specs = product(all_packages, self._gen_exclusion_paths())
|
||||
return set(starmap(self._exclude_pkg_path, excl_specs))
|
||||
|
||||
def _exclude_pkg_path(self, pkg, exclusion_path):
|
||||
"""
|
||||
Given a package name and exclusion path within that package,
|
||||
compute the full exclusion path.
|
||||
"""
|
||||
parts = pkg.split('.') + [exclusion_path]
|
||||
return os.path.join(self.install_dir, *parts)
|
||||
|
||||
@staticmethod
|
||||
def _all_packages(pkg_name):
|
||||
"""
|
||||
>>> list(install_lib._all_packages('foo.bar.baz'))
|
||||
['foo.bar.baz', 'foo.bar', 'foo']
|
||||
"""
|
||||
while pkg_name:
|
||||
yield pkg_name
|
||||
pkg_name, sep, child = pkg_name.rpartition('.')
|
||||
|
||||
def _get_SVEM_NSPs(self):
|
||||
"""
|
||||
Get namespace packages (list) but only for
|
||||
single_version_externally_managed installations and empty otherwise.
|
||||
"""
|
||||
# TODO: is it necessary to short-circuit here? i.e. what's the cost
|
||||
# if get_finalized_command is called even when namespace_packages is
|
||||
# False?
|
||||
if not self.distribution.namespace_packages:
|
||||
return []
|
||||
|
||||
install_cmd = self.get_finalized_command('install')
|
||||
svem = install_cmd.single_version_externally_managed
|
||||
|
||||
return self.distribution.namespace_packages if svem else []
|
||||
|
||||
@staticmethod
|
||||
def _gen_exclusion_paths():
|
||||
"""
|
||||
Generate file paths to be excluded for namespace packages (bytecode
|
||||
cache files).
|
||||
"""
|
||||
# always exclude the package module itself
|
||||
yield '__init__.py'
|
||||
|
||||
yield '__init__.pyc'
|
||||
yield '__init__.pyo'
|
||||
|
||||
if not hasattr(imp, 'get_tag'):
|
||||
return
|
||||
|
||||
base = os.path.join('__pycache__', '__init__.' + imp.get_tag())
|
||||
yield base + '.pyc'
|
||||
yield base + '.pyo'
|
||||
yield base + '.opt-1.pyc'
|
||||
yield base + '.opt-2.pyc'
|
||||
|
||||
def copy_tree(
|
||||
self, infile, outfile,
|
||||
preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1
|
||||
):
|
||||
assert preserve_mode and preserve_times and not preserve_symlinks
|
||||
exclude = self.get_exclusions()
|
||||
|
||||
if not exclude:
|
||||
import distutils.dir_util
|
||||
distutils.dir_util._multiarch = self.multiarch
|
||||
return orig.install_lib.copy_tree(self, infile, outfile)
|
||||
|
||||
# Exclude namespace package __init__.py* files from the output
|
||||
|
||||
from setuptools.archive_util import unpack_directory
|
||||
from distutils import log
|
||||
|
||||
outfiles = []
|
||||
|
||||
if self.multiarch:
|
||||
import sysconfig
|
||||
ext_suffix = sysconfig.get_config_var ('EXT_SUFFIX')
|
||||
if ext_suffix.endswith(self.multiarch + ext_suffix[-3:]):
|
||||
new_suffix = None
|
||||
else:
|
||||
new_suffix = "%s-%s%s" % (ext_suffix[:-3], self.multiarch, ext_suffix[-3:])
|
||||
|
||||
def pf(src, dst):
|
||||
if dst in exclude:
|
||||
log.warn("Skipping installation of %s (namespace package)",
|
||||
dst)
|
||||
return False
|
||||
|
||||
if self.multiarch and new_suffix and dst.endswith(ext_suffix) and not dst.endswith(new_suffix):
|
||||
dst = dst.replace(ext_suffix, new_suffix)
|
||||
log.info("renaming extension to %s", os.path.basename(dst))
|
||||
|
||||
log.info("copying %s -> %s", src, os.path.dirname(dst))
|
||||
outfiles.append(dst)
|
||||
return dst
|
||||
|
||||
unpack_directory(infile, outfile, pf)
|
||||
return outfiles
|
||||
|
||||
def get_outputs(self):
|
||||
outputs = orig.install_lib.get_outputs(self)
|
||||
exclude = self.get_exclusions()
|
||||
if exclude:
|
||||
return [f for f in outputs if f not in exclude]
|
||||
return outputs
|
||||
@@ -0,0 +1,65 @@
|
||||
from distutils import log
|
||||
import distutils.command.install_scripts as orig
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pkg_resources import Distribution, PathMetadata, ensure_directory
|
||||
|
||||
|
||||
class install_scripts(orig.install_scripts):
|
||||
"""Do normal script install, plus any egg_info wrapper scripts"""
|
||||
|
||||
def initialize_options(self):
|
||||
orig.install_scripts.initialize_options(self)
|
||||
self.no_ep = False
|
||||
|
||||
def run(self):
|
||||
import setuptools.command.easy_install as ei
|
||||
|
||||
self.run_command("egg_info")
|
||||
if self.distribution.scripts:
|
||||
orig.install_scripts.run(self) # run first to set up self.outfiles
|
||||
else:
|
||||
self.outfiles = []
|
||||
if self.no_ep:
|
||||
# don't install entry point scripts into .egg file!
|
||||
return
|
||||
|
||||
ei_cmd = self.get_finalized_command("egg_info")
|
||||
dist = Distribution(
|
||||
ei_cmd.egg_base, PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info),
|
||||
ei_cmd.egg_name, ei_cmd.egg_version,
|
||||
)
|
||||
bs_cmd = self.get_finalized_command('build_scripts')
|
||||
exec_param = getattr(bs_cmd, 'executable', None)
|
||||
bw_cmd = self.get_finalized_command("bdist_wininst")
|
||||
is_wininst = getattr(bw_cmd, '_is_running', False)
|
||||
writer = ei.ScriptWriter
|
||||
if is_wininst:
|
||||
exec_param = "python.exe"
|
||||
writer = ei.WindowsScriptWriter
|
||||
if exec_param == sys.executable:
|
||||
# In case the path to the Python executable contains a space, wrap
|
||||
# it so it's not split up.
|
||||
exec_param = [exec_param]
|
||||
# resolve the writer to the environment
|
||||
writer = writer.best()
|
||||
cmd = writer.command_spec_class.best().from_param(exec_param)
|
||||
for args in writer.get_args(dist, cmd.as_header()):
|
||||
self.write_script(*args)
|
||||
|
||||
def write_script(self, script_name, contents, mode="t", *ignored):
|
||||
"""Write an executable file to the scripts directory"""
|
||||
from setuptools.command.easy_install import chmod, current_umask
|
||||
|
||||
log.info("Installing %s script to %s", script_name, self.install_dir)
|
||||
target = os.path.join(self.install_dir, script_name)
|
||||
self.outfiles.append(target)
|
||||
|
||||
mask = current_umask()
|
||||
if not self.dry_run:
|
||||
ensure_directory(target)
|
||||
f = open(target, "w" + mode)
|
||||
f.write(contents)
|
||||
f.close()
|
||||
chmod(target, 0o777 - mask)
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0"
|
||||
processorArchitecture="X86"
|
||||
name="%(name)s"
|
||||
type="win32"/>
|
||||
<!-- Identify the application security requirements. -->
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
@@ -0,0 +1,136 @@
|
||||
import os
|
||||
from glob import glob
|
||||
from distutils.util import convert_path
|
||||
from distutils.command import sdist
|
||||
|
||||
from setuptools.extern.six.moves import filter
|
||||
|
||||
|
||||
class sdist_add_defaults:
|
||||
"""
|
||||
Mix-in providing forward-compatibility for functionality as found in
|
||||
distutils on Python 3.7.
|
||||
|
||||
Do not edit the code in this class except to update functionality
|
||||
as implemented in distutils. Instead, override in the subclass.
|
||||
"""
|
||||
|
||||
def add_defaults(self):
|
||||
"""Add all the default files to self.filelist:
|
||||
- README or README.txt
|
||||
- setup.py
|
||||
- test/test*.py
|
||||
- all pure Python modules mentioned in setup script
|
||||
- all files pointed by package_data (build_py)
|
||||
- all files defined in data_files.
|
||||
- all files defined as scripts.
|
||||
- all C sources listed as part of extensions or C libraries
|
||||
in the setup script (doesn't catch C headers!)
|
||||
Warns if (README or README.txt) or setup.py are missing; everything
|
||||
else is optional.
|
||||
"""
|
||||
self._add_defaults_standards()
|
||||
self._add_defaults_optional()
|
||||
self._add_defaults_python()
|
||||
self._add_defaults_data_files()
|
||||
self._add_defaults_ext()
|
||||
self._add_defaults_c_libs()
|
||||
self._add_defaults_scripts()
|
||||
|
||||
@staticmethod
|
||||
def _cs_path_exists(fspath):
|
||||
"""
|
||||
Case-sensitive path existence check
|
||||
|
||||
>>> sdist_add_defaults._cs_path_exists(__file__)
|
||||
True
|
||||
>>> sdist_add_defaults._cs_path_exists(__file__.upper())
|
||||
False
|
||||
"""
|
||||
if not os.path.exists(fspath):
|
||||
return False
|
||||
# make absolute so we always have a directory
|
||||
abspath = os.path.abspath(fspath)
|
||||
directory, filename = os.path.split(abspath)
|
||||
return filename in os.listdir(directory)
|
||||
|
||||
def _add_defaults_standards(self):
|
||||
standards = [self.READMES, self.distribution.script_name]
|
||||
for fn in standards:
|
||||
if isinstance(fn, tuple):
|
||||
alts = fn
|
||||
got_it = False
|
||||
for fn in alts:
|
||||
if self._cs_path_exists(fn):
|
||||
got_it = True
|
||||
self.filelist.append(fn)
|
||||
break
|
||||
|
||||
if not got_it:
|
||||
self.warn("standard file not found: should have one of " +
|
||||
', '.join(alts))
|
||||
else:
|
||||
if self._cs_path_exists(fn):
|
||||
self.filelist.append(fn)
|
||||
else:
|
||||
self.warn("standard file '%s' not found" % fn)
|
||||
|
||||
def _add_defaults_optional(self):
|
||||
optional = ['test/test*.py', 'setup.cfg']
|
||||
for pattern in optional:
|
||||
files = filter(os.path.isfile, glob(pattern))
|
||||
self.filelist.extend(files)
|
||||
|
||||
def _add_defaults_python(self):
|
||||
# build_py is used to get:
|
||||
# - python modules
|
||||
# - files defined in package_data
|
||||
build_py = self.get_finalized_command('build_py')
|
||||
|
||||
# getting python files
|
||||
if self.distribution.has_pure_modules():
|
||||
self.filelist.extend(build_py.get_source_files())
|
||||
|
||||
# getting package_data files
|
||||
# (computed in build_py.data_files by build_py.finalize_options)
|
||||
for pkg, src_dir, build_dir, filenames in build_py.data_files:
|
||||
for filename in filenames:
|
||||
self.filelist.append(os.path.join(src_dir, filename))
|
||||
|
||||
def _add_defaults_data_files(self):
|
||||
# getting distribution.data_files
|
||||
if self.distribution.has_data_files():
|
||||
for item in self.distribution.data_files:
|
||||
if isinstance(item, str):
|
||||
# plain file
|
||||
item = convert_path(item)
|
||||
if os.path.isfile(item):
|
||||
self.filelist.append(item)
|
||||
else:
|
||||
# a (dirname, filenames) tuple
|
||||
dirname, filenames = item
|
||||
for f in filenames:
|
||||
f = convert_path(f)
|
||||
if os.path.isfile(f):
|
||||
self.filelist.append(f)
|
||||
|
||||
def _add_defaults_ext(self):
|
||||
if self.distribution.has_ext_modules():
|
||||
build_ext = self.get_finalized_command('build_ext')
|
||||
self.filelist.extend(build_ext.get_source_files())
|
||||
|
||||
def _add_defaults_c_libs(self):
|
||||
if self.distribution.has_c_libraries():
|
||||
build_clib = self.get_finalized_command('build_clib')
|
||||
self.filelist.extend(build_clib.get_source_files())
|
||||
|
||||
def _add_defaults_scripts(self):
|
||||
if self.distribution.has_scripts():
|
||||
build_scripts = self.get_finalized_command('build_scripts')
|
||||
self.filelist.extend(build_scripts.get_source_files())
|
||||
|
||||
|
||||
if hasattr(sdist.sdist, '_add_defaults_standards'):
|
||||
# disable the functionality already available upstream
|
||||
class sdist_add_defaults:
|
||||
pass
|
||||
@@ -0,0 +1,18 @@
|
||||
from distutils import log
|
||||
import distutils.command.register as orig
|
||||
|
||||
|
||||
class register(orig.register):
|
||||
__doc__ = orig.register.__doc__
|
||||
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
from distutils.util import convert_path
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from setuptools.extern import six
|
||||
|
||||
from setuptools import Command
|
||||
|
||||
|
||||
class rotate(Command):
|
||||
"""Delete older distributions"""
|
||||
|
||||
description = "delete older distributions, keeping N newest files"
|
||||
user_options = [
|
||||
('match=', 'm', "patterns to match (required)"),
|
||||
('dist-dir=', 'd', "directory where the distributions are"),
|
||||
('keep=', 'k', "number of matching distributions to keep"),
|
||||
]
|
||||
|
||||
boolean_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
self.match = None
|
||||
self.dist_dir = None
|
||||
self.keep = None
|
||||
|
||||
def finalize_options(self):
|
||||
if self.match is None:
|
||||
raise DistutilsOptionError(
|
||||
"Must specify one or more (comma-separated) match patterns "
|
||||
"(e.g. '.zip' or '.egg')"
|
||||
)
|
||||
if self.keep is None:
|
||||
raise DistutilsOptionError("Must specify number of files to keep")
|
||||
try:
|
||||
self.keep = int(self.keep)
|
||||
except ValueError:
|
||||
raise DistutilsOptionError("--keep must be an integer")
|
||||
if isinstance(self.match, six.string_types):
|
||||
self.match = [
|
||||
convert_path(p.strip()) for p in self.match.split(',')
|
||||
]
|
||||
self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
|
||||
|
||||
def run(self):
|
||||
self.run_command("egg_info")
|
||||
from glob import glob
|
||||
|
||||
for pattern in self.match:
|
||||
pattern = self.distribution.get_name() + '*' + pattern
|
||||
files = glob(os.path.join(self.dist_dir, pattern))
|
||||
files = [(os.path.getmtime(f), f) for f in files]
|
||||
files.sort()
|
||||
files.reverse()
|
||||
|
||||
log.info("%d file(s) matching %s", len(files), pattern)
|
||||
files = files[self.keep:]
|
||||
for (t, f) in files:
|
||||
log.info("Deleting %s", f)
|
||||
if not self.dry_run:
|
||||
if os.path.isdir(f):
|
||||
shutil.rmtree(f)
|
||||
else:
|
||||
os.unlink(f)
|
||||
@@ -0,0 +1,22 @@
|
||||
from setuptools.command.setopt import edit_config, option_base
|
||||
|
||||
|
||||
class saveopts(option_base):
|
||||
"""Save command-line options to a file"""
|
||||
|
||||
description = "save supplied options to setup.cfg or other config file"
|
||||
|
||||
def run(self):
|
||||
dist = self.distribution
|
||||
settings = {}
|
||||
|
||||
for cmd in dist.command_options:
|
||||
|
||||
if cmd == 'saveopts':
|
||||
continue # don't save our own options!
|
||||
|
||||
for opt, (src, val) in dist.get_option_dict(cmd).items():
|
||||
if src == "command line":
|
||||
settings.setdefault(cmd, {})[opt] = val
|
||||
|
||||
edit_config(self.filename, settings, self.dry_run)
|
||||
@@ -0,0 +1,200 @@
|
||||
from distutils import log
|
||||
import distutils.command.sdist as orig
|
||||
import os
|
||||
import sys
|
||||
import io
|
||||
import contextlib
|
||||
|
||||
from setuptools.extern import six
|
||||
|
||||
from .py36compat import sdist_add_defaults
|
||||
|
||||
import pkg_resources
|
||||
|
||||
_default_revctrl = list
|
||||
|
||||
|
||||
def walk_revctrl(dirname=''):
|
||||
"""Find all files under revision control"""
|
||||
for ep in pkg_resources.iter_entry_points('setuptools.file_finders'):
|
||||
for item in ep.load()(dirname):
|
||||
yield item
|
||||
|
||||
|
||||
class sdist(sdist_add_defaults, orig.sdist):
|
||||
"""Smart sdist that finds anything supported by revision control"""
|
||||
|
||||
user_options = [
|
||||
('formats=', None,
|
||||
"formats for source distribution (comma-separated list)"),
|
||||
('keep-temp', 'k',
|
||||
"keep the distribution tree around after creating " +
|
||||
"archive file(s)"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put the source distribution archive(s) in "
|
||||
"[default: dist]"),
|
||||
]
|
||||
|
||||
negative_opt = {}
|
||||
|
||||
README_EXTENSIONS = ['', '.rst', '.txt', '.md']
|
||||
READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS)
|
||||
|
||||
def run(self):
|
||||
self.run_command('egg_info')
|
||||
ei_cmd = self.get_finalized_command('egg_info')
|
||||
self.filelist = ei_cmd.filelist
|
||||
self.filelist.append(os.path.join(ei_cmd.egg_info, 'SOURCES.txt'))
|
||||
self.check_readme()
|
||||
|
||||
# Run sub commands
|
||||
for cmd_name in self.get_sub_commands():
|
||||
self.run_command(cmd_name)
|
||||
|
||||
self.make_distribution()
|
||||
|
||||
dist_files = getattr(self.distribution, 'dist_files', [])
|
||||
for file in self.archive_files:
|
||||
data = ('sdist', '', file)
|
||||
if data not in dist_files:
|
||||
dist_files.append(data)
|
||||
|
||||
def initialize_options(self):
|
||||
orig.sdist.initialize_options(self)
|
||||
|
||||
self._default_to_gztar()
|
||||
|
||||
def _default_to_gztar(self):
|
||||
# only needed on Python prior to 3.6.
|
||||
if sys.version_info >= (3, 6, 0, 'beta', 1):
|
||||
return
|
||||
self.formats = ['gztar']
|
||||
|
||||
def make_distribution(self):
|
||||
"""
|
||||
Workaround for #516
|
||||
"""
|
||||
with self._remove_os_link():
|
||||
orig.sdist.make_distribution(self)
|
||||
|
||||
@staticmethod
|
||||
@contextlib.contextmanager
|
||||
def _remove_os_link():
|
||||
"""
|
||||
In a context, remove and restore os.link if it exists
|
||||
"""
|
||||
|
||||
class NoValue:
|
||||
pass
|
||||
|
||||
orig_val = getattr(os, 'link', NoValue)
|
||||
try:
|
||||
del os.link
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if orig_val is not NoValue:
|
||||
setattr(os, 'link', orig_val)
|
||||
|
||||
def __read_template_hack(self):
|
||||
# This grody hack closes the template file (MANIFEST.in) if an
|
||||
# exception occurs during read_template.
|
||||
# Doing so prevents an error when easy_install attempts to delete the
|
||||
# file.
|
||||
try:
|
||||
orig.sdist.read_template(self)
|
||||
except Exception:
|
||||
_, _, tb = sys.exc_info()
|
||||
tb.tb_next.tb_frame.f_locals['template'].close()
|
||||
raise
|
||||
|
||||
# Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle
|
||||
# has been fixed, so only override the method if we're using an earlier
|
||||
# Python.
|
||||
has_leaky_handle = (
|
||||
sys.version_info < (2, 7, 2)
|
||||
or (3, 0) <= sys.version_info < (3, 1, 4)
|
||||
or (3, 2) <= sys.version_info < (3, 2, 1)
|
||||
)
|
||||
if has_leaky_handle:
|
||||
read_template = __read_template_hack
|
||||
|
||||
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])
|
||||
|
||||
def _add_defaults_data_files(self):
|
||||
try:
|
||||
if six.PY2:
|
||||
sdist_add_defaults._add_defaults_data_files(self)
|
||||
else:
|
||||
super()._add_defaults_data_files()
|
||||
except TypeError:
|
||||
log.warn("data_files contains unexpected objects")
|
||||
|
||||
def check_readme(self):
|
||||
for f in self.READMES:
|
||||
if os.path.exists(f):
|
||||
return
|
||||
else:
|
||||
self.warn(
|
||||
"standard file not found: should have one of " +
|
||||
', '.join(self.READMES)
|
||||
)
|
||||
|
||||
def make_release_tree(self, base_dir, files):
|
||||
orig.sdist.make_release_tree(self, base_dir, files)
|
||||
|
||||
# Save any egg_info command line options used to create this sdist
|
||||
dest = os.path.join(base_dir, 'setup.cfg')
|
||||
if hasattr(os, 'link') and os.path.exists(dest):
|
||||
# unlink and re-copy, since it might be hard-linked, and
|
||||
# we don't want to change the source version
|
||||
os.unlink(dest)
|
||||
self.copy_file('setup.cfg', dest)
|
||||
|
||||
self.get_finalized_command('egg_info').save_version_info(dest)
|
||||
|
||||
def _manifest_is_not_generated(self):
|
||||
# check for special comment used in 2.7.1 and higher
|
||||
if not os.path.isfile(self.manifest):
|
||||
return False
|
||||
|
||||
with io.open(self.manifest, 'rb') as fp:
|
||||
first_line = fp.readline()
|
||||
return (first_line !=
|
||||
'# file GENERATED by distutils, do NOT edit\n'.encode())
|
||||
|
||||
def read_manifest(self):
|
||||
"""Read the manifest file (named by 'self.manifest') and use it to
|
||||
fill in 'self.filelist', the list of files to include in the source
|
||||
distribution.
|
||||
"""
|
||||
log.info("reading manifest file '%s'", self.manifest)
|
||||
manifest = open(self.manifest, 'rb')
|
||||
for line in manifest:
|
||||
# The manifest must contain UTF-8. See #303.
|
||||
if six.PY3:
|
||||
try:
|
||||
line = line.decode('UTF-8')
|
||||
except UnicodeDecodeError:
|
||||
log.warn("%r not UTF-8 decodable -- skipping" % line)
|
||||
continue
|
||||
# ignore comments and blank lines
|
||||
line = line.strip()
|
||||
if line.startswith('#') or not line:
|
||||
continue
|
||||
self.filelist.append(line)
|
||||
manifest.close()
|
||||
@@ -0,0 +1,149 @@
|
||||
from distutils.util import convert_path
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
import distutils
|
||||
import os
|
||||
|
||||
from setuptools.extern.six.moves import configparser
|
||||
|
||||
from setuptools import Command
|
||||
|
||||
__all__ = ['config_file', 'edit_config', 'option_base', 'setopt']
|
||||
|
||||
|
||||
def config_file(kind="local"):
|
||||
"""Get the filename of the distutils, local, global, or per-user config
|
||||
|
||||
`kind` must be one of "local", "global", or "user"
|
||||
"""
|
||||
if kind == 'local':
|
||||
return 'setup.cfg'
|
||||
if kind == 'global':
|
||||
return os.path.join(
|
||||
os.path.dirname(distutils.__file__), 'distutils.cfg'
|
||||
)
|
||||
if kind == 'user':
|
||||
dot = os.name == 'posix' and '.' or ''
|
||||
return os.path.expanduser(convert_path("~/%spydistutils.cfg" % dot))
|
||||
raise ValueError(
|
||||
"config_file() type must be 'local', 'global', or 'user'", kind
|
||||
)
|
||||
|
||||
|
||||
def edit_config(filename, settings, dry_run=False):
|
||||
"""Edit a configuration file to include `settings`
|
||||
|
||||
`settings` is a dictionary of dictionaries or ``None`` values, keyed by
|
||||
command/section name. A ``None`` value means to delete the entire section,
|
||||
while a dictionary lists settings to be changed or deleted in that section.
|
||||
A setting of ``None`` means to delete that setting.
|
||||
"""
|
||||
log.debug("Reading configuration from %s", filename)
|
||||
opts = configparser.RawConfigParser()
|
||||
opts.read([filename])
|
||||
for section, options in settings.items():
|
||||
if options is None:
|
||||
log.info("Deleting section [%s] from %s", section, filename)
|
||||
opts.remove_section(section)
|
||||
else:
|
||||
if not opts.has_section(section):
|
||||
log.debug("Adding new section [%s] to %s", section, filename)
|
||||
opts.add_section(section)
|
||||
for option, value in options.items():
|
||||
if value is None:
|
||||
log.debug(
|
||||
"Deleting %s.%s from %s",
|
||||
section, option, filename
|
||||
)
|
||||
opts.remove_option(section, option)
|
||||
if not opts.options(section):
|
||||
log.info("Deleting empty [%s] section from %s",
|
||||
section, filename)
|
||||
opts.remove_section(section)
|
||||
else:
|
||||
log.debug(
|
||||
"Setting %s.%s to %r in %s",
|
||||
section, option, value, filename
|
||||
)
|
||||
opts.set(section, option, value)
|
||||
|
||||
log.info("Writing %s", filename)
|
||||
if not dry_run:
|
||||
with open(filename, 'w') as f:
|
||||
opts.write(f)
|
||||
|
||||
|
||||
class option_base(Command):
|
||||
"""Abstract base class for commands that mess with config files"""
|
||||
|
||||
user_options = [
|
||||
('global-config', 'g',
|
||||
"save options to the site-wide distutils.cfg file"),
|
||||
('user-config', 'u',
|
||||
"save options to the current user's pydistutils.cfg file"),
|
||||
('filename=', 'f',
|
||||
"configuration file to use (default=setup.cfg)"),
|
||||
]
|
||||
|
||||
boolean_options = [
|
||||
'global-config', 'user-config',
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.global_config = None
|
||||
self.user_config = None
|
||||
self.filename = None
|
||||
|
||||
def finalize_options(self):
|
||||
filenames = []
|
||||
if self.global_config:
|
||||
filenames.append(config_file('global'))
|
||||
if self.user_config:
|
||||
filenames.append(config_file('user'))
|
||||
if self.filename is not None:
|
||||
filenames.append(self.filename)
|
||||
if not filenames:
|
||||
filenames.append(config_file('local'))
|
||||
if len(filenames) > 1:
|
||||
raise DistutilsOptionError(
|
||||
"Must specify only one configuration file option",
|
||||
filenames
|
||||
)
|
||||
self.filename, = filenames
|
||||
|
||||
|
||||
class setopt(option_base):
|
||||
"""Save command-line options to a file"""
|
||||
|
||||
description = "set an option in setup.cfg or another config file"
|
||||
|
||||
user_options = [
|
||||
('command=', 'c', 'command to set an option for'),
|
||||
('option=', 'o', 'option to set'),
|
||||
('set-value=', 's', 'value of the option'),
|
||||
('remove', 'r', 'remove (unset) the value'),
|
||||
] + option_base.user_options
|
||||
|
||||
boolean_options = option_base.boolean_options + ['remove']
|
||||
|
||||
def initialize_options(self):
|
||||
option_base.initialize_options(self)
|
||||
self.command = None
|
||||
self.option = None
|
||||
self.set_value = None
|
||||
self.remove = None
|
||||
|
||||
def finalize_options(self):
|
||||
option_base.finalize_options(self)
|
||||
if self.command is None or self.option is None:
|
||||
raise DistutilsOptionError("Must specify --command *and* --option")
|
||||
if self.set_value is None and not self.remove:
|
||||
raise DistutilsOptionError("Must specify --set-value or --remove")
|
||||
|
||||
def run(self):
|
||||
edit_config(
|
||||
self.filename, {
|
||||
self.command: {self.option.replace('-', '_'): self.set_value}
|
||||
},
|
||||
self.dry_run
|
||||
)
|
||||
@@ -0,0 +1,270 @@
|
||||
import os
|
||||
import operator
|
||||
import sys
|
||||
import contextlib
|
||||
import itertools
|
||||
import unittest
|
||||
from distutils.errors import DistutilsError, DistutilsOptionError
|
||||
from distutils import log
|
||||
from unittest import TestLoader
|
||||
|
||||
from setuptools.extern import six
|
||||
from setuptools.extern.six.moves import map, filter
|
||||
|
||||
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
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ScanningLoader(TestLoader):
|
||||
|
||||
def __init__(self):
|
||||
TestLoader.__init__(self)
|
||||
self._visited = set()
|
||||
|
||||
def loadTestsFromModule(self, module, pattern=None):
|
||||
"""Return a suite of all tests cases contained in the given module
|
||||
|
||||
If the module is a package, load tests from all the modules in it.
|
||||
If the module has an ``additional_tests`` function, call it and add
|
||||
the return value to the tests.
|
||||
"""
|
||||
if module in self._visited:
|
||||
return None
|
||||
self._visited.add(module)
|
||||
|
||||
tests = []
|
||||
tests.append(TestLoader.loadTestsFromModule(self, module))
|
||||
|
||||
if hasattr(module, "additional_tests"):
|
||||
tests.append(module.additional_tests())
|
||||
|
||||
if hasattr(module, '__path__'):
|
||||
for file in resource_listdir(module.__name__, ''):
|
||||
if file.endswith('.py') and file != '__init__.py':
|
||||
submodule = module.__name__ + '.' + file[:-3]
|
||||
else:
|
||||
if resource_exists(module.__name__, file + '/__init__.py'):
|
||||
submodule = module.__name__ + '.' + file
|
||||
else:
|
||||
continue
|
||||
tests.append(self.loadTestsFromName(submodule))
|
||||
|
||||
if len(tests) != 1:
|
||||
return self.suiteClass(tests)
|
||||
else:
|
||||
return tests[0] # don't create a nested suite for only one return
|
||||
|
||||
|
||||
# adapted from jaraco.classes.properties:NonDataProperty
|
||||
class NonDataProperty:
|
||||
def __init__(self, fget):
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
return self.fget(obj)
|
||||
|
||||
|
||||
class test(Command):
|
||||
"""Command to run unit tests after in-place build"""
|
||||
|
||||
description = "run unit tests after in-place build"
|
||||
|
||||
user_options = [
|
||||
('test-module=', 'm', "Run 'test_suite' in specified module"),
|
||||
('test-suite=', 's',
|
||||
"Run single test, case or suite (e.g. 'module.test_suite')"),
|
||||
('test-runner=', 'r', "Test runner to use"),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.test_suite = None
|
||||
self.test_module = None
|
||||
self.test_loader = None
|
||||
self.test_runner = None
|
||||
|
||||
def finalize_options(self):
|
||||
|
||||
if self.test_suite and self.test_module:
|
||||
msg = "You may specify a module or a suite, but not both"
|
||||
raise DistutilsOptionError(msg)
|
||||
|
||||
if self.test_suite is None:
|
||||
if self.test_module is None:
|
||||
self.test_suite = self.distribution.test_suite
|
||||
else:
|
||||
self.test_suite = self.test_module + ".test_suite"
|
||||
|
||||
if self.test_loader is None:
|
||||
self.test_loader = getattr(self.distribution, 'test_loader', None)
|
||||
if self.test_loader is None:
|
||||
self.test_loader = "setuptools.command.test:ScanningLoader"
|
||||
if self.test_runner is None:
|
||||
self.test_runner = getattr(self.distribution, 'test_runner', None)
|
||||
|
||||
@NonDataProperty
|
||||
def test_args(self):
|
||||
return list(self._test_args())
|
||||
|
||||
def _test_args(self):
|
||||
if not self.test_suite and sys.version_info >= (2, 7):
|
||||
yield 'discover'
|
||||
if self.verbose:
|
||||
yield '--verbose'
|
||||
if self.test_suite:
|
||||
yield self.test_suite
|
||||
|
||||
def with_project_on_sys_path(self, func):
|
||||
"""
|
||||
Backward compatibility for project_on_sys_path context.
|
||||
"""
|
||||
with self.project_on_sys_path():
|
||||
func()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def project_on_sys_path(self, include_dists=[]):
|
||||
with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False)
|
||||
|
||||
if with_2to3:
|
||||
# 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 extensions
|
||||
self.reinitialize_command('egg_info', egg_base=build_path)
|
||||
self.run_command('egg_info')
|
||||
|
||||
self.reinitialize_command('build_ext', inplace=0)
|
||||
self.run_command('build_ext')
|
||||
else:
|
||||
# Without 2to3 inplace works fine:
|
||||
self.run_command('egg_info')
|
||||
|
||||
# Build extensions in-place
|
||||
self.reinitialize_command('build_ext', inplace=1)
|
||||
self.run_command('build_ext')
|
||||
|
||||
ei_cmd = self.get_finalized_command("egg_info")
|
||||
|
||||
old_path = sys.path[:]
|
||||
old_modules = sys.modules.copy()
|
||||
|
||||
try:
|
||||
project_path = normalize_path(ei_cmd.egg_base)
|
||||
sys.path.insert(0, project_path)
|
||||
working_set.__init__()
|
||||
add_activation_listener(lambda dist: dist.activate())
|
||||
require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
|
||||
with self.paths_on_pythonpath([project_path]):
|
||||
yield
|
||||
finally:
|
||||
sys.path[:] = old_path
|
||||
sys.modules.clear()
|
||||
sys.modules.update(old_modules)
|
||||
working_set.__init__()
|
||||
|
||||
@staticmethod
|
||||
@contextlib.contextmanager
|
||||
def paths_on_pythonpath(paths):
|
||||
"""
|
||||
Add the indicated paths to the head of the PYTHONPATH environment
|
||||
variable so that subprocesses will also see the packages at
|
||||
these paths.
|
||||
|
||||
Do this in a context that restores the value on exit.
|
||||
"""
|
||||
nothing = object()
|
||||
orig_pythonpath = os.environ.get('PYTHONPATH', nothing)
|
||||
current_pythonpath = os.environ.get('PYTHONPATH', '')
|
||||
try:
|
||||
prefix = os.pathsep.join(paths)
|
||||
to_join = filter(None, [prefix, current_pythonpath])
|
||||
new_path = os.pathsep.join(to_join)
|
||||
if new_path:
|
||||
os.environ['PYTHONPATH'] = new_path
|
||||
yield
|
||||
finally:
|
||||
if orig_pythonpath is nothing:
|
||||
os.environ.pop('PYTHONPATH', None)
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = orig_pythonpath
|
||||
|
||||
@staticmethod
|
||||
def install_dists(dist):
|
||||
"""
|
||||
Install the requirements indicated by self.distribution and
|
||||
return an iterable of the dists that were built.
|
||||
"""
|
||||
ir_d = dist.fetch_build_eggs(dist.install_requires)
|
||||
tr_d = dist.fetch_build_eggs(dist.tests_require or [])
|
||||
er_d = dist.fetch_build_eggs(
|
||||
v for k, v in dist.extras_require.items()
|
||||
if k.startswith(':') and evaluate_marker(k[1:])
|
||||
)
|
||||
return itertools.chain(ir_d, tr_d, er_d)
|
||||
|
||||
def run(self):
|
||||
installed_dists = self.install_dists(self.distribution)
|
||||
|
||||
cmd = ' '.join(self._argv)
|
||||
if self.dry_run:
|
||||
self.announce('skipping "%s" (dry run)' % cmd)
|
||||
return
|
||||
|
||||
self.announce('running "%s"' % cmd)
|
||||
|
||||
paths = map(operator.attrgetter('location'), installed_dists)
|
||||
with self.paths_on_pythonpath(paths):
|
||||
with self.project_on_sys_path():
|
||||
self.run_tests()
|
||||
|
||||
def run_tests(self):
|
||||
# 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):
|
||||
module = self.test_suite.split('.')[0]
|
||||
if module in _namespace_packages:
|
||||
del_modules = []
|
||||
if module in sys.modules:
|
||||
del_modules.append(module)
|
||||
module += '.'
|
||||
for name in sys.modules:
|
||||
if name.startswith(module):
|
||||
del_modules.append(name)
|
||||
list(map(sys.modules.__delitem__, del_modules))
|
||||
|
||||
test = unittest.main(
|
||||
None, None, self._argv,
|
||||
testLoader=self._resolve_as_ep(self.test_loader),
|
||||
testRunner=self._resolve_as_ep(self.test_runner),
|
||||
exit=False,
|
||||
)
|
||||
if not test.result.wasSuccessful():
|
||||
msg = 'Test failed: %s' % test.result
|
||||
self.announce(msg, log.ERROR)
|
||||
raise DistutilsError(msg)
|
||||
|
||||
@property
|
||||
def _argv(self):
|
||||
return ['unittest'] + self.test_args
|
||||
|
||||
@staticmethod
|
||||
def _resolve_as_ep(val):
|
||||
"""
|
||||
Load the indicated attribute value, called, as a as if it were
|
||||
specified as an entry point.
|
||||
"""
|
||||
if val is None:
|
||||
return
|
||||
parsed = EntryPoint.parse("x=" + val)
|
||||
return parsed.resolve()()
|
||||
@@ -0,0 +1,196 @@
|
||||
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.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.
|
||||
"""
|
||||
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()
|
||||
)
|
||||
|
||||
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
|
||||
@@ -0,0 +1,206 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""upload_docs
|
||||
|
||||
Implements a Distutils 'upload_docs' subcommand (upload documentation to
|
||||
PyPI's pythonhosted.org).
|
||||
"""
|
||||
|
||||
from base64 import standard_b64encode
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsOptionError
|
||||
import os
|
||||
import socket
|
||||
import zipfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from setuptools.extern import six
|
||||
from setuptools.extern.six.moves import http_client, urllib
|
||||
|
||||
from pkg_resources import iter_entry_points
|
||||
from .upload import upload
|
||||
|
||||
|
||||
def _encode(s):
|
||||
errors = 'surrogateescape' if six.PY3 else 'strict'
|
||||
return s.encode('utf-8', errors)
|
||||
|
||||
|
||||
class upload_docs(upload):
|
||||
# override the default repository as upload_docs isn't
|
||||
# supported by Warehouse (and won't be).
|
||||
DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/'
|
||||
|
||||
description = 'Upload documentation to PyPI'
|
||||
|
||||
user_options = [
|
||||
('repository=', 'r',
|
||||
"url of repository [default: %s]" % upload.DEFAULT_REPOSITORY),
|
||||
('show-response', None,
|
||||
'display full response text from server'),
|
||||
('upload-dir=', None, 'directory to upload'),
|
||||
]
|
||||
boolean_options = upload.boolean_options
|
||||
|
||||
def has_sphinx(self):
|
||||
if self.upload_dir is None:
|
||||
for ep in iter_entry_points('distutils.commands', 'build_sphinx'):
|
||||
return True
|
||||
|
||||
sub_commands = [('build_sphinx', has_sphinx)]
|
||||
|
||||
def initialize_options(self):
|
||||
upload.initialize_options(self)
|
||||
self.upload_dir = None
|
||||
self.target_dir = None
|
||||
|
||||
def finalize_options(self):
|
||||
upload.finalize_options(self)
|
||||
if self.upload_dir is None:
|
||||
if self.has_sphinx():
|
||||
build_sphinx = self.get_finalized_command('build_sphinx')
|
||||
self.target_dir = build_sphinx.builder_target_dir
|
||||
else:
|
||||
build = self.get_finalized_command('build')
|
||||
self.target_dir = os.path.join(build.build_base, 'docs')
|
||||
else:
|
||||
self.ensure_dirname('upload_dir')
|
||||
self.target_dir = self.upload_dir
|
||||
if 'pypi.python.org' in self.repository:
|
||||
log.warn("Upload_docs command is deprecated. Use RTD instead.")
|
||||
self.announce('Using upload directory %s' % self.target_dir)
|
||||
|
||||
def create_zipfile(self, filename):
|
||||
zip_file = zipfile.ZipFile(filename, "w")
|
||||
try:
|
||||
self.mkpath(self.target_dir) # just in case
|
||||
for root, dirs, files in os.walk(self.target_dir):
|
||||
if root == self.target_dir and not files:
|
||||
tmpl = "no files found in upload directory '%s'"
|
||||
raise DistutilsOptionError(tmpl % self.target_dir)
|
||||
for name in files:
|
||||
full = os.path.join(root, name)
|
||||
relative = root[len(self.target_dir):].lstrip(os.path.sep)
|
||||
dest = os.path.join(relative, name)
|
||||
zip_file.write(full, dest)
|
||||
finally:
|
||||
zip_file.close()
|
||||
|
||||
def run(self):
|
||||
# Run sub commands
|
||||
for cmd_name in self.get_sub_commands():
|
||||
self.run_command(cmd_name)
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
name = self.distribution.metadata.get_name()
|
||||
zip_file = os.path.join(tmp_dir, "%s.zip" % name)
|
||||
try:
|
||||
self.create_zipfile(zip_file)
|
||||
self.upload_file(zip_file)
|
||||
finally:
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
@staticmethod
|
||||
def _build_part(item, sep_boundary):
|
||||
key, values = item
|
||||
title = '\nContent-Disposition: form-data; name="%s"' % key
|
||||
# handle multiple entries for the same name
|
||||
if not isinstance(values, list):
|
||||
values = [values]
|
||||
for value in values:
|
||||
if isinstance(value, tuple):
|
||||
title += '; filename="%s"' % value[0]
|
||||
value = value[1]
|
||||
else:
|
||||
value = _encode(value)
|
||||
yield sep_boundary
|
||||
yield _encode(title)
|
||||
yield b"\n\n"
|
||||
yield value
|
||||
if value and value[-1:] == b'\r':
|
||||
yield b'\n' # write an extra newline (lurve Macs)
|
||||
|
||||
@classmethod
|
||||
def _build_multipart(cls, data):
|
||||
"""
|
||||
Build up the MIME payload for the POST data
|
||||
"""
|
||||
boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
|
||||
sep_boundary = b'\n--' + boundary
|
||||
end_boundary = sep_boundary + b'--'
|
||||
end_items = end_boundary, b"\n",
|
||||
builder = functools.partial(
|
||||
cls._build_part,
|
||||
sep_boundary=sep_boundary,
|
||||
)
|
||||
part_groups = map(builder, data.items())
|
||||
parts = itertools.chain.from_iterable(part_groups)
|
||||
body_items = itertools.chain(parts, end_items)
|
||||
content_type = 'multipart/form-data; boundary=%s' % boundary.decode('ascii')
|
||||
return b''.join(body_items), content_type
|
||||
|
||||
def upload_file(self, filename):
|
||||
with open(filename, 'rb') as f:
|
||||
content = f.read()
|
||||
meta = self.distribution.metadata
|
||||
data = {
|
||||
':action': 'doc_upload',
|
||||
'name': meta.get_name(),
|
||||
'content': (os.path.basename(filename), content),
|
||||
}
|
||||
# set up the authentication
|
||||
credentials = _encode(self.username + ':' + self.password)
|
||||
credentials = standard_b64encode(credentials)
|
||||
if six.PY3:
|
||||
credentials = credentials.decode('ascii')
|
||||
auth = "Basic " + credentials
|
||||
|
||||
body, ct = self._build_multipart(data)
|
||||
|
||||
msg = "Submitting documentation to %s" % (self.repository)
|
||||
self.announce(msg, log.INFO)
|
||||
|
||||
# build the Request
|
||||
# We can't use urllib2 since we need to send the Basic
|
||||
# auth right with the first request
|
||||
schema, netloc, url, params, query, fragments = \
|
||||
urllib.parse.urlparse(self.repository)
|
||||
assert not params and not query and not fragments
|
||||
if schema == 'http':
|
||||
conn = http_client.HTTPConnection(netloc)
|
||||
elif schema == 'https':
|
||||
conn = http_client.HTTPSConnection(netloc)
|
||||
else:
|
||||
raise AssertionError("unsupported schema " + schema)
|
||||
|
||||
data = ''
|
||||
try:
|
||||
conn.connect()
|
||||
conn.putrequest("POST", url)
|
||||
content_type = ct
|
||||
conn.putheader('Content-type', content_type)
|
||||
conn.putheader('Content-length', str(len(body)))
|
||||
conn.putheader('Authorization', auth)
|
||||
conn.endheaders()
|
||||
conn.send(body)
|
||||
except socket.error as e:
|
||||
self.announce(str(e), log.ERROR)
|
||||
return
|
||||
|
||||
r = conn.getresponse()
|
||||
if r.status == 200:
|
||||
msg = 'Server response (%s): %s' % (r.status, r.reason)
|
||||
self.announce(msg, log.INFO)
|
||||
elif r.status == 301:
|
||||
location = r.getheader('Location')
|
||||
if location is None:
|
||||
location = 'https://pythonhosted.org/%s/' % meta.get_name()
|
||||
msg = 'Upload successful. Visit %s' % location
|
||||
self.announce(msg, log.INFO)
|
||||
else:
|
||||
msg = 'Upload failed (%s): %s' % (r.status, r.reason)
|
||||
self.announce(msg, log.ERROR)
|
||||
if self.show_response:
|
||||
print('-' * 75, r.read(), '-' * 75)
|
||||
@@ -0,0 +1,635 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
import warnings
|
||||
import functools
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from functools import wraps
|
||||
from importlib import import_module
|
||||
|
||||
from distutils.errors import DistutilsOptionError, DistutilsFileError
|
||||
from setuptools.extern.packaging.version import LegacyVersion, parse
|
||||
from setuptools.extern.six import string_types, PY3
|
||||
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
def read_configuration(
|
||||
filepath, find_others=False, ignore_option_errors=False):
|
||||
"""Read given configuration file and returns options from it as a dict.
|
||||
|
||||
:param str|unicode filepath: Path to configuration file
|
||||
to get options from.
|
||||
|
||||
:param bool find_others: Whether to search for other configuration files
|
||||
which could be on in various places.
|
||||
|
||||
:param bool ignore_option_errors: Whether to silently ignore
|
||||
options, values of which could not be resolved (e.g. due to exceptions
|
||||
in directives such as file:, attr:, etc.).
|
||||
If False exceptions are propagated as expected.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
from setuptools.dist import Distribution, _Distribution
|
||||
|
||||
filepath = os.path.abspath(filepath)
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
raise DistutilsFileError(
|
||||
'Configuration file %s does not exist.' % filepath)
|
||||
|
||||
current_directory = os.getcwd()
|
||||
os.chdir(os.path.dirname(filepath))
|
||||
|
||||
try:
|
||||
dist = Distribution()
|
||||
|
||||
filenames = dist.find_config_files() if find_others else []
|
||||
if filepath not in filenames:
|
||||
filenames.append(filepath)
|
||||
|
||||
_Distribution.parse_config_files(dist, filenames=filenames)
|
||||
|
||||
handlers = parse_configuration(
|
||||
dist, dist.command_options,
|
||||
ignore_option_errors=ignore_option_errors)
|
||||
|
||||
finally:
|
||||
os.chdir(current_directory)
|
||||
|
||||
return configuration_to_dict(handlers)
|
||||
|
||||
|
||||
def _get_option(target_obj, key):
|
||||
"""
|
||||
Given a target object and option key, get that option from
|
||||
the target object, either through a get_{key} method or
|
||||
from an attribute directly.
|
||||
"""
|
||||
getter_name = 'get_{key}'.format(**locals())
|
||||
by_attribute = functools.partial(getattr, target_obj, key)
|
||||
getter = getattr(target_obj, getter_name, by_attribute)
|
||||
return getter()
|
||||
|
||||
|
||||
def configuration_to_dict(handlers):
|
||||
"""Returns configuration data gathered by given handlers as a dict.
|
||||
|
||||
:param list[ConfigHandler] handlers: Handlers list,
|
||||
usually from parse_configuration()
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
config_dict = defaultdict(dict)
|
||||
|
||||
for handler in handlers:
|
||||
for option in handler.set_options:
|
||||
value = _get_option(handler.target_obj, option)
|
||||
config_dict[handler.section_prefix][option] = value
|
||||
|
||||
return config_dict
|
||||
|
||||
|
||||
def parse_configuration(
|
||||
distribution, command_options, ignore_option_errors=False):
|
||||
"""Performs additional parsing of configuration options
|
||||
for a distribution.
|
||||
|
||||
Returns a list of used option handlers.
|
||||
|
||||
:param Distribution distribution:
|
||||
:param dict command_options:
|
||||
:param bool ignore_option_errors: Whether to silently ignore
|
||||
options, values of which could not be resolved (e.g. due to exceptions
|
||||
in directives such as file:, attr:, etc.).
|
||||
If False exceptions are propagated as expected.
|
||||
:rtype: list
|
||||
"""
|
||||
options = ConfigOptionsHandler(
|
||||
distribution, command_options, ignore_option_errors)
|
||||
options.parse()
|
||||
|
||||
meta = ConfigMetadataHandler(
|
||||
distribution.metadata, command_options, ignore_option_errors,
|
||||
distribution.package_dir)
|
||||
meta.parse()
|
||||
|
||||
return meta, options
|
||||
|
||||
|
||||
class ConfigHandler:
|
||||
"""Handles metadata supplied in configuration files."""
|
||||
|
||||
section_prefix = None
|
||||
"""Prefix for config sections handled by this handler.
|
||||
Must be provided by class heirs.
|
||||
|
||||
"""
|
||||
|
||||
aliases = {}
|
||||
"""Options aliases.
|
||||
For compatibility with various packages. E.g.: d2to1 and pbr.
|
||||
Note: `-` in keys is replaced with `_` by config parser.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, target_obj, options, ignore_option_errors=False):
|
||||
sections = {}
|
||||
|
||||
section_prefix = self.section_prefix
|
||||
for section_name, section_options in options.items():
|
||||
if not section_name.startswith(section_prefix):
|
||||
continue
|
||||
|
||||
section_name = section_name.replace(section_prefix, '').strip('.')
|
||||
sections[section_name] = section_options
|
||||
|
||||
self.ignore_option_errors = ignore_option_errors
|
||||
self.target_obj = target_obj
|
||||
self.sections = sections
|
||||
self.set_options = []
|
||||
|
||||
@property
|
||||
def parsers(self):
|
||||
"""Metadata item name to parser function mapping."""
|
||||
raise NotImplementedError(
|
||||
'%s must provide .parsers property' % self.__class__.__name__)
|
||||
|
||||
def __setitem__(self, option_name, value):
|
||||
unknown = tuple()
|
||||
target_obj = self.target_obj
|
||||
|
||||
# Translate alias into real name.
|
||||
option_name = self.aliases.get(option_name, option_name)
|
||||
|
||||
current_value = getattr(target_obj, option_name, unknown)
|
||||
|
||||
if current_value is unknown:
|
||||
raise KeyError(option_name)
|
||||
|
||||
if current_value:
|
||||
# Already inhabited. Skipping.
|
||||
return
|
||||
|
||||
skip_option = False
|
||||
parser = self.parsers.get(option_name)
|
||||
if parser:
|
||||
try:
|
||||
value = parser(value)
|
||||
|
||||
except Exception:
|
||||
skip_option = True
|
||||
if not self.ignore_option_errors:
|
||||
raise
|
||||
|
||||
if skip_option:
|
||||
return
|
||||
|
||||
setter = getattr(target_obj, 'set_%s' % option_name, None)
|
||||
if setter is None:
|
||||
setattr(target_obj, option_name, value)
|
||||
else:
|
||||
setter(value)
|
||||
|
||||
self.set_options.append(option_name)
|
||||
|
||||
@classmethod
|
||||
def _parse_list(cls, value, separator=','):
|
||||
"""Represents value as a list.
|
||||
|
||||
Value is split either by separator (defaults to comma) or by lines.
|
||||
|
||||
:param value:
|
||||
:param separator: List items separator character.
|
||||
:rtype: list
|
||||
"""
|
||||
if isinstance(value, list): # _get_parser_compound case
|
||||
return value
|
||||
|
||||
if '\n' in value:
|
||||
value = value.splitlines()
|
||||
else:
|
||||
value = value.split(separator)
|
||||
|
||||
return [chunk.strip() for chunk in value if chunk.strip()]
|
||||
|
||||
@classmethod
|
||||
def _parse_dict(cls, value):
|
||||
"""Represents value as a dict.
|
||||
|
||||
:param value:
|
||||
:rtype: dict
|
||||
"""
|
||||
separator = '='
|
||||
result = {}
|
||||
for line in cls._parse_list(value):
|
||||
key, sep, val = line.partition(separator)
|
||||
if sep != separator:
|
||||
raise DistutilsOptionError(
|
||||
'Unable to parse option value to dict: %s' % value)
|
||||
result[key.strip()] = val.strip()
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _parse_bool(cls, value):
|
||||
"""Represents value as boolean.
|
||||
|
||||
:param value:
|
||||
:rtype: bool
|
||||
"""
|
||||
value = value.lower()
|
||||
return value in ('1', 'true', 'yes')
|
||||
|
||||
@classmethod
|
||||
def _parse_file(cls, value):
|
||||
"""Represents value as a string, allowing including text
|
||||
from nearest files using `file:` directive.
|
||||
|
||||
Directive is sandboxed and won't reach anything outside
|
||||
directory with setup.py.
|
||||
|
||||
Examples:
|
||||
file: LICENSE
|
||||
file: README.rst, CHANGELOG.md, src/file.txt
|
||||
|
||||
:param str value:
|
||||
:rtype: str
|
||||
"""
|
||||
include_directive = 'file:'
|
||||
|
||||
if not isinstance(value, string_types):
|
||||
return value
|
||||
|
||||
if not value.startswith(include_directive):
|
||||
return value
|
||||
|
||||
spec = value[len(include_directive):]
|
||||
filepaths = (os.path.abspath(path.strip()) for path in spec.split(','))
|
||||
return '\n'.join(
|
||||
cls._read_file(path)
|
||||
for path in filepaths
|
||||
if (cls._assert_local(path) or True)
|
||||
and os.path.isfile(path)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _assert_local(filepath):
|
||||
if not filepath.startswith(os.getcwd()):
|
||||
raise DistutilsOptionError(
|
||||
'`file:` directive can not access %s' % filepath)
|
||||
|
||||
@staticmethod
|
||||
def _read_file(filepath):
|
||||
with io.open(filepath, encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
@classmethod
|
||||
def _parse_attr(cls, value, package_dir=None):
|
||||
"""Represents value as a module attribute.
|
||||
|
||||
Examples:
|
||||
attr: package.attr
|
||||
attr: package.module.attr
|
||||
|
||||
:param str value:
|
||||
:rtype: str
|
||||
"""
|
||||
attr_directive = 'attr:'
|
||||
if not value.startswith(attr_directive):
|
||||
return value
|
||||
|
||||
attrs_path = value.replace(attr_directive, '').strip().split('.')
|
||||
attr_name = attrs_path.pop()
|
||||
|
||||
module_name = '.'.join(attrs_path)
|
||||
module_name = module_name or '__init__'
|
||||
|
||||
parent_path = os.getcwd()
|
||||
if package_dir:
|
||||
if attrs_path[0] in package_dir:
|
||||
# A custom path was specified for the module we want to import
|
||||
custom_path = package_dir[attrs_path[0]]
|
||||
parts = custom_path.rsplit('/', 1)
|
||||
if len(parts) > 1:
|
||||
parent_path = os.path.join(os.getcwd(), parts[0])
|
||||
module_name = parts[1]
|
||||
else:
|
||||
module_name = custom_path
|
||||
elif '' in package_dir:
|
||||
# A custom parent directory was specified for all root modules
|
||||
parent_path = os.path.join(os.getcwd(), package_dir[''])
|
||||
sys.path.insert(0, parent_path)
|
||||
try:
|
||||
module = import_module(module_name)
|
||||
value = getattr(module, attr_name)
|
||||
|
||||
finally:
|
||||
sys.path = sys.path[1:]
|
||||
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _get_parser_compound(cls, *parse_methods):
|
||||
"""Returns parser function to represents value as a list.
|
||||
|
||||
Parses a value applying given methods one after another.
|
||||
|
||||
:param parse_methods:
|
||||
:rtype: callable
|
||||
"""
|
||||
def parse(value):
|
||||
parsed = value
|
||||
|
||||
for method in parse_methods:
|
||||
parsed = method(parsed)
|
||||
|
||||
return parsed
|
||||
|
||||
return parse
|
||||
|
||||
@classmethod
|
||||
def _parse_section_to_dict(cls, section_options, values_parser=None):
|
||||
"""Parses section options into a dictionary.
|
||||
|
||||
Optionally applies a given parser to values.
|
||||
|
||||
:param dict section_options:
|
||||
:param callable values_parser:
|
||||
:rtype: dict
|
||||
"""
|
||||
value = {}
|
||||
values_parser = values_parser or (lambda val: val)
|
||||
for key, (_, val) in section_options.items():
|
||||
value[key] = values_parser(val)
|
||||
return value
|
||||
|
||||
def parse_section(self, section_options):
|
||||
"""Parses configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
for (name, (_, value)) in section_options.items():
|
||||
try:
|
||||
self[name] = value
|
||||
|
||||
except KeyError:
|
||||
pass # Keep silent for a new option may appear anytime.
|
||||
|
||||
def parse(self):
|
||||
"""Parses configuration file items from one
|
||||
or more related sections.
|
||||
|
||||
"""
|
||||
for section_name, section_options in self.sections.items():
|
||||
|
||||
method_postfix = ''
|
||||
if section_name: # [section.option] variant
|
||||
method_postfix = '_%s' % section_name
|
||||
|
||||
section_parser_method = getattr(
|
||||
self,
|
||||
# Dots in section names are tranlsated into dunderscores.
|
||||
('parse_section%s' % method_postfix).replace('.', '__'),
|
||||
None)
|
||||
|
||||
if section_parser_method is None:
|
||||
raise DistutilsOptionError(
|
||||
'Unsupported distribution option section: [%s.%s]' % (
|
||||
self.section_prefix, section_name))
|
||||
|
||||
section_parser_method(section_options)
|
||||
|
||||
def _deprecated_config_handler(self, func, msg, warning_class):
|
||||
""" this function will wrap around parameters that are deprecated
|
||||
|
||||
:param msg: deprecation message
|
||||
:param warning_class: class of warning exception to be raised
|
||||
:param func: function to be wrapped around
|
||||
"""
|
||||
@wraps(func)
|
||||
def config_handler(*args, **kwargs):
|
||||
warnings.warn(msg, warning_class)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return config_handler
|
||||
|
||||
|
||||
class ConfigMetadataHandler(ConfigHandler):
|
||||
|
||||
section_prefix = 'metadata'
|
||||
|
||||
aliases = {
|
||||
'home_page': 'url',
|
||||
'summary': 'description',
|
||||
'classifier': 'classifiers',
|
||||
'platform': 'platforms',
|
||||
}
|
||||
|
||||
strict_mode = False
|
||||
"""We need to keep it loose, to be partially compatible with
|
||||
`pbr` and `d2to1` packages which also uses `metadata` section.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, target_obj, options, ignore_option_errors=False,
|
||||
package_dir=None):
|
||||
super(ConfigMetadataHandler, self).__init__(target_obj, options,
|
||||
ignore_option_errors)
|
||||
self.package_dir = package_dir
|
||||
|
||||
@property
|
||||
def parsers(self):
|
||||
"""Metadata item name to parser function mapping."""
|
||||
parse_list = self._parse_list
|
||||
parse_file = self._parse_file
|
||||
parse_dict = self._parse_dict
|
||||
|
||||
return {
|
||||
'platforms': parse_list,
|
||||
'keywords': parse_list,
|
||||
'provides': parse_list,
|
||||
'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,
|
||||
'description': parse_file,
|
||||
'long_description': parse_file,
|
||||
'version': self._parse_version,
|
||||
'project_urls': parse_dict,
|
||||
}
|
||||
|
||||
def _parse_version(self, value):
|
||||
"""Parses `version` option value.
|
||||
|
||||
:param value:
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
version = self._parse_file(value)
|
||||
|
||||
if version != value:
|
||||
version = version.strip()
|
||||
# Be strict about versions loaded from file because it's easy to
|
||||
# accidentally include newlines and other unintended content
|
||||
if isinstance(parse(version), LegacyVersion):
|
||||
tmpl = (
|
||||
'Version loaded from {value} does not '
|
||||
'comply with PEP 440: {version}'
|
||||
)
|
||||
raise DistutilsOptionError(tmpl.format(**locals()))
|
||||
|
||||
return version
|
||||
|
||||
version = self._parse_attr(value, self.package_dir)
|
||||
|
||||
if callable(version):
|
||||
version = version()
|
||||
|
||||
if not isinstance(version, string_types):
|
||||
if hasattr(version, '__iter__'):
|
||||
version = '.'.join(map(str, version))
|
||||
else:
|
||||
version = '%s' % version
|
||||
|
||||
return version
|
||||
|
||||
|
||||
class ConfigOptionsHandler(ConfigHandler):
|
||||
|
||||
section_prefix = 'options'
|
||||
|
||||
@property
|
||||
def parsers(self):
|
||||
"""Metadata item name to parser function mapping."""
|
||||
parse_list = self._parse_list
|
||||
parse_list_semicolon = partial(self._parse_list, separator=';')
|
||||
parse_bool = self._parse_bool
|
||||
parse_dict = self._parse_dict
|
||||
|
||||
return {
|
||||
'zip_safe': parse_bool,
|
||||
'use_2to3': parse_bool,
|
||||
'include_package_data': parse_bool,
|
||||
'package_dir': parse_dict,
|
||||
'use_2to3_fixers': parse_list,
|
||||
'use_2to3_exclude_fixers': parse_list,
|
||||
'convert_2to3_doctests': parse_list,
|
||||
'scripts': parse_list,
|
||||
'eager_resources': parse_list,
|
||||
'dependency_links': parse_list,
|
||||
'namespace_packages': parse_list,
|
||||
'install_requires': parse_list_semicolon,
|
||||
'setup_requires': parse_list_semicolon,
|
||||
'tests_require': parse_list_semicolon,
|
||||
'packages': self._parse_packages,
|
||||
'entry_points': self._parse_file,
|
||||
'py_modules': parse_list,
|
||||
}
|
||||
|
||||
def _parse_packages(self, value):
|
||||
"""Parses `packages` option value.
|
||||
|
||||
:param value:
|
||||
:rtype: list
|
||||
"""
|
||||
find_directives = ['find:', 'find_namespace:']
|
||||
trimmed_value = value.strip()
|
||||
|
||||
if trimmed_value not in find_directives:
|
||||
return self._parse_list(value)
|
||||
|
||||
findns = trimmed_value == find_directives[1]
|
||||
if findns and not PY3:
|
||||
raise DistutilsOptionError(
|
||||
'find_namespace: directive is unsupported on Python < 3.3')
|
||||
|
||||
# Read function arguments from a dedicated section.
|
||||
find_kwargs = self.parse_section_packages__find(
|
||||
self.sections.get('packages.find', {}))
|
||||
|
||||
if findns:
|
||||
from setuptools import find_namespace_packages as find_packages
|
||||
else:
|
||||
from setuptools import find_packages
|
||||
|
||||
return find_packages(**find_kwargs)
|
||||
|
||||
def parse_section_packages__find(self, section_options):
|
||||
"""Parses `packages.find` configuration file section.
|
||||
|
||||
To be used in conjunction with _parse_packages().
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
section_data = self._parse_section_to_dict(
|
||||
section_options, self._parse_list)
|
||||
|
||||
valid_keys = ['where', 'include', 'exclude']
|
||||
|
||||
find_kwargs = dict(
|
||||
[(k, v) for k, v in section_data.items() if k in valid_keys and v])
|
||||
|
||||
where = find_kwargs.get('where')
|
||||
if where is not None:
|
||||
find_kwargs['where'] = where[0] # cast list to single val
|
||||
|
||||
return find_kwargs
|
||||
|
||||
def parse_section_entry_points(self, section_options):
|
||||
"""Parses `entry_points` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
parsed = self._parse_section_to_dict(section_options, self._parse_list)
|
||||
self['entry_points'] = parsed
|
||||
|
||||
def _parse_package_data(self, section_options):
|
||||
parsed = self._parse_section_to_dict(section_options, self._parse_list)
|
||||
|
||||
root = parsed.get('*')
|
||||
if root:
|
||||
parsed[''] = root
|
||||
del parsed['*']
|
||||
|
||||
return parsed
|
||||
|
||||
def parse_section_package_data(self, section_options):
|
||||
"""Parses `package_data` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
self['package_data'] = self._parse_package_data(section_options)
|
||||
|
||||
def parse_section_exclude_package_data(self, section_options):
|
||||
"""Parses `exclude_package_data` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
self['exclude_package_data'] = self._parse_package_data(
|
||||
section_options)
|
||||
|
||||
def parse_section_extras_require(self, section_options):
|
||||
"""Parses `extras_require` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
parse_list = partial(self._parse_list, separator=';')
|
||||
self['extras_require'] = self._parse_section_to_dict(
|
||||
section_options, parse_list)
|
||||
|
||||
def parse_section_data_files(self, section_options):
|
||||
"""Parses `data_files` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
parsed = self._parse_section_to_dict(section_options, self._parse_list)
|
||||
self['data_files'] = [(k, v) for k, v in parsed.items()]
|
||||
@@ -0,0 +1,23 @@
|
||||
from distutils.dep_util import newer_group
|
||||
|
||||
# yes, this is was almost entirely copy-pasted from
|
||||
# 'newer_pairwise()', this is just another convenience
|
||||
# function.
|
||||
def newer_pairwise_group(sources_groups, targets):
|
||||
"""Walk both arguments in parallel, testing if each source group is newer
|
||||
than its corresponding target. Returns a pair of lists (sources_groups,
|
||||
targets) where sources is newer than target, according to the semantics
|
||||
of 'newer_group()'.
|
||||
"""
|
||||
if len(sources_groups) != len(targets):
|
||||
raise ValueError("'sources_group' and 'targets' must be the same length")
|
||||
|
||||
# build a pair of lists (sources_groups, targets) where source is newer
|
||||
n_sources = []
|
||||
n_targets = []
|
||||
for i in range(len(sources_groups)):
|
||||
if newer_group(sources_groups[i], targets[i]):
|
||||
n_sources.append(sources_groups[i])
|
||||
n_targets.append(targets[i])
|
||||
|
||||
return n_sources, n_targets
|
||||
@@ -0,0 +1,186 @@
|
||||
import sys
|
||||
import imp
|
||||
import marshal
|
||||
from distutils.version import StrictVersion
|
||||
from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN
|
||||
|
||||
from .py33compat import Bytecode
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Require', 'find_module', 'get_module_constant', 'extract_constant'
|
||||
]
|
||||
|
||||
|
||||
class Require:
|
||||
"""A prerequisite to building or installing a distribution"""
|
||||
|
||||
def __init__(self, name, requested_version, module, homepage='',
|
||||
attribute=None, format=None):
|
||||
|
||||
if format is None and requested_version is not None:
|
||||
format = StrictVersion
|
||||
|
||||
if format is not None:
|
||||
requested_version = format(requested_version)
|
||||
if attribute is None:
|
||||
attribute = '__version__'
|
||||
|
||||
self.__dict__.update(locals())
|
||||
del self.self
|
||||
|
||||
def full_name(self):
|
||||
"""Return full package/distribution name, w/version"""
|
||||
if self.requested_version is not None:
|
||||
return '%s-%s' % (self.name, self.requested_version)
|
||||
return self.name
|
||||
|
||||
def version_ok(self, version):
|
||||
"""Is 'version' sufficiently up-to-date?"""
|
||||
return self.attribute is None or self.format is None or \
|
||||
str(version) != "unknown" and version >= self.requested_version
|
||||
|
||||
def get_version(self, paths=None, default="unknown"):
|
||||
"""Get version number of installed module, 'None', or 'default'
|
||||
|
||||
Search 'paths' for module. If not found, return 'None'. If found,
|
||||
return the extracted version attribute, or 'default' if no version
|
||||
attribute was specified, or the value cannot be determined without
|
||||
importing the module. The version is formatted according to the
|
||||
requirement's version format (if any), unless it is 'None' or the
|
||||
supplied 'default'.
|
||||
"""
|
||||
|
||||
if self.attribute is None:
|
||||
try:
|
||||
f, p, i = find_module(self.module, paths)
|
||||
if f:
|
||||
f.close()
|
||||
return default
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
v = get_module_constant(self.module, self.attribute, default, paths)
|
||||
|
||||
if v is not None and v is not default and self.format is not None:
|
||||
return self.format(v)
|
||||
|
||||
return v
|
||||
|
||||
def is_present(self, paths=None):
|
||||
"""Return true if dependency is present on 'paths'"""
|
||||
return self.get_version(paths) is not None
|
||||
|
||||
def is_current(self, paths=None):
|
||||
"""Return true if dependency is present and up-to-date on 'paths'"""
|
||||
version = self.get_version(paths)
|
||||
if version is None:
|
||||
return False
|
||||
return self.version_ok(version)
|
||||
|
||||
|
||||
def find_module(module, paths=None):
|
||||
"""Just like 'imp.find_module()', but with package support"""
|
||||
|
||||
parts = module.split('.')
|
||||
|
||||
while parts:
|
||||
part = parts.pop(0)
|
||||
f, path, (suffix, mode, kind) = info = imp.find_module(part, paths)
|
||||
|
||||
if kind == PKG_DIRECTORY:
|
||||
parts = parts or ['__init__']
|
||||
paths = [path]
|
||||
|
||||
elif parts:
|
||||
raise ImportError("Can't find %r in %s" % (parts, module))
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def get_module_constant(module, symbol, default=-1, paths=None):
|
||||
"""Find 'module' by searching 'paths', and extract 'symbol'
|
||||
|
||||
Return 'None' if 'module' does not exist on 'paths', or it does not define
|
||||
'symbol'. If the module defines 'symbol' as a constant, return the
|
||||
constant. Otherwise, return 'default'."""
|
||||
|
||||
try:
|
||||
f, path, (suffix, mode, kind) = find_module(module, paths)
|
||||
except ImportError:
|
||||
# Module doesn't exist
|
||||
return None
|
||||
|
||||
try:
|
||||
if kind == PY_COMPILED:
|
||||
f.read(8) # skip magic & date
|
||||
code = marshal.load(f)
|
||||
elif kind == PY_FROZEN:
|
||||
code = imp.get_frozen_object(module)
|
||||
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()
|
||||
|
||||
return extract_constant(code, symbol, default)
|
||||
|
||||
|
||||
def extract_constant(code, symbol, default=-1):
|
||||
"""Extract the constant value of 'symbol' from 'code'
|
||||
|
||||
If the name 'symbol' is bound to a constant value by the Python code
|
||||
object 'code', return that value. If 'symbol' is bound to an expression,
|
||||
return 'default'. Otherwise, return 'None'.
|
||||
|
||||
Return value is based on the first assignment to 'symbol'. 'symbol' must
|
||||
be a global, or at least a non-"fast" local in the code block. That is,
|
||||
only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
|
||||
must be present in 'code.co_names'.
|
||||
"""
|
||||
if symbol not in code.co_names:
|
||||
# name's not there, can't possibly be an assignment
|
||||
return None
|
||||
|
||||
name_idx = list(code.co_names).index(symbol)
|
||||
|
||||
STORE_NAME = 90
|
||||
STORE_GLOBAL = 97
|
||||
LOAD_CONST = 100
|
||||
|
||||
const = default
|
||||
|
||||
for byte_code in Bytecode(code):
|
||||
op = byte_code.opcode
|
||||
arg = byte_code.arg
|
||||
|
||||
if op == LOAD_CONST:
|
||||
const = code.co_consts[arg]
|
||||
elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL):
|
||||
return const
|
||||
else:
|
||||
const = default
|
||||
|
||||
|
||||
def _update_globals():
|
||||
"""
|
||||
Patch the globals to remove the objects not available on some platforms.
|
||||
|
||||
XXX it'd be better to test assertions about bytecode instead.
|
||||
"""
|
||||
|
||||
if not sys.platform.startswith('java') and sys.platform != 'cli':
|
||||
return
|
||||
incompatible = 'extract_constant', 'get_module_constant'
|
||||
for name in incompatible:
|
||||
del globals()[name]
|
||||
__all__.remove(name)
|
||||
|
||||
|
||||
_update_globals()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
import re
|
||||
import functools
|
||||
import distutils.core
|
||||
import distutils.errors
|
||||
import distutils.extension
|
||||
|
||||
from setuptools.extern.six.moves import map
|
||||
|
||||
from .monkey import get_unpatched
|
||||
|
||||
|
||||
def _have_cython():
|
||||
"""
|
||||
Return True if Cython can be imported.
|
||||
"""
|
||||
cython_impl = 'Cython.Distutils.build_ext'
|
||||
try:
|
||||
# from (cython_impl) import build_ext
|
||||
__import__(cython_impl, fromlist=['build_ext']).build_ext
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
# for compatibility
|
||||
have_pyrex = _have_cython
|
||||
|
||||
_Extension = get_unpatched(distutils.core.Extension)
|
||||
|
||||
|
||||
class Extension(_Extension):
|
||||
"""Extension that uses '.c' files in place of '.pyx' files"""
|
||||
|
||||
def __init__(self, name, sources, *args, **kw):
|
||||
# The *args is needed for compatibility as calls may use positional
|
||||
# arguments. py_limited_api may be set only via keyword.
|
||||
self.py_limited_api = kw.pop("py_limited_api", False)
|
||||
_Extension.__init__(self, name, sources, *args, **kw)
|
||||
|
||||
def _convert_pyx_sources_to_lang(self):
|
||||
"""
|
||||
Replace sources with .pyx extensions to sources with the target
|
||||
language extension. This mechanism allows language authors to supply
|
||||
pre-converted sources but to prefer the .pyx sources.
|
||||
"""
|
||||
if _have_cython():
|
||||
# the build has Cython, so allow it to compile the .pyx files
|
||||
return
|
||||
lang = self.language or ''
|
||||
target_ext = '.cpp' if lang.lower() == 'c++' else '.c'
|
||||
sub = functools.partial(re.sub, '.pyx$', target_ext)
|
||||
self.sources = list(map(sub, self.sources))
|
||||
|
||||
|
||||
class Library(Extension):
|
||||
"""Just like a regular Extension, but built as a library instead"""
|
||||
@@ -0,0 +1,73 @@
|
||||
import sys
|
||||
|
||||
|
||||
class VendorImporter:
|
||||
"""
|
||||
A PEP 302 meta path importer for finding optionally-vendored
|
||||
or otherwise naturally-installed packages from root_name.
|
||||
"""
|
||||
|
||||
def __init__(self, root_name, vendored_names=(), vendor_pkg=None):
|
||||
self.root_name = root_name
|
||||
self.vendored_names = set(vendored_names)
|
||||
self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor')
|
||||
|
||||
@property
|
||||
def search_path(self):
|
||||
"""
|
||||
Search first the vendor package then as a natural package.
|
||||
"""
|
||||
yield self.vendor_pkg + '.'
|
||||
yield ''
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
"""
|
||||
Return self when fullname starts with root_name and the
|
||||
target module is one vendored through this importer.
|
||||
"""
|
||||
root, base, target = fullname.partition(self.root_name + '.')
|
||||
if root:
|
||||
return
|
||||
if not any(map(target.startswith, self.vendored_names)):
|
||||
return
|
||||
return self
|
||||
|
||||
def load_module(self, fullname):
|
||||
"""
|
||||
Iterate over the search path to locate and load fullname.
|
||||
"""
|
||||
root, base, target = fullname.partition(self.root_name + '.')
|
||||
for prefix in self.search_path:
|
||||
try:
|
||||
extant = prefix + target
|
||||
__import__(extant)
|
||||
mod = sys.modules[extant]
|
||||
sys.modules[fullname] = mod
|
||||
# mysterious hack:
|
||||
# Remove the reference to the extant package/module
|
||||
# on later Python versions to cause relative imports
|
||||
# in the vendor package to resolve the same modules
|
||||
# as those going through this importer.
|
||||
if sys.version_info >= (3, ):
|
||||
del sys.modules[extant]
|
||||
return mod
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
raise ImportError(
|
||||
"The '{target}' package is required; "
|
||||
"normally this is bundled with this package so if you get "
|
||||
"this warning, consult the packager of your "
|
||||
"distribution.".format(**locals())
|
||||
)
|
||||
|
||||
def install(self):
|
||||
"""
|
||||
Install this importer into sys.meta_path if not already present.
|
||||
"""
|
||||
if self not in sys.meta_path:
|
||||
sys.meta_path.append(self)
|
||||
|
||||
|
||||
names = 'six', 'packaging', 'pyparsing',
|
||||
VendorImporter(__name__, names, 'setuptools._vendor').install()
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
# 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<major>[0-9]+)\.(?P<minor>[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)
|
||||
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Filename globbing utility. Mostly a copy of `glob` from Python 3.5.
|
||||
|
||||
Changes include:
|
||||
* `yield from` and PEP3102 `*` removed.
|
||||
* Hidden files are not ignored.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import fnmatch
|
||||
|
||||
__all__ = ["glob", "iglob", "escape"]
|
||||
|
||||
|
||||
def glob(pathname, recursive=False):
|
||||
"""Return a list of paths matching a pathname pattern.
|
||||
|
||||
The pattern may contain simple shell-style wildcards a la
|
||||
fnmatch. However, unlike fnmatch, filenames starting with a
|
||||
dot are special cases that are not matched by '*' and '?'
|
||||
patterns.
|
||||
|
||||
If recursive is true, the pattern '**' will match any files and
|
||||
zero or more directories and subdirectories.
|
||||
"""
|
||||
return list(iglob(pathname, recursive=recursive))
|
||||
|
||||
|
||||
def iglob(pathname, recursive=False):
|
||||
"""Return an iterator which yields the paths matching a pathname pattern.
|
||||
|
||||
The pattern may contain simple shell-style wildcards a la
|
||||
fnmatch. However, unlike fnmatch, filenames starting with a
|
||||
dot are special cases that are not matched by '*' and '?'
|
||||
patterns.
|
||||
|
||||
If recursive is true, the pattern '**' will match any files and
|
||||
zero or more directories and subdirectories.
|
||||
"""
|
||||
it = _iglob(pathname, recursive)
|
||||
if recursive and _isrecursive(pathname):
|
||||
s = next(it) # skip empty string
|
||||
assert not s
|
||||
return it
|
||||
|
||||
|
||||
def _iglob(pathname, recursive):
|
||||
dirname, basename = os.path.split(pathname)
|
||||
if not has_magic(pathname):
|
||||
if basename:
|
||||
if os.path.lexists(pathname):
|
||||
yield pathname
|
||||
else:
|
||||
# Patterns ending with a slash should match only directories
|
||||
if os.path.isdir(dirname):
|
||||
yield pathname
|
||||
return
|
||||
if not dirname:
|
||||
if recursive and _isrecursive(basename):
|
||||
for x in glob2(dirname, basename):
|
||||
yield x
|
||||
else:
|
||||
for x in glob1(dirname, basename):
|
||||
yield x
|
||||
return
|
||||
# `os.path.split()` returns the argument itself as a dirname if it is a
|
||||
# drive or UNC path. Prevent an infinite recursion if a drive or UNC path
|
||||
# contains magic characters (i.e. r'\\?\C:').
|
||||
if dirname != pathname and has_magic(dirname):
|
||||
dirs = _iglob(dirname, recursive)
|
||||
else:
|
||||
dirs = [dirname]
|
||||
if has_magic(basename):
|
||||
if recursive and _isrecursive(basename):
|
||||
glob_in_dir = glob2
|
||||
else:
|
||||
glob_in_dir = glob1
|
||||
else:
|
||||
glob_in_dir = glob0
|
||||
for dirname in dirs:
|
||||
for name in glob_in_dir(dirname, basename):
|
||||
yield os.path.join(dirname, name)
|
||||
|
||||
|
||||
# These 2 helper functions non-recursively glob inside a literal directory.
|
||||
# They return a list of basenames. `glob1` accepts a pattern while `glob0`
|
||||
# takes a literal basename (so it only has to check for its existence).
|
||||
|
||||
|
||||
def glob1(dirname, pattern):
|
||||
if not dirname:
|
||||
if isinstance(pattern, bytes):
|
||||
dirname = os.curdir.encode('ASCII')
|
||||
else:
|
||||
dirname = os.curdir
|
||||
try:
|
||||
names = os.listdir(dirname)
|
||||
except OSError:
|
||||
return []
|
||||
return fnmatch.filter(names, pattern)
|
||||
|
||||
|
||||
def glob0(dirname, basename):
|
||||
if not basename:
|
||||
# `os.path.split()` returns an empty basename for paths ending with a
|
||||
# directory separator. 'q*x/' should match only directories.
|
||||
if os.path.isdir(dirname):
|
||||
return [basename]
|
||||
else:
|
||||
if os.path.lexists(os.path.join(dirname, basename)):
|
||||
return [basename]
|
||||
return []
|
||||
|
||||
|
||||
# This helper function recursively yields relative pathnames inside a literal
|
||||
# directory.
|
||||
|
||||
|
||||
def glob2(dirname, pattern):
|
||||
assert _isrecursive(pattern)
|
||||
yield pattern[:0]
|
||||
for x in _rlistdir(dirname):
|
||||
yield x
|
||||
|
||||
|
||||
# Recursively yields relative pathnames inside a literal directory.
|
||||
def _rlistdir(dirname):
|
||||
if not dirname:
|
||||
if isinstance(dirname, bytes):
|
||||
dirname = os.curdir.encode('ASCII')
|
||||
else:
|
||||
dirname = os.curdir
|
||||
try:
|
||||
names = os.listdir(dirname)
|
||||
except os.error:
|
||||
return
|
||||
for x in names:
|
||||
yield x
|
||||
path = os.path.join(dirname, x) if dirname else x
|
||||
for y in _rlistdir(path):
|
||||
yield os.path.join(x, y)
|
||||
|
||||
|
||||
magic_check = re.compile('([*?[])')
|
||||
magic_check_bytes = re.compile(b'([*?[])')
|
||||
|
||||
|
||||
def has_magic(s):
|
||||
if isinstance(s, bytes):
|
||||
match = magic_check_bytes.search(s)
|
||||
else:
|
||||
match = magic_check.search(s)
|
||||
return match is not None
|
||||
|
||||
|
||||
def _isrecursive(pattern):
|
||||
if isinstance(pattern, bytes):
|
||||
return pattern == b'**'
|
||||
else:
|
||||
return pattern == '**'
|
||||
|
||||
|
||||
def escape(pathname):
|
||||
"""Escape all special characters.
|
||||
"""
|
||||
# Escaping is done by wrapping any of "*?[" between square brackets.
|
||||
# Metacharacters do not work in the drive part and shouldn't be escaped.
|
||||
drive, pathname = os.path.splitdrive(pathname)
|
||||
if isinstance(pathname, bytes):
|
||||
pathname = magic_check_bytes.sub(br'[\1]', pathname)
|
||||
else:
|
||||
pathname = magic_check.sub(r'[\1]', pathname)
|
||||
return drive + pathname
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
Launch the Python script on the command line after
|
||||
setuptools is bootstrapped via import.
|
||||
"""
|
||||
|
||||
# Note that setuptools gets imported implicitly by the
|
||||
# invocation of this script using python -m setuptools.launch
|
||||
|
||||
import tokenize
|
||||
import sys
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Run the script in sys.argv[1] as if it had
|
||||
been invoked naturally.
|
||||
"""
|
||||
__builtins__
|
||||
script_name = sys.argv[1]
|
||||
namespace = dict(
|
||||
__file__=script_name,
|
||||
__name__='__main__',
|
||||
__doc__=None,
|
||||
)
|
||||
sys.argv[:] = sys.argv[1:]
|
||||
|
||||
open_ = getattr(tokenize, 'open', open)
|
||||
script = open_(script_name).read()
|
||||
norm_script = script.replace('\\r\\n', '\\n')
|
||||
code = compile(norm_script, script_name, 'exec')
|
||||
exec(code, namespace)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Customized Mixin2to3 support:
|
||||
|
||||
- adds support for converting doctests
|
||||
|
||||
|
||||
This module raises an ImportError on Python 2.
|
||||
"""
|
||||
|
||||
from distutils.util import Mixin2to3 as _Mixin2to3
|
||||
from distutils import log
|
||||
from lib2to3.refactor import RefactoringTool, get_fixers_from_package
|
||||
|
||||
import setuptools
|
||||
|
||||
|
||||
class DistutilsRefactoringTool(RefactoringTool):
|
||||
def log_error(self, msg, *args, **kw):
|
||||
log.error(msg, *args)
|
||||
|
||||
def log_message(self, msg, *args):
|
||||
log.info(msg, *args)
|
||||
|
||||
def log_debug(self, msg, *args):
|
||||
log.debug(msg, *args)
|
||||
|
||||
|
||||
class Mixin2to3(_Mixin2to3):
|
||||
def run_2to3(self, files, doctests=False):
|
||||
# See of the distribution option has been set, otherwise check the
|
||||
# setuptools default.
|
||||
if self.distribution.use_2to3 is not True:
|
||||
return
|
||||
if not files:
|
||||
return
|
||||
log.info("Fixing " + " ".join(files))
|
||||
self.__build_fixer_names()
|
||||
self.__exclude_fixers()
|
||||
if doctests:
|
||||
if setuptools.run_2to3_on_doctests:
|
||||
r = DistutilsRefactoringTool(self.fixer_names)
|
||||
r.refactor(files, write=True, doctests_only=True)
|
||||
else:
|
||||
_Mixin2to3.run_2to3(self, files)
|
||||
|
||||
def __build_fixer_names(self):
|
||||
if self.fixer_names:
|
||||
return
|
||||
self.fixer_names = []
|
||||
for p in setuptools.lib2to3_fixer_packages:
|
||||
self.fixer_names.extend(get_fixers_from_package(p))
|
||||
if self.distribution.use_2to3_fixers is not None:
|
||||
for p in self.distribution.use_2to3_fixers:
|
||||
self.fixer_names.extend(get_fixers_from_package(p))
|
||||
|
||||
def __exclude_fixers(self):
|
||||
excluded_fixers = getattr(self, 'exclude_fixers', [])
|
||||
if self.distribution.use_2to3_exclude_fixers is not None:
|
||||
excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers)
|
||||
for fixer_name in excluded_fixers:
|
||||
if fixer_name in self.fixer_names:
|
||||
self.fixer_names.remove(fixer_name)
|
||||
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
Monkey patching of distutils.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import distutils.filelist
|
||||
import platform
|
||||
import types
|
||||
import functools
|
||||
from importlib import import_module
|
||||
import inspect
|
||||
|
||||
from setuptools.extern import six
|
||||
|
||||
import setuptools
|
||||
|
||||
__all__ = []
|
||||
"""
|
||||
Everything is private. Contact the project team
|
||||
if you think you need this functionality.
|
||||
"""
|
||||
|
||||
|
||||
def _get_mro(cls):
|
||||
"""
|
||||
Returns the bases classes for cls sorted by the MRO.
|
||||
|
||||
Works around an issue on Jython where inspect.getmro will not return all
|
||||
base classes if multiple classes share the same name. Instead, this
|
||||
function will return a tuple containing the class itself, and the contents
|
||||
of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024.
|
||||
"""
|
||||
if platform.python_implementation() == "Jython":
|
||||
return (cls,) + cls.__bases__
|
||||
return inspect.getmro(cls)
|
||||
|
||||
|
||||
def get_unpatched(item):
|
||||
lookup = (
|
||||
get_unpatched_class if isinstance(item, six.class_types) else
|
||||
get_unpatched_function if isinstance(item, types.FunctionType) else
|
||||
lambda item: None
|
||||
)
|
||||
return lookup(item)
|
||||
|
||||
|
||||
def get_unpatched_class(cls):
|
||||
"""Protect against re-patching the distutils if reloaded
|
||||
|
||||
Also ensures that no other distutils extension monkeypatched the distutils
|
||||
first.
|
||||
"""
|
||||
external_bases = (
|
||||
cls
|
||||
for cls in _get_mro(cls)
|
||||
if not cls.__module__.startswith('setuptools')
|
||||
)
|
||||
base = next(external_bases)
|
||||
if not base.__module__.startswith('distutils'):
|
||||
msg = "distutils has already been patched by %r" % cls
|
||||
raise AssertionError(msg)
|
||||
return base
|
||||
|
||||
|
||||
def patch_all():
|
||||
# we can't patch distutils.cmd, alas
|
||||
distutils.core.Command = setuptools.Command
|
||||
|
||||
has_issue_12885 = sys.version_info <= (3, 5, 3)
|
||||
|
||||
if has_issue_12885:
|
||||
# fix findall bug in distutils (http://bugs.python.org/issue12885)
|
||||
distutils.filelist.findall = setuptools.findall
|
||||
|
||||
needs_warehouse = (
|
||||
sys.version_info < (2, 7, 13)
|
||||
or
|
||||
(3, 4) < sys.version_info < (3, 4, 6)
|
||||
or
|
||||
(3, 5) < sys.version_info <= (3, 5, 3)
|
||||
)
|
||||
|
||||
if needs_warehouse:
|
||||
warehouse = 'https://upload.pypi.org/legacy/'
|
||||
distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse
|
||||
|
||||
_patch_distribution_metadata()
|
||||
|
||||
# Install Distribution throughout the distutils
|
||||
for module in distutils.dist, distutils.core, distutils.cmd:
|
||||
module.Distribution = setuptools.dist.Distribution
|
||||
|
||||
# Install the patched Extension
|
||||
distutils.core.Extension = setuptools.extension.Extension
|
||||
distutils.extension.Extension = setuptools.extension.Extension
|
||||
if 'distutils.command.build_ext' in sys.modules:
|
||||
sys.modules['distutils.command.build_ext'].Extension = (
|
||||
setuptools.extension.Extension
|
||||
)
|
||||
|
||||
patch_for_msvc_specialized_compiler()
|
||||
|
||||
|
||||
def _patch_distribution_metadata():
|
||||
"""Patch write_pkg_file and read_pkg_file for higher metadata standards"""
|
||||
for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'):
|
||||
new_val = getattr(setuptools.dist, attr)
|
||||
setattr(distutils.dist.DistributionMetadata, attr, new_val)
|
||||
|
||||
|
||||
def patch_func(replacement, target_mod, func_name):
|
||||
"""
|
||||
Patch func_name in target_mod with replacement
|
||||
|
||||
Important - original must be resolved by name to avoid
|
||||
patching an already patched function.
|
||||
"""
|
||||
original = getattr(target_mod, func_name)
|
||||
|
||||
# set the 'unpatched' attribute on the replacement to
|
||||
# point to the original.
|
||||
vars(replacement).setdefault('unpatched', original)
|
||||
|
||||
# replace the function in the original module
|
||||
setattr(target_mod, func_name, replacement)
|
||||
|
||||
|
||||
def get_unpatched_function(candidate):
|
||||
return getattr(candidate, 'unpatched')
|
||||
|
||||
|
||||
def patch_for_msvc_specialized_compiler():
|
||||
"""
|
||||
Patch functions in distutils to use standalone Microsoft Visual C++
|
||||
compilers.
|
||||
"""
|
||||
# import late to avoid circular imports on Python < 3.5
|
||||
msvc = import_module('setuptools.msvc')
|
||||
|
||||
if platform.system() != 'Windows':
|
||||
# Compilers only availables on Microsoft Windows
|
||||
return
|
||||
|
||||
def patch_params(mod_name, func_name):
|
||||
"""
|
||||
Prepare the parameters for patch_func to patch indicated function.
|
||||
"""
|
||||
repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_'
|
||||
repl_name = repl_prefix + func_name.lstrip('_')
|
||||
repl = getattr(msvc, repl_name)
|
||||
mod = import_module(mod_name)
|
||||
if not hasattr(mod, func_name):
|
||||
raise ImportError(func_name)
|
||||
return repl, mod, func_name
|
||||
|
||||
# Python 2.7 to 3.4
|
||||
msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler')
|
||||
|
||||
# Python 3.5+
|
||||
msvc14 = functools.partial(patch_params, 'distutils._msvccompiler')
|
||||
|
||||
try:
|
||||
# Patch distutils.msvc9compiler
|
||||
patch_func(*msvc9('find_vcvarsall'))
|
||||
patch_func(*msvc9('query_vcvarsall'))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Patch distutils._msvccompiler._get_vc_env
|
||||
patch_func(*msvc14('_get_vc_env'))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Patch distutils._msvccompiler.gen_lib_options for Numpy
|
||||
patch_func(*msvc14('gen_lib_options'))
|
||||
except ImportError:
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,107 @@
|
||||
import os
|
||||
from distutils import log
|
||||
import itertools
|
||||
|
||||
from setuptools.extern.six.moves import map
|
||||
|
||||
|
||||
flatten = itertools.chain.from_iterable
|
||||
|
||||
|
||||
class Installer:
|
||||
|
||||
nspkg_ext = '-nspkg.pth'
|
||||
|
||||
def install_namespaces(self):
|
||||
nsp = self._get_all_ns_packages()
|
||||
if not nsp:
|
||||
return
|
||||
filename, ext = os.path.splitext(self._get_target())
|
||||
filename += self.nspkg_ext
|
||||
self.outputs.append(filename)
|
||||
log.info("Installing %s", filename)
|
||||
lines = map(self._gen_nspkg_line, nsp)
|
||||
|
||||
if self.dry_run:
|
||||
# always generate the lines, even in dry run
|
||||
list(lines)
|
||||
return
|
||||
|
||||
with open(filename, 'wt') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def uninstall_namespaces(self):
|
||||
filename, ext = os.path.splitext(self._get_target())
|
||||
filename += self.nspkg_ext
|
||||
if not os.path.exists(filename):
|
||||
return
|
||||
log.info("Removing %s", filename)
|
||||
os.remove(filename)
|
||||
|
||||
def _get_target(self):
|
||||
return self.target
|
||||
|
||||
_nspkg_tmpl = (
|
||||
"import sys, types, os",
|
||||
"has_mfs = sys.version_info > (3, 5)",
|
||||
"p = os.path.join(%(root)s, *%(pth)r)",
|
||||
"importlib = has_mfs and __import__('importlib.util')",
|
||||
"has_mfs and __import__('importlib.machinery')",
|
||||
"m = has_mfs and "
|
||||
"sys.modules.setdefault(%(pkg)r, "
|
||||
"importlib.util.module_from_spec("
|
||||
"importlib.machinery.PathFinder.find_spec(%(pkg)r, "
|
||||
"[os.path.dirname(p)])))",
|
||||
"m = m or "
|
||||
"sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))",
|
||||
"mp = (m or []) and m.__dict__.setdefault('__path__',[])",
|
||||
"(p not in mp) and mp.append(p)",
|
||||
)
|
||||
"lines for the namespace installer"
|
||||
|
||||
_nspkg_tmpl_multi = (
|
||||
'm and setattr(sys.modules[%(parent)r], %(child)r, m)',
|
||||
)
|
||||
"additional line(s) when a parent package is indicated"
|
||||
|
||||
def _get_root(self):
|
||||
return "sys._getframe(1).f_locals['sitedir']"
|
||||
|
||||
def _gen_nspkg_line(self, pkg):
|
||||
# ensure pkg is not a unicode string under Python 2.7
|
||||
pkg = str(pkg)
|
||||
pth = tuple(pkg.split('.'))
|
||||
root = self._get_root()
|
||||
tmpl_lines = self._nspkg_tmpl
|
||||
parent, sep, child = pkg.rpartition('.')
|
||||
if parent:
|
||||
tmpl_lines += self._nspkg_tmpl_multi
|
||||
return ';'.join(tmpl_lines) % locals() + '\n'
|
||||
|
||||
def _get_all_ns_packages(self):
|
||||
"""Return sorted list of all package namespaces"""
|
||||
pkgs = self.distribution.namespace_packages or []
|
||||
return sorted(flatten(map(self._pkg_names, pkgs)))
|
||||
|
||||
@staticmethod
|
||||
def _pkg_names(pkg):
|
||||
"""
|
||||
Given a namespace package, yield the components of that
|
||||
package.
|
||||
|
||||
>>> names = Installer._pkg_names('a.b.c')
|
||||
>>> set(names) == set(['a', 'a.b', 'a.b.c'])
|
||||
True
|
||||
"""
|
||||
parts = pkg.split('.')
|
||||
while parts:
|
||||
yield '.'.join(parts)
|
||||
parts.pop()
|
||||
|
||||
|
||||
class DevelopInstaller(Installer):
|
||||
def _get_root(self):
|
||||
return repr(str(self.egg_path))
|
||||
|
||||
def _get_target(self):
|
||||
return self.egg_link
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,319 @@
|
||||
# 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()
|
||||
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Compatibility Support for Python 2.7 and earlier
|
||||
"""
|
||||
|
||||
import platform
|
||||
|
||||
from setuptools.extern import six
|
||||
|
||||
|
||||
def get_all_headers(message, key):
|
||||
"""
|
||||
Given an HTTPMessage, return all headers matching a given key.
|
||||
"""
|
||||
return message.get_all(key)
|
||||
|
||||
|
||||
if six.PY2:
|
||||
def get_all_headers(message, key):
|
||||
return message.getheaders(key)
|
||||
|
||||
|
||||
linux_py2_ascii = (
|
||||
platform.system() == 'Linux' and
|
||||
six.PY2
|
||||
)
|
||||
|
||||
rmtree_safe = str if linux_py2_ascii else lambda x: x
|
||||
"""Workaround for http://bugs.python.org/issue24672"""
|
||||
@@ -0,0 +1,32 @@
|
||||
__all__ = []
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
try:
|
||||
# Python >=3.2
|
||||
from tempfile import TemporaryDirectory
|
||||
except ImportError:
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
class TemporaryDirectory:
|
||||
"""
|
||||
Very simple temporary directory context manager.
|
||||
Will try to delete afterward, but will also ignore OS and similar
|
||||
errors on deletion.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.name = None # Handle mkdtemp raising an exception
|
||||
self.name = tempfile.mkdtemp()
|
||||
|
||||
def __enter__(self):
|
||||
return self.name
|
||||
|
||||
def __exit__(self, exctype, excvalue, exctrace):
|
||||
try:
|
||||
shutil.rmtree(self.name, True)
|
||||
except OSError: # removal errors are not the only possible
|
||||
pass
|
||||
self.name = None
|
||||
@@ -0,0 +1,55 @@
|
||||
import dis
|
||||
import array
|
||||
import collections
|
||||
|
||||
try:
|
||||
import html
|
||||
except ImportError:
|
||||
html = None
|
||||
|
||||
from setuptools.extern import six
|
||||
from setuptools.extern.six.moves import html_parser
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
OpArg = collections.namedtuple('OpArg', 'opcode arg')
|
||||
|
||||
|
||||
class Bytecode_compat:
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
def __iter__(self):
|
||||
"""Yield '(op,arg)' pair for each operation in code object 'code'"""
|
||||
|
||||
bytes = array.array('b', self.code.co_code)
|
||||
eof = len(self.code.co_code)
|
||||
|
||||
ptr = 0
|
||||
extended_arg = 0
|
||||
|
||||
while ptr < eof:
|
||||
|
||||
op = bytes[ptr]
|
||||
|
||||
if op >= dis.HAVE_ARGUMENT:
|
||||
|
||||
arg = bytes[ptr + 1] + bytes[ptr + 2] * 256 + extended_arg
|
||||
ptr += 3
|
||||
|
||||
if op == dis.EXTENDED_ARG:
|
||||
long_type = six.integer_types[-1]
|
||||
extended_arg = arg * long_type(65536)
|
||||
continue
|
||||
|
||||
else:
|
||||
arg = None
|
||||
ptr += 1
|
||||
|
||||
yield OpArg(op, arg)
|
||||
|
||||
|
||||
Bytecode = getattr(dis, 'Bytecode', Bytecode_compat)
|
||||
|
||||
|
||||
unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape)
|
||||
@@ -0,0 +1,82 @@
|
||||
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
|
||||
@@ -0,0 +1,491 @@
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import operator
|
||||
import functools
|
||||
import itertools
|
||||
import re
|
||||
import contextlib
|
||||
import pickle
|
||||
import textwrap
|
||||
|
||||
from setuptools.extern import six
|
||||
from setuptools.extern.six.moves import builtins, map
|
||||
|
||||
import pkg_resources.py31compat
|
||||
|
||||
if sys.platform.startswith('java'):
|
||||
import org.python.modules.posix.PosixModule as _os
|
||||
else:
|
||||
_os = sys.modules[os.name]
|
||||
try:
|
||||
_file = file
|
||||
except NameError:
|
||||
_file = None
|
||||
_open = open
|
||||
from distutils.errors import DistutilsError
|
||||
from pkg_resources import working_set
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup",
|
||||
]
|
||||
|
||||
|
||||
def _execfile(filename, globals, locals=None):
|
||||
"""
|
||||
Python 3 implementation of execfile.
|
||||
"""
|
||||
mode = 'rb'
|
||||
with open(filename, mode) as stream:
|
||||
script = stream.read()
|
||||
if locals is None:
|
||||
locals = globals
|
||||
code = compile(script, filename, 'exec')
|
||||
exec(code, globals, locals)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def save_argv(repl=None):
|
||||
saved = sys.argv[:]
|
||||
if repl is not None:
|
||||
sys.argv[:] = repl
|
||||
try:
|
||||
yield saved
|
||||
finally:
|
||||
sys.argv[:] = saved
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def save_path():
|
||||
saved = sys.path[:]
|
||||
try:
|
||||
yield saved
|
||||
finally:
|
||||
sys.path[:] = saved
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def override_temp(replacement):
|
||||
"""
|
||||
Monkey-patch tempfile.tempdir with replacement, ensuring it exists
|
||||
"""
|
||||
pkg_resources.py31compat.makedirs(replacement, exist_ok=True)
|
||||
|
||||
saved = tempfile.tempdir
|
||||
|
||||
tempfile.tempdir = replacement
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
tempfile.tempdir = saved
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def pushd(target):
|
||||
saved = os.getcwd()
|
||||
os.chdir(target)
|
||||
try:
|
||||
yield saved
|
||||
finally:
|
||||
os.chdir(saved)
|
||||
|
||||
|
||||
class UnpickleableException(Exception):
|
||||
"""
|
||||
An exception representing another Exception that could not be pickled.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def dump(type, exc):
|
||||
"""
|
||||
Always return a dumped (pickled) type and exc. If exc can't be pickled,
|
||||
wrap it in UnpickleableException first.
|
||||
"""
|
||||
try:
|
||||
return pickle.dumps(type), pickle.dumps(exc)
|
||||
except Exception:
|
||||
# get UnpickleableException inside the sandbox
|
||||
from setuptools.sandbox import UnpickleableException as cls
|
||||
return cls.dump(cls, cls(repr(exc)))
|
||||
|
||||
|
||||
class ExceptionSaver:
|
||||
"""
|
||||
A Context Manager that will save an exception, serialized, and restore it
|
||||
later.
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, exc, tb):
|
||||
if not exc:
|
||||
return
|
||||
|
||||
# dump the exception
|
||||
self._saved = UnpickleableException.dump(type, exc)
|
||||
self._tb = tb
|
||||
|
||||
# suppress the exception
|
||||
return True
|
||||
|
||||
def resume(self):
|
||||
"restore and re-raise any exception"
|
||||
|
||||
if '_saved' not in vars(self):
|
||||
return
|
||||
|
||||
type, exc = map(pickle.loads, self._saved)
|
||||
six.reraise(type, exc, self._tb)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def save_modules():
|
||||
"""
|
||||
Context in which imported modules are saved.
|
||||
|
||||
Translates exceptions internal to the context into the equivalent exception
|
||||
outside the context.
|
||||
"""
|
||||
saved = sys.modules.copy()
|
||||
with ExceptionSaver() as saved_exc:
|
||||
yield saved
|
||||
|
||||
sys.modules.update(saved)
|
||||
# remove any modules imported since
|
||||
del_modules = (
|
||||
mod_name for mod_name in sys.modules
|
||||
if mod_name not in saved
|
||||
# exclude any encodings modules. See #285
|
||||
and not mod_name.startswith('encodings.')
|
||||
)
|
||||
_clear_modules(del_modules)
|
||||
|
||||
saved_exc.resume()
|
||||
|
||||
|
||||
def _clear_modules(module_names):
|
||||
for mod_name in list(module_names):
|
||||
del sys.modules[mod_name]
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def save_pkg_resources_state():
|
||||
saved = pkg_resources.__getstate__()
|
||||
try:
|
||||
yield saved
|
||||
finally:
|
||||
pkg_resources.__setstate__(saved)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def setup_context(setup_dir):
|
||||
temp_dir = os.path.join(setup_dir, 'temp')
|
||||
with save_pkg_resources_state():
|
||||
with save_modules():
|
||||
hide_setuptools()
|
||||
with save_path():
|
||||
with save_argv():
|
||||
with override_temp(temp_dir):
|
||||
with pushd(setup_dir):
|
||||
# ensure setuptools commands are available
|
||||
__import__('setuptools')
|
||||
yield
|
||||
|
||||
|
||||
def _needs_hiding(mod_name):
|
||||
"""
|
||||
>>> _needs_hiding('setuptools')
|
||||
True
|
||||
>>> _needs_hiding('pkg_resources')
|
||||
True
|
||||
>>> _needs_hiding('setuptools_plugin')
|
||||
False
|
||||
>>> _needs_hiding('setuptools.__init__')
|
||||
True
|
||||
>>> _needs_hiding('distutils')
|
||||
True
|
||||
>>> _needs_hiding('os')
|
||||
False
|
||||
>>> _needs_hiding('Cython')
|
||||
True
|
||||
"""
|
||||
pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)')
|
||||
return bool(pattern.match(mod_name))
|
||||
|
||||
|
||||
def hide_setuptools():
|
||||
"""
|
||||
Remove references to setuptools' modules from sys.modules to allow the
|
||||
invocation to import the most appropriate setuptools. This technique is
|
||||
necessary to avoid issues such as #315 where setuptools upgrading itself
|
||||
would fail to find a function declared in the metadata.
|
||||
"""
|
||||
modules = filter(_needs_hiding, sys.modules)
|
||||
_clear_modules(modules)
|
||||
|
||||
|
||||
def run_setup(setup_script, args):
|
||||
"""Run a distutils setup script, sandboxed in its directory"""
|
||||
setup_dir = os.path.abspath(os.path.dirname(setup_script))
|
||||
with setup_context(setup_dir):
|
||||
try:
|
||||
sys.argv[:] = [setup_script] + list(args)
|
||||
sys.path.insert(0, setup_dir)
|
||||
# reset to include setup dir, w/clean callback list
|
||||
working_set.__init__()
|
||||
working_set.callbacks.append(lambda dist: dist.activate())
|
||||
|
||||
# __file__ should be a byte string on Python 2 (#712)
|
||||
dunder_file = (
|
||||
setup_script
|
||||
if isinstance(setup_script, str) else
|
||||
setup_script.encode(sys.getfilesystemencoding())
|
||||
)
|
||||
|
||||
with DirectorySandbox(setup_dir):
|
||||
ns = dict(__file__=dunder_file, __name__='__main__')
|
||||
_execfile(setup_script, ns)
|
||||
except SystemExit as v:
|
||||
if v.args and v.args[0]:
|
||||
raise
|
||||
# Normal exit, just return
|
||||
|
||||
|
||||
class AbstractSandbox:
|
||||
"""Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
|
||||
|
||||
_active = False
|
||||
|
||||
def __init__(self):
|
||||
self._attrs = [
|
||||
name for name in dir(_os)
|
||||
if not name.startswith('_') and hasattr(self, name)
|
||||
]
|
||||
|
||||
def _copy(self, source):
|
||||
for name in self._attrs:
|
||||
setattr(os, name, getattr(source, name))
|
||||
|
||||
def __enter__(self):
|
||||
self._copy(self)
|
||||
if _file:
|
||||
builtins.file = self._file
|
||||
builtins.open = self._open
|
||||
self._active = True
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self._active = False
|
||||
if _file:
|
||||
builtins.file = _file
|
||||
builtins.open = _open
|
||||
self._copy(_os)
|
||||
|
||||
def run(self, func):
|
||||
"""Run 'func' under os sandboxing"""
|
||||
with self:
|
||||
return func()
|
||||
|
||||
def _mk_dual_path_wrapper(name):
|
||||
original = getattr(_os, name)
|
||||
|
||||
def wrap(self, src, dst, *args, **kw):
|
||||
if self._active:
|
||||
src, dst = self._remap_pair(name, src, dst, *args, **kw)
|
||||
return original(src, dst, *args, **kw)
|
||||
|
||||
return wrap
|
||||
|
||||
for name in ["rename", "link", "symlink"]:
|
||||
if hasattr(_os, name):
|
||||
locals()[name] = _mk_dual_path_wrapper(name)
|
||||
|
||||
def _mk_single_path_wrapper(name, original=None):
|
||||
original = original or getattr(_os, name)
|
||||
|
||||
def wrap(self, path, *args, **kw):
|
||||
if self._active:
|
||||
path = self._remap_input(name, path, *args, **kw)
|
||||
return original(path, *args, **kw)
|
||||
|
||||
return wrap
|
||||
|
||||
if _file:
|
||||
_file = _mk_single_path_wrapper('file', _file)
|
||||
_open = _mk_single_path_wrapper('open', _open)
|
||||
for name in [
|
||||
"stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir",
|
||||
"remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat",
|
||||
"startfile", "mkfifo", "mknod", "pathconf", "access"
|
||||
]:
|
||||
if hasattr(_os, name):
|
||||
locals()[name] = _mk_single_path_wrapper(name)
|
||||
|
||||
def _mk_single_with_return(name):
|
||||
original = getattr(_os, name)
|
||||
|
||||
def wrap(self, path, *args, **kw):
|
||||
if self._active:
|
||||
path = self._remap_input(name, path, *args, **kw)
|
||||
return self._remap_output(name, original(path, *args, **kw))
|
||||
return original(path, *args, **kw)
|
||||
|
||||
return wrap
|
||||
|
||||
for name in ['readlink', 'tempnam']:
|
||||
if hasattr(_os, name):
|
||||
locals()[name] = _mk_single_with_return(name)
|
||||
|
||||
def _mk_query(name):
|
||||
original = getattr(_os, name)
|
||||
|
||||
def wrap(self, *args, **kw):
|
||||
retval = original(*args, **kw)
|
||||
if self._active:
|
||||
return self._remap_output(name, retval)
|
||||
return retval
|
||||
|
||||
return wrap
|
||||
|
||||
for name in ['getcwd', 'tmpnam']:
|
||||
if hasattr(_os, name):
|
||||
locals()[name] = _mk_query(name)
|
||||
|
||||
def _validate_path(self, path):
|
||||
"""Called to remap or validate any path, whether input or output"""
|
||||
return path
|
||||
|
||||
def _remap_input(self, operation, path, *args, **kw):
|
||||
"""Called for path inputs"""
|
||||
return self._validate_path(path)
|
||||
|
||||
def _remap_output(self, operation, path):
|
||||
"""Called for path outputs"""
|
||||
return self._validate_path(path)
|
||||
|
||||
def _remap_pair(self, operation, src, dst, *args, **kw):
|
||||
"""Called for path pairs like rename, link, and symlink operations"""
|
||||
return (
|
||||
self._remap_input(operation + '-from', src, *args, **kw),
|
||||
self._remap_input(operation + '-to', dst, *args, **kw)
|
||||
)
|
||||
|
||||
|
||||
if hasattr(os, 'devnull'):
|
||||
_EXCEPTIONS = [os.devnull,]
|
||||
else:
|
||||
_EXCEPTIONS = []
|
||||
|
||||
|
||||
class DirectorySandbox(AbstractSandbox):
|
||||
"""Restrict operations to a single subdirectory - pseudo-chroot"""
|
||||
|
||||
write_ops = dict.fromkeys([
|
||||
"open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir",
|
||||
"utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam",
|
||||
])
|
||||
|
||||
_exception_patterns = [
|
||||
# Allow lib2to3 to attempt to save a pickled grammar object (#121)
|
||||
r'.*lib2to3.*\.pickle$',
|
||||
]
|
||||
"exempt writing to paths that match the pattern"
|
||||
|
||||
def __init__(self, sandbox, exceptions=_EXCEPTIONS):
|
||||
self._sandbox = os.path.normcase(os.path.realpath(sandbox))
|
||||
self._prefix = os.path.join(self._sandbox, '')
|
||||
self._exceptions = [
|
||||
os.path.normcase(os.path.realpath(path))
|
||||
for path in exceptions
|
||||
]
|
||||
AbstractSandbox.__init__(self)
|
||||
|
||||
def _violation(self, operation, *args, **kw):
|
||||
from setuptools.sandbox import SandboxViolation
|
||||
raise SandboxViolation(operation, args, kw)
|
||||
|
||||
if _file:
|
||||
|
||||
def _file(self, path, mode='r', *args, **kw):
|
||||
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
|
||||
self._violation("file", path, mode, *args, **kw)
|
||||
return _file(path, mode, *args, **kw)
|
||||
|
||||
def _open(self, path, mode='r', *args, **kw):
|
||||
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
|
||||
self._violation("open", path, mode, *args, **kw)
|
||||
return _open(path, mode, *args, **kw)
|
||||
|
||||
def tmpnam(self):
|
||||
self._violation("tmpnam")
|
||||
|
||||
def _ok(self, path):
|
||||
active = self._active
|
||||
try:
|
||||
self._active = False
|
||||
realpath = os.path.normcase(os.path.realpath(path))
|
||||
return (
|
||||
self._exempted(realpath)
|
||||
or realpath == self._sandbox
|
||||
or realpath.startswith(self._prefix)
|
||||
)
|
||||
finally:
|
||||
self._active = active
|
||||
|
||||
def _exempted(self, filepath):
|
||||
start_matches = (
|
||||
filepath.startswith(exception)
|
||||
for exception in self._exceptions
|
||||
)
|
||||
pattern_matches = (
|
||||
re.match(pattern, filepath)
|
||||
for pattern in self._exception_patterns
|
||||
)
|
||||
candidates = itertools.chain(start_matches, pattern_matches)
|
||||
return any(candidates)
|
||||
|
||||
def _remap_input(self, operation, path, *args, **kw):
|
||||
"""Called for path inputs"""
|
||||
if operation in self.write_ops and not self._ok(path):
|
||||
self._violation(operation, os.path.realpath(path), *args, **kw)
|
||||
return path
|
||||
|
||||
def _remap_pair(self, operation, src, dst, *args, **kw):
|
||||
"""Called for path pairs like rename, link, and symlink operations"""
|
||||
if not self._ok(src) or not self._ok(dst):
|
||||
self._violation(operation, src, dst, *args, **kw)
|
||||
return (src, dst)
|
||||
|
||||
def open(self, file, flags, mode=0o777, *args, **kw):
|
||||
"""Called for low-level os.open()"""
|
||||
if flags & WRITE_FLAGS and not self._ok(file):
|
||||
self._violation("os.open", file, flags, mode, *args, **kw)
|
||||
return _os.open(file, flags, mode, *args, **kw)
|
||||
|
||||
|
||||
WRITE_FLAGS = functools.reduce(
|
||||
operator.or_, [getattr(_os, a, 0) for a in
|
||||
"O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()]
|
||||
)
|
||||
|
||||
|
||||
class SandboxViolation(DistutilsError):
|
||||
"""A setup script attempted to modify the filesystem outside the sandbox"""
|
||||
|
||||
tmpl = textwrap.dedent("""
|
||||
SandboxViolation: {cmd}{args!r} {kwargs}
|
||||
|
||||
The package setup script has attempted to modify files on your system
|
||||
that are not within the EasyInstall build area, and has been aborted.
|
||||
|
||||
This package cannot be safely installed by EasyInstall, and may not
|
||||
support alternate installation locations even if you run its setup
|
||||
script by hand. Please inform the package's author and the EasyInstall
|
||||
maintainers to find out if a fix or workaround is available.
|
||||
""").lstrip()
|
||||
|
||||
def __str__(self):
|
||||
cmd, args, kwargs = self.args
|
||||
return self.tmpl.format(**locals())
|
||||
@@ -0,0 +1,6 @@
|
||||
# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r
|
||||
__requires__ = %(spec)r
|
||||
__import__('pkg_resources').require(%(spec)r)
|
||||
__file__ = %(dev_path)r
|
||||
with open(__file__) as f:
|
||||
exec(compile(f.read(), __file__, 'exec'))
|
||||
@@ -0,0 +1,3 @@
|
||||
# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r
|
||||
__requires__ = %(spec)r
|
||||
__import__('pkg_resources').run_script(%(spec)r, %(script_name)r)
|
||||
@@ -0,0 +1,74 @@
|
||||
def __boot():
|
||||
import sys
|
||||
import os
|
||||
PYTHONPATH = os.environ.get('PYTHONPATH')
|
||||
if PYTHONPATH is None or (sys.platform == 'win32' and not PYTHONPATH):
|
||||
PYTHONPATH = []
|
||||
else:
|
||||
PYTHONPATH = PYTHONPATH.split(os.pathsep)
|
||||
|
||||
pic = getattr(sys, 'path_importer_cache', {})
|
||||
stdpath = sys.path[len(PYTHONPATH):]
|
||||
mydir = os.path.dirname(__file__)
|
||||
|
||||
for item in stdpath:
|
||||
if item == mydir or not item:
|
||||
continue # skip if current dir. on Windows, or my own directory
|
||||
importer = pic.get(item)
|
||||
if importer is not None:
|
||||
loader = importer.find_module('site')
|
||||
if loader is not None:
|
||||
# This should actually reload the current module
|
||||
loader.load_module('site')
|
||||
break
|
||||
else:
|
||||
try:
|
||||
import imp # Avoid import loop in Python 3
|
||||
stream, path, descr = imp.find_module('site', [item])
|
||||
except ImportError:
|
||||
continue
|
||||
if stream is None:
|
||||
continue
|
||||
try:
|
||||
# This should actually reload the current module
|
||||
imp.load_module('site', stream, path, descr)
|
||||
finally:
|
||||
stream.close()
|
||||
break
|
||||
else:
|
||||
raise ImportError("Couldn't find the real 'site' module")
|
||||
|
||||
known_paths = dict([(makepath(item)[1], 1) for item in sys.path]) # 2.2 comp
|
||||
|
||||
oldpos = getattr(sys, '__egginsert', 0) # save old insertion position
|
||||
sys.__egginsert = 0 # and reset the current one
|
||||
|
||||
for item in PYTHONPATH:
|
||||
addsitedir(item)
|
||||
|
||||
sys.__egginsert += oldpos # restore effective old position
|
||||
|
||||
d, nd = makepath(stdpath[0])
|
||||
insert_at = None
|
||||
new_path = []
|
||||
|
||||
for item in sys.path:
|
||||
p, np = makepath(item)
|
||||
|
||||
if np == nd and insert_at is None:
|
||||
# We've hit the first 'system' path entry, so added entries go here
|
||||
insert_at = len(new_path)
|
||||
|
||||
if np in known_paths or insert_at is None:
|
||||
new_path.append(item)
|
||||
else:
|
||||
# new path after the insert point, back-insert it
|
||||
new_path.insert(insert_at, item)
|
||||
insert_at += 1
|
||||
|
||||
sys.path[:] = new_path
|
||||
|
||||
|
||||
if __name__ == 'site':
|
||||
__boot()
|
||||
del __boot
|
||||
@@ -0,0 +1,260 @@
|
||||
import os
|
||||
import socket
|
||||
import atexit
|
||||
import re
|
||||
import functools
|
||||
|
||||
from setuptools.extern.six.moves import urllib, http_client, map, filter
|
||||
|
||||
from pkg_resources import ResolutionError, ExtractionError
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
ssl = None
|
||||
|
||||
__all__ = [
|
||||
'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths',
|
||||
'opener_for'
|
||||
]
|
||||
|
||||
cert_paths = """
|
||||
/etc/pki/tls/certs/ca-bundle.crt
|
||||
/etc/ssl/certs/ca-certificates.crt
|
||||
/usr/share/ssl/certs/ca-bundle.crt
|
||||
/usr/local/share/certs/ca-root.crt
|
||||
/etc/ssl/cert.pem
|
||||
/System/Library/OpenSSL/certs/cert.pem
|
||||
/usr/local/share/certs/ca-root-nss.crt
|
||||
/etc/ssl/ca-bundle.pem
|
||||
""".strip().split()
|
||||
|
||||
try:
|
||||
HTTPSHandler = urllib.request.HTTPSHandler
|
||||
HTTPSConnection = http_client.HTTPSConnection
|
||||
except AttributeError:
|
||||
HTTPSHandler = HTTPSConnection = object
|
||||
|
||||
is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection)
|
||||
|
||||
|
||||
try:
|
||||
from ssl import CertificateError, match_hostname
|
||||
except ImportError:
|
||||
try:
|
||||
from backports.ssl_match_hostname import CertificateError
|
||||
from backports.ssl_match_hostname import match_hostname
|
||||
except ImportError:
|
||||
CertificateError = None
|
||||
match_hostname = None
|
||||
|
||||
if not CertificateError:
|
||||
|
||||
class CertificateError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
pats = []
|
||||
if not dn:
|
||||
return False
|
||||
|
||||
# Ported from python3-syntax:
|
||||
# leftmost, *remainder = dn.split(r'.')
|
||||
parts = dn.split(r'.')
|
||||
leftmost = parts[0]
|
||||
remainder = parts[1:]
|
||||
|
||||
wildcards = leftmost.count('*')
|
||||
if wildcards > max_wildcards:
|
||||
# Issue #17980: avoid denials of service by refusing more
|
||||
# than one wildcard per fragment. A survey of established
|
||||
# policy among SSL implementations showed it to be a
|
||||
# reasonable choice.
|
||||
raise CertificateError(
|
||||
"too many wildcards in certificate DNS name: " + repr(dn))
|
||||
|
||||
# speed up common case w/o wildcards
|
||||
if not wildcards:
|
||||
return dn.lower() == hostname.lower()
|
||||
|
||||
# RFC 6125, section 6.4.3, subitem 1.
|
||||
# The client SHOULD NOT attempt to match a presented identifier in which
|
||||
# the wildcard character comprises a label other than the left-most label.
|
||||
if leftmost == '*':
|
||||
# When '*' is a fragment by itself, it matches a non-empty dotless
|
||||
# fragment.
|
||||
pats.append('[^.]+')
|
||||
elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
|
||||
# RFC 6125, section 6.4.3, subitem 3.
|
||||
# The client SHOULD NOT attempt to match a presented identifier
|
||||
# where the wildcard character is embedded within an A-label or
|
||||
# U-label of an internationalized domain name.
|
||||
pats.append(re.escape(leftmost))
|
||||
else:
|
||||
# Otherwise, '*' matches any dotless string, e.g. www*
|
||||
pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
|
||||
|
||||
# add the remaining fragments, ignore any wildcards
|
||||
for frag in remainder:
|
||||
pats.append(re.escape(frag))
|
||||
|
||||
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
||||
return pat.match(hostname)
|
||||
|
||||
def match_hostname(cert, hostname):
|
||||
"""Verify that *cert* (in decoded format as returned by
|
||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
|
||||
rules are followed, but IP addresses are not accepted for *hostname*.
|
||||
|
||||
CertificateError is raised on failure. On success, the function
|
||||
returns nothing.
|
||||
"""
|
||||
if not cert:
|
||||
raise ValueError("empty or no certificate")
|
||||
dnsnames = []
|
||||
san = cert.get('subjectAltName', ())
|
||||
for key, value in san:
|
||||
if key == 'DNS':
|
||||
if _dnsname_match(value, hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if not dnsnames:
|
||||
# The subject is only checked when there is no dNSName entry
|
||||
# in subjectAltName
|
||||
for sub in cert.get('subject', ()):
|
||||
for key, value in sub:
|
||||
# XXX according to RFC 2818, the most specific Common Name
|
||||
# must be used.
|
||||
if key == 'commonName':
|
||||
if _dnsname_match(value, hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if len(dnsnames) > 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match either of %s"
|
||||
% (hostname, ', '.join(map(repr, dnsnames))))
|
||||
elif len(dnsnames) == 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match %r"
|
||||
% (hostname, dnsnames[0]))
|
||||
else:
|
||||
raise CertificateError("no appropriate commonName or "
|
||||
"subjectAltName fields were found")
|
||||
|
||||
|
||||
class VerifyingHTTPSHandler(HTTPSHandler):
|
||||
"""Simple verifying handler: no auth, subclasses, timeouts, etc."""
|
||||
|
||||
def __init__(self, ca_bundle):
|
||||
self.ca_bundle = ca_bundle
|
||||
HTTPSHandler.__init__(self)
|
||||
|
||||
def https_open(self, req):
|
||||
return self.do_open(
|
||||
lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req
|
||||
)
|
||||
|
||||
|
||||
class VerifyingHTTPSConn(HTTPSConnection):
|
||||
"""Simple verifying connection: no auth, subclasses, timeouts, etc."""
|
||||
|
||||
def __init__(self, host, ca_bundle, **kw):
|
||||
HTTPSConnection.__init__(self, host, **kw)
|
||||
self.ca_bundle = ca_bundle
|
||||
|
||||
def connect(self):
|
||||
sock = socket.create_connection(
|
||||
(self.host, self.port), getattr(self, 'source_address', None)
|
||||
)
|
||||
|
||||
# Handle the socket if a (proxy) tunnel is present
|
||||
if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None):
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
# http://bugs.python.org/issue7776: Python>=3.4.1 and >=2.7.7
|
||||
# change self.host to mean the proxy server host when tunneling is
|
||||
# being used. Adapt, since we are interested in the destination
|
||||
# host for the match_hostname() comparison.
|
||||
actual_host = self._tunnel_host
|
||||
else:
|
||||
actual_host = self.host
|
||||
|
||||
if hasattr(ssl, 'create_default_context'):
|
||||
ctx = ssl.create_default_context(cafile=self.ca_bundle)
|
||||
self.sock = ctx.wrap_socket(sock, server_hostname=actual_host)
|
||||
else:
|
||||
# This is for python < 2.7.9 and < 3.4?
|
||||
self.sock = ssl.wrap_socket(
|
||||
sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
|
||||
)
|
||||
try:
|
||||
match_hostname(self.sock.getpeercert(), actual_host)
|
||||
except CertificateError:
|
||||
self.sock.shutdown(socket.SHUT_RDWR)
|
||||
self.sock.close()
|
||||
raise
|
||||
|
||||
|
||||
def opener_for(ca_bundle=None):
|
||||
"""Get a urlopen() replacement that uses ca_bundle for verification"""
|
||||
return urllib.request.build_opener(
|
||||
VerifyingHTTPSHandler(ca_bundle or find_ca_bundle())
|
||||
).open
|
||||
|
||||
|
||||
# from jaraco.functools
|
||||
def once(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not hasattr(func, 'always_returns'):
|
||||
func.always_returns = func(*args, **kwargs)
|
||||
return func.always_returns
|
||||
return wrapper
|
||||
|
||||
|
||||
@once
|
||||
def get_win_certfile():
|
||||
try:
|
||||
import wincertstore
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
class CertFile(wincertstore.CertFile):
|
||||
def __init__(self):
|
||||
super(CertFile, self).__init__()
|
||||
atexit.register(self.close)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
super(CertFile, self).close()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
_wincerts = CertFile()
|
||||
_wincerts.addstore('CA')
|
||||
_wincerts.addstore('ROOT')
|
||||
return _wincerts.name
|
||||
|
||||
|
||||
def find_ca_bundle():
|
||||
"""Return an existing CA bundle path, or None"""
|
||||
extant_cert_paths = filter(os.path.isfile, cert_paths)
|
||||
return (
|
||||
get_win_certfile()
|
||||
or next(extant_cert_paths, None)
|
||||
or _certifi_where()
|
||||
)
|
||||
|
||||
|
||||
def _certifi_where():
|
||||
try:
|
||||
return __import__('certifi').where()
|
||||
except (ImportError, ResolutionError, ExtractionError):
|
||||
pass
|
||||
@@ -0,0 +1,44 @@
|
||||
import unicodedata
|
||||
import sys
|
||||
|
||||
from setuptools.extern import six
|
||||
|
||||
|
||||
# HFS Plus uses decomposed UTF-8
|
||||
def decompose(path):
|
||||
if isinstance(path, six.text_type):
|
||||
return unicodedata.normalize('NFD', path)
|
||||
try:
|
||||
path = path.decode('utf-8')
|
||||
path = unicodedata.normalize('NFD', path)
|
||||
path = path.encode('utf-8')
|
||||
except UnicodeError:
|
||||
pass # Not UTF-8
|
||||
return path
|
||||
|
||||
|
||||
def filesys_decode(path):
|
||||
"""
|
||||
Ensure that the given path is decoded,
|
||||
NONE when no expected encoding works
|
||||
"""
|
||||
|
||||
if isinstance(path, six.text_type):
|
||||
return path
|
||||
|
||||
fs_enc = sys.getfilesystemencoding() or 'utf-8'
|
||||
candidates = fs_enc, 'utf-8'
|
||||
|
||||
for enc in candidates:
|
||||
try:
|
||||
return path.decode(enc)
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
|
||||
def try_encode(string, enc):
|
||||
"turn unicode encoding into a functional routine"
|
||||
try:
|
||||
return string.encode(enc)
|
||||
except UnicodeEncodeError:
|
||||
return None
|
||||
@@ -0,0 +1,6 @@
|
||||
import pkg_resources
|
||||
|
||||
try:
|
||||
__version__ = pkg_resources.get_distribution('setuptools').version
|
||||
except Exception:
|
||||
__version__ = 'unknown'
|
||||
@@ -0,0 +1,210 @@
|
||||
"""Wheels support."""
|
||||
|
||||
from distutils.util import get_platform
|
||||
import email
|
||||
import itertools
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
from pkg_resources import Distribution, PathMetadata, parse_version
|
||||
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
|
||||
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
WHEEL_NAME = re.compile(
|
||||
r"""^(?P<project_name>.+?)-(?P<version>\d.*?)
|
||||
((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)
|
||||
)\.whl$""",
|
||||
re.VERBOSE).match
|
||||
|
||||
NAMESPACE_PACKAGE_INIT = '''\
|
||||
try:
|
||||
__import__('pkg_resources').declare_namespace(__name__)
|
||||
except ImportError:
|
||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||
'''
|
||||
|
||||
|
||||
def unpack(src_dir, dst_dir):
|
||||
'''Move everything under `src_dir` to `dst_dir`, and delete the former.'''
|
||||
for dirpath, dirnames, filenames in os.walk(src_dir):
|
||||
subdir = os.path.relpath(dirpath, src_dir)
|
||||
for f in filenames:
|
||||
src = os.path.join(dirpath, f)
|
||||
dst = os.path.join(dst_dir, subdir, f)
|
||||
os.renames(src, dst)
|
||||
for n, d in reversed(list(enumerate(dirnames))):
|
||||
src = os.path.join(dirpath, d)
|
||||
dst = os.path.join(dst_dir, subdir, d)
|
||||
if not os.path.exists(dst):
|
||||
# Directory does not exist in destination,
|
||||
# rename it and prune it from os.walk list.
|
||||
os.renames(src, dst)
|
||||
del dirnames[n]
|
||||
# Cleanup.
|
||||
for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True):
|
||||
assert not filenames
|
||||
os.rmdir(dirpath)
|
||||
|
||||
|
||||
class Wheel:
|
||||
|
||||
def __init__(self, filename):
|
||||
match = WHEEL_NAME(os.path.basename(filename))
|
||||
if match is None:
|
||||
raise ValueError('invalid wheel name: %r' % filename)
|
||||
self.filename = filename
|
||||
for k, v in match.groupdict().items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def tags(self):
|
||||
'''List tags (py_version, abi, platform) supported by this wheel.'''
|
||||
return itertools.product(
|
||||
self.py_version.split('.'),
|
||||
self.abi.split('.'),
|
||||
self.platform.split('.'),
|
||||
)
|
||||
|
||||
def is_compatible(self):
|
||||
'''Is the wheel is compatible with the current platform?'''
|
||||
supported_tags = pep425tags.get_supported()
|
||||
return next((True for t in self.tags() if t in supported_tags), False)
|
||||
|
||||
def egg_name(self):
|
||||
return Distribution(
|
||||
project_name=self.project_name, version=self.version,
|
||||
platform=(None if self.platform == 'any' else get_platform()),
|
||||
).egg_name() + '.egg'
|
||||
|
||||
def get_dist_info(self, zf):
|
||||
# find the correct name of the .dist-info dir in the wheel file
|
||||
for member in zf.namelist():
|
||||
dirname = posixpath.dirname(member)
|
||||
if (dirname.endswith('.dist-info') and
|
||||
canonicalize_name(dirname).startswith(
|
||||
canonicalize_name(self.project_name))):
|
||||
return dirname
|
||||
raise ValueError("unsupported wheel format. .dist-info not found")
|
||||
|
||||
def install_as_egg(self, destination_eggdir):
|
||||
'''Install wheel as an egg directory.'''
|
||||
with zipfile.ZipFile(self.filename) as zf:
|
||||
self._install_as_egg(destination_eggdir, zf)
|
||||
|
||||
def _install_as_egg(self, destination_eggdir, zf):
|
||||
dist_basename = '%s-%s' % (self.project_name, self.version)
|
||||
dist_info = self.get_dist_info(zf)
|
||||
dist_data = '%s.data' % dist_basename
|
||||
egg_info = os.path.join(destination_eggdir, 'EGG-INFO')
|
||||
|
||||
self._convert_metadata(zf, destination_eggdir, dist_info, egg_info)
|
||||
self._move_data_entries(destination_eggdir, dist_data)
|
||||
self._fix_namespace_packages(egg_info, destination_eggdir)
|
||||
|
||||
@staticmethod
|
||||
def _convert_metadata(zf, destination_eggdir, dist_info, egg_info):
|
||||
def get_metadata(name):
|
||||
with zf.open(posixpath.join(dist_info, name)) as fp:
|
||||
value = fp.read().decode('utf-8') if PY3 else fp.read()
|
||||
return email.parser.Parser().parsestr(value)
|
||||
|
||||
wheel_metadata = get_metadata('WHEEL')
|
||||
# Check wheel format version is supported.
|
||||
wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))
|
||||
wheel_v1 = (
|
||||
parse_version('1.0') <= wheel_version < parse_version('2.0dev0')
|
||||
)
|
||||
if not wheel_v1:
|
||||
raise ValueError(
|
||||
'unsupported wheel format version: %s' % wheel_version)
|
||||
# Extract to target directory.
|
||||
os.mkdir(destination_eggdir)
|
||||
zf.extractall(destination_eggdir)
|
||||
# Convert metadata.
|
||||
dist_info = os.path.join(destination_eggdir, dist_info)
|
||||
dist = Distribution.from_location(
|
||||
destination_eggdir, dist_info,
|
||||
metadata=PathMetadata(destination_eggdir, dist_info),
|
||||
)
|
||||
|
||||
# Note: Evaluate and strip markers now,
|
||||
# as it's difficult to convert back from the syntax:
|
||||
# foobar; "linux" in sys_platform and extra == 'test'
|
||||
def raw_req(req):
|
||||
req.marker = None
|
||||
return str(req)
|
||||
install_requires = list(sorted(map(raw_req, dist.requires())))
|
||||
extras_require = {
|
||||
extra: sorted(
|
||||
req
|
||||
for req in map(raw_req, dist.requires((extra,)))
|
||||
if req not in install_requires
|
||||
)
|
||||
for extra in dist.extras
|
||||
}
|
||||
os.rename(dist_info, egg_info)
|
||||
os.rename(
|
||||
os.path.join(egg_info, 'METADATA'),
|
||||
os.path.join(egg_info, 'PKG-INFO'),
|
||||
)
|
||||
setup_dist = SetuptoolsDistribution(
|
||||
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'),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _move_data_entries(destination_eggdir, dist_data):
|
||||
"""Move data entries to their correct location."""
|
||||
dist_data = os.path.join(destination_eggdir, dist_data)
|
||||
dist_data_scripts = os.path.join(dist_data, 'scripts')
|
||||
if os.path.exists(dist_data_scripts):
|
||||
egg_info_scripts = os.path.join(
|
||||
destination_eggdir, 'EGG-INFO', 'scripts')
|
||||
os.mkdir(egg_info_scripts)
|
||||
for entry in os.listdir(dist_data_scripts):
|
||||
# Remove bytecode, as it's not properly handled
|
||||
# during easy_install scripts install phase.
|
||||
if entry.endswith('.pyc'):
|
||||
os.unlink(os.path.join(dist_data_scripts, entry))
|
||||
else:
|
||||
os.rename(
|
||||
os.path.join(dist_data_scripts, entry),
|
||||
os.path.join(egg_info_scripts, entry),
|
||||
)
|
||||
os.rmdir(dist_data_scripts)
|
||||
for subdir in filter(os.path.exists, (
|
||||
os.path.join(dist_data, d)
|
||||
for d in ('data', 'headers', 'purelib', 'platlib')
|
||||
)):
|
||||
unpack(subdir, destination_eggdir)
|
||||
if os.path.exists(dist_data):
|
||||
os.rmdir(dist_data)
|
||||
|
||||
@staticmethod
|
||||
def _fix_namespace_packages(egg_info, destination_eggdir):
|
||||
namespace_packages = os.path.join(
|
||||
egg_info, 'namespace_packages.txt')
|
||||
if os.path.exists(namespace_packages):
|
||||
with open(namespace_packages) as fp:
|
||||
namespace_packages = fp.read().split()
|
||||
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):
|
||||
with open(mod_init, 'w') as fp:
|
||||
fp.write(NAMESPACE_PACKAGE_INIT)
|
||||
@@ -0,0 +1,29 @@
|
||||
import platform
|
||||
import ctypes
|
||||
|
||||
|
||||
def windows_only(func):
|
||||
if platform.system() != 'Windows':
|
||||
return lambda *args, **kwargs: None
|
||||
return func
|
||||
|
||||
|
||||
@windows_only
|
||||
def hide_file(path):
|
||||
"""
|
||||
Set the hidden attribute on a file or directory.
|
||||
|
||||
From http://stackoverflow.com/questions/19622133/
|
||||
|
||||
`path` must be text.
|
||||
"""
|
||||
__import__('ctypes.wintypes')
|
||||
SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW
|
||||
SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD
|
||||
SetFileAttributes.restype = ctypes.wintypes.BOOL
|
||||
|
||||
FILE_ATTRIBUTE_HIDDEN = 0x02
|
||||
|
||||
ret = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN)
|
||||
if not ret:
|
||||
raise ctypes.WinError()
|
||||
Reference in New Issue
Block a user