Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +1 -0
- Alina-SDXL-step00000505.safetensors +3 -0
- Alina-SDXL-step00001010.safetensors +3 -0
- Alina-SDXL-step00001515.safetensors +3 -0
- Alina-SDXL-step00002020.safetensors +3 -0
- Alina-SDXL-step00002525.safetensors +3 -0
- Alina-SDXL-step00004545.safetensors +3 -0
- Alina-SDXL-step00006060.safetensors +3 -0
- Alina-SDXL-step00006565.safetensors +3 -0
- Alina-SDXL-step00008585.safetensors +3 -0
- Alina-SDXL-step00009090.safetensors +3 -0
- Alina-SDXL-step00009595.safetensors +3 -0
- Alina-SDXL.safetensors +3 -0
- sd_xl_base_1.0.safetensors +3 -0
- venv/lib/python3.10/site-packages/distutils-precedence.pth +3 -0
- venv/lib/python3.10/site-packages/setuptools/__init__.py +242 -0
- venv/lib/python3.10/site-packages/setuptools/__pycache__/archive_util.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/setuptools/__pycache__/glob.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/setuptools/_deprecation_warning.py +7 -0
- venv/lib/python3.10/site-packages/setuptools/_imp.py +82 -0
- venv/lib/python3.10/site-packages/setuptools/archive_util.py +205 -0
- venv/lib/python3.10/site-packages/setuptools/build_meta.py +290 -0
- venv/lib/python3.10/site-packages/setuptools/cli-32.exe +0 -0
- venv/lib/python3.10/site-packages/setuptools/cli-64.exe +0 -0
- venv/lib/python3.10/site-packages/setuptools/cli-arm64.exe +3 -0
- venv/lib/python3.10/site-packages/setuptools/cli.exe +0 -0
- venv/lib/python3.10/site-packages/setuptools/config.py +751 -0
- venv/lib/python3.10/site-packages/setuptools/dep_util.py +25 -0
- venv/lib/python3.10/site-packages/setuptools/depends.py +176 -0
- venv/lib/python3.10/site-packages/setuptools/dist.py +1156 -0
- venv/lib/python3.10/site-packages/setuptools/errors.py +40 -0
- venv/lib/python3.10/site-packages/setuptools/extension.py +55 -0
- venv/lib/python3.10/site-packages/setuptools/glob.py +167 -0
- venv/lib/python3.10/site-packages/setuptools/gui-32.exe +0 -0
- venv/lib/python3.10/site-packages/setuptools/gui-64.exe +0 -0
- venv/lib/python3.10/site-packages/setuptools/gui.exe +0 -0
- venv/lib/python3.10/site-packages/setuptools/installer.py +104 -0
- venv/lib/python3.10/site-packages/setuptools/launch.py +36 -0
- venv/lib/python3.10/site-packages/setuptools/monkey.py +177 -0
- venv/lib/python3.10/site-packages/setuptools/msvc.py +1805 -0
- venv/lib/python3.10/site-packages/setuptools/namespaces.py +107 -0
- venv/lib/python3.10/site-packages/setuptools/package_index.py +1150 -0
- venv/lib/python3.10/site-packages/setuptools/py34compat.py +13 -0
- venv/lib/python3.10/site-packages/setuptools/sandbox.py +530 -0
- venv/lib/python3.10/site-packages/setuptools/script (dev).tmpl +6 -0
- venv/lib/python3.10/site-packages/setuptools/script.tmpl +3 -0
- venv/lib/python3.10/site-packages/setuptools/unicode_utils.py +42 -0
- venv/lib/python3.10/site-packages/setuptools/version.py +6 -0
- venv/lib/python3.10/site-packages/setuptools/wheel.py +213 -0
- venv/lib/python3.10/site-packages/setuptools/windows_support.py +29 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
venv/lib/python3.10/site-packages/setuptools/cli-arm64.exe filter=lfs diff=lfs merge=lfs -text
|
Alina-SDXL-step00000505.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f3a6ead6634c714e19d61db7ac271c94e959322a0fa7bf186b1098e64b0cd380
|
| 3 |
+
size 6938043451
|
Alina-SDXL-step00001010.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ef938c19a1fbf2ac950d71ab6fc3042f6266923b169d82915a884ca6a99ec8d7
|
| 3 |
+
size 6938043451
|
Alina-SDXL-step00001515.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7f140c3bc9f2b754e46334e0a65f815e395538d633292ed766c5f237af48466b
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00002020.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8e858b6e774859d4dedb5c3f1b8c499947aa089b21f26c69c84a71af77e2a352
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00002525.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1543d51164601fd2ed78fbb32a474a4c63f765ecc4b9d8392493b1415964bf36
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00004545.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a0fd34f03bc95c3ec648df88a96cf450de4ae44f8e2f5f6624e7b84d64ded924
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00006060.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:63354552fbbb2a6df5924636720b20d22f4c4bfc04ac8d41f139c304ac1958db
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00006565.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:071b4a6976ebdddf87ffefe6ceaf2a4a47ec0d221402dd81bf1863445125683d
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00008585.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9a712b42111da7af4d43f099146de963e62bf79ffad859aadacfc1929d907933
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00009090.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:82aecbd449b01cd887337495c225ababa3377e1d0619c8537fa227d498bc77fa
|
| 3 |
+
size 6938043304
|
Alina-SDXL-step00009595.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3b60084283da69642d91dc176c3a282d90baf95b6b2f854790d59514c3f6d58e
|
| 3 |
+
size 6938043304
|
Alina-SDXL.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:99c6e6641eef8a135aaeb8335c2d7982e16c9ad72390904e391ace393dcdba85
|
| 3 |
+
size 6938043304
|
sd_xl_base_1.0.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:31e35c80fc4829d14f90153f4c74cd59c90b779f6afe05a74cd6120b893f7e5b
|
| 3 |
+
size 6938078334
|
venv/lib/python3.10/site-packages/distutils-precedence.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7ea7ffef3fe2a117ee12c68ed6553617f0d7fd2f0590257c25c484959a3b7373
|
| 3 |
+
size 152
|
venv/lib/python3.10/site-packages/setuptools/__init__.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Extensions to the 'distutils' for large or complex distributions"""
|
| 2 |
+
|
| 3 |
+
from fnmatch import fnmatchcase
|
| 4 |
+
import functools
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
|
| 8 |
+
import _distutils_hack.override # noqa: F401
|
| 9 |
+
|
| 10 |
+
import distutils.core
|
| 11 |
+
from distutils.errors import DistutilsOptionError
|
| 12 |
+
from distutils.util import convert_path
|
| 13 |
+
|
| 14 |
+
from ._deprecation_warning import SetuptoolsDeprecationWarning
|
| 15 |
+
|
| 16 |
+
import setuptools.version
|
| 17 |
+
from setuptools.extension import Extension
|
| 18 |
+
from setuptools.dist import Distribution
|
| 19 |
+
from setuptools.depends import Require
|
| 20 |
+
from . import monkey
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
__all__ = [
|
| 24 |
+
'setup',
|
| 25 |
+
'Distribution',
|
| 26 |
+
'Command',
|
| 27 |
+
'Extension',
|
| 28 |
+
'Require',
|
| 29 |
+
'SetuptoolsDeprecationWarning',
|
| 30 |
+
'find_packages',
|
| 31 |
+
'find_namespace_packages',
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
__version__ = setuptools.version.__version__
|
| 35 |
+
|
| 36 |
+
bootstrap_install_from = None
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class PackageFinder:
|
| 40 |
+
"""
|
| 41 |
+
Generate a list of all Python packages found within a directory
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
@classmethod
|
| 45 |
+
def find(cls, where='.', exclude=(), include=('*',)):
|
| 46 |
+
"""Return a list all Python packages found within directory 'where'
|
| 47 |
+
|
| 48 |
+
'where' is the root directory which will be searched for packages. It
|
| 49 |
+
should be supplied as a "cross-platform" (i.e. URL-style) path; it will
|
| 50 |
+
be converted to the appropriate local path syntax.
|
| 51 |
+
|
| 52 |
+
'exclude' is a sequence of package names to exclude; '*' can be used
|
| 53 |
+
as a wildcard in the names, such that 'foo.*' will exclude all
|
| 54 |
+
subpackages of 'foo' (but not 'foo' itself).
|
| 55 |
+
|
| 56 |
+
'include' is a sequence of package names to include. If it's
|
| 57 |
+
specified, only the named packages will be included. If it's not
|
| 58 |
+
specified, all found packages will be included. 'include' can contain
|
| 59 |
+
shell style wildcard patterns just like 'exclude'.
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
return list(
|
| 63 |
+
cls._find_packages_iter(
|
| 64 |
+
convert_path(where),
|
| 65 |
+
cls._build_filter('ez_setup', '*__pycache__', *exclude),
|
| 66 |
+
cls._build_filter(*include),
|
| 67 |
+
)
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
@classmethod
|
| 71 |
+
def _find_packages_iter(cls, where, exclude, include):
|
| 72 |
+
"""
|
| 73 |
+
All the packages found in 'where' that pass the 'include' filter, but
|
| 74 |
+
not the 'exclude' filter.
|
| 75 |
+
"""
|
| 76 |
+
for root, dirs, files in os.walk(where, followlinks=True):
|
| 77 |
+
# Copy dirs to iterate over it, then empty dirs.
|
| 78 |
+
all_dirs = dirs[:]
|
| 79 |
+
dirs[:] = []
|
| 80 |
+
|
| 81 |
+
for dir in all_dirs:
|
| 82 |
+
full_path = os.path.join(root, dir)
|
| 83 |
+
rel_path = os.path.relpath(full_path, where)
|
| 84 |
+
package = rel_path.replace(os.path.sep, '.')
|
| 85 |
+
|
| 86 |
+
# Skip directory trees that are not valid packages
|
| 87 |
+
if '.' in dir or not cls._looks_like_package(full_path):
|
| 88 |
+
continue
|
| 89 |
+
|
| 90 |
+
# Should this package be included?
|
| 91 |
+
if include(package) and not exclude(package):
|
| 92 |
+
yield package
|
| 93 |
+
|
| 94 |
+
# Keep searching subdirectories, as there may be more packages
|
| 95 |
+
# down there, even if the parent was excluded.
|
| 96 |
+
dirs.append(dir)
|
| 97 |
+
|
| 98 |
+
@staticmethod
|
| 99 |
+
def _looks_like_package(path):
|
| 100 |
+
"""Does a directory look like a package?"""
|
| 101 |
+
return os.path.isfile(os.path.join(path, '__init__.py'))
|
| 102 |
+
|
| 103 |
+
@staticmethod
|
| 104 |
+
def _build_filter(*patterns):
|
| 105 |
+
"""
|
| 106 |
+
Given a list of patterns, return a callable that will be true only if
|
| 107 |
+
the input matches at least one of the patterns.
|
| 108 |
+
"""
|
| 109 |
+
return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class PEP420PackageFinder(PackageFinder):
|
| 113 |
+
@staticmethod
|
| 114 |
+
def _looks_like_package(path):
|
| 115 |
+
return True
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
find_packages = PackageFinder.find
|
| 119 |
+
find_namespace_packages = PEP420PackageFinder.find
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _install_setup_requires(attrs):
|
| 123 |
+
# Note: do not use `setuptools.Distribution` directly, as
|
| 124 |
+
# our PEP 517 backend patch `distutils.core.Distribution`.
|
| 125 |
+
class MinimalDistribution(distutils.core.Distribution):
|
| 126 |
+
"""
|
| 127 |
+
A minimal version of a distribution for supporting the
|
| 128 |
+
fetch_build_eggs interface.
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
def __init__(self, attrs):
|
| 132 |
+
_incl = 'dependency_links', 'setup_requires'
|
| 133 |
+
filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
|
| 134 |
+
distutils.core.Distribution.__init__(self, filtered)
|
| 135 |
+
|
| 136 |
+
def finalize_options(self):
|
| 137 |
+
"""
|
| 138 |
+
Disable finalize_options to avoid building the working set.
|
| 139 |
+
Ref #2158.
|
| 140 |
+
"""
|
| 141 |
+
|
| 142 |
+
dist = MinimalDistribution(attrs)
|
| 143 |
+
|
| 144 |
+
# Honor setup.cfg's options.
|
| 145 |
+
dist.parse_config_files(ignore_option_errors=True)
|
| 146 |
+
if dist.setup_requires:
|
| 147 |
+
dist.fetch_build_eggs(dist.setup_requires)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def setup(**attrs):
|
| 151 |
+
# Make sure we have any requirements needed to interpret 'attrs'.
|
| 152 |
+
_install_setup_requires(attrs)
|
| 153 |
+
return distutils.core.setup(**attrs)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
setup.__doc__ = distutils.core.setup.__doc__
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
_Command = monkey.get_unpatched(distutils.core.Command)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class Command(_Command):
|
| 163 |
+
__doc__ = _Command.__doc__
|
| 164 |
+
|
| 165 |
+
command_consumes_arguments = False
|
| 166 |
+
|
| 167 |
+
def __init__(self, dist, **kw):
|
| 168 |
+
"""
|
| 169 |
+
Construct the command for dist, updating
|
| 170 |
+
vars(self) with any keyword parameters.
|
| 171 |
+
"""
|
| 172 |
+
_Command.__init__(self, dist)
|
| 173 |
+
vars(self).update(kw)
|
| 174 |
+
|
| 175 |
+
def _ensure_stringlike(self, option, what, default=None):
|
| 176 |
+
val = getattr(self, option)
|
| 177 |
+
if val is None:
|
| 178 |
+
setattr(self, option, default)
|
| 179 |
+
return default
|
| 180 |
+
elif not isinstance(val, str):
|
| 181 |
+
raise DistutilsOptionError(
|
| 182 |
+
"'%s' must be a %s (got `%s`)" % (option, what, val)
|
| 183 |
+
)
|
| 184 |
+
return val
|
| 185 |
+
|
| 186 |
+
def ensure_string_list(self, option):
|
| 187 |
+
r"""Ensure that 'option' is a list of strings. If 'option' is
|
| 188 |
+
currently a string, we split it either on /,\s*/ or /\s+/, so
|
| 189 |
+
"foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
|
| 190 |
+
["foo", "bar", "baz"].
|
| 191 |
+
"""
|
| 192 |
+
val = getattr(self, option)
|
| 193 |
+
if val is None:
|
| 194 |
+
return
|
| 195 |
+
elif isinstance(val, str):
|
| 196 |
+
setattr(self, option, re.split(r',\s*|\s+', val))
|
| 197 |
+
else:
|
| 198 |
+
if isinstance(val, list):
|
| 199 |
+
ok = all(isinstance(v, str) for v in val)
|
| 200 |
+
else:
|
| 201 |
+
ok = False
|
| 202 |
+
if not ok:
|
| 203 |
+
raise DistutilsOptionError(
|
| 204 |
+
"'%s' must be a list of strings (got %r)" % (option, val)
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
def reinitialize_command(self, command, reinit_subcommands=0, **kw):
|
| 208 |
+
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
|
| 209 |
+
vars(cmd).update(kw)
|
| 210 |
+
return cmd
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def _find_all_simple(path):
|
| 214 |
+
"""
|
| 215 |
+
Find all files under 'path'
|
| 216 |
+
"""
|
| 217 |
+
results = (
|
| 218 |
+
os.path.join(base, file)
|
| 219 |
+
for base, dirs, files in os.walk(path, followlinks=True)
|
| 220 |
+
for file in files
|
| 221 |
+
)
|
| 222 |
+
return filter(os.path.isfile, results)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def findall(dir=os.curdir):
|
| 226 |
+
"""
|
| 227 |
+
Find all files under 'dir' and return the list of full filenames.
|
| 228 |
+
Unless dir is '.', return full filenames with dir prepended.
|
| 229 |
+
"""
|
| 230 |
+
files = _find_all_simple(dir)
|
| 231 |
+
if dir == os.curdir:
|
| 232 |
+
make_rel = functools.partial(os.path.relpath, start=dir)
|
| 233 |
+
files = map(make_rel, files)
|
| 234 |
+
return list(files)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
class sic(str):
|
| 238 |
+
"""Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
# Apply monkey patches
|
| 242 |
+
monkey.patch_all()
|
venv/lib/python3.10/site-packages/setuptools/__pycache__/archive_util.cpython-310.pyc
ADDED
|
Binary file (5.85 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/__pycache__/glob.cpython-310.pyc
ADDED
|
Binary file (3.74 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/_deprecation_warning.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class SetuptoolsDeprecationWarning(Warning):
|
| 2 |
+
"""
|
| 3 |
+
Base class for warning deprecations in ``setuptools``
|
| 4 |
+
|
| 5 |
+
This class is not derived from ``DeprecationWarning``, and as such is
|
| 6 |
+
visible by default.
|
| 7 |
+
"""
|
venv/lib/python3.10/site-packages/setuptools/_imp.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Re-implementation of find_module and get_frozen_object
|
| 3 |
+
from the deprecated imp module.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import importlib.util
|
| 8 |
+
import importlib.machinery
|
| 9 |
+
|
| 10 |
+
from .py34compat import module_from_spec
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
PY_SOURCE = 1
|
| 14 |
+
PY_COMPILED = 2
|
| 15 |
+
C_EXTENSION = 3
|
| 16 |
+
C_BUILTIN = 6
|
| 17 |
+
PY_FROZEN = 7
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def find_spec(module, paths):
|
| 21 |
+
finder = (
|
| 22 |
+
importlib.machinery.PathFinder().find_spec
|
| 23 |
+
if isinstance(paths, list) else
|
| 24 |
+
importlib.util.find_spec
|
| 25 |
+
)
|
| 26 |
+
return finder(module, paths)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def find_module(module, paths=None):
|
| 30 |
+
"""Just like 'imp.find_module()', but with package support"""
|
| 31 |
+
spec = find_spec(module, paths)
|
| 32 |
+
if spec is None:
|
| 33 |
+
raise ImportError("Can't find %s" % module)
|
| 34 |
+
if not spec.has_location and hasattr(spec, 'submodule_search_locations'):
|
| 35 |
+
spec = importlib.util.spec_from_loader('__init__.py', spec.loader)
|
| 36 |
+
|
| 37 |
+
kind = -1
|
| 38 |
+
file = None
|
| 39 |
+
static = isinstance(spec.loader, type)
|
| 40 |
+
if spec.origin == 'frozen' or static and issubclass(
|
| 41 |
+
spec.loader, importlib.machinery.FrozenImporter):
|
| 42 |
+
kind = PY_FROZEN
|
| 43 |
+
path = None # imp compabilty
|
| 44 |
+
suffix = mode = '' # imp compatibility
|
| 45 |
+
elif spec.origin == 'built-in' or static and issubclass(
|
| 46 |
+
spec.loader, importlib.machinery.BuiltinImporter):
|
| 47 |
+
kind = C_BUILTIN
|
| 48 |
+
path = None # imp compabilty
|
| 49 |
+
suffix = mode = '' # imp compatibility
|
| 50 |
+
elif spec.has_location:
|
| 51 |
+
path = spec.origin
|
| 52 |
+
suffix = os.path.splitext(path)[1]
|
| 53 |
+
mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb'
|
| 54 |
+
|
| 55 |
+
if suffix in importlib.machinery.SOURCE_SUFFIXES:
|
| 56 |
+
kind = PY_SOURCE
|
| 57 |
+
elif suffix in importlib.machinery.BYTECODE_SUFFIXES:
|
| 58 |
+
kind = PY_COMPILED
|
| 59 |
+
elif suffix in importlib.machinery.EXTENSION_SUFFIXES:
|
| 60 |
+
kind = C_EXTENSION
|
| 61 |
+
|
| 62 |
+
if kind in {PY_SOURCE, PY_COMPILED}:
|
| 63 |
+
file = open(path, mode)
|
| 64 |
+
else:
|
| 65 |
+
path = None
|
| 66 |
+
suffix = mode = ''
|
| 67 |
+
|
| 68 |
+
return file, path, (suffix, mode, kind)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def get_frozen_object(module, paths=None):
|
| 72 |
+
spec = find_spec(module, paths)
|
| 73 |
+
if not spec:
|
| 74 |
+
raise ImportError("Can't find %s" % module)
|
| 75 |
+
return spec.loader.get_code(module)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def get_module(module, paths, info):
|
| 79 |
+
spec = find_spec(module, paths)
|
| 80 |
+
if not spec:
|
| 81 |
+
raise ImportError("Can't find %s" % module)
|
| 82 |
+
return module_from_spec(spec)
|
venv/lib/python3.10/site-packages/setuptools/archive_util.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utilities for extracting common archive formats"""
|
| 2 |
+
|
| 3 |
+
import zipfile
|
| 4 |
+
import tarfile
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import posixpath
|
| 8 |
+
import contextlib
|
| 9 |
+
from distutils.errors import DistutilsError
|
| 10 |
+
|
| 11 |
+
from pkg_resources import ensure_directory
|
| 12 |
+
|
| 13 |
+
__all__ = [
|
| 14 |
+
"unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter",
|
| 15 |
+
"UnrecognizedFormat", "extraction_drivers", "unpack_directory",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class UnrecognizedFormat(DistutilsError):
|
| 20 |
+
"""Couldn't recognize the archive type"""
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def default_filter(src, dst):
|
| 24 |
+
"""The default progress/filter callback; returns True for all files"""
|
| 25 |
+
return dst
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def unpack_archive(
|
| 29 |
+
filename, extract_dir, progress_filter=default_filter,
|
| 30 |
+
drivers=None):
|
| 31 |
+
"""Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat``
|
| 32 |
+
|
| 33 |
+
`progress_filter` is a function taking two arguments: a source path
|
| 34 |
+
internal to the archive ('/'-separated), and a filesystem path where it
|
| 35 |
+
will be extracted. The callback must return the desired extract path
|
| 36 |
+
(which may be the same as the one passed in), or else ``None`` to skip
|
| 37 |
+
that file or directory. The callback can thus be used to report on the
|
| 38 |
+
progress of the extraction, as well as to filter the items extracted or
|
| 39 |
+
alter their extraction paths.
|
| 40 |
+
|
| 41 |
+
`drivers`, if supplied, must be a non-empty sequence of functions with the
|
| 42 |
+
same signature as this function (minus the `drivers` argument), that raise
|
| 43 |
+
``UnrecognizedFormat`` if they do not support extracting the designated
|
| 44 |
+
archive type. The `drivers` are tried in sequence until one is found that
|
| 45 |
+
does not raise an error, or until all are exhausted (in which case
|
| 46 |
+
``UnrecognizedFormat`` is raised). If you do not supply a sequence of
|
| 47 |
+
drivers, the module's ``extraction_drivers`` constant will be used, which
|
| 48 |
+
means that ``unpack_zipfile`` and ``unpack_tarfile`` will be tried, in that
|
| 49 |
+
order.
|
| 50 |
+
"""
|
| 51 |
+
for driver in drivers or extraction_drivers:
|
| 52 |
+
try:
|
| 53 |
+
driver(filename, extract_dir, progress_filter)
|
| 54 |
+
except UnrecognizedFormat:
|
| 55 |
+
continue
|
| 56 |
+
else:
|
| 57 |
+
return
|
| 58 |
+
else:
|
| 59 |
+
raise UnrecognizedFormat(
|
| 60 |
+
"Not a recognized archive type: %s" % filename
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def unpack_directory(filename, extract_dir, progress_filter=default_filter):
|
| 65 |
+
""""Unpack" a directory, using the same interface as for archives
|
| 66 |
+
|
| 67 |
+
Raises ``UnrecognizedFormat`` if `filename` is not a directory
|
| 68 |
+
"""
|
| 69 |
+
if not os.path.isdir(filename):
|
| 70 |
+
raise UnrecognizedFormat("%s is not a directory" % filename)
|
| 71 |
+
|
| 72 |
+
paths = {
|
| 73 |
+
filename: ('', extract_dir),
|
| 74 |
+
}
|
| 75 |
+
for base, dirs, files in os.walk(filename):
|
| 76 |
+
src, dst = paths[base]
|
| 77 |
+
for d in dirs:
|
| 78 |
+
paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d)
|
| 79 |
+
for f in files:
|
| 80 |
+
target = os.path.join(dst, f)
|
| 81 |
+
target = progress_filter(src + f, target)
|
| 82 |
+
if not target:
|
| 83 |
+
# skip non-files
|
| 84 |
+
continue
|
| 85 |
+
ensure_directory(target)
|
| 86 |
+
f = os.path.join(base, f)
|
| 87 |
+
shutil.copyfile(f, target)
|
| 88 |
+
shutil.copystat(f, target)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
|
| 92 |
+
"""Unpack zip `filename` to `extract_dir`
|
| 93 |
+
|
| 94 |
+
Raises ``UnrecognizedFormat`` if `filename` is not a zipfile (as determined
|
| 95 |
+
by ``zipfile.is_zipfile()``). See ``unpack_archive()`` for an explanation
|
| 96 |
+
of the `progress_filter` argument.
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
if not zipfile.is_zipfile(filename):
|
| 100 |
+
raise UnrecognizedFormat("%s is not a zip file" % (filename,))
|
| 101 |
+
|
| 102 |
+
with zipfile.ZipFile(filename) as z:
|
| 103 |
+
for info in z.infolist():
|
| 104 |
+
name = info.filename
|
| 105 |
+
|
| 106 |
+
# don't extract absolute paths or ones with .. in them
|
| 107 |
+
if name.startswith('/') or '..' in name.split('/'):
|
| 108 |
+
continue
|
| 109 |
+
|
| 110 |
+
target = os.path.join(extract_dir, *name.split('/'))
|
| 111 |
+
target = progress_filter(name, target)
|
| 112 |
+
if not target:
|
| 113 |
+
continue
|
| 114 |
+
if name.endswith('/'):
|
| 115 |
+
# directory
|
| 116 |
+
ensure_directory(target)
|
| 117 |
+
else:
|
| 118 |
+
# file
|
| 119 |
+
ensure_directory(target)
|
| 120 |
+
data = z.read(info.filename)
|
| 121 |
+
with open(target, 'wb') as f:
|
| 122 |
+
f.write(data)
|
| 123 |
+
unix_attributes = info.external_attr >> 16
|
| 124 |
+
if unix_attributes:
|
| 125 |
+
os.chmod(target, unix_attributes)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def _resolve_tar_file_or_dir(tar_obj, tar_member_obj):
|
| 129 |
+
"""Resolve any links and extract link targets as normal files."""
|
| 130 |
+
while tar_member_obj is not None and (
|
| 131 |
+
tar_member_obj.islnk() or tar_member_obj.issym()):
|
| 132 |
+
linkpath = tar_member_obj.linkname
|
| 133 |
+
if tar_member_obj.issym():
|
| 134 |
+
base = posixpath.dirname(tar_member_obj.name)
|
| 135 |
+
linkpath = posixpath.join(base, linkpath)
|
| 136 |
+
linkpath = posixpath.normpath(linkpath)
|
| 137 |
+
tar_member_obj = tar_obj._getmember(linkpath)
|
| 138 |
+
|
| 139 |
+
is_file_or_dir = (
|
| 140 |
+
tar_member_obj is not None and
|
| 141 |
+
(tar_member_obj.isfile() or tar_member_obj.isdir())
|
| 142 |
+
)
|
| 143 |
+
if is_file_or_dir:
|
| 144 |
+
return tar_member_obj
|
| 145 |
+
|
| 146 |
+
raise LookupError('Got unknown file type')
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _iter_open_tar(tar_obj, extract_dir, progress_filter):
|
| 150 |
+
"""Emit member-destination pairs from a tar archive."""
|
| 151 |
+
# don't do any chowning!
|
| 152 |
+
tar_obj.chown = lambda *args: None
|
| 153 |
+
|
| 154 |
+
with contextlib.closing(tar_obj):
|
| 155 |
+
for member in tar_obj:
|
| 156 |
+
name = member.name
|
| 157 |
+
# don't extract absolute paths or ones with .. in them
|
| 158 |
+
if name.startswith('/') or '..' in name.split('/'):
|
| 159 |
+
continue
|
| 160 |
+
|
| 161 |
+
prelim_dst = os.path.join(extract_dir, *name.split('/'))
|
| 162 |
+
|
| 163 |
+
try:
|
| 164 |
+
member = _resolve_tar_file_or_dir(tar_obj, member)
|
| 165 |
+
except LookupError:
|
| 166 |
+
continue
|
| 167 |
+
|
| 168 |
+
final_dst = progress_filter(name, prelim_dst)
|
| 169 |
+
if not final_dst:
|
| 170 |
+
continue
|
| 171 |
+
|
| 172 |
+
if final_dst.endswith(os.sep):
|
| 173 |
+
final_dst = final_dst[:-1]
|
| 174 |
+
|
| 175 |
+
yield member, final_dst
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
|
| 179 |
+
"""Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
|
| 180 |
+
|
| 181 |
+
Raises ``UnrecognizedFormat`` if `filename` is not a tarfile (as determined
|
| 182 |
+
by ``tarfile.open()``). See ``unpack_archive()`` for an explanation
|
| 183 |
+
of the `progress_filter` argument.
|
| 184 |
+
"""
|
| 185 |
+
try:
|
| 186 |
+
tarobj = tarfile.open(filename)
|
| 187 |
+
except tarfile.TarError as e:
|
| 188 |
+
raise UnrecognizedFormat(
|
| 189 |
+
"%s is not a compressed or uncompressed tar file" % (filename,)
|
| 190 |
+
) from e
|
| 191 |
+
|
| 192 |
+
for member, final_dst in _iter_open_tar(
|
| 193 |
+
tarobj, extract_dir, progress_filter,
|
| 194 |
+
):
|
| 195 |
+
try:
|
| 196 |
+
# XXX Ugh
|
| 197 |
+
tarobj._extract_member(member, final_dst)
|
| 198 |
+
except tarfile.ExtractError:
|
| 199 |
+
# chown/chmod/mkfifo/mknode/makedev failed
|
| 200 |
+
pass
|
| 201 |
+
|
| 202 |
+
return True
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile
|
venv/lib/python3.10/site-packages/setuptools/build_meta.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""A PEP 517 interface to setuptools
|
| 2 |
+
|
| 3 |
+
Previously, when a user or a command line tool (let's call it a "frontend")
|
| 4 |
+
needed to make a request of setuptools to take a certain action, for
|
| 5 |
+
example, generating a list of installation requirements, the frontend would
|
| 6 |
+
would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line.
|
| 7 |
+
|
| 8 |
+
PEP 517 defines a different method of interfacing with setuptools. Rather
|
| 9 |
+
than calling "setup.py" directly, the frontend should:
|
| 10 |
+
|
| 11 |
+
1. Set the current directory to the directory with a setup.py file
|
| 12 |
+
2. Import this module into a safe python interpreter (one in which
|
| 13 |
+
setuptools can potentially set global variables or crash hard).
|
| 14 |
+
3. Call one of the functions defined in PEP 517.
|
| 15 |
+
|
| 16 |
+
What each function does is defined in PEP 517. However, here is a "casual"
|
| 17 |
+
definition of the functions (this definition should not be relied on for
|
| 18 |
+
bug reports or API stability):
|
| 19 |
+
|
| 20 |
+
- `build_wheel`: build a wheel in the folder and return the basename
|
| 21 |
+
- `get_requires_for_build_wheel`: get the `setup_requires` to build
|
| 22 |
+
- `prepare_metadata_for_build_wheel`: get the `install_requires`
|
| 23 |
+
- `build_sdist`: build an sdist in the folder and return the basename
|
| 24 |
+
- `get_requires_for_build_sdist`: get the `setup_requires` to build
|
| 25 |
+
|
| 26 |
+
Again, this is not a formal definition! Just a "taste" of the module.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
import io
|
| 30 |
+
import os
|
| 31 |
+
import sys
|
| 32 |
+
import tokenize
|
| 33 |
+
import shutil
|
| 34 |
+
import contextlib
|
| 35 |
+
import tempfile
|
| 36 |
+
import warnings
|
| 37 |
+
|
| 38 |
+
import setuptools
|
| 39 |
+
import distutils
|
| 40 |
+
|
| 41 |
+
from pkg_resources import parse_requirements
|
| 42 |
+
|
| 43 |
+
__all__ = ['get_requires_for_build_sdist',
|
| 44 |
+
'get_requires_for_build_wheel',
|
| 45 |
+
'prepare_metadata_for_build_wheel',
|
| 46 |
+
'build_wheel',
|
| 47 |
+
'build_sdist',
|
| 48 |
+
'__legacy__',
|
| 49 |
+
'SetupRequirementsError']
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class SetupRequirementsError(BaseException):
|
| 53 |
+
def __init__(self, specifiers):
|
| 54 |
+
self.specifiers = specifiers
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class Distribution(setuptools.dist.Distribution):
|
| 58 |
+
def fetch_build_eggs(self, specifiers):
|
| 59 |
+
specifier_list = list(map(str, parse_requirements(specifiers)))
|
| 60 |
+
|
| 61 |
+
raise SetupRequirementsError(specifier_list)
|
| 62 |
+
|
| 63 |
+
@classmethod
|
| 64 |
+
@contextlib.contextmanager
|
| 65 |
+
def patch(cls):
|
| 66 |
+
"""
|
| 67 |
+
Replace
|
| 68 |
+
distutils.dist.Distribution with this class
|
| 69 |
+
for the duration of this context.
|
| 70 |
+
"""
|
| 71 |
+
orig = distutils.core.Distribution
|
| 72 |
+
distutils.core.Distribution = cls
|
| 73 |
+
try:
|
| 74 |
+
yield
|
| 75 |
+
finally:
|
| 76 |
+
distutils.core.Distribution = orig
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@contextlib.contextmanager
|
| 80 |
+
def no_install_setup_requires():
|
| 81 |
+
"""Temporarily disable installing setup_requires
|
| 82 |
+
|
| 83 |
+
Under PEP 517, the backend reports build dependencies to the frontend,
|
| 84 |
+
and the frontend is responsible for ensuring they're installed.
|
| 85 |
+
So setuptools (acting as a backend) should not try to install them.
|
| 86 |
+
"""
|
| 87 |
+
orig = setuptools._install_setup_requires
|
| 88 |
+
setuptools._install_setup_requires = lambda attrs: None
|
| 89 |
+
try:
|
| 90 |
+
yield
|
| 91 |
+
finally:
|
| 92 |
+
setuptools._install_setup_requires = orig
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _get_immediate_subdirectories(a_dir):
|
| 96 |
+
return [name for name in os.listdir(a_dir)
|
| 97 |
+
if os.path.isdir(os.path.join(a_dir, name))]
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _file_with_extension(directory, extension):
|
| 101 |
+
matching = (
|
| 102 |
+
f for f in os.listdir(directory)
|
| 103 |
+
if f.endswith(extension)
|
| 104 |
+
)
|
| 105 |
+
try:
|
| 106 |
+
file, = matching
|
| 107 |
+
except ValueError:
|
| 108 |
+
raise ValueError(
|
| 109 |
+
'No distribution was found. Ensure that `setup.py` '
|
| 110 |
+
'is not empty and that it calls `setup()`.')
|
| 111 |
+
return file
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _open_setup_script(setup_script):
|
| 115 |
+
if not os.path.exists(setup_script):
|
| 116 |
+
# Supply a default setup.py
|
| 117 |
+
return io.StringIO(u"from setuptools import setup; setup()")
|
| 118 |
+
|
| 119 |
+
return getattr(tokenize, 'open', open)(setup_script)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@contextlib.contextmanager
|
| 123 |
+
def suppress_known_deprecation():
|
| 124 |
+
with warnings.catch_warnings():
|
| 125 |
+
warnings.filterwarnings('ignore', 'setup.py install is deprecated')
|
| 126 |
+
yield
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class _BuildMetaBackend(object):
|
| 130 |
+
|
| 131 |
+
def _fix_config(self, config_settings):
|
| 132 |
+
config_settings = config_settings or {}
|
| 133 |
+
config_settings.setdefault('--global-option', [])
|
| 134 |
+
return config_settings
|
| 135 |
+
|
| 136 |
+
def _get_build_requires(self, config_settings, requirements):
|
| 137 |
+
config_settings = self._fix_config(config_settings)
|
| 138 |
+
|
| 139 |
+
sys.argv = sys.argv[:1] + ['egg_info'] + \
|
| 140 |
+
config_settings["--global-option"]
|
| 141 |
+
try:
|
| 142 |
+
with Distribution.patch():
|
| 143 |
+
self.run_setup()
|
| 144 |
+
except SetupRequirementsError as e:
|
| 145 |
+
requirements += e.specifiers
|
| 146 |
+
|
| 147 |
+
return requirements
|
| 148 |
+
|
| 149 |
+
def run_setup(self, setup_script='setup.py'):
|
| 150 |
+
# Note that we can reuse our build directory between calls
|
| 151 |
+
# Correctness comes first, then optimization later
|
| 152 |
+
__file__ = setup_script
|
| 153 |
+
__name__ = '__main__'
|
| 154 |
+
|
| 155 |
+
with _open_setup_script(__file__) as f:
|
| 156 |
+
code = f.read().replace(r'\r\n', r'\n')
|
| 157 |
+
|
| 158 |
+
exec(compile(code, __file__, 'exec'), locals())
|
| 159 |
+
|
| 160 |
+
def get_requires_for_build_wheel(self, config_settings=None):
|
| 161 |
+
config_settings = self._fix_config(config_settings)
|
| 162 |
+
return self._get_build_requires(
|
| 163 |
+
config_settings, requirements=['wheel'])
|
| 164 |
+
|
| 165 |
+
def get_requires_for_build_sdist(self, config_settings=None):
|
| 166 |
+
config_settings = self._fix_config(config_settings)
|
| 167 |
+
return self._get_build_requires(config_settings, requirements=[])
|
| 168 |
+
|
| 169 |
+
def prepare_metadata_for_build_wheel(self, metadata_directory,
|
| 170 |
+
config_settings=None):
|
| 171 |
+
sys.argv = sys.argv[:1] + [
|
| 172 |
+
'dist_info', '--egg-base', metadata_directory]
|
| 173 |
+
with no_install_setup_requires():
|
| 174 |
+
self.run_setup()
|
| 175 |
+
|
| 176 |
+
dist_info_directory = metadata_directory
|
| 177 |
+
while True:
|
| 178 |
+
dist_infos = [f for f in os.listdir(dist_info_directory)
|
| 179 |
+
if f.endswith('.dist-info')]
|
| 180 |
+
|
| 181 |
+
if (
|
| 182 |
+
len(dist_infos) == 0 and
|
| 183 |
+
len(_get_immediate_subdirectories(dist_info_directory)) == 1
|
| 184 |
+
):
|
| 185 |
+
|
| 186 |
+
dist_info_directory = os.path.join(
|
| 187 |
+
dist_info_directory, os.listdir(dist_info_directory)[0])
|
| 188 |
+
continue
|
| 189 |
+
|
| 190 |
+
assert len(dist_infos) == 1
|
| 191 |
+
break
|
| 192 |
+
|
| 193 |
+
# PEP 517 requires that the .dist-info directory be placed in the
|
| 194 |
+
# metadata_directory. To comply, we MUST copy the directory to the root
|
| 195 |
+
if dist_info_directory != metadata_directory:
|
| 196 |
+
shutil.move(
|
| 197 |
+
os.path.join(dist_info_directory, dist_infos[0]),
|
| 198 |
+
metadata_directory)
|
| 199 |
+
shutil.rmtree(dist_info_directory, ignore_errors=True)
|
| 200 |
+
|
| 201 |
+
return dist_infos[0]
|
| 202 |
+
|
| 203 |
+
def _build_with_temp_dir(self, setup_command, result_extension,
|
| 204 |
+
result_directory, config_settings):
|
| 205 |
+
config_settings = self._fix_config(config_settings)
|
| 206 |
+
result_directory = os.path.abspath(result_directory)
|
| 207 |
+
|
| 208 |
+
# Build in a temporary directory, then copy to the target.
|
| 209 |
+
os.makedirs(result_directory, exist_ok=True)
|
| 210 |
+
with tempfile.TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
|
| 211 |
+
sys.argv = (sys.argv[:1] + setup_command +
|
| 212 |
+
['--dist-dir', tmp_dist_dir] +
|
| 213 |
+
config_settings["--global-option"])
|
| 214 |
+
with no_install_setup_requires():
|
| 215 |
+
self.run_setup()
|
| 216 |
+
|
| 217 |
+
result_basename = _file_with_extension(
|
| 218 |
+
tmp_dist_dir, result_extension)
|
| 219 |
+
result_path = os.path.join(result_directory, result_basename)
|
| 220 |
+
if os.path.exists(result_path):
|
| 221 |
+
# os.rename will fail overwriting on non-Unix.
|
| 222 |
+
os.remove(result_path)
|
| 223 |
+
os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)
|
| 224 |
+
|
| 225 |
+
return result_basename
|
| 226 |
+
|
| 227 |
+
def build_wheel(self, wheel_directory, config_settings=None,
|
| 228 |
+
metadata_directory=None):
|
| 229 |
+
with suppress_known_deprecation():
|
| 230 |
+
return self._build_with_temp_dir(['bdist_wheel'], '.whl',
|
| 231 |
+
wheel_directory, config_settings)
|
| 232 |
+
|
| 233 |
+
def build_sdist(self, sdist_directory, config_settings=None):
|
| 234 |
+
return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],
|
| 235 |
+
'.tar.gz', sdist_directory,
|
| 236 |
+
config_settings)
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
class _BuildMetaLegacyBackend(_BuildMetaBackend):
|
| 240 |
+
"""Compatibility backend for setuptools
|
| 241 |
+
|
| 242 |
+
This is a version of setuptools.build_meta that endeavors
|
| 243 |
+
to maintain backwards
|
| 244 |
+
compatibility with pre-PEP 517 modes of invocation. It
|
| 245 |
+
exists as a temporary
|
| 246 |
+
bridge between the old packaging mechanism and the new
|
| 247 |
+
packaging mechanism,
|
| 248 |
+
and will eventually be removed.
|
| 249 |
+
"""
|
| 250 |
+
def run_setup(self, setup_script='setup.py'):
|
| 251 |
+
# In order to maintain compatibility with scripts assuming that
|
| 252 |
+
# the setup.py script is in a directory on the PYTHONPATH, inject
|
| 253 |
+
# '' into sys.path. (pypa/setuptools#1642)
|
| 254 |
+
sys_path = list(sys.path) # Save the original path
|
| 255 |
+
|
| 256 |
+
script_dir = os.path.dirname(os.path.abspath(setup_script))
|
| 257 |
+
if script_dir not in sys.path:
|
| 258 |
+
sys.path.insert(0, script_dir)
|
| 259 |
+
|
| 260 |
+
# Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to
|
| 261 |
+
# get the directory of the source code. They expect it to refer to the
|
| 262 |
+
# setup.py script.
|
| 263 |
+
sys_argv_0 = sys.argv[0]
|
| 264 |
+
sys.argv[0] = setup_script
|
| 265 |
+
|
| 266 |
+
try:
|
| 267 |
+
super(_BuildMetaLegacyBackend,
|
| 268 |
+
self).run_setup(setup_script=setup_script)
|
| 269 |
+
finally:
|
| 270 |
+
# While PEP 517 frontends should be calling each hook in a fresh
|
| 271 |
+
# subprocess according to the standard (and thus it should not be
|
| 272 |
+
# strictly necessary to restore the old sys.path), we'll restore
|
| 273 |
+
# the original path so that the path manipulation does not persist
|
| 274 |
+
# within the hook after run_setup is called.
|
| 275 |
+
sys.path[:] = sys_path
|
| 276 |
+
sys.argv[0] = sys_argv_0
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
# The primary backend
|
| 280 |
+
_BACKEND = _BuildMetaBackend()
|
| 281 |
+
|
| 282 |
+
get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
|
| 283 |
+
get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
|
| 284 |
+
prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
|
| 285 |
+
build_wheel = _BACKEND.build_wheel
|
| 286 |
+
build_sdist = _BACKEND.build_sdist
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
# The legacy backend
|
| 290 |
+
__legacy__ = _BuildMetaLegacyBackend()
|
venv/lib/python3.10/site-packages/setuptools/cli-32.exe
ADDED
|
Binary file (65.5 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/cli-64.exe
ADDED
|
Binary file (74.8 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/cli-arm64.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a3d6a6c68c2e759f7c36f35687f6b60d163c2e1a0846a4c07a4c4006a96d88c7
|
| 3 |
+
size 137216
|
venv/lib/python3.10/site-packages/setuptools/cli.exe
ADDED
|
Binary file (65.5 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/config.py
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ast
|
| 2 |
+
import io
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
import warnings
|
| 7 |
+
import functools
|
| 8 |
+
import importlib
|
| 9 |
+
from collections import defaultdict
|
| 10 |
+
from functools import partial
|
| 11 |
+
from functools import wraps
|
| 12 |
+
from glob import iglob
|
| 13 |
+
import contextlib
|
| 14 |
+
|
| 15 |
+
from distutils.errors import DistutilsOptionError, DistutilsFileError
|
| 16 |
+
from setuptools.extern.packaging.version import Version, InvalidVersion
|
| 17 |
+
from setuptools.extern.packaging.specifiers import SpecifierSet
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class StaticModule:
|
| 21 |
+
"""
|
| 22 |
+
Attempt to load the module by the name
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self, name):
|
| 26 |
+
spec = importlib.util.find_spec(name)
|
| 27 |
+
with open(spec.origin) as strm:
|
| 28 |
+
src = strm.read()
|
| 29 |
+
module = ast.parse(src)
|
| 30 |
+
vars(self).update(locals())
|
| 31 |
+
del self.self
|
| 32 |
+
|
| 33 |
+
def __getattr__(self, attr):
|
| 34 |
+
try:
|
| 35 |
+
return next(
|
| 36 |
+
ast.literal_eval(statement.value)
|
| 37 |
+
for statement in self.module.body
|
| 38 |
+
if isinstance(statement, ast.Assign)
|
| 39 |
+
for target in statement.targets
|
| 40 |
+
if isinstance(target, ast.Name) and target.id == attr
|
| 41 |
+
)
|
| 42 |
+
except Exception as e:
|
| 43 |
+
raise AttributeError(
|
| 44 |
+
"{self.name} has no attribute {attr}".format(**locals())
|
| 45 |
+
) from e
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@contextlib.contextmanager
|
| 49 |
+
def patch_path(path):
|
| 50 |
+
"""
|
| 51 |
+
Add path to front of sys.path for the duration of the context.
|
| 52 |
+
"""
|
| 53 |
+
try:
|
| 54 |
+
sys.path.insert(0, path)
|
| 55 |
+
yield
|
| 56 |
+
finally:
|
| 57 |
+
sys.path.remove(path)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def read_configuration(filepath, find_others=False, ignore_option_errors=False):
|
| 61 |
+
"""Read given configuration file and returns options from it as a dict.
|
| 62 |
+
|
| 63 |
+
:param str|unicode filepath: Path to configuration file
|
| 64 |
+
to get options from.
|
| 65 |
+
|
| 66 |
+
:param bool find_others: Whether to search for other configuration files
|
| 67 |
+
which could be on in various places.
|
| 68 |
+
|
| 69 |
+
:param bool ignore_option_errors: Whether to silently ignore
|
| 70 |
+
options, values of which could not be resolved (e.g. due to exceptions
|
| 71 |
+
in directives such as file:, attr:, etc.).
|
| 72 |
+
If False exceptions are propagated as expected.
|
| 73 |
+
|
| 74 |
+
:rtype: dict
|
| 75 |
+
"""
|
| 76 |
+
from setuptools.dist import Distribution, _Distribution
|
| 77 |
+
|
| 78 |
+
filepath = os.path.abspath(filepath)
|
| 79 |
+
|
| 80 |
+
if not os.path.isfile(filepath):
|
| 81 |
+
raise DistutilsFileError('Configuration file %s does not exist.' % filepath)
|
| 82 |
+
|
| 83 |
+
current_directory = os.getcwd()
|
| 84 |
+
os.chdir(os.path.dirname(filepath))
|
| 85 |
+
|
| 86 |
+
try:
|
| 87 |
+
dist = Distribution()
|
| 88 |
+
|
| 89 |
+
filenames = dist.find_config_files() if find_others else []
|
| 90 |
+
if filepath not in filenames:
|
| 91 |
+
filenames.append(filepath)
|
| 92 |
+
|
| 93 |
+
_Distribution.parse_config_files(dist, filenames=filenames)
|
| 94 |
+
|
| 95 |
+
handlers = parse_configuration(
|
| 96 |
+
dist, dist.command_options, ignore_option_errors=ignore_option_errors
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
finally:
|
| 100 |
+
os.chdir(current_directory)
|
| 101 |
+
|
| 102 |
+
return configuration_to_dict(handlers)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _get_option(target_obj, key):
|
| 106 |
+
"""
|
| 107 |
+
Given a target object and option key, get that option from
|
| 108 |
+
the target object, either through a get_{key} method or
|
| 109 |
+
from an attribute directly.
|
| 110 |
+
"""
|
| 111 |
+
getter_name = 'get_{key}'.format(**locals())
|
| 112 |
+
by_attribute = functools.partial(getattr, target_obj, key)
|
| 113 |
+
getter = getattr(target_obj, getter_name, by_attribute)
|
| 114 |
+
return getter()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def configuration_to_dict(handlers):
|
| 118 |
+
"""Returns configuration data gathered by given handlers as a dict.
|
| 119 |
+
|
| 120 |
+
:param list[ConfigHandler] handlers: Handlers list,
|
| 121 |
+
usually from parse_configuration()
|
| 122 |
+
|
| 123 |
+
:rtype: dict
|
| 124 |
+
"""
|
| 125 |
+
config_dict = defaultdict(dict)
|
| 126 |
+
|
| 127 |
+
for handler in handlers:
|
| 128 |
+
for option in handler.set_options:
|
| 129 |
+
value = _get_option(handler.target_obj, option)
|
| 130 |
+
config_dict[handler.section_prefix][option] = value
|
| 131 |
+
|
| 132 |
+
return config_dict
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def parse_configuration(distribution, command_options, ignore_option_errors=False):
|
| 136 |
+
"""Performs additional parsing of configuration options
|
| 137 |
+
for a distribution.
|
| 138 |
+
|
| 139 |
+
Returns a list of used option handlers.
|
| 140 |
+
|
| 141 |
+
:param Distribution distribution:
|
| 142 |
+
:param dict command_options:
|
| 143 |
+
:param bool ignore_option_errors: Whether to silently ignore
|
| 144 |
+
options, values of which could not be resolved (e.g. due to exceptions
|
| 145 |
+
in directives such as file:, attr:, etc.).
|
| 146 |
+
If False exceptions are propagated as expected.
|
| 147 |
+
:rtype: list
|
| 148 |
+
"""
|
| 149 |
+
options = ConfigOptionsHandler(distribution, command_options, ignore_option_errors)
|
| 150 |
+
options.parse()
|
| 151 |
+
|
| 152 |
+
meta = ConfigMetadataHandler(
|
| 153 |
+
distribution.metadata,
|
| 154 |
+
command_options,
|
| 155 |
+
ignore_option_errors,
|
| 156 |
+
distribution.package_dir,
|
| 157 |
+
)
|
| 158 |
+
meta.parse()
|
| 159 |
+
|
| 160 |
+
return meta, options
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
class ConfigHandler:
|
| 164 |
+
"""Handles metadata supplied in configuration files."""
|
| 165 |
+
|
| 166 |
+
section_prefix = None
|
| 167 |
+
"""Prefix for config sections handled by this handler.
|
| 168 |
+
Must be provided by class heirs.
|
| 169 |
+
|
| 170 |
+
"""
|
| 171 |
+
|
| 172 |
+
aliases = {}
|
| 173 |
+
"""Options aliases.
|
| 174 |
+
For compatibility with various packages. E.g.: d2to1 and pbr.
|
| 175 |
+
Note: `-` in keys is replaced with `_` by config parser.
|
| 176 |
+
|
| 177 |
+
"""
|
| 178 |
+
|
| 179 |
+
def __init__(self, target_obj, options, ignore_option_errors=False):
|
| 180 |
+
sections = {}
|
| 181 |
+
|
| 182 |
+
section_prefix = self.section_prefix
|
| 183 |
+
for section_name, section_options in options.items():
|
| 184 |
+
if not section_name.startswith(section_prefix):
|
| 185 |
+
continue
|
| 186 |
+
|
| 187 |
+
section_name = section_name.replace(section_prefix, '').strip('.')
|
| 188 |
+
sections[section_name] = section_options
|
| 189 |
+
|
| 190 |
+
self.ignore_option_errors = ignore_option_errors
|
| 191 |
+
self.target_obj = target_obj
|
| 192 |
+
self.sections = sections
|
| 193 |
+
self.set_options = []
|
| 194 |
+
|
| 195 |
+
@property
|
| 196 |
+
def parsers(self):
|
| 197 |
+
"""Metadata item name to parser function mapping."""
|
| 198 |
+
raise NotImplementedError(
|
| 199 |
+
'%s must provide .parsers property' % self.__class__.__name__
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
def __setitem__(self, option_name, value):
|
| 203 |
+
unknown = tuple()
|
| 204 |
+
target_obj = self.target_obj
|
| 205 |
+
|
| 206 |
+
# Translate alias into real name.
|
| 207 |
+
option_name = self.aliases.get(option_name, option_name)
|
| 208 |
+
|
| 209 |
+
current_value = getattr(target_obj, option_name, unknown)
|
| 210 |
+
|
| 211 |
+
if current_value is unknown:
|
| 212 |
+
raise KeyError(option_name)
|
| 213 |
+
|
| 214 |
+
if current_value:
|
| 215 |
+
# Already inhabited. Skipping.
|
| 216 |
+
return
|
| 217 |
+
|
| 218 |
+
skip_option = False
|
| 219 |
+
parser = self.parsers.get(option_name)
|
| 220 |
+
if parser:
|
| 221 |
+
try:
|
| 222 |
+
value = parser(value)
|
| 223 |
+
|
| 224 |
+
except Exception:
|
| 225 |
+
skip_option = True
|
| 226 |
+
if not self.ignore_option_errors:
|
| 227 |
+
raise
|
| 228 |
+
|
| 229 |
+
if skip_option:
|
| 230 |
+
return
|
| 231 |
+
|
| 232 |
+
setter = getattr(target_obj, 'set_%s' % option_name, None)
|
| 233 |
+
if setter is None:
|
| 234 |
+
setattr(target_obj, option_name, value)
|
| 235 |
+
else:
|
| 236 |
+
setter(value)
|
| 237 |
+
|
| 238 |
+
self.set_options.append(option_name)
|
| 239 |
+
|
| 240 |
+
@classmethod
|
| 241 |
+
def _parse_list(cls, value, separator=','):
|
| 242 |
+
"""Represents value as a list.
|
| 243 |
+
|
| 244 |
+
Value is split either by separator (defaults to comma) or by lines.
|
| 245 |
+
|
| 246 |
+
:param value:
|
| 247 |
+
:param separator: List items separator character.
|
| 248 |
+
:rtype: list
|
| 249 |
+
"""
|
| 250 |
+
if isinstance(value, list): # _get_parser_compound case
|
| 251 |
+
return value
|
| 252 |
+
|
| 253 |
+
if '\n' in value:
|
| 254 |
+
value = value.splitlines()
|
| 255 |
+
else:
|
| 256 |
+
value = value.split(separator)
|
| 257 |
+
|
| 258 |
+
return [chunk.strip() for chunk in value if chunk.strip()]
|
| 259 |
+
|
| 260 |
+
@classmethod
|
| 261 |
+
def _parse_list_glob(cls, value, separator=','):
|
| 262 |
+
"""Equivalent to _parse_list() but expands any glob patterns using glob().
|
| 263 |
+
|
| 264 |
+
However, unlike with glob() calls, the results remain relative paths.
|
| 265 |
+
|
| 266 |
+
:param value:
|
| 267 |
+
:param separator: List items separator character.
|
| 268 |
+
:rtype: list
|
| 269 |
+
"""
|
| 270 |
+
glob_characters = ('*', '?', '[', ']', '{', '}')
|
| 271 |
+
values = cls._parse_list(value, separator=separator)
|
| 272 |
+
expanded_values = []
|
| 273 |
+
for value in values:
|
| 274 |
+
|
| 275 |
+
# Has globby characters?
|
| 276 |
+
if any(char in value for char in glob_characters):
|
| 277 |
+
# then expand the glob pattern while keeping paths *relative*:
|
| 278 |
+
expanded_values.extend(sorted(
|
| 279 |
+
os.path.relpath(path, os.getcwd())
|
| 280 |
+
for path in iglob(os.path.abspath(value))))
|
| 281 |
+
|
| 282 |
+
else:
|
| 283 |
+
# take the value as-is:
|
| 284 |
+
expanded_values.append(value)
|
| 285 |
+
|
| 286 |
+
return expanded_values
|
| 287 |
+
|
| 288 |
+
@classmethod
|
| 289 |
+
def _parse_dict(cls, value):
|
| 290 |
+
"""Represents value as a dict.
|
| 291 |
+
|
| 292 |
+
:param value:
|
| 293 |
+
:rtype: dict
|
| 294 |
+
"""
|
| 295 |
+
separator = '='
|
| 296 |
+
result = {}
|
| 297 |
+
for line in cls._parse_list(value):
|
| 298 |
+
key, sep, val = line.partition(separator)
|
| 299 |
+
if sep != separator:
|
| 300 |
+
raise DistutilsOptionError(
|
| 301 |
+
'Unable to parse option value to dict: %s' % value
|
| 302 |
+
)
|
| 303 |
+
result[key.strip()] = val.strip()
|
| 304 |
+
|
| 305 |
+
return result
|
| 306 |
+
|
| 307 |
+
@classmethod
|
| 308 |
+
def _parse_bool(cls, value):
|
| 309 |
+
"""Represents value as boolean.
|
| 310 |
+
|
| 311 |
+
:param value:
|
| 312 |
+
:rtype: bool
|
| 313 |
+
"""
|
| 314 |
+
value = value.lower()
|
| 315 |
+
return value in ('1', 'true', 'yes')
|
| 316 |
+
|
| 317 |
+
@classmethod
|
| 318 |
+
def _exclude_files_parser(cls, key):
|
| 319 |
+
"""Returns a parser function to make sure field inputs
|
| 320 |
+
are not files.
|
| 321 |
+
|
| 322 |
+
Parses a value after getting the key so error messages are
|
| 323 |
+
more informative.
|
| 324 |
+
|
| 325 |
+
:param key:
|
| 326 |
+
:rtype: callable
|
| 327 |
+
"""
|
| 328 |
+
|
| 329 |
+
def parser(value):
|
| 330 |
+
exclude_directive = 'file:'
|
| 331 |
+
if value.startswith(exclude_directive):
|
| 332 |
+
raise ValueError(
|
| 333 |
+
'Only strings are accepted for the {0} field, '
|
| 334 |
+
'files are not accepted'.format(key)
|
| 335 |
+
)
|
| 336 |
+
return value
|
| 337 |
+
|
| 338 |
+
return parser
|
| 339 |
+
|
| 340 |
+
@classmethod
|
| 341 |
+
def _parse_file(cls, value):
|
| 342 |
+
"""Represents value as a string, allowing including text
|
| 343 |
+
from nearest files using `file:` directive.
|
| 344 |
+
|
| 345 |
+
Directive is sandboxed and won't reach anything outside
|
| 346 |
+
directory with setup.py.
|
| 347 |
+
|
| 348 |
+
Examples:
|
| 349 |
+
file: README.rst, CHANGELOG.md, src/file.txt
|
| 350 |
+
|
| 351 |
+
:param str value:
|
| 352 |
+
:rtype: str
|
| 353 |
+
"""
|
| 354 |
+
include_directive = 'file:'
|
| 355 |
+
|
| 356 |
+
if not isinstance(value, str):
|
| 357 |
+
return value
|
| 358 |
+
|
| 359 |
+
if not value.startswith(include_directive):
|
| 360 |
+
return value
|
| 361 |
+
|
| 362 |
+
spec = value[len(include_directive) :]
|
| 363 |
+
filepaths = (os.path.abspath(path.strip()) for path in spec.split(','))
|
| 364 |
+
return '\n'.join(
|
| 365 |
+
cls._read_file(path)
|
| 366 |
+
for path in filepaths
|
| 367 |
+
if (cls._assert_local(path) or True) and os.path.isfile(path)
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
@staticmethod
|
| 371 |
+
def _assert_local(filepath):
|
| 372 |
+
if not filepath.startswith(os.getcwd()):
|
| 373 |
+
raise DistutilsOptionError('`file:` directive can not access %s' % filepath)
|
| 374 |
+
|
| 375 |
+
@staticmethod
|
| 376 |
+
def _read_file(filepath):
|
| 377 |
+
with io.open(filepath, encoding='utf-8') as f:
|
| 378 |
+
return f.read()
|
| 379 |
+
|
| 380 |
+
@classmethod
|
| 381 |
+
def _parse_attr(cls, value, package_dir=None):
|
| 382 |
+
"""Represents value as a module attribute.
|
| 383 |
+
|
| 384 |
+
Examples:
|
| 385 |
+
attr: package.attr
|
| 386 |
+
attr: package.module.attr
|
| 387 |
+
|
| 388 |
+
:param str value:
|
| 389 |
+
:rtype: str
|
| 390 |
+
"""
|
| 391 |
+
attr_directive = 'attr:'
|
| 392 |
+
if not value.startswith(attr_directive):
|
| 393 |
+
return value
|
| 394 |
+
|
| 395 |
+
attrs_path = value.replace(attr_directive, '').strip().split('.')
|
| 396 |
+
attr_name = attrs_path.pop()
|
| 397 |
+
|
| 398 |
+
module_name = '.'.join(attrs_path)
|
| 399 |
+
module_name = module_name or '__init__'
|
| 400 |
+
|
| 401 |
+
parent_path = os.getcwd()
|
| 402 |
+
if package_dir:
|
| 403 |
+
if attrs_path[0] in package_dir:
|
| 404 |
+
# A custom path was specified for the module we want to import
|
| 405 |
+
custom_path = package_dir[attrs_path[0]]
|
| 406 |
+
parts = custom_path.rsplit('/', 1)
|
| 407 |
+
if len(parts) > 1:
|
| 408 |
+
parent_path = os.path.join(os.getcwd(), parts[0])
|
| 409 |
+
module_name = parts[1]
|
| 410 |
+
else:
|
| 411 |
+
module_name = custom_path
|
| 412 |
+
elif '' in package_dir:
|
| 413 |
+
# A custom parent directory was specified for all root modules
|
| 414 |
+
parent_path = os.path.join(os.getcwd(), package_dir[''])
|
| 415 |
+
|
| 416 |
+
with patch_path(parent_path):
|
| 417 |
+
try:
|
| 418 |
+
# attempt to load value statically
|
| 419 |
+
return getattr(StaticModule(module_name), attr_name)
|
| 420 |
+
except Exception:
|
| 421 |
+
# fallback to simple import
|
| 422 |
+
module = importlib.import_module(module_name)
|
| 423 |
+
|
| 424 |
+
return getattr(module, attr_name)
|
| 425 |
+
|
| 426 |
+
@classmethod
|
| 427 |
+
def _get_parser_compound(cls, *parse_methods):
|
| 428 |
+
"""Returns parser function to represents value as a list.
|
| 429 |
+
|
| 430 |
+
Parses a value applying given methods one after another.
|
| 431 |
+
|
| 432 |
+
:param parse_methods:
|
| 433 |
+
:rtype: callable
|
| 434 |
+
"""
|
| 435 |
+
|
| 436 |
+
def parse(value):
|
| 437 |
+
parsed = value
|
| 438 |
+
|
| 439 |
+
for method in parse_methods:
|
| 440 |
+
parsed = method(parsed)
|
| 441 |
+
|
| 442 |
+
return parsed
|
| 443 |
+
|
| 444 |
+
return parse
|
| 445 |
+
|
| 446 |
+
@classmethod
|
| 447 |
+
def _parse_section_to_dict(cls, section_options, values_parser=None):
|
| 448 |
+
"""Parses section options into a dictionary.
|
| 449 |
+
|
| 450 |
+
Optionally applies a given parser to values.
|
| 451 |
+
|
| 452 |
+
:param dict section_options:
|
| 453 |
+
:param callable values_parser:
|
| 454 |
+
:rtype: dict
|
| 455 |
+
"""
|
| 456 |
+
value = {}
|
| 457 |
+
values_parser = values_parser or (lambda val: val)
|
| 458 |
+
for key, (_, val) in section_options.items():
|
| 459 |
+
value[key] = values_parser(val)
|
| 460 |
+
return value
|
| 461 |
+
|
| 462 |
+
def parse_section(self, section_options):
|
| 463 |
+
"""Parses configuration file section.
|
| 464 |
+
|
| 465 |
+
:param dict section_options:
|
| 466 |
+
"""
|
| 467 |
+
for (name, (_, value)) in section_options.items():
|
| 468 |
+
try:
|
| 469 |
+
self[name] = value
|
| 470 |
+
|
| 471 |
+
except KeyError:
|
| 472 |
+
pass # Keep silent for a new option may appear anytime.
|
| 473 |
+
|
| 474 |
+
def parse(self):
|
| 475 |
+
"""Parses configuration file items from one
|
| 476 |
+
or more related sections.
|
| 477 |
+
|
| 478 |
+
"""
|
| 479 |
+
for section_name, section_options in self.sections.items():
|
| 480 |
+
|
| 481 |
+
method_postfix = ''
|
| 482 |
+
if section_name: # [section.option] variant
|
| 483 |
+
method_postfix = '_%s' % section_name
|
| 484 |
+
|
| 485 |
+
section_parser_method = getattr(
|
| 486 |
+
self,
|
| 487 |
+
# Dots in section names are translated into dunderscores.
|
| 488 |
+
('parse_section%s' % method_postfix).replace('.', '__'),
|
| 489 |
+
None,
|
| 490 |
+
)
|
| 491 |
+
|
| 492 |
+
if section_parser_method is None:
|
| 493 |
+
raise DistutilsOptionError(
|
| 494 |
+
'Unsupported distribution option section: [%s.%s]'
|
| 495 |
+
% (self.section_prefix, section_name)
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
section_parser_method(section_options)
|
| 499 |
+
|
| 500 |
+
def _deprecated_config_handler(self, func, msg, warning_class):
|
| 501 |
+
"""this function will wrap around parameters that are deprecated
|
| 502 |
+
|
| 503 |
+
:param msg: deprecation message
|
| 504 |
+
:param warning_class: class of warning exception to be raised
|
| 505 |
+
:param func: function to be wrapped around
|
| 506 |
+
"""
|
| 507 |
+
|
| 508 |
+
@wraps(func)
|
| 509 |
+
def config_handler(*args, **kwargs):
|
| 510 |
+
warnings.warn(msg, warning_class)
|
| 511 |
+
return func(*args, **kwargs)
|
| 512 |
+
|
| 513 |
+
return config_handler
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
class ConfigMetadataHandler(ConfigHandler):
|
| 517 |
+
|
| 518 |
+
section_prefix = 'metadata'
|
| 519 |
+
|
| 520 |
+
aliases = {
|
| 521 |
+
'home_page': 'url',
|
| 522 |
+
'summary': 'description',
|
| 523 |
+
'classifier': 'classifiers',
|
| 524 |
+
'platform': 'platforms',
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
strict_mode = False
|
| 528 |
+
"""We need to keep it loose, to be partially compatible with
|
| 529 |
+
`pbr` and `d2to1` packages which also uses `metadata` section.
|
| 530 |
+
|
| 531 |
+
"""
|
| 532 |
+
|
| 533 |
+
def __init__(
|
| 534 |
+
self, target_obj, options, ignore_option_errors=False, package_dir=None
|
| 535 |
+
):
|
| 536 |
+
super(ConfigMetadataHandler, self).__init__(
|
| 537 |
+
target_obj, options, ignore_option_errors
|
| 538 |
+
)
|
| 539 |
+
self.package_dir = package_dir
|
| 540 |
+
|
| 541 |
+
@property
|
| 542 |
+
def parsers(self):
|
| 543 |
+
"""Metadata item name to parser function mapping."""
|
| 544 |
+
parse_list = self._parse_list
|
| 545 |
+
parse_file = self._parse_file
|
| 546 |
+
parse_dict = self._parse_dict
|
| 547 |
+
exclude_files_parser = self._exclude_files_parser
|
| 548 |
+
|
| 549 |
+
return {
|
| 550 |
+
'platforms': parse_list,
|
| 551 |
+
'keywords': parse_list,
|
| 552 |
+
'provides': parse_list,
|
| 553 |
+
'requires': self._deprecated_config_handler(
|
| 554 |
+
parse_list,
|
| 555 |
+
"The requires parameter is deprecated, please use "
|
| 556 |
+
"install_requires for runtime dependencies.",
|
| 557 |
+
DeprecationWarning,
|
| 558 |
+
),
|
| 559 |
+
'obsoletes': parse_list,
|
| 560 |
+
'classifiers': self._get_parser_compound(parse_file, parse_list),
|
| 561 |
+
'license': exclude_files_parser('license'),
|
| 562 |
+
'license_file': self._deprecated_config_handler(
|
| 563 |
+
exclude_files_parser('license_file'),
|
| 564 |
+
"The license_file parameter is deprecated, "
|
| 565 |
+
"use license_files instead.",
|
| 566 |
+
DeprecationWarning,
|
| 567 |
+
),
|
| 568 |
+
'license_files': parse_list,
|
| 569 |
+
'description': parse_file,
|
| 570 |
+
'long_description': parse_file,
|
| 571 |
+
'version': self._parse_version,
|
| 572 |
+
'project_urls': parse_dict,
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
def _parse_version(self, value):
|
| 576 |
+
"""Parses `version` option value.
|
| 577 |
+
|
| 578 |
+
:param value:
|
| 579 |
+
:rtype: str
|
| 580 |
+
|
| 581 |
+
"""
|
| 582 |
+
version = self._parse_file(value)
|
| 583 |
+
|
| 584 |
+
if version != value:
|
| 585 |
+
version = version.strip()
|
| 586 |
+
# Be strict about versions loaded from file because it's easy to
|
| 587 |
+
# accidentally include newlines and other unintended content
|
| 588 |
+
try:
|
| 589 |
+
Version(version)
|
| 590 |
+
except InvalidVersion:
|
| 591 |
+
tmpl = (
|
| 592 |
+
'Version loaded from {value} does not '
|
| 593 |
+
'comply with PEP 440: {version}'
|
| 594 |
+
)
|
| 595 |
+
raise DistutilsOptionError(tmpl.format(**locals()))
|
| 596 |
+
|
| 597 |
+
return version
|
| 598 |
+
|
| 599 |
+
version = self._parse_attr(value, self.package_dir)
|
| 600 |
+
|
| 601 |
+
if callable(version):
|
| 602 |
+
version = version()
|
| 603 |
+
|
| 604 |
+
if not isinstance(version, str):
|
| 605 |
+
if hasattr(version, '__iter__'):
|
| 606 |
+
version = '.'.join(map(str, version))
|
| 607 |
+
else:
|
| 608 |
+
version = '%s' % version
|
| 609 |
+
|
| 610 |
+
return version
|
| 611 |
+
|
| 612 |
+
|
| 613 |
+
class ConfigOptionsHandler(ConfigHandler):
|
| 614 |
+
|
| 615 |
+
section_prefix = 'options'
|
| 616 |
+
|
| 617 |
+
@property
|
| 618 |
+
def parsers(self):
|
| 619 |
+
"""Metadata item name to parser function mapping."""
|
| 620 |
+
parse_list = self._parse_list
|
| 621 |
+
parse_list_semicolon = partial(self._parse_list, separator=';')
|
| 622 |
+
parse_bool = self._parse_bool
|
| 623 |
+
parse_dict = self._parse_dict
|
| 624 |
+
parse_cmdclass = self._parse_cmdclass
|
| 625 |
+
|
| 626 |
+
return {
|
| 627 |
+
'zip_safe': parse_bool,
|
| 628 |
+
'include_package_data': parse_bool,
|
| 629 |
+
'package_dir': parse_dict,
|
| 630 |
+
'scripts': parse_list,
|
| 631 |
+
'eager_resources': parse_list,
|
| 632 |
+
'dependency_links': parse_list,
|
| 633 |
+
'namespace_packages': parse_list,
|
| 634 |
+
'install_requires': parse_list_semicolon,
|
| 635 |
+
'setup_requires': parse_list_semicolon,
|
| 636 |
+
'tests_require': parse_list_semicolon,
|
| 637 |
+
'packages': self._parse_packages,
|
| 638 |
+
'entry_points': self._parse_file,
|
| 639 |
+
'py_modules': parse_list,
|
| 640 |
+
'python_requires': SpecifierSet,
|
| 641 |
+
'cmdclass': parse_cmdclass,
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
def _parse_cmdclass(self, value):
|
| 645 |
+
def resolve_class(qualified_class_name):
|
| 646 |
+
idx = qualified_class_name.rfind('.')
|
| 647 |
+
class_name = qualified_class_name[idx + 1 :]
|
| 648 |
+
pkg_name = qualified_class_name[:idx]
|
| 649 |
+
|
| 650 |
+
module = __import__(pkg_name)
|
| 651 |
+
|
| 652 |
+
return getattr(module, class_name)
|
| 653 |
+
|
| 654 |
+
return {k: resolve_class(v) for k, v in self._parse_dict(value).items()}
|
| 655 |
+
|
| 656 |
+
def _parse_packages(self, value):
|
| 657 |
+
"""Parses `packages` option value.
|
| 658 |
+
|
| 659 |
+
:param value:
|
| 660 |
+
:rtype: list
|
| 661 |
+
"""
|
| 662 |
+
find_directives = ['find:', 'find_namespace:']
|
| 663 |
+
trimmed_value = value.strip()
|
| 664 |
+
|
| 665 |
+
if trimmed_value not in find_directives:
|
| 666 |
+
return self._parse_list(value)
|
| 667 |
+
|
| 668 |
+
findns = trimmed_value == find_directives[1]
|
| 669 |
+
|
| 670 |
+
# Read function arguments from a dedicated section.
|
| 671 |
+
find_kwargs = self.parse_section_packages__find(
|
| 672 |
+
self.sections.get('packages.find', {})
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
if findns:
|
| 676 |
+
from setuptools import find_namespace_packages as find_packages
|
| 677 |
+
else:
|
| 678 |
+
from setuptools import find_packages
|
| 679 |
+
|
| 680 |
+
return find_packages(**find_kwargs)
|
| 681 |
+
|
| 682 |
+
def parse_section_packages__find(self, section_options):
|
| 683 |
+
"""Parses `packages.find` configuration file section.
|
| 684 |
+
|
| 685 |
+
To be used in conjunction with _parse_packages().
|
| 686 |
+
|
| 687 |
+
:param dict section_options:
|
| 688 |
+
"""
|
| 689 |
+
section_data = self._parse_section_to_dict(section_options, self._parse_list)
|
| 690 |
+
|
| 691 |
+
valid_keys = ['where', 'include', 'exclude']
|
| 692 |
+
|
| 693 |
+
find_kwargs = dict(
|
| 694 |
+
[(k, v) for k, v in section_data.items() if k in valid_keys and v]
|
| 695 |
+
)
|
| 696 |
+
|
| 697 |
+
where = find_kwargs.get('where')
|
| 698 |
+
if where is not None:
|
| 699 |
+
find_kwargs['where'] = where[0] # cast list to single val
|
| 700 |
+
|
| 701 |
+
return find_kwargs
|
| 702 |
+
|
| 703 |
+
def parse_section_entry_points(self, section_options):
|
| 704 |
+
"""Parses `entry_points` configuration file section.
|
| 705 |
+
|
| 706 |
+
:param dict section_options:
|
| 707 |
+
"""
|
| 708 |
+
parsed = self._parse_section_to_dict(section_options, self._parse_list)
|
| 709 |
+
self['entry_points'] = parsed
|
| 710 |
+
|
| 711 |
+
def _parse_package_data(self, section_options):
|
| 712 |
+
parsed = self._parse_section_to_dict(section_options, self._parse_list)
|
| 713 |
+
|
| 714 |
+
root = parsed.get('*')
|
| 715 |
+
if root:
|
| 716 |
+
parsed[''] = root
|
| 717 |
+
del parsed['*']
|
| 718 |
+
|
| 719 |
+
return parsed
|
| 720 |
+
|
| 721 |
+
def parse_section_package_data(self, section_options):
|
| 722 |
+
"""Parses `package_data` configuration file section.
|
| 723 |
+
|
| 724 |
+
:param dict section_options:
|
| 725 |
+
"""
|
| 726 |
+
self['package_data'] = self._parse_package_data(section_options)
|
| 727 |
+
|
| 728 |
+
def parse_section_exclude_package_data(self, section_options):
|
| 729 |
+
"""Parses `exclude_package_data` configuration file section.
|
| 730 |
+
|
| 731 |
+
:param dict section_options:
|
| 732 |
+
"""
|
| 733 |
+
self['exclude_package_data'] = self._parse_package_data(section_options)
|
| 734 |
+
|
| 735 |
+
def parse_section_extras_require(self, section_options):
|
| 736 |
+
"""Parses `extras_require` configuration file section.
|
| 737 |
+
|
| 738 |
+
:param dict section_options:
|
| 739 |
+
"""
|
| 740 |
+
parse_list = partial(self._parse_list, separator=';')
|
| 741 |
+
self['extras_require'] = self._parse_section_to_dict(
|
| 742 |
+
section_options, parse_list
|
| 743 |
+
)
|
| 744 |
+
|
| 745 |
+
def parse_section_data_files(self, section_options):
|
| 746 |
+
"""Parses `data_files` configuration file section.
|
| 747 |
+
|
| 748 |
+
:param dict section_options:
|
| 749 |
+
"""
|
| 750 |
+
parsed = self._parse_section_to_dict(section_options, self._parse_list_glob)
|
| 751 |
+
self['data_files'] = [(k, v) for k, v in parsed.items()]
|
venv/lib/python3.10/site-packages/setuptools/dep_util.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from distutils.dep_util import newer_group
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
# yes, this is was almost entirely copy-pasted from
|
| 5 |
+
# 'newer_pairwise()', this is just another convenience
|
| 6 |
+
# function.
|
| 7 |
+
def newer_pairwise_group(sources_groups, targets):
|
| 8 |
+
"""Walk both arguments in parallel, testing if each source group is newer
|
| 9 |
+
than its corresponding target. Returns a pair of lists (sources_groups,
|
| 10 |
+
targets) where sources is newer than target, according to the semantics
|
| 11 |
+
of 'newer_group()'.
|
| 12 |
+
"""
|
| 13 |
+
if len(sources_groups) != len(targets):
|
| 14 |
+
raise ValueError(
|
| 15 |
+
"'sources_group' and 'targets' must be the same length")
|
| 16 |
+
|
| 17 |
+
# build a pair of lists (sources_groups, targets) where source is newer
|
| 18 |
+
n_sources = []
|
| 19 |
+
n_targets = []
|
| 20 |
+
for i in range(len(sources_groups)):
|
| 21 |
+
if newer_group(sources_groups[i], targets[i]):
|
| 22 |
+
n_sources.append(sources_groups[i])
|
| 23 |
+
n_targets.append(targets[i])
|
| 24 |
+
|
| 25 |
+
return n_sources, n_targets
|
venv/lib/python3.10/site-packages/setuptools/depends.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import marshal
|
| 3 |
+
import contextlib
|
| 4 |
+
import dis
|
| 5 |
+
|
| 6 |
+
from setuptools.extern.packaging import version
|
| 7 |
+
|
| 8 |
+
from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
|
| 9 |
+
from . import _imp
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
__all__ = [
|
| 13 |
+
'Require', 'find_module', 'get_module_constant', 'extract_constant'
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class Require:
|
| 18 |
+
"""A prerequisite to building or installing a distribution"""
|
| 19 |
+
|
| 20 |
+
def __init__(
|
| 21 |
+
self, name, requested_version, module, homepage='',
|
| 22 |
+
attribute=None, format=None):
|
| 23 |
+
|
| 24 |
+
if format is None and requested_version is not None:
|
| 25 |
+
format = version.Version
|
| 26 |
+
|
| 27 |
+
if format is not None:
|
| 28 |
+
requested_version = format(requested_version)
|
| 29 |
+
if attribute is None:
|
| 30 |
+
attribute = '__version__'
|
| 31 |
+
|
| 32 |
+
self.__dict__.update(locals())
|
| 33 |
+
del self.self
|
| 34 |
+
|
| 35 |
+
def full_name(self):
|
| 36 |
+
"""Return full package/distribution name, w/version"""
|
| 37 |
+
if self.requested_version is not None:
|
| 38 |
+
return '%s-%s' % (self.name, self.requested_version)
|
| 39 |
+
return self.name
|
| 40 |
+
|
| 41 |
+
def version_ok(self, version):
|
| 42 |
+
"""Is 'version' sufficiently up-to-date?"""
|
| 43 |
+
return self.attribute is None or self.format is None or \
|
| 44 |
+
str(version) != "unknown" and self.format(version) >= self.requested_version
|
| 45 |
+
|
| 46 |
+
def get_version(self, paths=None, default="unknown"):
|
| 47 |
+
"""Get version number of installed module, 'None', or 'default'
|
| 48 |
+
|
| 49 |
+
Search 'paths' for module. If not found, return 'None'. If found,
|
| 50 |
+
return the extracted version attribute, or 'default' if no version
|
| 51 |
+
attribute was specified, or the value cannot be determined without
|
| 52 |
+
importing the module. The version is formatted according to the
|
| 53 |
+
requirement's version format (if any), unless it is 'None' or the
|
| 54 |
+
supplied 'default'.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
if self.attribute is None:
|
| 58 |
+
try:
|
| 59 |
+
f, p, i = find_module(self.module, paths)
|
| 60 |
+
if f:
|
| 61 |
+
f.close()
|
| 62 |
+
return default
|
| 63 |
+
except ImportError:
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
v = get_module_constant(self.module, self.attribute, default, paths)
|
| 67 |
+
|
| 68 |
+
if v is not None and v is not default and self.format is not None:
|
| 69 |
+
return self.format(v)
|
| 70 |
+
|
| 71 |
+
return v
|
| 72 |
+
|
| 73 |
+
def is_present(self, paths=None):
|
| 74 |
+
"""Return true if dependency is present on 'paths'"""
|
| 75 |
+
return self.get_version(paths) is not None
|
| 76 |
+
|
| 77 |
+
def is_current(self, paths=None):
|
| 78 |
+
"""Return true if dependency is present and up-to-date on 'paths'"""
|
| 79 |
+
version = self.get_version(paths)
|
| 80 |
+
if version is None:
|
| 81 |
+
return False
|
| 82 |
+
return self.version_ok(str(version))
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def maybe_close(f):
|
| 86 |
+
@contextlib.contextmanager
|
| 87 |
+
def empty():
|
| 88 |
+
yield
|
| 89 |
+
return
|
| 90 |
+
if not f:
|
| 91 |
+
return empty()
|
| 92 |
+
|
| 93 |
+
return contextlib.closing(f)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def get_module_constant(module, symbol, default=-1, paths=None):
|
| 97 |
+
"""Find 'module' by searching 'paths', and extract 'symbol'
|
| 98 |
+
|
| 99 |
+
Return 'None' if 'module' does not exist on 'paths', or it does not define
|
| 100 |
+
'symbol'. If the module defines 'symbol' as a constant, return the
|
| 101 |
+
constant. Otherwise, return 'default'."""
|
| 102 |
+
|
| 103 |
+
try:
|
| 104 |
+
f, path, (suffix, mode, kind) = info = find_module(module, paths)
|
| 105 |
+
except ImportError:
|
| 106 |
+
# Module doesn't exist
|
| 107 |
+
return None
|
| 108 |
+
|
| 109 |
+
with maybe_close(f):
|
| 110 |
+
if kind == PY_COMPILED:
|
| 111 |
+
f.read(8) # skip magic & date
|
| 112 |
+
code = marshal.load(f)
|
| 113 |
+
elif kind == PY_FROZEN:
|
| 114 |
+
code = _imp.get_frozen_object(module, paths)
|
| 115 |
+
elif kind == PY_SOURCE:
|
| 116 |
+
code = compile(f.read(), path, 'exec')
|
| 117 |
+
else:
|
| 118 |
+
# Not something we can parse; we'll have to import it. :(
|
| 119 |
+
imported = _imp.get_module(module, paths, info)
|
| 120 |
+
return getattr(imported, symbol, None)
|
| 121 |
+
|
| 122 |
+
return extract_constant(code, symbol, default)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def extract_constant(code, symbol, default=-1):
|
| 126 |
+
"""Extract the constant value of 'symbol' from 'code'
|
| 127 |
+
|
| 128 |
+
If the name 'symbol' is bound to a constant value by the Python code
|
| 129 |
+
object 'code', return that value. If 'symbol' is bound to an expression,
|
| 130 |
+
return 'default'. Otherwise, return 'None'.
|
| 131 |
+
|
| 132 |
+
Return value is based on the first assignment to 'symbol'. 'symbol' must
|
| 133 |
+
be a global, or at least a non-"fast" local in the code block. That is,
|
| 134 |
+
only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
|
| 135 |
+
must be present in 'code.co_names'.
|
| 136 |
+
"""
|
| 137 |
+
if symbol not in code.co_names:
|
| 138 |
+
# name's not there, can't possibly be an assignment
|
| 139 |
+
return None
|
| 140 |
+
|
| 141 |
+
name_idx = list(code.co_names).index(symbol)
|
| 142 |
+
|
| 143 |
+
STORE_NAME = 90
|
| 144 |
+
STORE_GLOBAL = 97
|
| 145 |
+
LOAD_CONST = 100
|
| 146 |
+
|
| 147 |
+
const = default
|
| 148 |
+
|
| 149 |
+
for byte_code in dis.Bytecode(code):
|
| 150 |
+
op = byte_code.opcode
|
| 151 |
+
arg = byte_code.arg
|
| 152 |
+
|
| 153 |
+
if op == LOAD_CONST:
|
| 154 |
+
const = code.co_consts[arg]
|
| 155 |
+
elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL):
|
| 156 |
+
return const
|
| 157 |
+
else:
|
| 158 |
+
const = default
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def _update_globals():
|
| 162 |
+
"""
|
| 163 |
+
Patch the globals to remove the objects not available on some platforms.
|
| 164 |
+
|
| 165 |
+
XXX it'd be better to test assertions about bytecode instead.
|
| 166 |
+
"""
|
| 167 |
+
|
| 168 |
+
if not sys.platform.startswith('java') and sys.platform != 'cli':
|
| 169 |
+
return
|
| 170 |
+
incompatible = 'extract_constant', 'get_module_constant'
|
| 171 |
+
for name in incompatible:
|
| 172 |
+
del globals()[name]
|
| 173 |
+
__all__.remove(name)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
_update_globals()
|
venv/lib/python3.10/site-packages/setuptools/dist.py
ADDED
|
@@ -0,0 +1,1156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__all__ = ['Distribution']
|
| 3 |
+
|
| 4 |
+
import io
|
| 5 |
+
import sys
|
| 6 |
+
import re
|
| 7 |
+
import os
|
| 8 |
+
import warnings
|
| 9 |
+
import numbers
|
| 10 |
+
import distutils.log
|
| 11 |
+
import distutils.core
|
| 12 |
+
import distutils.cmd
|
| 13 |
+
import distutils.dist
|
| 14 |
+
import distutils.command
|
| 15 |
+
from distutils.util import strtobool
|
| 16 |
+
from distutils.debug import DEBUG
|
| 17 |
+
from distutils.fancy_getopt import translate_longopt
|
| 18 |
+
from glob import iglob
|
| 19 |
+
import itertools
|
| 20 |
+
import textwrap
|
| 21 |
+
from typing import List, Optional, TYPE_CHECKING
|
| 22 |
+
|
| 23 |
+
from collections import defaultdict
|
| 24 |
+
from email import message_from_file
|
| 25 |
+
|
| 26 |
+
from distutils.errors import DistutilsOptionError, DistutilsSetupError
|
| 27 |
+
from distutils.util import rfc822_escape
|
| 28 |
+
|
| 29 |
+
from setuptools.extern import packaging
|
| 30 |
+
from setuptools.extern import ordered_set
|
| 31 |
+
from setuptools.extern.more_itertools import unique_everseen
|
| 32 |
+
|
| 33 |
+
from . import SetuptoolsDeprecationWarning
|
| 34 |
+
|
| 35 |
+
import setuptools
|
| 36 |
+
import setuptools.command
|
| 37 |
+
from setuptools import windows_support
|
| 38 |
+
from setuptools.monkey import get_unpatched
|
| 39 |
+
from setuptools.config import parse_configuration
|
| 40 |
+
import pkg_resources
|
| 41 |
+
from setuptools.extern.packaging import version
|
| 42 |
+
|
| 43 |
+
if TYPE_CHECKING:
|
| 44 |
+
from email.message import Message
|
| 45 |
+
|
| 46 |
+
__import__('setuptools.extern.packaging.specifiers')
|
| 47 |
+
__import__('setuptools.extern.packaging.version')
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _get_unpatched(cls):
|
| 51 |
+
warnings.warn("Do not call this function", DistDeprecationWarning)
|
| 52 |
+
return get_unpatched(cls)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def get_metadata_version(self):
|
| 56 |
+
mv = getattr(self, 'metadata_version', None)
|
| 57 |
+
if mv is None:
|
| 58 |
+
mv = version.Version('2.1')
|
| 59 |
+
self.metadata_version = mv
|
| 60 |
+
return mv
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def rfc822_unescape(content: str) -> str:
|
| 64 |
+
"""Reverse RFC-822 escaping by removing leading whitespaces from content."""
|
| 65 |
+
lines = content.splitlines()
|
| 66 |
+
if len(lines) == 1:
|
| 67 |
+
return lines[0].lstrip()
|
| 68 |
+
return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:]))))
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _read_field_from_msg(msg: "Message", field: str) -> Optional[str]:
|
| 72 |
+
"""Read Message header field."""
|
| 73 |
+
value = msg[field]
|
| 74 |
+
if value == 'UNKNOWN':
|
| 75 |
+
return None
|
| 76 |
+
return value
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _read_field_unescaped_from_msg(msg: "Message", field: str) -> Optional[str]:
|
| 80 |
+
"""Read Message header field and apply rfc822_unescape."""
|
| 81 |
+
value = _read_field_from_msg(msg, field)
|
| 82 |
+
if value is None:
|
| 83 |
+
return value
|
| 84 |
+
return rfc822_unescape(value)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]:
|
| 88 |
+
"""Read Message header field and return all results as list."""
|
| 89 |
+
values = msg.get_all(field, None)
|
| 90 |
+
if values == []:
|
| 91 |
+
return None
|
| 92 |
+
return values
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _read_payload_from_msg(msg: "Message") -> Optional[str]:
|
| 96 |
+
value = msg.get_payload().strip()
|
| 97 |
+
if value == 'UNKNOWN':
|
| 98 |
+
return None
|
| 99 |
+
return value
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def read_pkg_file(self, file):
|
| 103 |
+
"""Reads the metadata values from a file object."""
|
| 104 |
+
msg = message_from_file(file)
|
| 105 |
+
|
| 106 |
+
self.metadata_version = version.Version(msg['metadata-version'])
|
| 107 |
+
self.name = _read_field_from_msg(msg, 'name')
|
| 108 |
+
self.version = _read_field_from_msg(msg, 'version')
|
| 109 |
+
self.description = _read_field_from_msg(msg, 'summary')
|
| 110 |
+
# we are filling author only.
|
| 111 |
+
self.author = _read_field_from_msg(msg, 'author')
|
| 112 |
+
self.maintainer = None
|
| 113 |
+
self.author_email = _read_field_from_msg(msg, 'author-email')
|
| 114 |
+
self.maintainer_email = None
|
| 115 |
+
self.url = _read_field_from_msg(msg, 'home-page')
|
| 116 |
+
self.license = _read_field_unescaped_from_msg(msg, 'license')
|
| 117 |
+
|
| 118 |
+
if 'download-url' in msg:
|
| 119 |
+
self.download_url = _read_field_from_msg(msg, 'download-url')
|
| 120 |
+
else:
|
| 121 |
+
self.download_url = None
|
| 122 |
+
|
| 123 |
+
self.long_description = _read_field_unescaped_from_msg(msg, 'description')
|
| 124 |
+
if (
|
| 125 |
+
self.long_description is None and
|
| 126 |
+
self.metadata_version >= version.Version('2.1')
|
| 127 |
+
):
|
| 128 |
+
self.long_description = _read_payload_from_msg(msg)
|
| 129 |
+
self.description = _read_field_from_msg(msg, 'summary')
|
| 130 |
+
|
| 131 |
+
if 'keywords' in msg:
|
| 132 |
+
self.keywords = _read_field_from_msg(msg, 'keywords').split(',')
|
| 133 |
+
|
| 134 |
+
self.platforms = _read_list_from_msg(msg, 'platform')
|
| 135 |
+
self.classifiers = _read_list_from_msg(msg, 'classifier')
|
| 136 |
+
|
| 137 |
+
# PEP 314 - these fields only exist in 1.1
|
| 138 |
+
if self.metadata_version == version.Version('1.1'):
|
| 139 |
+
self.requires = _read_list_from_msg(msg, 'requires')
|
| 140 |
+
self.provides = _read_list_from_msg(msg, 'provides')
|
| 141 |
+
self.obsoletes = _read_list_from_msg(msg, 'obsoletes')
|
| 142 |
+
else:
|
| 143 |
+
self.requires = None
|
| 144 |
+
self.provides = None
|
| 145 |
+
self.obsoletes = None
|
| 146 |
+
|
| 147 |
+
self.license_files = _read_list_from_msg(msg, 'license-file')
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def single_line(val):
|
| 151 |
+
"""
|
| 152 |
+
Quick and dirty validation for Summary pypa/setuptools#1390.
|
| 153 |
+
"""
|
| 154 |
+
if '\n' in val:
|
| 155 |
+
# TODO: Replace with `raise ValueError("newlines not allowed")`
|
| 156 |
+
# after reviewing #2893.
|
| 157 |
+
warnings.warn("newlines not allowed and will break in the future")
|
| 158 |
+
val = val.strip().split('\n')[0]
|
| 159 |
+
return val
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# Based on Python 3.5 version
|
| 163 |
+
def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
|
| 164 |
+
"""Write the PKG-INFO format data to a file object."""
|
| 165 |
+
version = self.get_metadata_version()
|
| 166 |
+
|
| 167 |
+
def write_field(key, value):
|
| 168 |
+
file.write("%s: %s\n" % (key, value))
|
| 169 |
+
|
| 170 |
+
write_field('Metadata-Version', str(version))
|
| 171 |
+
write_field('Name', self.get_name())
|
| 172 |
+
write_field('Version', self.get_version())
|
| 173 |
+
write_field('Summary', single_line(self.get_description()))
|
| 174 |
+
write_field('Home-page', self.get_url())
|
| 175 |
+
|
| 176 |
+
optional_fields = (
|
| 177 |
+
('Author', 'author'),
|
| 178 |
+
('Author-email', 'author_email'),
|
| 179 |
+
('Maintainer', 'maintainer'),
|
| 180 |
+
('Maintainer-email', 'maintainer_email'),
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
for field, attr in optional_fields:
|
| 184 |
+
attr_val = getattr(self, attr, None)
|
| 185 |
+
if attr_val is not None:
|
| 186 |
+
write_field(field, attr_val)
|
| 187 |
+
|
| 188 |
+
license = rfc822_escape(self.get_license())
|
| 189 |
+
write_field('License', license)
|
| 190 |
+
if self.download_url:
|
| 191 |
+
write_field('Download-URL', self.download_url)
|
| 192 |
+
for project_url in self.project_urls.items():
|
| 193 |
+
write_field('Project-URL', '%s, %s' % project_url)
|
| 194 |
+
|
| 195 |
+
keywords = ','.join(self.get_keywords())
|
| 196 |
+
if keywords:
|
| 197 |
+
write_field('Keywords', keywords)
|
| 198 |
+
|
| 199 |
+
for platform in self.get_platforms():
|
| 200 |
+
write_field('Platform', platform)
|
| 201 |
+
|
| 202 |
+
self._write_list(file, 'Classifier', self.get_classifiers())
|
| 203 |
+
|
| 204 |
+
# PEP 314
|
| 205 |
+
self._write_list(file, 'Requires', self.get_requires())
|
| 206 |
+
self._write_list(file, 'Provides', self.get_provides())
|
| 207 |
+
self._write_list(file, 'Obsoletes', self.get_obsoletes())
|
| 208 |
+
|
| 209 |
+
# Setuptools specific for PEP 345
|
| 210 |
+
if hasattr(self, 'python_requires'):
|
| 211 |
+
write_field('Requires-Python', self.python_requires)
|
| 212 |
+
|
| 213 |
+
# PEP 566
|
| 214 |
+
if self.long_description_content_type:
|
| 215 |
+
write_field('Description-Content-Type', self.long_description_content_type)
|
| 216 |
+
if self.provides_extras:
|
| 217 |
+
for extra in sorted(self.provides_extras):
|
| 218 |
+
write_field('Provides-Extra', extra)
|
| 219 |
+
|
| 220 |
+
self._write_list(file, 'License-File', self.license_files or [])
|
| 221 |
+
|
| 222 |
+
file.write("\n%s\n\n" % self.get_long_description())
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
sequence = tuple, list
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def check_importable(dist, attr, value):
|
| 229 |
+
try:
|
| 230 |
+
ep = pkg_resources.EntryPoint.parse('x=' + value)
|
| 231 |
+
assert not ep.extras
|
| 232 |
+
except (TypeError, ValueError, AttributeError, AssertionError) as e:
|
| 233 |
+
raise DistutilsSetupError(
|
| 234 |
+
"%r must be importable 'module:attrs' string (got %r)" % (attr, value)
|
| 235 |
+
) from e
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def assert_string_list(dist, attr, value):
|
| 239 |
+
"""Verify that value is a string list"""
|
| 240 |
+
try:
|
| 241 |
+
# verify that value is a list or tuple to exclude unordered
|
| 242 |
+
# or single-use iterables
|
| 243 |
+
assert isinstance(value, (list, tuple))
|
| 244 |
+
# verify that elements of value are strings
|
| 245 |
+
assert ''.join(value) != value
|
| 246 |
+
except (TypeError, ValueError, AttributeError, AssertionError) as e:
|
| 247 |
+
raise DistutilsSetupError(
|
| 248 |
+
"%r must be a list of strings (got %r)" % (attr, value)
|
| 249 |
+
) from e
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def check_nsp(dist, attr, value):
|
| 253 |
+
"""Verify that namespace packages are valid"""
|
| 254 |
+
ns_packages = value
|
| 255 |
+
assert_string_list(dist, attr, ns_packages)
|
| 256 |
+
for nsp in ns_packages:
|
| 257 |
+
if not dist.has_contents_for(nsp):
|
| 258 |
+
raise DistutilsSetupError(
|
| 259 |
+
"Distribution contains no modules or packages for "
|
| 260 |
+
+ "namespace package %r" % nsp
|
| 261 |
+
)
|
| 262 |
+
parent, sep, child = nsp.rpartition('.')
|
| 263 |
+
if parent and parent not in ns_packages:
|
| 264 |
+
distutils.log.warn(
|
| 265 |
+
"WARNING: %r is declared as a package namespace, but %r"
|
| 266 |
+
" is not: please correct this in setup.py",
|
| 267 |
+
nsp,
|
| 268 |
+
parent,
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def check_extras(dist, attr, value):
|
| 273 |
+
"""Verify that extras_require mapping is valid"""
|
| 274 |
+
try:
|
| 275 |
+
list(itertools.starmap(_check_extra, value.items()))
|
| 276 |
+
except (TypeError, ValueError, AttributeError) as e:
|
| 277 |
+
raise DistutilsSetupError(
|
| 278 |
+
"'extras_require' must be a dictionary whose values are "
|
| 279 |
+
"strings or lists of strings containing valid project/version "
|
| 280 |
+
"requirement specifiers."
|
| 281 |
+
) from e
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def _check_extra(extra, reqs):
|
| 285 |
+
name, sep, marker = extra.partition(':')
|
| 286 |
+
if marker and pkg_resources.invalid_marker(marker):
|
| 287 |
+
raise DistutilsSetupError("Invalid environment marker: " + marker)
|
| 288 |
+
list(pkg_resources.parse_requirements(reqs))
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def assert_bool(dist, attr, value):
|
| 292 |
+
"""Verify that value is True, False, 0, or 1"""
|
| 293 |
+
if bool(value) != value:
|
| 294 |
+
tmpl = "{attr!r} must be a boolean value (got {value!r})"
|
| 295 |
+
raise DistutilsSetupError(tmpl.format(attr=attr, value=value))
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def invalid_unless_false(dist, attr, value):
|
| 299 |
+
if not value:
|
| 300 |
+
warnings.warn(f"{attr} is ignored.", DistDeprecationWarning)
|
| 301 |
+
return
|
| 302 |
+
raise DistutilsSetupError(f"{attr} is invalid.")
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def check_requirements(dist, attr, value):
|
| 306 |
+
"""Verify that install_requires is a valid requirements list"""
|
| 307 |
+
try:
|
| 308 |
+
list(pkg_resources.parse_requirements(value))
|
| 309 |
+
if isinstance(value, (dict, set)):
|
| 310 |
+
raise TypeError("Unordered types are not allowed")
|
| 311 |
+
except (TypeError, ValueError) as error:
|
| 312 |
+
tmpl = (
|
| 313 |
+
"{attr!r} must be a string or list of strings "
|
| 314 |
+
"containing valid project/version requirement specifiers; {error}"
|
| 315 |
+
)
|
| 316 |
+
raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def check_specifier(dist, attr, value):
|
| 320 |
+
"""Verify that value is a valid version specifier"""
|
| 321 |
+
try:
|
| 322 |
+
packaging.specifiers.SpecifierSet(value)
|
| 323 |
+
except (packaging.specifiers.InvalidSpecifier, AttributeError) as error:
|
| 324 |
+
tmpl = (
|
| 325 |
+
"{attr!r} must be a string " "containing valid version specifiers; {error}"
|
| 326 |
+
)
|
| 327 |
+
raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def check_entry_points(dist, attr, value):
|
| 331 |
+
"""Verify that entry_points map is parseable"""
|
| 332 |
+
try:
|
| 333 |
+
pkg_resources.EntryPoint.parse_map(value)
|
| 334 |
+
except ValueError as e:
|
| 335 |
+
raise DistutilsSetupError(e) from e
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def check_test_suite(dist, attr, value):
|
| 339 |
+
if not isinstance(value, str):
|
| 340 |
+
raise DistutilsSetupError("test_suite must be a string")
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def check_package_data(dist, attr, value):
|
| 344 |
+
"""Verify that value is a dictionary of package names to glob lists"""
|
| 345 |
+
if not isinstance(value, dict):
|
| 346 |
+
raise DistutilsSetupError(
|
| 347 |
+
"{!r} must be a dictionary mapping package names to lists of "
|
| 348 |
+
"string wildcard patterns".format(attr)
|
| 349 |
+
)
|
| 350 |
+
for k, v in value.items():
|
| 351 |
+
if not isinstance(k, str):
|
| 352 |
+
raise DistutilsSetupError(
|
| 353 |
+
"keys of {!r} dict must be strings (got {!r})".format(attr, k)
|
| 354 |
+
)
|
| 355 |
+
assert_string_list(dist, 'values of {!r} dict'.format(attr), v)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def check_packages(dist, attr, value):
|
| 359 |
+
for pkgname in value:
|
| 360 |
+
if not re.match(r'\w+(\.\w+)*', pkgname):
|
| 361 |
+
distutils.log.warn(
|
| 362 |
+
"WARNING: %r not a valid package name; please use only "
|
| 363 |
+
".-separated package names in setup.py",
|
| 364 |
+
pkgname,
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
_Distribution = get_unpatched(distutils.core.Distribution)
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
class Distribution(_Distribution):
|
| 372 |
+
"""Distribution with support for tests and package data
|
| 373 |
+
|
| 374 |
+
This is an enhanced version of 'distutils.dist.Distribution' that
|
| 375 |
+
effectively adds the following new optional keyword arguments to 'setup()':
|
| 376 |
+
|
| 377 |
+
'install_requires' -- a string or sequence of strings specifying project
|
| 378 |
+
versions that the distribution requires when installed, in the format
|
| 379 |
+
used by 'pkg_resources.require()'. They will be installed
|
| 380 |
+
automatically when the package is installed. If you wish to use
|
| 381 |
+
packages that are not available in PyPI, or want to give your users an
|
| 382 |
+
alternate download location, you can add a 'find_links' option to the
|
| 383 |
+
'[easy_install]' section of your project's 'setup.cfg' file, and then
|
| 384 |
+
setuptools will scan the listed web pages for links that satisfy the
|
| 385 |
+
requirements.
|
| 386 |
+
|
| 387 |
+
'extras_require' -- a dictionary mapping names of optional "extras" to the
|
| 388 |
+
additional requirement(s) that using those extras incurs. For example,
|
| 389 |
+
this::
|
| 390 |
+
|
| 391 |
+
extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])
|
| 392 |
+
|
| 393 |
+
indicates that the distribution can optionally provide an extra
|
| 394 |
+
capability called "reST", but it can only be used if docutils and
|
| 395 |
+
reSTedit are installed. If the user installs your package using
|
| 396 |
+
EasyInstall and requests one of your extras, the corresponding
|
| 397 |
+
additional requirements will be installed if needed.
|
| 398 |
+
|
| 399 |
+
'test_suite' -- the name of a test suite to run for the 'test' command.
|
| 400 |
+
If the user runs 'python setup.py test', the package will be installed,
|
| 401 |
+
and the named test suite will be run. The format is the same as
|
| 402 |
+
would be used on a 'unittest.py' command line. That is, it is the
|
| 403 |
+
dotted name of an object to import and call to generate a test suite.
|
| 404 |
+
|
| 405 |
+
'package_data' -- a dictionary mapping package names to lists of filenames
|
| 406 |
+
or globs to use to find data files contained in the named packages.
|
| 407 |
+
If the dictionary has filenames or globs listed under '""' (the empty
|
| 408 |
+
string), those names will be searched for in every package, in addition
|
| 409 |
+
to any names for the specific package. Data files found using these
|
| 410 |
+
names/globs will be installed along with the package, in the same
|
| 411 |
+
location as the package. Note that globs are allowed to reference
|
| 412 |
+
the contents of non-package subdirectories, as long as you use '/' as
|
| 413 |
+
a path separator. (Globs are automatically converted to
|
| 414 |
+
platform-specific paths at runtime.)
|
| 415 |
+
|
| 416 |
+
In addition to these new keywords, this class also has several new methods
|
| 417 |
+
for manipulating the distribution's contents. For example, the 'include()'
|
| 418 |
+
and 'exclude()' methods can be thought of as in-place add and subtract
|
| 419 |
+
commands that add or remove packages, modules, extensions, and so on from
|
| 420 |
+
the distribution.
|
| 421 |
+
"""
|
| 422 |
+
|
| 423 |
+
_DISTUTILS_UNSUPPORTED_METADATA = {
|
| 424 |
+
'long_description_content_type': lambda: None,
|
| 425 |
+
'project_urls': dict,
|
| 426 |
+
'provides_extras': ordered_set.OrderedSet,
|
| 427 |
+
'license_file': lambda: None,
|
| 428 |
+
'license_files': lambda: None,
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
_patched_dist = None
|
| 432 |
+
|
| 433 |
+
def patch_missing_pkg_info(self, attrs):
|
| 434 |
+
# Fake up a replacement for the data that would normally come from
|
| 435 |
+
# PKG-INFO, but which might not yet be built if this is a fresh
|
| 436 |
+
# checkout.
|
| 437 |
+
#
|
| 438 |
+
if not attrs or 'name' not in attrs or 'version' not in attrs:
|
| 439 |
+
return
|
| 440 |
+
key = pkg_resources.safe_name(str(attrs['name'])).lower()
|
| 441 |
+
dist = pkg_resources.working_set.by_key.get(key)
|
| 442 |
+
if dist is not None and not dist.has_metadata('PKG-INFO'):
|
| 443 |
+
dist._version = pkg_resources.safe_version(str(attrs['version']))
|
| 444 |
+
self._patched_dist = dist
|
| 445 |
+
|
| 446 |
+
def __init__(self, attrs=None):
|
| 447 |
+
have_package_data = hasattr(self, "package_data")
|
| 448 |
+
if not have_package_data:
|
| 449 |
+
self.package_data = {}
|
| 450 |
+
attrs = attrs or {}
|
| 451 |
+
self.dist_files = []
|
| 452 |
+
# Filter-out setuptools' specific options.
|
| 453 |
+
self.src_root = attrs.pop("src_root", None)
|
| 454 |
+
self.patch_missing_pkg_info(attrs)
|
| 455 |
+
self.dependency_links = attrs.pop('dependency_links', [])
|
| 456 |
+
self.setup_requires = attrs.pop('setup_requires', [])
|
| 457 |
+
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
|
| 458 |
+
vars(self).setdefault(ep.name, None)
|
| 459 |
+
_Distribution.__init__(
|
| 460 |
+
self,
|
| 461 |
+
{
|
| 462 |
+
k: v
|
| 463 |
+
for k, v in attrs.items()
|
| 464 |
+
if k not in self._DISTUTILS_UNSUPPORTED_METADATA
|
| 465 |
+
},
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
self._set_metadata_defaults(attrs)
|
| 469 |
+
|
| 470 |
+
self.metadata.version = self._normalize_version(
|
| 471 |
+
self._validate_version(self.metadata.version)
|
| 472 |
+
)
|
| 473 |
+
self._finalize_requires()
|
| 474 |
+
|
| 475 |
+
def _set_metadata_defaults(self, attrs):
|
| 476 |
+
"""
|
| 477 |
+
Fill-in missing metadata fields not supported by distutils.
|
| 478 |
+
Some fields may have been set by other tools (e.g. pbr).
|
| 479 |
+
Those fields (vars(self.metadata)) take precedence to
|
| 480 |
+
supplied attrs.
|
| 481 |
+
"""
|
| 482 |
+
for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items():
|
| 483 |
+
vars(self.metadata).setdefault(option, attrs.get(option, default()))
|
| 484 |
+
|
| 485 |
+
@staticmethod
|
| 486 |
+
def _normalize_version(version):
|
| 487 |
+
if isinstance(version, setuptools.sic) or version is None:
|
| 488 |
+
return version
|
| 489 |
+
|
| 490 |
+
normalized = str(packaging.version.Version(version))
|
| 491 |
+
if version != normalized:
|
| 492 |
+
tmpl = "Normalizing '{version}' to '{normalized}'"
|
| 493 |
+
warnings.warn(tmpl.format(**locals()))
|
| 494 |
+
return normalized
|
| 495 |
+
return version
|
| 496 |
+
|
| 497 |
+
@staticmethod
|
| 498 |
+
def _validate_version(version):
|
| 499 |
+
if isinstance(version, numbers.Number):
|
| 500 |
+
# Some people apparently take "version number" too literally :)
|
| 501 |
+
version = str(version)
|
| 502 |
+
|
| 503 |
+
if version is not None:
|
| 504 |
+
try:
|
| 505 |
+
packaging.version.Version(version)
|
| 506 |
+
except (packaging.version.InvalidVersion, TypeError):
|
| 507 |
+
warnings.warn(
|
| 508 |
+
"The version specified (%r) is an invalid version, this "
|
| 509 |
+
"may not work as expected with newer versions of "
|
| 510 |
+
"setuptools, pip, and PyPI. Please see PEP 440 for more "
|
| 511 |
+
"details." % version
|
| 512 |
+
)
|
| 513 |
+
return setuptools.sic(version)
|
| 514 |
+
return version
|
| 515 |
+
|
| 516 |
+
def _finalize_requires(self):
|
| 517 |
+
"""
|
| 518 |
+
Set `metadata.python_requires` and fix environment markers
|
| 519 |
+
in `install_requires` and `extras_require`.
|
| 520 |
+
"""
|
| 521 |
+
if getattr(self, 'python_requires', None):
|
| 522 |
+
self.metadata.python_requires = self.python_requires
|
| 523 |
+
|
| 524 |
+
if getattr(self, 'extras_require', None):
|
| 525 |
+
for extra in self.extras_require.keys():
|
| 526 |
+
# Since this gets called multiple times at points where the
|
| 527 |
+
# keys have become 'converted' extras, ensure that we are only
|
| 528 |
+
# truly adding extras we haven't seen before here.
|
| 529 |
+
extra = extra.split(':')[0]
|
| 530 |
+
if extra:
|
| 531 |
+
self.metadata.provides_extras.add(extra)
|
| 532 |
+
|
| 533 |
+
self._convert_extras_requirements()
|
| 534 |
+
self._move_install_requirements_markers()
|
| 535 |
+
|
| 536 |
+
def _convert_extras_requirements(self):
|
| 537 |
+
"""
|
| 538 |
+
Convert requirements in `extras_require` of the form
|
| 539 |
+
`"extra": ["barbazquux; {marker}"]` to
|
| 540 |
+
`"extra:{marker}": ["barbazquux"]`.
|
| 541 |
+
"""
|
| 542 |
+
spec_ext_reqs = getattr(self, 'extras_require', None) or {}
|
| 543 |
+
self._tmp_extras_require = defaultdict(list)
|
| 544 |
+
for section, v in spec_ext_reqs.items():
|
| 545 |
+
# Do not strip empty sections.
|
| 546 |
+
self._tmp_extras_require[section]
|
| 547 |
+
for r in pkg_resources.parse_requirements(v):
|
| 548 |
+
suffix = self._suffix_for(r)
|
| 549 |
+
self._tmp_extras_require[section + suffix].append(r)
|
| 550 |
+
|
| 551 |
+
@staticmethod
|
| 552 |
+
def _suffix_for(req):
|
| 553 |
+
"""
|
| 554 |
+
For a requirement, return the 'extras_require' suffix for
|
| 555 |
+
that requirement.
|
| 556 |
+
"""
|
| 557 |
+
return ':' + str(req.marker) if req.marker else ''
|
| 558 |
+
|
| 559 |
+
def _move_install_requirements_markers(self):
|
| 560 |
+
"""
|
| 561 |
+
Move requirements in `install_requires` that are using environment
|
| 562 |
+
markers `extras_require`.
|
| 563 |
+
"""
|
| 564 |
+
|
| 565 |
+
# divide the install_requires into two sets, simple ones still
|
| 566 |
+
# handled by install_requires and more complex ones handled
|
| 567 |
+
# by extras_require.
|
| 568 |
+
|
| 569 |
+
def is_simple_req(req):
|
| 570 |
+
return not req.marker
|
| 571 |
+
|
| 572 |
+
spec_inst_reqs = getattr(self, 'install_requires', None) or ()
|
| 573 |
+
inst_reqs = list(pkg_resources.parse_requirements(spec_inst_reqs))
|
| 574 |
+
simple_reqs = filter(is_simple_req, inst_reqs)
|
| 575 |
+
complex_reqs = itertools.filterfalse(is_simple_req, inst_reqs)
|
| 576 |
+
self.install_requires = list(map(str, simple_reqs))
|
| 577 |
+
|
| 578 |
+
for r in complex_reqs:
|
| 579 |
+
self._tmp_extras_require[':' + str(r.marker)].append(r)
|
| 580 |
+
self.extras_require = dict(
|
| 581 |
+
(k, [str(r) for r in map(self._clean_req, v)])
|
| 582 |
+
for k, v in self._tmp_extras_require.items()
|
| 583 |
+
)
|
| 584 |
+
|
| 585 |
+
def _clean_req(self, req):
|
| 586 |
+
"""
|
| 587 |
+
Given a Requirement, remove environment markers and return it.
|
| 588 |
+
"""
|
| 589 |
+
req.marker = None
|
| 590 |
+
return req
|
| 591 |
+
|
| 592 |
+
def _finalize_license_files(self):
|
| 593 |
+
"""Compute names of all license files which should be included."""
|
| 594 |
+
license_files: Optional[List[str]] = self.metadata.license_files
|
| 595 |
+
patterns: List[str] = license_files if license_files else []
|
| 596 |
+
|
| 597 |
+
license_file: Optional[str] = self.metadata.license_file
|
| 598 |
+
if license_file and license_file not in patterns:
|
| 599 |
+
patterns.append(license_file)
|
| 600 |
+
|
| 601 |
+
if license_files is None and license_file is None:
|
| 602 |
+
# Default patterns match the ones wheel uses
|
| 603 |
+
# See https://wheel.readthedocs.io/en/stable/user_guide.html
|
| 604 |
+
# -> 'Including license files in the generated wheel file'
|
| 605 |
+
patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
|
| 606 |
+
|
| 607 |
+
self.metadata.license_files = list(
|
| 608 |
+
unique_everseen(self._expand_patterns(patterns))
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
@staticmethod
|
| 612 |
+
def _expand_patterns(patterns):
|
| 613 |
+
"""
|
| 614 |
+
>>> list(Distribution._expand_patterns(['LICENSE']))
|
| 615 |
+
['LICENSE']
|
| 616 |
+
>>> list(Distribution._expand_patterns(['setup.cfg', 'LIC*']))
|
| 617 |
+
['setup.cfg', 'LICENSE']
|
| 618 |
+
"""
|
| 619 |
+
return (
|
| 620 |
+
path
|
| 621 |
+
for pattern in patterns
|
| 622 |
+
for path in sorted(iglob(pattern))
|
| 623 |
+
if not path.endswith('~') and os.path.isfile(path)
|
| 624 |
+
)
|
| 625 |
+
|
| 626 |
+
# FIXME: 'Distribution._parse_config_files' is too complex (14)
|
| 627 |
+
def _parse_config_files(self, filenames=None): # noqa: C901
|
| 628 |
+
"""
|
| 629 |
+
Adapted from distutils.dist.Distribution.parse_config_files,
|
| 630 |
+
this method provides the same functionality in subtly-improved
|
| 631 |
+
ways.
|
| 632 |
+
"""
|
| 633 |
+
from configparser import ConfigParser
|
| 634 |
+
|
| 635 |
+
# Ignore install directory options if we have a venv
|
| 636 |
+
ignore_options = (
|
| 637 |
+
[]
|
| 638 |
+
if sys.prefix == sys.base_prefix
|
| 639 |
+
else [
|
| 640 |
+
'install-base',
|
| 641 |
+
'install-platbase',
|
| 642 |
+
'install-lib',
|
| 643 |
+
'install-platlib',
|
| 644 |
+
'install-purelib',
|
| 645 |
+
'install-headers',
|
| 646 |
+
'install-scripts',
|
| 647 |
+
'install-data',
|
| 648 |
+
'prefix',
|
| 649 |
+
'exec-prefix',
|
| 650 |
+
'home',
|
| 651 |
+
'user',
|
| 652 |
+
'root',
|
| 653 |
+
]
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
+
ignore_options = frozenset(ignore_options)
|
| 657 |
+
|
| 658 |
+
if filenames is None:
|
| 659 |
+
filenames = self.find_config_files()
|
| 660 |
+
|
| 661 |
+
if DEBUG:
|
| 662 |
+
self.announce("Distribution.parse_config_files():")
|
| 663 |
+
|
| 664 |
+
parser = ConfigParser()
|
| 665 |
+
parser.optionxform = str
|
| 666 |
+
for filename in filenames:
|
| 667 |
+
with io.open(filename, encoding='utf-8') as reader:
|
| 668 |
+
if DEBUG:
|
| 669 |
+
self.announce(" reading {filename}".format(**locals()))
|
| 670 |
+
parser.read_file(reader)
|
| 671 |
+
for section in parser.sections():
|
| 672 |
+
options = parser.options(section)
|
| 673 |
+
opt_dict = self.get_option_dict(section)
|
| 674 |
+
|
| 675 |
+
for opt in options:
|
| 676 |
+
if opt == '__name__' or opt in ignore_options:
|
| 677 |
+
continue
|
| 678 |
+
|
| 679 |
+
val = parser.get(section, opt)
|
| 680 |
+
opt = self.warn_dash_deprecation(opt, section)
|
| 681 |
+
opt = self.make_option_lowercase(opt, section)
|
| 682 |
+
opt_dict[opt] = (filename, val)
|
| 683 |
+
|
| 684 |
+
# Make the ConfigParser forget everything (so we retain
|
| 685 |
+
# the original filenames that options come from)
|
| 686 |
+
parser.__init__()
|
| 687 |
+
|
| 688 |
+
if 'global' not in self.command_options:
|
| 689 |
+
return
|
| 690 |
+
|
| 691 |
+
# If there was a "global" section in the config file, use it
|
| 692 |
+
# to set Distribution options.
|
| 693 |
+
|
| 694 |
+
for (opt, (src, val)) in self.command_options['global'].items():
|
| 695 |
+
alias = self.negative_opt.get(opt)
|
| 696 |
+
if alias:
|
| 697 |
+
val = not strtobool(val)
|
| 698 |
+
elif opt in ('verbose', 'dry_run'): # ugh!
|
| 699 |
+
val = strtobool(val)
|
| 700 |
+
|
| 701 |
+
try:
|
| 702 |
+
setattr(self, alias or opt, val)
|
| 703 |
+
except ValueError as e:
|
| 704 |
+
raise DistutilsOptionError(e) from e
|
| 705 |
+
|
| 706 |
+
def warn_dash_deprecation(self, opt, section):
|
| 707 |
+
if section in (
|
| 708 |
+
'options.extras_require',
|
| 709 |
+
'options.data_files',
|
| 710 |
+
):
|
| 711 |
+
return opt
|
| 712 |
+
|
| 713 |
+
underscore_opt = opt.replace('-', '_')
|
| 714 |
+
commands = distutils.command.__all__ + self._setuptools_commands()
|
| 715 |
+
if (
|
| 716 |
+
not section.startswith('options')
|
| 717 |
+
and section != 'metadata'
|
| 718 |
+
and section not in commands
|
| 719 |
+
):
|
| 720 |
+
return underscore_opt
|
| 721 |
+
|
| 722 |
+
if '-' in opt:
|
| 723 |
+
warnings.warn(
|
| 724 |
+
"Usage of dash-separated '%s' will not be supported in future "
|
| 725 |
+
"versions. Please use the underscore name '%s' instead"
|
| 726 |
+
% (opt, underscore_opt)
|
| 727 |
+
)
|
| 728 |
+
return underscore_opt
|
| 729 |
+
|
| 730 |
+
def _setuptools_commands(self):
|
| 731 |
+
try:
|
| 732 |
+
dist = pkg_resources.get_distribution('setuptools')
|
| 733 |
+
return list(dist.get_entry_map('distutils.commands'))
|
| 734 |
+
except pkg_resources.DistributionNotFound:
|
| 735 |
+
# during bootstrapping, distribution doesn't exist
|
| 736 |
+
return []
|
| 737 |
+
|
| 738 |
+
def make_option_lowercase(self, opt, section):
|
| 739 |
+
if section != 'metadata' or opt.islower():
|
| 740 |
+
return opt
|
| 741 |
+
|
| 742 |
+
lowercase_opt = opt.lower()
|
| 743 |
+
warnings.warn(
|
| 744 |
+
"Usage of uppercase key '%s' in '%s' will be deprecated in future "
|
| 745 |
+
"versions. Please use lowercase '%s' instead"
|
| 746 |
+
% (opt, section, lowercase_opt)
|
| 747 |
+
)
|
| 748 |
+
return lowercase_opt
|
| 749 |
+
|
| 750 |
+
# FIXME: 'Distribution._set_command_options' is too complex (14)
|
| 751 |
+
def _set_command_options(self, command_obj, option_dict=None): # noqa: C901
|
| 752 |
+
"""
|
| 753 |
+
Set the options for 'command_obj' from 'option_dict'. Basically
|
| 754 |
+
this means copying elements of a dictionary ('option_dict') to
|
| 755 |
+
attributes of an instance ('command').
|
| 756 |
+
|
| 757 |
+
'command_obj' must be a Command instance. If 'option_dict' is not
|
| 758 |
+
supplied, uses the standard option dictionary for this command
|
| 759 |
+
(from 'self.command_options').
|
| 760 |
+
|
| 761 |
+
(Adopted from distutils.dist.Distribution._set_command_options)
|
| 762 |
+
"""
|
| 763 |
+
command_name = command_obj.get_command_name()
|
| 764 |
+
if option_dict is None:
|
| 765 |
+
option_dict = self.get_option_dict(command_name)
|
| 766 |
+
|
| 767 |
+
if DEBUG:
|
| 768 |
+
self.announce(" setting options for '%s' command:" % command_name)
|
| 769 |
+
for (option, (source, value)) in option_dict.items():
|
| 770 |
+
if DEBUG:
|
| 771 |
+
self.announce(" %s = %s (from %s)" % (option, value, source))
|
| 772 |
+
try:
|
| 773 |
+
bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]
|
| 774 |
+
except AttributeError:
|
| 775 |
+
bool_opts = []
|
| 776 |
+
try:
|
| 777 |
+
neg_opt = command_obj.negative_opt
|
| 778 |
+
except AttributeError:
|
| 779 |
+
neg_opt = {}
|
| 780 |
+
|
| 781 |
+
try:
|
| 782 |
+
is_string = isinstance(value, str)
|
| 783 |
+
if option in neg_opt and is_string:
|
| 784 |
+
setattr(command_obj, neg_opt[option], not strtobool(value))
|
| 785 |
+
elif option in bool_opts and is_string:
|
| 786 |
+
setattr(command_obj, option, strtobool(value))
|
| 787 |
+
elif hasattr(command_obj, option):
|
| 788 |
+
setattr(command_obj, option, value)
|
| 789 |
+
else:
|
| 790 |
+
raise DistutilsOptionError(
|
| 791 |
+
"error in %s: command '%s' has no such option '%s'"
|
| 792 |
+
% (source, command_name, option)
|
| 793 |
+
)
|
| 794 |
+
except ValueError as e:
|
| 795 |
+
raise DistutilsOptionError(e) from e
|
| 796 |
+
|
| 797 |
+
def parse_config_files(self, filenames=None, ignore_option_errors=False):
|
| 798 |
+
"""Parses configuration files from various levels
|
| 799 |
+
and loads configuration.
|
| 800 |
+
|
| 801 |
+
"""
|
| 802 |
+
self._parse_config_files(filenames=filenames)
|
| 803 |
+
|
| 804 |
+
parse_configuration(
|
| 805 |
+
self, self.command_options, ignore_option_errors=ignore_option_errors
|
| 806 |
+
)
|
| 807 |
+
self._finalize_requires()
|
| 808 |
+
self._finalize_license_files()
|
| 809 |
+
|
| 810 |
+
def fetch_build_eggs(self, requires):
|
| 811 |
+
"""Resolve pre-setup requirements"""
|
| 812 |
+
resolved_dists = pkg_resources.working_set.resolve(
|
| 813 |
+
pkg_resources.parse_requirements(requires),
|
| 814 |
+
installer=self.fetch_build_egg,
|
| 815 |
+
replace_conflicting=True,
|
| 816 |
+
)
|
| 817 |
+
for dist in resolved_dists:
|
| 818 |
+
pkg_resources.working_set.add(dist, replace=True)
|
| 819 |
+
return resolved_dists
|
| 820 |
+
|
| 821 |
+
def finalize_options(self):
|
| 822 |
+
"""
|
| 823 |
+
Allow plugins to apply arbitrary operations to the
|
| 824 |
+
distribution. Each hook may optionally define a 'order'
|
| 825 |
+
to influence the order of execution. Smaller numbers
|
| 826 |
+
go first and the default is 0.
|
| 827 |
+
"""
|
| 828 |
+
group = 'setuptools.finalize_distribution_options'
|
| 829 |
+
|
| 830 |
+
def by_order(hook):
|
| 831 |
+
return getattr(hook, 'order', 0)
|
| 832 |
+
|
| 833 |
+
defined = pkg_resources.iter_entry_points(group)
|
| 834 |
+
filtered = itertools.filterfalse(self._removed, defined)
|
| 835 |
+
loaded = map(lambda e: e.load(), filtered)
|
| 836 |
+
for ep in sorted(loaded, key=by_order):
|
| 837 |
+
ep(self)
|
| 838 |
+
|
| 839 |
+
@staticmethod
|
| 840 |
+
def _removed(ep):
|
| 841 |
+
"""
|
| 842 |
+
When removing an entry point, if metadata is loaded
|
| 843 |
+
from an older version of Setuptools, that removed
|
| 844 |
+
entry point will attempt to be loaded and will fail.
|
| 845 |
+
See #2765 for more details.
|
| 846 |
+
"""
|
| 847 |
+
removed = {
|
| 848 |
+
# removed 2021-09-05
|
| 849 |
+
'2to3_doctests',
|
| 850 |
+
}
|
| 851 |
+
return ep.name in removed
|
| 852 |
+
|
| 853 |
+
def _finalize_setup_keywords(self):
|
| 854 |
+
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
|
| 855 |
+
value = getattr(self, ep.name, None)
|
| 856 |
+
if value is not None:
|
| 857 |
+
ep.require(installer=self.fetch_build_egg)
|
| 858 |
+
ep.load()(self, ep.name, value)
|
| 859 |
+
|
| 860 |
+
def get_egg_cache_dir(self):
|
| 861 |
+
egg_cache_dir = os.path.join(os.curdir, '.eggs')
|
| 862 |
+
if not os.path.exists(egg_cache_dir):
|
| 863 |
+
os.mkdir(egg_cache_dir)
|
| 864 |
+
windows_support.hide_file(egg_cache_dir)
|
| 865 |
+
readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt')
|
| 866 |
+
with open(readme_txt_filename, 'w') as f:
|
| 867 |
+
f.write(
|
| 868 |
+
'This directory contains eggs that were downloaded '
|
| 869 |
+
'by setuptools to build, test, and run plug-ins.\n\n'
|
| 870 |
+
)
|
| 871 |
+
f.write(
|
| 872 |
+
'This directory caches those eggs to prevent '
|
| 873 |
+
'repeated downloads.\n\n'
|
| 874 |
+
)
|
| 875 |
+
f.write('However, it is safe to delete this directory.\n\n')
|
| 876 |
+
|
| 877 |
+
return egg_cache_dir
|
| 878 |
+
|
| 879 |
+
def fetch_build_egg(self, req):
|
| 880 |
+
"""Fetch an egg needed for building"""
|
| 881 |
+
from setuptools.installer import fetch_build_egg
|
| 882 |
+
|
| 883 |
+
return fetch_build_egg(self, req)
|
| 884 |
+
|
| 885 |
+
def get_command_class(self, command):
|
| 886 |
+
"""Pluggable version of get_command_class()"""
|
| 887 |
+
if command in self.cmdclass:
|
| 888 |
+
return self.cmdclass[command]
|
| 889 |
+
|
| 890 |
+
eps = pkg_resources.iter_entry_points('distutils.commands', command)
|
| 891 |
+
for ep in eps:
|
| 892 |
+
ep.require(installer=self.fetch_build_egg)
|
| 893 |
+
self.cmdclass[command] = cmdclass = ep.load()
|
| 894 |
+
return cmdclass
|
| 895 |
+
else:
|
| 896 |
+
return _Distribution.get_command_class(self, command)
|
| 897 |
+
|
| 898 |
+
def print_commands(self):
|
| 899 |
+
for ep in pkg_resources.iter_entry_points('distutils.commands'):
|
| 900 |
+
if ep.name not in self.cmdclass:
|
| 901 |
+
# don't require extras as the commands won't be invoked
|
| 902 |
+
cmdclass = ep.resolve()
|
| 903 |
+
self.cmdclass[ep.name] = cmdclass
|
| 904 |
+
return _Distribution.print_commands(self)
|
| 905 |
+
|
| 906 |
+
def get_command_list(self):
|
| 907 |
+
for ep in pkg_resources.iter_entry_points('distutils.commands'):
|
| 908 |
+
if ep.name not in self.cmdclass:
|
| 909 |
+
# don't require extras as the commands won't be invoked
|
| 910 |
+
cmdclass = ep.resolve()
|
| 911 |
+
self.cmdclass[ep.name] = cmdclass
|
| 912 |
+
return _Distribution.get_command_list(self)
|
| 913 |
+
|
| 914 |
+
def include(self, **attrs):
|
| 915 |
+
"""Add items to distribution that are named in keyword arguments
|
| 916 |
+
|
| 917 |
+
For example, 'dist.include(py_modules=["x"])' would add 'x' to
|
| 918 |
+
the distribution's 'py_modules' attribute, if it was not already
|
| 919 |
+
there.
|
| 920 |
+
|
| 921 |
+
Currently, this method only supports inclusion for attributes that are
|
| 922 |
+
lists or tuples. If you need to add support for adding to other
|
| 923 |
+
attributes in this or a subclass, you can add an '_include_X' method,
|
| 924 |
+
where 'X' is the name of the attribute. The method will be called with
|
| 925 |
+
the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})'
|
| 926 |
+
will try to call 'dist._include_foo({"bar":"baz"})', which can then
|
| 927 |
+
handle whatever special inclusion logic is needed.
|
| 928 |
+
"""
|
| 929 |
+
for k, v in attrs.items():
|
| 930 |
+
include = getattr(self, '_include_' + k, None)
|
| 931 |
+
if include:
|
| 932 |
+
include(v)
|
| 933 |
+
else:
|
| 934 |
+
self._include_misc(k, v)
|
| 935 |
+
|
| 936 |
+
def exclude_package(self, package):
|
| 937 |
+
"""Remove packages, modules, and extensions in named package"""
|
| 938 |
+
|
| 939 |
+
pfx = package + '.'
|
| 940 |
+
if self.packages:
|
| 941 |
+
self.packages = [
|
| 942 |
+
p for p in self.packages if p != package and not p.startswith(pfx)
|
| 943 |
+
]
|
| 944 |
+
|
| 945 |
+
if self.py_modules:
|
| 946 |
+
self.py_modules = [
|
| 947 |
+
p for p in self.py_modules if p != package and not p.startswith(pfx)
|
| 948 |
+
]
|
| 949 |
+
|
| 950 |
+
if self.ext_modules:
|
| 951 |
+
self.ext_modules = [
|
| 952 |
+
p
|
| 953 |
+
for p in self.ext_modules
|
| 954 |
+
if p.name != package and not p.name.startswith(pfx)
|
| 955 |
+
]
|
| 956 |
+
|
| 957 |
+
def has_contents_for(self, package):
|
| 958 |
+
"""Return true if 'exclude_package(package)' would do something"""
|
| 959 |
+
|
| 960 |
+
pfx = package + '.'
|
| 961 |
+
|
| 962 |
+
for p in self.iter_distribution_names():
|
| 963 |
+
if p == package or p.startswith(pfx):
|
| 964 |
+
return True
|
| 965 |
+
|
| 966 |
+
def _exclude_misc(self, name, value):
|
| 967 |
+
"""Handle 'exclude()' for list/tuple attrs without a special handler"""
|
| 968 |
+
if not isinstance(value, sequence):
|
| 969 |
+
raise DistutilsSetupError(
|
| 970 |
+
"%s: setting must be a list or tuple (%r)" % (name, value)
|
| 971 |
+
)
|
| 972 |
+
try:
|
| 973 |
+
old = getattr(self, name)
|
| 974 |
+
except AttributeError as e:
|
| 975 |
+
raise DistutilsSetupError("%s: No such distribution setting" % name) from e
|
| 976 |
+
if old is not None and not isinstance(old, sequence):
|
| 977 |
+
raise DistutilsSetupError(
|
| 978 |
+
name + ": this setting cannot be changed via include/exclude"
|
| 979 |
+
)
|
| 980 |
+
elif old:
|
| 981 |
+
setattr(self, name, [item for item in old if item not in value])
|
| 982 |
+
|
| 983 |
+
def _include_misc(self, name, value):
|
| 984 |
+
"""Handle 'include()' for list/tuple attrs without a special handler"""
|
| 985 |
+
|
| 986 |
+
if not isinstance(value, sequence):
|
| 987 |
+
raise DistutilsSetupError("%s: setting must be a list (%r)" % (name, value))
|
| 988 |
+
try:
|
| 989 |
+
old = getattr(self, name)
|
| 990 |
+
except AttributeError as e:
|
| 991 |
+
raise DistutilsSetupError("%s: No such distribution setting" % name) from e
|
| 992 |
+
if old is None:
|
| 993 |
+
setattr(self, name, value)
|
| 994 |
+
elif not isinstance(old, sequence):
|
| 995 |
+
raise DistutilsSetupError(
|
| 996 |
+
name + ": this setting cannot be changed via include/exclude"
|
| 997 |
+
)
|
| 998 |
+
else:
|
| 999 |
+
new = [item for item in value if item not in old]
|
| 1000 |
+
setattr(self, name, old + new)
|
| 1001 |
+
|
| 1002 |
+
def exclude(self, **attrs):
|
| 1003 |
+
"""Remove items from distribution that are named in keyword arguments
|
| 1004 |
+
|
| 1005 |
+
For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from
|
| 1006 |
+
the distribution's 'py_modules' attribute. Excluding packages uses
|
| 1007 |
+
the 'exclude_package()' method, so all of the package's contained
|
| 1008 |
+
packages, modules, and extensions are also excluded.
|
| 1009 |
+
|
| 1010 |
+
Currently, this method only supports exclusion from attributes that are
|
| 1011 |
+
lists or tuples. If you need to add support for excluding from other
|
| 1012 |
+
attributes in this or a subclass, you can add an '_exclude_X' method,
|
| 1013 |
+
where 'X' is the name of the attribute. The method will be called with
|
| 1014 |
+
the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})'
|
| 1015 |
+
will try to call 'dist._exclude_foo({"bar":"baz"})', which can then
|
| 1016 |
+
handle whatever special exclusion logic is needed.
|
| 1017 |
+
"""
|
| 1018 |
+
for k, v in attrs.items():
|
| 1019 |
+
exclude = getattr(self, '_exclude_' + k, None)
|
| 1020 |
+
if exclude:
|
| 1021 |
+
exclude(v)
|
| 1022 |
+
else:
|
| 1023 |
+
self._exclude_misc(k, v)
|
| 1024 |
+
|
| 1025 |
+
def _exclude_packages(self, packages):
|
| 1026 |
+
if not isinstance(packages, sequence):
|
| 1027 |
+
raise DistutilsSetupError(
|
| 1028 |
+
"packages: setting must be a list or tuple (%r)" % (packages,)
|
| 1029 |
+
)
|
| 1030 |
+
list(map(self.exclude_package, packages))
|
| 1031 |
+
|
| 1032 |
+
def _parse_command_opts(self, parser, args):
|
| 1033 |
+
# Remove --with-X/--without-X options when processing command args
|
| 1034 |
+
self.global_options = self.__class__.global_options
|
| 1035 |
+
self.negative_opt = self.__class__.negative_opt
|
| 1036 |
+
|
| 1037 |
+
# First, expand any aliases
|
| 1038 |
+
command = args[0]
|
| 1039 |
+
aliases = self.get_option_dict('aliases')
|
| 1040 |
+
while command in aliases:
|
| 1041 |
+
src, alias = aliases[command]
|
| 1042 |
+
del aliases[command] # ensure each alias can expand only once!
|
| 1043 |
+
import shlex
|
| 1044 |
+
|
| 1045 |
+
args[:1] = shlex.split(alias, True)
|
| 1046 |
+
command = args[0]
|
| 1047 |
+
|
| 1048 |
+
nargs = _Distribution._parse_command_opts(self, parser, args)
|
| 1049 |
+
|
| 1050 |
+
# Handle commands that want to consume all remaining arguments
|
| 1051 |
+
cmd_class = self.get_command_class(command)
|
| 1052 |
+
if getattr(cmd_class, 'command_consumes_arguments', None):
|
| 1053 |
+
self.get_option_dict(command)['args'] = ("command line", nargs)
|
| 1054 |
+
if nargs is not None:
|
| 1055 |
+
return []
|
| 1056 |
+
|
| 1057 |
+
return nargs
|
| 1058 |
+
|
| 1059 |
+
def get_cmdline_options(self):
|
| 1060 |
+
"""Return a '{cmd: {opt:val}}' map of all command-line options
|
| 1061 |
+
|
| 1062 |
+
Option names are all long, but do not include the leading '--', and
|
| 1063 |
+
contain dashes rather than underscores. If the option doesn't take
|
| 1064 |
+
an argument (e.g. '--quiet'), the 'val' is 'None'.
|
| 1065 |
+
|
| 1066 |
+
Note that options provided by config files are intentionally excluded.
|
| 1067 |
+
"""
|
| 1068 |
+
|
| 1069 |
+
d = {}
|
| 1070 |
+
|
| 1071 |
+
for cmd, opts in self.command_options.items():
|
| 1072 |
+
|
| 1073 |
+
for opt, (src, val) in opts.items():
|
| 1074 |
+
|
| 1075 |
+
if src != "command line":
|
| 1076 |
+
continue
|
| 1077 |
+
|
| 1078 |
+
opt = opt.replace('_', '-')
|
| 1079 |
+
|
| 1080 |
+
if val == 0:
|
| 1081 |
+
cmdobj = self.get_command_obj(cmd)
|
| 1082 |
+
neg_opt = self.negative_opt.copy()
|
| 1083 |
+
neg_opt.update(getattr(cmdobj, 'negative_opt', {}))
|
| 1084 |
+
for neg, pos in neg_opt.items():
|
| 1085 |
+
if pos == opt:
|
| 1086 |
+
opt = neg
|
| 1087 |
+
val = None
|
| 1088 |
+
break
|
| 1089 |
+
else:
|
| 1090 |
+
raise AssertionError("Shouldn't be able to get here")
|
| 1091 |
+
|
| 1092 |
+
elif val == 1:
|
| 1093 |
+
val = None
|
| 1094 |
+
|
| 1095 |
+
d.setdefault(cmd, {})[opt] = val
|
| 1096 |
+
|
| 1097 |
+
return d
|
| 1098 |
+
|
| 1099 |
+
def iter_distribution_names(self):
|
| 1100 |
+
"""Yield all packages, modules, and extension names in distribution"""
|
| 1101 |
+
|
| 1102 |
+
for pkg in self.packages or ():
|
| 1103 |
+
yield pkg
|
| 1104 |
+
|
| 1105 |
+
for module in self.py_modules or ():
|
| 1106 |
+
yield module
|
| 1107 |
+
|
| 1108 |
+
for ext in self.ext_modules or ():
|
| 1109 |
+
if isinstance(ext, tuple):
|
| 1110 |
+
name, buildinfo = ext
|
| 1111 |
+
else:
|
| 1112 |
+
name = ext.name
|
| 1113 |
+
if name.endswith('module'):
|
| 1114 |
+
name = name[:-6]
|
| 1115 |
+
yield name
|
| 1116 |
+
|
| 1117 |
+
def handle_display_options(self, option_order):
|
| 1118 |
+
"""If there were any non-global "display-only" options
|
| 1119 |
+
(--help-commands or the metadata display options) on the command
|
| 1120 |
+
line, display the requested info and return true; else return
|
| 1121 |
+
false.
|
| 1122 |
+
"""
|
| 1123 |
+
import sys
|
| 1124 |
+
|
| 1125 |
+
if self.help_commands:
|
| 1126 |
+
return _Distribution.handle_display_options(self, option_order)
|
| 1127 |
+
|
| 1128 |
+
# Stdout may be StringIO (e.g. in tests)
|
| 1129 |
+
if not isinstance(sys.stdout, io.TextIOWrapper):
|
| 1130 |
+
return _Distribution.handle_display_options(self, option_order)
|
| 1131 |
+
|
| 1132 |
+
# Don't wrap stdout if utf-8 is already the encoding. Provides
|
| 1133 |
+
# workaround for #334.
|
| 1134 |
+
if sys.stdout.encoding.lower() in ('utf-8', 'utf8'):
|
| 1135 |
+
return _Distribution.handle_display_options(self, option_order)
|
| 1136 |
+
|
| 1137 |
+
# Print metadata in UTF-8 no matter the platform
|
| 1138 |
+
encoding = sys.stdout.encoding
|
| 1139 |
+
errors = sys.stdout.errors
|
| 1140 |
+
newline = sys.platform != 'win32' and '\n' or None
|
| 1141 |
+
line_buffering = sys.stdout.line_buffering
|
| 1142 |
+
|
| 1143 |
+
sys.stdout = io.TextIOWrapper(
|
| 1144 |
+
sys.stdout.detach(), 'utf-8', errors, newline, line_buffering
|
| 1145 |
+
)
|
| 1146 |
+
try:
|
| 1147 |
+
return _Distribution.handle_display_options(self, option_order)
|
| 1148 |
+
finally:
|
| 1149 |
+
sys.stdout = io.TextIOWrapper(
|
| 1150 |
+
sys.stdout.detach(), encoding, errors, newline, line_buffering
|
| 1151 |
+
)
|
| 1152 |
+
|
| 1153 |
+
|
| 1154 |
+
class DistDeprecationWarning(SetuptoolsDeprecationWarning):
|
| 1155 |
+
"""Class for warning about deprecations in dist in
|
| 1156 |
+
setuptools. Not ignored by default, unlike DeprecationWarning."""
|
venv/lib/python3.10/site-packages/setuptools/errors.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""setuptools.errors
|
| 2 |
+
|
| 3 |
+
Provides exceptions used by setuptools modules.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from distutils import errors as _distutils_errors
|
| 7 |
+
from distutils.errors import DistutilsError
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class RemovedCommandError(DistutilsError, RuntimeError):
|
| 11 |
+
"""Error used for commands that have been removed in setuptools.
|
| 12 |
+
|
| 13 |
+
Since ``setuptools`` is built on ``distutils``, simply removing a command
|
| 14 |
+
from ``setuptools`` will make the behavior fall back to ``distutils``; this
|
| 15 |
+
error is raised if a command exists in ``distutils`` but has been actively
|
| 16 |
+
removed in ``setuptools``.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Re-export errors from distutils to facilitate the migration to PEP632
|
| 21 |
+
|
| 22 |
+
ByteCompileError = _distutils_errors.DistutilsByteCompileError
|
| 23 |
+
CCompilerError = _distutils_errors.CCompilerError
|
| 24 |
+
ClassError = _distutils_errors.DistutilsClassError
|
| 25 |
+
CompileError = _distutils_errors.CompileError
|
| 26 |
+
ExecError = _distutils_errors.DistutilsExecError
|
| 27 |
+
FileError = _distutils_errors.DistutilsFileError
|
| 28 |
+
InternalError = _distutils_errors.DistutilsInternalError
|
| 29 |
+
LibError = _distutils_errors.LibError
|
| 30 |
+
LinkError = _distutils_errors.LinkError
|
| 31 |
+
ModuleError = _distutils_errors.DistutilsModuleError
|
| 32 |
+
OptionError = _distutils_errors.DistutilsOptionError
|
| 33 |
+
PlatformError = _distutils_errors.DistutilsPlatformError
|
| 34 |
+
PreprocessError = _distutils_errors.PreprocessError
|
| 35 |
+
SetupError = _distutils_errors.DistutilsSetupError
|
| 36 |
+
TemplateError = _distutils_errors.DistutilsTemplateError
|
| 37 |
+
UnknownFileError = _distutils_errors.UnknownFileError
|
| 38 |
+
|
| 39 |
+
# The root error class in the hierarchy
|
| 40 |
+
BaseError = _distutils_errors.DistutilsError
|
venv/lib/python3.10/site-packages/setuptools/extension.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import functools
|
| 3 |
+
import distutils.core
|
| 4 |
+
import distutils.errors
|
| 5 |
+
import distutils.extension
|
| 6 |
+
|
| 7 |
+
from .monkey import get_unpatched
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _have_cython():
|
| 11 |
+
"""
|
| 12 |
+
Return True if Cython can be imported.
|
| 13 |
+
"""
|
| 14 |
+
cython_impl = 'Cython.Distutils.build_ext'
|
| 15 |
+
try:
|
| 16 |
+
# from (cython_impl) import build_ext
|
| 17 |
+
__import__(cython_impl, fromlist=['build_ext']).build_ext
|
| 18 |
+
return True
|
| 19 |
+
except Exception:
|
| 20 |
+
pass
|
| 21 |
+
return False
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# for compatibility
|
| 25 |
+
have_pyrex = _have_cython
|
| 26 |
+
|
| 27 |
+
_Extension = get_unpatched(distutils.core.Extension)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class Extension(_Extension):
|
| 31 |
+
"""Extension that uses '.c' files in place of '.pyx' files"""
|
| 32 |
+
|
| 33 |
+
def __init__(self, name, sources, *args, **kw):
|
| 34 |
+
# The *args is needed for compatibility as calls may use positional
|
| 35 |
+
# arguments. py_limited_api may be set only via keyword.
|
| 36 |
+
self.py_limited_api = kw.pop("py_limited_api", False)
|
| 37 |
+
_Extension.__init__(self, name, sources, *args, **kw)
|
| 38 |
+
|
| 39 |
+
def _convert_pyx_sources_to_lang(self):
|
| 40 |
+
"""
|
| 41 |
+
Replace sources with .pyx extensions to sources with the target
|
| 42 |
+
language extension. This mechanism allows language authors to supply
|
| 43 |
+
pre-converted sources but to prefer the .pyx sources.
|
| 44 |
+
"""
|
| 45 |
+
if _have_cython():
|
| 46 |
+
# the build has Cython, so allow it to compile the .pyx files
|
| 47 |
+
return
|
| 48 |
+
lang = self.language or ''
|
| 49 |
+
target_ext = '.cpp' if lang.lower() == 'c++' else '.c'
|
| 50 |
+
sub = functools.partial(re.sub, '.pyx$', target_ext)
|
| 51 |
+
self.sources = list(map(sub, self.sources))
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class Library(Extension):
|
| 55 |
+
"""Just like a regular Extension, but built as a library instead"""
|
venv/lib/python3.10/site-packages/setuptools/glob.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Filename globbing utility. Mostly a copy of `glob` from Python 3.5.
|
| 3 |
+
|
| 4 |
+
Changes include:
|
| 5 |
+
* `yield from` and PEP3102 `*` removed.
|
| 6 |
+
* Hidden files are not ignored.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import re
|
| 11 |
+
import fnmatch
|
| 12 |
+
|
| 13 |
+
__all__ = ["glob", "iglob", "escape"]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def glob(pathname, recursive=False):
|
| 17 |
+
"""Return a list of paths matching a pathname pattern.
|
| 18 |
+
|
| 19 |
+
The pattern may contain simple shell-style wildcards a la
|
| 20 |
+
fnmatch. However, unlike fnmatch, filenames starting with a
|
| 21 |
+
dot are special cases that are not matched by '*' and '?'
|
| 22 |
+
patterns.
|
| 23 |
+
|
| 24 |
+
If recursive is true, the pattern '**' will match any files and
|
| 25 |
+
zero or more directories and subdirectories.
|
| 26 |
+
"""
|
| 27 |
+
return list(iglob(pathname, recursive=recursive))
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def iglob(pathname, recursive=False):
|
| 31 |
+
"""Return an iterator which yields the paths matching a pathname pattern.
|
| 32 |
+
|
| 33 |
+
The pattern may contain simple shell-style wildcards a la
|
| 34 |
+
fnmatch. However, unlike fnmatch, filenames starting with a
|
| 35 |
+
dot are special cases that are not matched by '*' and '?'
|
| 36 |
+
patterns.
|
| 37 |
+
|
| 38 |
+
If recursive is true, the pattern '**' will match any files and
|
| 39 |
+
zero or more directories and subdirectories.
|
| 40 |
+
"""
|
| 41 |
+
it = _iglob(pathname, recursive)
|
| 42 |
+
if recursive and _isrecursive(pathname):
|
| 43 |
+
s = next(it) # skip empty string
|
| 44 |
+
assert not s
|
| 45 |
+
return it
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _iglob(pathname, recursive):
|
| 49 |
+
dirname, basename = os.path.split(pathname)
|
| 50 |
+
glob_in_dir = glob2 if recursive and _isrecursive(basename) else glob1
|
| 51 |
+
|
| 52 |
+
if not has_magic(pathname):
|
| 53 |
+
if basename:
|
| 54 |
+
if os.path.lexists(pathname):
|
| 55 |
+
yield pathname
|
| 56 |
+
else:
|
| 57 |
+
# Patterns ending with a slash should match only directories
|
| 58 |
+
if os.path.isdir(dirname):
|
| 59 |
+
yield pathname
|
| 60 |
+
return
|
| 61 |
+
|
| 62 |
+
if not dirname:
|
| 63 |
+
yield from glob_in_dir(dirname, basename)
|
| 64 |
+
return
|
| 65 |
+
# `os.path.split()` returns the argument itself as a dirname if it is a
|
| 66 |
+
# drive or UNC path. Prevent an infinite recursion if a drive or UNC path
|
| 67 |
+
# contains magic characters (i.e. r'\\?\C:').
|
| 68 |
+
if dirname != pathname and has_magic(dirname):
|
| 69 |
+
dirs = _iglob(dirname, recursive)
|
| 70 |
+
else:
|
| 71 |
+
dirs = [dirname]
|
| 72 |
+
if not has_magic(basename):
|
| 73 |
+
glob_in_dir = glob0
|
| 74 |
+
for dirname in dirs:
|
| 75 |
+
for name in glob_in_dir(dirname, basename):
|
| 76 |
+
yield os.path.join(dirname, name)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# These 2 helper functions non-recursively glob inside a literal directory.
|
| 80 |
+
# They return a list of basenames. `glob1` accepts a pattern while `glob0`
|
| 81 |
+
# takes a literal basename (so it only has to check for its existence).
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def glob1(dirname, pattern):
|
| 85 |
+
if not dirname:
|
| 86 |
+
if isinstance(pattern, bytes):
|
| 87 |
+
dirname = os.curdir.encode('ASCII')
|
| 88 |
+
else:
|
| 89 |
+
dirname = os.curdir
|
| 90 |
+
try:
|
| 91 |
+
names = os.listdir(dirname)
|
| 92 |
+
except OSError:
|
| 93 |
+
return []
|
| 94 |
+
return fnmatch.filter(names, pattern)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def glob0(dirname, basename):
|
| 98 |
+
if not basename:
|
| 99 |
+
# `os.path.split()` returns an empty basename for paths ending with a
|
| 100 |
+
# directory separator. 'q*x/' should match only directories.
|
| 101 |
+
if os.path.isdir(dirname):
|
| 102 |
+
return [basename]
|
| 103 |
+
else:
|
| 104 |
+
if os.path.lexists(os.path.join(dirname, basename)):
|
| 105 |
+
return [basename]
|
| 106 |
+
return []
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# This helper function recursively yields relative pathnames inside a literal
|
| 110 |
+
# directory.
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def glob2(dirname, pattern):
|
| 114 |
+
assert _isrecursive(pattern)
|
| 115 |
+
yield pattern[:0]
|
| 116 |
+
for x in _rlistdir(dirname):
|
| 117 |
+
yield x
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# Recursively yields relative pathnames inside a literal directory.
|
| 121 |
+
def _rlistdir(dirname):
|
| 122 |
+
if not dirname:
|
| 123 |
+
if isinstance(dirname, bytes):
|
| 124 |
+
dirname = os.curdir.encode('ASCII')
|
| 125 |
+
else:
|
| 126 |
+
dirname = os.curdir
|
| 127 |
+
try:
|
| 128 |
+
names = os.listdir(dirname)
|
| 129 |
+
except os.error:
|
| 130 |
+
return
|
| 131 |
+
for x in names:
|
| 132 |
+
yield x
|
| 133 |
+
path = os.path.join(dirname, x) if dirname else x
|
| 134 |
+
for y in _rlistdir(path):
|
| 135 |
+
yield os.path.join(x, y)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
magic_check = re.compile('([*?[])')
|
| 139 |
+
magic_check_bytes = re.compile(b'([*?[])')
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def has_magic(s):
|
| 143 |
+
if isinstance(s, bytes):
|
| 144 |
+
match = magic_check_bytes.search(s)
|
| 145 |
+
else:
|
| 146 |
+
match = magic_check.search(s)
|
| 147 |
+
return match is not None
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def _isrecursive(pattern):
|
| 151 |
+
if isinstance(pattern, bytes):
|
| 152 |
+
return pattern == b'**'
|
| 153 |
+
else:
|
| 154 |
+
return pattern == '**'
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def escape(pathname):
|
| 158 |
+
"""Escape all special characters.
|
| 159 |
+
"""
|
| 160 |
+
# Escaping is done by wrapping any of "*?[" between square brackets.
|
| 161 |
+
# Metacharacters do not work in the drive part and shouldn't be escaped.
|
| 162 |
+
drive, pathname = os.path.splitdrive(pathname)
|
| 163 |
+
if isinstance(pathname, bytes):
|
| 164 |
+
pathname = magic_check_bytes.sub(br'[\1]', pathname)
|
| 165 |
+
else:
|
| 166 |
+
pathname = magic_check.sub(r'[\1]', pathname)
|
| 167 |
+
return drive + pathname
|
venv/lib/python3.10/site-packages/setuptools/gui-32.exe
ADDED
|
Binary file (65.5 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/gui-64.exe
ADDED
|
Binary file (75.3 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/gui.exe
ADDED
|
Binary file (65.5 kB). View file
|
|
|
venv/lib/python3.10/site-packages/setuptools/installer.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import glob
|
| 2 |
+
import os
|
| 3 |
+
import subprocess
|
| 4 |
+
import sys
|
| 5 |
+
import tempfile
|
| 6 |
+
import warnings
|
| 7 |
+
from distutils import log
|
| 8 |
+
from distutils.errors import DistutilsError
|
| 9 |
+
|
| 10 |
+
import pkg_resources
|
| 11 |
+
from setuptools.wheel import Wheel
|
| 12 |
+
from ._deprecation_warning import SetuptoolsDeprecationWarning
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _fixup_find_links(find_links):
|
| 16 |
+
"""Ensure find-links option end-up being a list of strings."""
|
| 17 |
+
if isinstance(find_links, str):
|
| 18 |
+
return find_links.split()
|
| 19 |
+
assert isinstance(find_links, (tuple, list))
|
| 20 |
+
return find_links
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def fetch_build_egg(dist, req): # noqa: C901 # is too complex (16) # FIXME
|
| 24 |
+
"""Fetch an egg needed for building.
|
| 25 |
+
|
| 26 |
+
Use pip/wheel to fetch/build a wheel."""
|
| 27 |
+
warnings.warn(
|
| 28 |
+
"setuptools.installer is deprecated. Requirements should "
|
| 29 |
+
"be satisfied by a PEP 517 installer.",
|
| 30 |
+
SetuptoolsDeprecationWarning,
|
| 31 |
+
)
|
| 32 |
+
# Warn if wheel is not available
|
| 33 |
+
try:
|
| 34 |
+
pkg_resources.get_distribution('wheel')
|
| 35 |
+
except pkg_resources.DistributionNotFound:
|
| 36 |
+
dist.announce('WARNING: The wheel package is not available.', log.WARN)
|
| 37 |
+
# Ignore environment markers; if supplied, it is required.
|
| 38 |
+
req = strip_marker(req)
|
| 39 |
+
# Take easy_install options into account, but do not override relevant
|
| 40 |
+
# pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll
|
| 41 |
+
# take precedence.
|
| 42 |
+
opts = dist.get_option_dict('easy_install')
|
| 43 |
+
if 'allow_hosts' in opts:
|
| 44 |
+
raise DistutilsError('the `allow-hosts` option is not supported '
|
| 45 |
+
'when using pip to install requirements.')
|
| 46 |
+
quiet = 'PIP_QUIET' not in os.environ and 'PIP_VERBOSE' not in os.environ
|
| 47 |
+
if 'PIP_INDEX_URL' in os.environ:
|
| 48 |
+
index_url = None
|
| 49 |
+
elif 'index_url' in opts:
|
| 50 |
+
index_url = opts['index_url'][1]
|
| 51 |
+
else:
|
| 52 |
+
index_url = None
|
| 53 |
+
find_links = (
|
| 54 |
+
_fixup_find_links(opts['find_links'][1])[:] if 'find_links' in opts
|
| 55 |
+
else []
|
| 56 |
+
)
|
| 57 |
+
if dist.dependency_links:
|
| 58 |
+
find_links.extend(dist.dependency_links)
|
| 59 |
+
eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
|
| 60 |
+
environment = pkg_resources.Environment()
|
| 61 |
+
for egg_dist in pkg_resources.find_distributions(eggs_dir):
|
| 62 |
+
if egg_dist in req and environment.can_add(egg_dist):
|
| 63 |
+
return egg_dist
|
| 64 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 65 |
+
cmd = [
|
| 66 |
+
sys.executable, '-m', 'pip',
|
| 67 |
+
'--disable-pip-version-check',
|
| 68 |
+
'wheel', '--no-deps',
|
| 69 |
+
'-w', tmpdir,
|
| 70 |
+
]
|
| 71 |
+
if quiet:
|
| 72 |
+
cmd.append('--quiet')
|
| 73 |
+
if index_url is not None:
|
| 74 |
+
cmd.extend(('--index-url', index_url))
|
| 75 |
+
for link in find_links or []:
|
| 76 |
+
cmd.extend(('--find-links', link))
|
| 77 |
+
# If requirement is a PEP 508 direct URL, directly pass
|
| 78 |
+
# the URL to pip, as `req @ url` does not work on the
|
| 79 |
+
# command line.
|
| 80 |
+
cmd.append(req.url or str(req))
|
| 81 |
+
try:
|
| 82 |
+
subprocess.check_call(cmd)
|
| 83 |
+
except subprocess.CalledProcessError as e:
|
| 84 |
+
raise DistutilsError(str(e)) from e
|
| 85 |
+
wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
|
| 86 |
+
dist_location = os.path.join(eggs_dir, wheel.egg_name())
|
| 87 |
+
wheel.install_as_egg(dist_location)
|
| 88 |
+
dist_metadata = pkg_resources.PathMetadata(
|
| 89 |
+
dist_location, os.path.join(dist_location, 'EGG-INFO'))
|
| 90 |
+
dist = pkg_resources.Distribution.from_filename(
|
| 91 |
+
dist_location, metadata=dist_metadata)
|
| 92 |
+
return dist
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def strip_marker(req):
|
| 96 |
+
"""
|
| 97 |
+
Return a new requirement without the environment marker to avoid
|
| 98 |
+
calling pip with something like `babel; extra == "i18n"`, which
|
| 99 |
+
would always be ignored.
|
| 100 |
+
"""
|
| 101 |
+
# create a copy to avoid mutating the input
|
| 102 |
+
req = pkg_resources.Requirement.parse(str(req))
|
| 103 |
+
req.marker = None
|
| 104 |
+
return req
|
venv/lib/python3.10/site-packages/setuptools/launch.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Launch the Python script on the command line after
|
| 3 |
+
setuptools is bootstrapped via import.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
# Note that setuptools gets imported implicitly by the
|
| 7 |
+
# invocation of this script using python -m setuptools.launch
|
| 8 |
+
|
| 9 |
+
import tokenize
|
| 10 |
+
import sys
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def run():
|
| 14 |
+
"""
|
| 15 |
+
Run the script in sys.argv[1] as if it had
|
| 16 |
+
been invoked naturally.
|
| 17 |
+
"""
|
| 18 |
+
__builtins__
|
| 19 |
+
script_name = sys.argv[1]
|
| 20 |
+
namespace = dict(
|
| 21 |
+
__file__=script_name,
|
| 22 |
+
__name__='__main__',
|
| 23 |
+
__doc__=None,
|
| 24 |
+
)
|
| 25 |
+
sys.argv[:] = sys.argv[1:]
|
| 26 |
+
|
| 27 |
+
open_ = getattr(tokenize, 'open', open)
|
| 28 |
+
with open_(script_name) as fid:
|
| 29 |
+
script = fid.read()
|
| 30 |
+
norm_script = script.replace('\\r\\n', '\\n')
|
| 31 |
+
code = compile(norm_script, script_name, 'exec')
|
| 32 |
+
exec(code, namespace)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
if __name__ == '__main__':
|
| 36 |
+
run()
|
venv/lib/python3.10/site-packages/setuptools/monkey.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Monkey patching of distutils.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import sys
|
| 6 |
+
import distutils.filelist
|
| 7 |
+
import platform
|
| 8 |
+
import types
|
| 9 |
+
import functools
|
| 10 |
+
from importlib import import_module
|
| 11 |
+
import inspect
|
| 12 |
+
|
| 13 |
+
import setuptools
|
| 14 |
+
|
| 15 |
+
__all__ = []
|
| 16 |
+
"""
|
| 17 |
+
Everything is private. Contact the project team
|
| 18 |
+
if you think you need this functionality.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _get_mro(cls):
|
| 23 |
+
"""
|
| 24 |
+
Returns the bases classes for cls sorted by the MRO.
|
| 25 |
+
|
| 26 |
+
Works around an issue on Jython where inspect.getmro will not return all
|
| 27 |
+
base classes if multiple classes share the same name. Instead, this
|
| 28 |
+
function will return a tuple containing the class itself, and the contents
|
| 29 |
+
of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024.
|
| 30 |
+
"""
|
| 31 |
+
if platform.python_implementation() == "Jython":
|
| 32 |
+
return (cls,) + cls.__bases__
|
| 33 |
+
return inspect.getmro(cls)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def get_unpatched(item):
|
| 37 |
+
lookup = (
|
| 38 |
+
get_unpatched_class if isinstance(item, type) else
|
| 39 |
+
get_unpatched_function if isinstance(item, types.FunctionType) else
|
| 40 |
+
lambda item: None
|
| 41 |
+
)
|
| 42 |
+
return lookup(item)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_unpatched_class(cls):
|
| 46 |
+
"""Protect against re-patching the distutils if reloaded
|
| 47 |
+
|
| 48 |
+
Also ensures that no other distutils extension monkeypatched the distutils
|
| 49 |
+
first.
|
| 50 |
+
"""
|
| 51 |
+
external_bases = (
|
| 52 |
+
cls
|
| 53 |
+
for cls in _get_mro(cls)
|
| 54 |
+
if not cls.__module__.startswith('setuptools')
|
| 55 |
+
)
|
| 56 |
+
base = next(external_bases)
|
| 57 |
+
if not base.__module__.startswith('distutils'):
|
| 58 |
+
msg = "distutils has already been patched by %r" % cls
|
| 59 |
+
raise AssertionError(msg)
|
| 60 |
+
return base
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def patch_all():
|
| 64 |
+
# we can't patch distutils.cmd, alas
|
| 65 |
+
distutils.core.Command = setuptools.Command
|
| 66 |
+
|
| 67 |
+
has_issue_12885 = sys.version_info <= (3, 5, 3)
|
| 68 |
+
|
| 69 |
+
if has_issue_12885:
|
| 70 |
+
# fix findall bug in distutils (http://bugs.python.org/issue12885)
|
| 71 |
+
distutils.filelist.findall = setuptools.findall
|
| 72 |
+
|
| 73 |
+
needs_warehouse = (
|
| 74 |
+
sys.version_info < (2, 7, 13)
|
| 75 |
+
or
|
| 76 |
+
(3, 4) < sys.version_info < (3, 4, 6)
|
| 77 |
+
or
|
| 78 |
+
(3, 5) < sys.version_info <= (3, 5, 3)
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
if needs_warehouse:
|
| 82 |
+
warehouse = 'https://upload.pypi.org/legacy/'
|
| 83 |
+
distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse
|
| 84 |
+
|
| 85 |
+
_patch_distribution_metadata()
|
| 86 |
+
|
| 87 |
+
# Install Distribution throughout the distutils
|
| 88 |
+
for module in distutils.dist, distutils.core, distutils.cmd:
|
| 89 |
+
module.Distribution = setuptools.dist.Distribution
|
| 90 |
+
|
| 91 |
+
# Install the patched Extension
|
| 92 |
+
distutils.core.Extension = setuptools.extension.Extension
|
| 93 |
+
distutils.extension.Extension = setuptools.extension.Extension
|
| 94 |
+
if 'distutils.command.build_ext' in sys.modules:
|
| 95 |
+
sys.modules['distutils.command.build_ext'].Extension = (
|
| 96 |
+
setuptools.extension.Extension
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
patch_for_msvc_specialized_compiler()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _patch_distribution_metadata():
|
| 103 |
+
"""Patch write_pkg_file and read_pkg_file for higher metadata standards"""
|
| 104 |
+
for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'):
|
| 105 |
+
new_val = getattr(setuptools.dist, attr)
|
| 106 |
+
setattr(distutils.dist.DistributionMetadata, attr, new_val)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def patch_func(replacement, target_mod, func_name):
|
| 110 |
+
"""
|
| 111 |
+
Patch func_name in target_mod with replacement
|
| 112 |
+
|
| 113 |
+
Important - original must be resolved by name to avoid
|
| 114 |
+
patching an already patched function.
|
| 115 |
+
"""
|
| 116 |
+
original = getattr(target_mod, func_name)
|
| 117 |
+
|
| 118 |
+
# set the 'unpatched' attribute on the replacement to
|
| 119 |
+
# point to the original.
|
| 120 |
+
vars(replacement).setdefault('unpatched', original)
|
| 121 |
+
|
| 122 |
+
# replace the function in the original module
|
| 123 |
+
setattr(target_mod, func_name, replacement)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def get_unpatched_function(candidate):
|
| 127 |
+
return getattr(candidate, 'unpatched')
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def patch_for_msvc_specialized_compiler():
|
| 131 |
+
"""
|
| 132 |
+
Patch functions in distutils to use standalone Microsoft Visual C++
|
| 133 |
+
compilers.
|
| 134 |
+
"""
|
| 135 |
+
# import late to avoid circular imports on Python < 3.5
|
| 136 |
+
msvc = import_module('setuptools.msvc')
|
| 137 |
+
|
| 138 |
+
if platform.system() != 'Windows':
|
| 139 |
+
# Compilers only available on Microsoft Windows
|
| 140 |
+
return
|
| 141 |
+
|
| 142 |
+
def patch_params(mod_name, func_name):
|
| 143 |
+
"""
|
| 144 |
+
Prepare the parameters for patch_func to patch indicated function.
|
| 145 |
+
"""
|
| 146 |
+
repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_'
|
| 147 |
+
repl_name = repl_prefix + func_name.lstrip('_')
|
| 148 |
+
repl = getattr(msvc, repl_name)
|
| 149 |
+
mod = import_module(mod_name)
|
| 150 |
+
if not hasattr(mod, func_name):
|
| 151 |
+
raise ImportError(func_name)
|
| 152 |
+
return repl, mod, func_name
|
| 153 |
+
|
| 154 |
+
# Python 2.7 to 3.4
|
| 155 |
+
msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler')
|
| 156 |
+
|
| 157 |
+
# Python 3.5+
|
| 158 |
+
msvc14 = functools.partial(patch_params, 'distutils._msvccompiler')
|
| 159 |
+
|
| 160 |
+
try:
|
| 161 |
+
# Patch distutils.msvc9compiler
|
| 162 |
+
patch_func(*msvc9('find_vcvarsall'))
|
| 163 |
+
patch_func(*msvc9('query_vcvarsall'))
|
| 164 |
+
except ImportError:
|
| 165 |
+
pass
|
| 166 |
+
|
| 167 |
+
try:
|
| 168 |
+
# Patch distutils._msvccompiler._get_vc_env
|
| 169 |
+
patch_func(*msvc14('_get_vc_env'))
|
| 170 |
+
except ImportError:
|
| 171 |
+
pass
|
| 172 |
+
|
| 173 |
+
try:
|
| 174 |
+
# Patch distutils._msvccompiler.gen_lib_options for Numpy
|
| 175 |
+
patch_func(*msvc14('gen_lib_options'))
|
| 176 |
+
except ImportError:
|
| 177 |
+
pass
|
venv/lib/python3.10/site-packages/setuptools/msvc.py
ADDED
|
@@ -0,0 +1,1805 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Improved support for Microsoft Visual C++ compilers.
|
| 3 |
+
|
| 4 |
+
Known supported compilers:
|
| 5 |
+
--------------------------
|
| 6 |
+
Microsoft Visual C++ 9.0:
|
| 7 |
+
Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
|
| 8 |
+
Microsoft Windows SDK 6.1 (x86, x64, ia64)
|
| 9 |
+
Microsoft Windows SDK 7.0 (x86, x64, ia64)
|
| 10 |
+
|
| 11 |
+
Microsoft Visual C++ 10.0:
|
| 12 |
+
Microsoft Windows SDK 7.1 (x86, x64, ia64)
|
| 13 |
+
|
| 14 |
+
Microsoft Visual C++ 14.X:
|
| 15 |
+
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
|
| 16 |
+
Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
|
| 17 |
+
Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)
|
| 18 |
+
|
| 19 |
+
This may also support compilers shipped with compatible Visual Studio versions.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import json
|
| 23 |
+
from io import open
|
| 24 |
+
from os import listdir, pathsep
|
| 25 |
+
from os.path import join, isfile, isdir, dirname
|
| 26 |
+
import sys
|
| 27 |
+
import contextlib
|
| 28 |
+
import platform
|
| 29 |
+
import itertools
|
| 30 |
+
import subprocess
|
| 31 |
+
import distutils.errors
|
| 32 |
+
from setuptools.extern.packaging.version import LegacyVersion
|
| 33 |
+
from setuptools.extern.more_itertools import unique_everseen
|
| 34 |
+
|
| 35 |
+
from .monkey import get_unpatched
|
| 36 |
+
|
| 37 |
+
if platform.system() == 'Windows':
|
| 38 |
+
import winreg
|
| 39 |
+
from os import environ
|
| 40 |
+
else:
|
| 41 |
+
# Mock winreg and environ so the module can be imported on this platform.
|
| 42 |
+
|
| 43 |
+
class winreg:
|
| 44 |
+
HKEY_USERS = None
|
| 45 |
+
HKEY_CURRENT_USER = None
|
| 46 |
+
HKEY_LOCAL_MACHINE = None
|
| 47 |
+
HKEY_CLASSES_ROOT = None
|
| 48 |
+
|
| 49 |
+
environ = dict()
|
| 50 |
+
|
| 51 |
+
_msvc9_suppress_errors = (
|
| 52 |
+
# msvc9compiler isn't available on some platforms
|
| 53 |
+
ImportError,
|
| 54 |
+
|
| 55 |
+
# msvc9compiler raises DistutilsPlatformError in some
|
| 56 |
+
# environments. See #1118.
|
| 57 |
+
distutils.errors.DistutilsPlatformError,
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
from distutils.msvc9compiler import Reg
|
| 62 |
+
except _msvc9_suppress_errors:
|
| 63 |
+
pass
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def msvc9_find_vcvarsall(version):
|
| 67 |
+
"""
|
| 68 |
+
Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
|
| 69 |
+
compiler build for Python
|
| 70 |
+
(VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
|
| 71 |
+
|
| 72 |
+
Fall back to original behavior when the standalone compiler is not
|
| 73 |
+
available.
|
| 74 |
+
|
| 75 |
+
Redirect the path of "vcvarsall.bat".
|
| 76 |
+
|
| 77 |
+
Parameters
|
| 78 |
+
----------
|
| 79 |
+
version: float
|
| 80 |
+
Required Microsoft Visual C++ version.
|
| 81 |
+
|
| 82 |
+
Return
|
| 83 |
+
------
|
| 84 |
+
str
|
| 85 |
+
vcvarsall.bat path
|
| 86 |
+
"""
|
| 87 |
+
vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
|
| 88 |
+
key = vc_base % ('', version)
|
| 89 |
+
try:
|
| 90 |
+
# Per-user installs register the compiler path here
|
| 91 |
+
productdir = Reg.get_value(key, "installdir")
|
| 92 |
+
except KeyError:
|
| 93 |
+
try:
|
| 94 |
+
# All-user installs on a 64-bit system register here
|
| 95 |
+
key = vc_base % ('Wow6432Node\\', version)
|
| 96 |
+
productdir = Reg.get_value(key, "installdir")
|
| 97 |
+
except KeyError:
|
| 98 |
+
productdir = None
|
| 99 |
+
|
| 100 |
+
if productdir:
|
| 101 |
+
vcvarsall = join(productdir, "vcvarsall.bat")
|
| 102 |
+
if isfile(vcvarsall):
|
| 103 |
+
return vcvarsall
|
| 104 |
+
|
| 105 |
+
return get_unpatched(msvc9_find_vcvarsall)(version)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
|
| 109 |
+
"""
|
| 110 |
+
Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
|
| 111 |
+
Microsoft Visual C++ 9.0 and 10.0 compilers.
|
| 112 |
+
|
| 113 |
+
Set environment without use of "vcvarsall.bat".
|
| 114 |
+
|
| 115 |
+
Parameters
|
| 116 |
+
----------
|
| 117 |
+
ver: float
|
| 118 |
+
Required Microsoft Visual C++ version.
|
| 119 |
+
arch: str
|
| 120 |
+
Target architecture.
|
| 121 |
+
|
| 122 |
+
Return
|
| 123 |
+
------
|
| 124 |
+
dict
|
| 125 |
+
environment
|
| 126 |
+
"""
|
| 127 |
+
# Try to get environment from vcvarsall.bat (Classical way)
|
| 128 |
+
try:
|
| 129 |
+
orig = get_unpatched(msvc9_query_vcvarsall)
|
| 130 |
+
return orig(ver, arch, *args, **kwargs)
|
| 131 |
+
except distutils.errors.DistutilsPlatformError:
|
| 132 |
+
# Pass error if Vcvarsall.bat is missing
|
| 133 |
+
pass
|
| 134 |
+
except ValueError:
|
| 135 |
+
# Pass error if environment not set after executing vcvarsall.bat
|
| 136 |
+
pass
|
| 137 |
+
|
| 138 |
+
# If error, try to set environment directly
|
| 139 |
+
try:
|
| 140 |
+
return EnvironmentInfo(arch, ver).return_env()
|
| 141 |
+
except distutils.errors.DistutilsPlatformError as exc:
|
| 142 |
+
_augment_exception(exc, ver, arch)
|
| 143 |
+
raise
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def _msvc14_find_vc2015():
|
| 147 |
+
"""Python 3.8 "distutils/_msvccompiler.py" backport"""
|
| 148 |
+
try:
|
| 149 |
+
key = winreg.OpenKey(
|
| 150 |
+
winreg.HKEY_LOCAL_MACHINE,
|
| 151 |
+
r"Software\Microsoft\VisualStudio\SxS\VC7",
|
| 152 |
+
0,
|
| 153 |
+
winreg.KEY_READ | winreg.KEY_WOW64_32KEY
|
| 154 |
+
)
|
| 155 |
+
except OSError:
|
| 156 |
+
return None, None
|
| 157 |
+
|
| 158 |
+
best_version = 0
|
| 159 |
+
best_dir = None
|
| 160 |
+
with key:
|
| 161 |
+
for i in itertools.count():
|
| 162 |
+
try:
|
| 163 |
+
v, vc_dir, vt = winreg.EnumValue(key, i)
|
| 164 |
+
except OSError:
|
| 165 |
+
break
|
| 166 |
+
if v and vt == winreg.REG_SZ and isdir(vc_dir):
|
| 167 |
+
try:
|
| 168 |
+
version = int(float(v))
|
| 169 |
+
except (ValueError, TypeError):
|
| 170 |
+
continue
|
| 171 |
+
if version >= 14 and version > best_version:
|
| 172 |
+
best_version, best_dir = version, vc_dir
|
| 173 |
+
return best_version, best_dir
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _msvc14_find_vc2017():
|
| 177 |
+
"""Python 3.8 "distutils/_msvccompiler.py" backport
|
| 178 |
+
|
| 179 |
+
Returns "15, path" based on the result of invoking vswhere.exe
|
| 180 |
+
If no install is found, returns "None, None"
|
| 181 |
+
|
| 182 |
+
The version is returned to avoid unnecessarily changing the function
|
| 183 |
+
result. It may be ignored when the path is not None.
|
| 184 |
+
|
| 185 |
+
If vswhere.exe is not available, by definition, VS 2017 is not
|
| 186 |
+
installed.
|
| 187 |
+
"""
|
| 188 |
+
root = environ.get("ProgramFiles(x86)") or environ.get("ProgramFiles")
|
| 189 |
+
if not root:
|
| 190 |
+
return None, None
|
| 191 |
+
|
| 192 |
+
try:
|
| 193 |
+
path = subprocess.check_output([
|
| 194 |
+
join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),
|
| 195 |
+
"-latest",
|
| 196 |
+
"-prerelease",
|
| 197 |
+
"-requiresAny",
|
| 198 |
+
"-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
| 199 |
+
"-requires", "Microsoft.VisualStudio.Workload.WDExpress",
|
| 200 |
+
"-property", "installationPath",
|
| 201 |
+
"-products", "*",
|
| 202 |
+
]).decode(encoding="mbcs", errors="strict").strip()
|
| 203 |
+
except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
|
| 204 |
+
return None, None
|
| 205 |
+
|
| 206 |
+
path = join(path, "VC", "Auxiliary", "Build")
|
| 207 |
+
if isdir(path):
|
| 208 |
+
return 15, path
|
| 209 |
+
|
| 210 |
+
return None, None
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
PLAT_SPEC_TO_RUNTIME = {
|
| 214 |
+
'x86': 'x86',
|
| 215 |
+
'x86_amd64': 'x64',
|
| 216 |
+
'x86_arm': 'arm',
|
| 217 |
+
'x86_arm64': 'arm64'
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def _msvc14_find_vcvarsall(plat_spec):
|
| 222 |
+
"""Python 3.8 "distutils/_msvccompiler.py" backport"""
|
| 223 |
+
_, best_dir = _msvc14_find_vc2017()
|
| 224 |
+
vcruntime = None
|
| 225 |
+
|
| 226 |
+
if plat_spec in PLAT_SPEC_TO_RUNTIME:
|
| 227 |
+
vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec]
|
| 228 |
+
else:
|
| 229 |
+
vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
|
| 230 |
+
|
| 231 |
+
if best_dir:
|
| 232 |
+
vcredist = join(best_dir, "..", "..", "redist", "MSVC", "**",
|
| 233 |
+
vcruntime_plat, "Microsoft.VC14*.CRT",
|
| 234 |
+
"vcruntime140.dll")
|
| 235 |
+
try:
|
| 236 |
+
import glob
|
| 237 |
+
vcruntime = glob.glob(vcredist, recursive=True)[-1]
|
| 238 |
+
except (ImportError, OSError, LookupError):
|
| 239 |
+
vcruntime = None
|
| 240 |
+
|
| 241 |
+
if not best_dir:
|
| 242 |
+
best_version, best_dir = _msvc14_find_vc2015()
|
| 243 |
+
if best_version:
|
| 244 |
+
vcruntime = join(best_dir, 'redist', vcruntime_plat,
|
| 245 |
+
"Microsoft.VC140.CRT", "vcruntime140.dll")
|
| 246 |
+
|
| 247 |
+
if not best_dir:
|
| 248 |
+
return None, None
|
| 249 |
+
|
| 250 |
+
vcvarsall = join(best_dir, "vcvarsall.bat")
|
| 251 |
+
if not isfile(vcvarsall):
|
| 252 |
+
return None, None
|
| 253 |
+
|
| 254 |
+
if not vcruntime or not isfile(vcruntime):
|
| 255 |
+
vcruntime = None
|
| 256 |
+
|
| 257 |
+
return vcvarsall, vcruntime
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def _msvc14_get_vc_env(plat_spec):
|
| 261 |
+
"""Python 3.8 "distutils/_msvccompiler.py" backport"""
|
| 262 |
+
if "DISTUTILS_USE_SDK" in environ:
|
| 263 |
+
return {
|
| 264 |
+
key.lower(): value
|
| 265 |
+
for key, value in environ.items()
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
vcvarsall, vcruntime = _msvc14_find_vcvarsall(plat_spec)
|
| 269 |
+
if not vcvarsall:
|
| 270 |
+
raise distutils.errors.DistutilsPlatformError(
|
| 271 |
+
"Unable to find vcvarsall.bat"
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
try:
|
| 275 |
+
out = subprocess.check_output(
|
| 276 |
+
'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
|
| 277 |
+
stderr=subprocess.STDOUT,
|
| 278 |
+
).decode('utf-16le', errors='replace')
|
| 279 |
+
except subprocess.CalledProcessError as exc:
|
| 280 |
+
raise distutils.errors.DistutilsPlatformError(
|
| 281 |
+
"Error executing {}".format(exc.cmd)
|
| 282 |
+
) from exc
|
| 283 |
+
|
| 284 |
+
env = {
|
| 285 |
+
key.lower(): value
|
| 286 |
+
for key, _, value in
|
| 287 |
+
(line.partition('=') for line in out.splitlines())
|
| 288 |
+
if key and value
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
if vcruntime:
|
| 292 |
+
env['py_vcruntime_redist'] = vcruntime
|
| 293 |
+
return env
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def msvc14_get_vc_env(plat_spec):
|
| 297 |
+
"""
|
| 298 |
+
Patched "distutils._msvccompiler._get_vc_env" for support extra
|
| 299 |
+
Microsoft Visual C++ 14.X compilers.
|
| 300 |
+
|
| 301 |
+
Set environment without use of "vcvarsall.bat".
|
| 302 |
+
|
| 303 |
+
Parameters
|
| 304 |
+
----------
|
| 305 |
+
plat_spec: str
|
| 306 |
+
Target architecture.
|
| 307 |
+
|
| 308 |
+
Return
|
| 309 |
+
------
|
| 310 |
+
dict
|
| 311 |
+
environment
|
| 312 |
+
"""
|
| 313 |
+
|
| 314 |
+
# Always use backport from CPython 3.8
|
| 315 |
+
try:
|
| 316 |
+
return _msvc14_get_vc_env(plat_spec)
|
| 317 |
+
except distutils.errors.DistutilsPlatformError as exc:
|
| 318 |
+
_augment_exception(exc, 14.0)
|
| 319 |
+
raise
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def msvc14_gen_lib_options(*args, **kwargs):
|
| 323 |
+
"""
|
| 324 |
+
Patched "distutils._msvccompiler.gen_lib_options" for fix
|
| 325 |
+
compatibility between "numpy.distutils" and "distutils._msvccompiler"
|
| 326 |
+
(for Numpy < 1.11.2)
|
| 327 |
+
"""
|
| 328 |
+
if "numpy.distutils" in sys.modules:
|
| 329 |
+
import numpy as np
|
| 330 |
+
if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'):
|
| 331 |
+
return np.distutils.ccompiler.gen_lib_options(*args, **kwargs)
|
| 332 |
+
return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs)
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def _augment_exception(exc, version, arch=''):
|
| 336 |
+
"""
|
| 337 |
+
Add details to the exception message to help guide the user
|
| 338 |
+
as to what action will resolve it.
|
| 339 |
+
"""
|
| 340 |
+
# Error if MSVC++ directory not found or environment not set
|
| 341 |
+
message = exc.args[0]
|
| 342 |
+
|
| 343 |
+
if "vcvarsall" in message.lower() or "visual c" in message.lower():
|
| 344 |
+
# Special error message if MSVC++ not installed
|
| 345 |
+
tmpl = 'Microsoft Visual C++ {version:0.1f} or greater is required.'
|
| 346 |
+
message = tmpl.format(**locals())
|
| 347 |
+
msdownload = 'www.microsoft.com/download/details.aspx?id=%d'
|
| 348 |
+
if version == 9.0:
|
| 349 |
+
if arch.lower().find('ia64') > -1:
|
| 350 |
+
# For VC++ 9.0, if IA64 support is needed, redirect user
|
| 351 |
+
# to Windows SDK 7.0.
|
| 352 |
+
# Note: No download link available from Microsoft.
|
| 353 |
+
message += ' Get it with "Microsoft Windows SDK 7.0"'
|
| 354 |
+
else:
|
| 355 |
+
# For VC++ 9.0 redirect user to Vc++ for Python 2.7 :
|
| 356 |
+
# This redirection link is maintained by Microsoft.
|
| 357 |
+
# Contact vspython@microsoft.com if it needs updating.
|
| 358 |
+
message += ' Get it from http://aka.ms/vcpython27'
|
| 359 |
+
elif version == 10.0:
|
| 360 |
+
# For VC++ 10.0 Redirect user to Windows SDK 7.1
|
| 361 |
+
message += ' Get it with "Microsoft Windows SDK 7.1": '
|
| 362 |
+
message += msdownload % 8279
|
| 363 |
+
elif version >= 14.0:
|
| 364 |
+
# For VC++ 14.X Redirect user to latest Visual C++ Build Tools
|
| 365 |
+
message += (' Get it with "Microsoft C++ Build Tools": '
|
| 366 |
+
r'https://visualstudio.microsoft.com'
|
| 367 |
+
r'/visual-cpp-build-tools/')
|
| 368 |
+
|
| 369 |
+
exc.args = (message, )
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
class PlatformInfo:
|
| 373 |
+
"""
|
| 374 |
+
Current and Target Architectures information.
|
| 375 |
+
|
| 376 |
+
Parameters
|
| 377 |
+
----------
|
| 378 |
+
arch: str
|
| 379 |
+
Target architecture.
|
| 380 |
+
"""
|
| 381 |
+
current_cpu = environ.get('processor_architecture', '').lower()
|
| 382 |
+
|
| 383 |
+
def __init__(self, arch):
|
| 384 |
+
self.arch = arch.lower().replace('x64', 'amd64')
|
| 385 |
+
|
| 386 |
+
@property
|
| 387 |
+
def target_cpu(self):
|
| 388 |
+
"""
|
| 389 |
+
Return Target CPU architecture.
|
| 390 |
+
|
| 391 |
+
Return
|
| 392 |
+
------
|
| 393 |
+
str
|
| 394 |
+
Target CPU
|
| 395 |
+
"""
|
| 396 |
+
return self.arch[self.arch.find('_') + 1:]
|
| 397 |
+
|
| 398 |
+
def target_is_x86(self):
|
| 399 |
+
"""
|
| 400 |
+
Return True if target CPU is x86 32 bits..
|
| 401 |
+
|
| 402 |
+
Return
|
| 403 |
+
------
|
| 404 |
+
bool
|
| 405 |
+
CPU is x86 32 bits
|
| 406 |
+
"""
|
| 407 |
+
return self.target_cpu == 'x86'
|
| 408 |
+
|
| 409 |
+
def current_is_x86(self):
|
| 410 |
+
"""
|
| 411 |
+
Return True if current CPU is x86 32 bits..
|
| 412 |
+
|
| 413 |
+
Return
|
| 414 |
+
------
|
| 415 |
+
bool
|
| 416 |
+
CPU is x86 32 bits
|
| 417 |
+
"""
|
| 418 |
+
return self.current_cpu == 'x86'
|
| 419 |
+
|
| 420 |
+
def current_dir(self, hidex86=False, x64=False):
|
| 421 |
+
"""
|
| 422 |
+
Current platform specific subfolder.
|
| 423 |
+
|
| 424 |
+
Parameters
|
| 425 |
+
----------
|
| 426 |
+
hidex86: bool
|
| 427 |
+
return '' and not '\x86' if architecture is x86.
|
| 428 |
+
x64: bool
|
| 429 |
+
return '\x64' and not '\amd64' if architecture is amd64.
|
| 430 |
+
|
| 431 |
+
Return
|
| 432 |
+
------
|
| 433 |
+
str
|
| 434 |
+
subfolder: '\target', or '' (see hidex86 parameter)
|
| 435 |
+
"""
|
| 436 |
+
return (
|
| 437 |
+
'' if (self.current_cpu == 'x86' and hidex86) else
|
| 438 |
+
r'\x64' if (self.current_cpu == 'amd64' and x64) else
|
| 439 |
+
r'\%s' % self.current_cpu
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
def target_dir(self, hidex86=False, x64=False):
|
| 443 |
+
r"""
|
| 444 |
+
Target platform specific subfolder.
|
| 445 |
+
|
| 446 |
+
Parameters
|
| 447 |
+
----------
|
| 448 |
+
hidex86: bool
|
| 449 |
+
return '' and not '\x86' if architecture is x86.
|
| 450 |
+
x64: bool
|
| 451 |
+
return '\x64' and not '\amd64' if architecture is amd64.
|
| 452 |
+
|
| 453 |
+
Return
|
| 454 |
+
------
|
| 455 |
+
str
|
| 456 |
+
subfolder: '\current', or '' (see hidex86 parameter)
|
| 457 |
+
"""
|
| 458 |
+
return (
|
| 459 |
+
'' if (self.target_cpu == 'x86' and hidex86) else
|
| 460 |
+
r'\x64' if (self.target_cpu == 'amd64' and x64) else
|
| 461 |
+
r'\%s' % self.target_cpu
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
def cross_dir(self, forcex86=False):
|
| 465 |
+
r"""
|
| 466 |
+
Cross platform specific subfolder.
|
| 467 |
+
|
| 468 |
+
Parameters
|
| 469 |
+
----------
|
| 470 |
+
forcex86: bool
|
| 471 |
+
Use 'x86' as current architecture even if current architecture is
|
| 472 |
+
not x86.
|
| 473 |
+
|
| 474 |
+
Return
|
| 475 |
+
------
|
| 476 |
+
str
|
| 477 |
+
subfolder: '' if target architecture is current architecture,
|
| 478 |
+
'\current_target' if not.
|
| 479 |
+
"""
|
| 480 |
+
current = 'x86' if forcex86 else self.current_cpu
|
| 481 |
+
return (
|
| 482 |
+
'' if self.target_cpu == current else
|
| 483 |
+
self.target_dir().replace('\\', '\\%s_' % current)
|
| 484 |
+
)
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
class RegistryInfo:
|
| 488 |
+
"""
|
| 489 |
+
Microsoft Visual Studio related registry information.
|
| 490 |
+
|
| 491 |
+
Parameters
|
| 492 |
+
----------
|
| 493 |
+
platform_info: PlatformInfo
|
| 494 |
+
"PlatformInfo" instance.
|
| 495 |
+
"""
|
| 496 |
+
HKEYS = (winreg.HKEY_USERS,
|
| 497 |
+
winreg.HKEY_CURRENT_USER,
|
| 498 |
+
winreg.HKEY_LOCAL_MACHINE,
|
| 499 |
+
winreg.HKEY_CLASSES_ROOT)
|
| 500 |
+
|
| 501 |
+
def __init__(self, platform_info):
|
| 502 |
+
self.pi = platform_info
|
| 503 |
+
|
| 504 |
+
@property
|
| 505 |
+
def visualstudio(self):
|
| 506 |
+
"""
|
| 507 |
+
Microsoft Visual Studio root registry key.
|
| 508 |
+
|
| 509 |
+
Return
|
| 510 |
+
------
|
| 511 |
+
str
|
| 512 |
+
Registry key
|
| 513 |
+
"""
|
| 514 |
+
return 'VisualStudio'
|
| 515 |
+
|
| 516 |
+
@property
|
| 517 |
+
def sxs(self):
|
| 518 |
+
"""
|
| 519 |
+
Microsoft Visual Studio SxS registry key.
|
| 520 |
+
|
| 521 |
+
Return
|
| 522 |
+
------
|
| 523 |
+
str
|
| 524 |
+
Registry key
|
| 525 |
+
"""
|
| 526 |
+
return join(self.visualstudio, 'SxS')
|
| 527 |
+
|
| 528 |
+
@property
|
| 529 |
+
def vc(self):
|
| 530 |
+
"""
|
| 531 |
+
Microsoft Visual C++ VC7 registry key.
|
| 532 |
+
|
| 533 |
+
Return
|
| 534 |
+
------
|
| 535 |
+
str
|
| 536 |
+
Registry key
|
| 537 |
+
"""
|
| 538 |
+
return join(self.sxs, 'VC7')
|
| 539 |
+
|
| 540 |
+
@property
|
| 541 |
+
def vs(self):
|
| 542 |
+
"""
|
| 543 |
+
Microsoft Visual Studio VS7 registry key.
|
| 544 |
+
|
| 545 |
+
Return
|
| 546 |
+
------
|
| 547 |
+
str
|
| 548 |
+
Registry key
|
| 549 |
+
"""
|
| 550 |
+
return join(self.sxs, 'VS7')
|
| 551 |
+
|
| 552 |
+
@property
|
| 553 |
+
def vc_for_python(self):
|
| 554 |
+
"""
|
| 555 |
+
Microsoft Visual C++ for Python registry key.
|
| 556 |
+
|
| 557 |
+
Return
|
| 558 |
+
------
|
| 559 |
+
str
|
| 560 |
+
Registry key
|
| 561 |
+
"""
|
| 562 |
+
return r'DevDiv\VCForPython'
|
| 563 |
+
|
| 564 |
+
@property
|
| 565 |
+
def microsoft_sdk(self):
|
| 566 |
+
"""
|
| 567 |
+
Microsoft SDK registry key.
|
| 568 |
+
|
| 569 |
+
Return
|
| 570 |
+
------
|
| 571 |
+
str
|
| 572 |
+
Registry key
|
| 573 |
+
"""
|
| 574 |
+
return 'Microsoft SDKs'
|
| 575 |
+
|
| 576 |
+
@property
|
| 577 |
+
def windows_sdk(self):
|
| 578 |
+
"""
|
| 579 |
+
Microsoft Windows/Platform SDK registry key.
|
| 580 |
+
|
| 581 |
+
Return
|
| 582 |
+
------
|
| 583 |
+
str
|
| 584 |
+
Registry key
|
| 585 |
+
"""
|
| 586 |
+
return join(self.microsoft_sdk, 'Windows')
|
| 587 |
+
|
| 588 |
+
@property
|
| 589 |
+
def netfx_sdk(self):
|
| 590 |
+
"""
|
| 591 |
+
Microsoft .NET Framework SDK registry key.
|
| 592 |
+
|
| 593 |
+
Return
|
| 594 |
+
------
|
| 595 |
+
str
|
| 596 |
+
Registry key
|
| 597 |
+
"""
|
| 598 |
+
return join(self.microsoft_sdk, 'NETFXSDK')
|
| 599 |
+
|
| 600 |
+
@property
|
| 601 |
+
def windows_kits_roots(self):
|
| 602 |
+
"""
|
| 603 |
+
Microsoft Windows Kits Roots registry key.
|
| 604 |
+
|
| 605 |
+
Return
|
| 606 |
+
------
|
| 607 |
+
str
|
| 608 |
+
Registry key
|
| 609 |
+
"""
|
| 610 |
+
return r'Windows Kits\Installed Roots'
|
| 611 |
+
|
| 612 |
+
def microsoft(self, key, x86=False):
|
| 613 |
+
"""
|
| 614 |
+
Return key in Microsoft software registry.
|
| 615 |
+
|
| 616 |
+
Parameters
|
| 617 |
+
----------
|
| 618 |
+
key: str
|
| 619 |
+
Registry key path where look.
|
| 620 |
+
x86: str
|
| 621 |
+
Force x86 software registry.
|
| 622 |
+
|
| 623 |
+
Return
|
| 624 |
+
------
|
| 625 |
+
str
|
| 626 |
+
Registry key
|
| 627 |
+
"""
|
| 628 |
+
node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
|
| 629 |
+
return join('Software', node64, 'Microsoft', key)
|
| 630 |
+
|
| 631 |
+
def lookup(self, key, name):
|
| 632 |
+
"""
|
| 633 |
+
Look for values in registry in Microsoft software registry.
|
| 634 |
+
|
| 635 |
+
Parameters
|
| 636 |
+
----------
|
| 637 |
+
key: str
|
| 638 |
+
Registry key path where look.
|
| 639 |
+
name: str
|
| 640 |
+
Value name to find.
|
| 641 |
+
|
| 642 |
+
Return
|
| 643 |
+
------
|
| 644 |
+
str
|
| 645 |
+
value
|
| 646 |
+
"""
|
| 647 |
+
key_read = winreg.KEY_READ
|
| 648 |
+
openkey = winreg.OpenKey
|
| 649 |
+
closekey = winreg.CloseKey
|
| 650 |
+
ms = self.microsoft
|
| 651 |
+
for hkey in self.HKEYS:
|
| 652 |
+
bkey = None
|
| 653 |
+
try:
|
| 654 |
+
bkey = openkey(hkey, ms(key), 0, key_read)
|
| 655 |
+
except (OSError, IOError):
|
| 656 |
+
if not self.pi.current_is_x86():
|
| 657 |
+
try:
|
| 658 |
+
bkey = openkey(hkey, ms(key, True), 0, key_read)
|
| 659 |
+
except (OSError, IOError):
|
| 660 |
+
continue
|
| 661 |
+
else:
|
| 662 |
+
continue
|
| 663 |
+
try:
|
| 664 |
+
return winreg.QueryValueEx(bkey, name)[0]
|
| 665 |
+
except (OSError, IOError):
|
| 666 |
+
pass
|
| 667 |
+
finally:
|
| 668 |
+
if bkey:
|
| 669 |
+
closekey(bkey)
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
class SystemInfo:
|
| 673 |
+
"""
|
| 674 |
+
Microsoft Windows and Visual Studio related system information.
|
| 675 |
+
|
| 676 |
+
Parameters
|
| 677 |
+
----------
|
| 678 |
+
registry_info: RegistryInfo
|
| 679 |
+
"RegistryInfo" instance.
|
| 680 |
+
vc_ver: float
|
| 681 |
+
Required Microsoft Visual C++ version.
|
| 682 |
+
"""
|
| 683 |
+
|
| 684 |
+
# Variables and properties in this class use originals CamelCase variables
|
| 685 |
+
# names from Microsoft source files for more easy comparison.
|
| 686 |
+
WinDir = environ.get('WinDir', '')
|
| 687 |
+
ProgramFiles = environ.get('ProgramFiles', '')
|
| 688 |
+
ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
|
| 689 |
+
|
| 690 |
+
def __init__(self, registry_info, vc_ver=None):
|
| 691 |
+
self.ri = registry_info
|
| 692 |
+
self.pi = self.ri.pi
|
| 693 |
+
|
| 694 |
+
self.known_vs_paths = self.find_programdata_vs_vers()
|
| 695 |
+
|
| 696 |
+
# Except for VS15+, VC version is aligned with VS version
|
| 697 |
+
self.vs_ver = self.vc_ver = (
|
| 698 |
+
vc_ver or self._find_latest_available_vs_ver())
|
| 699 |
+
|
| 700 |
+
def _find_latest_available_vs_ver(self):
|
| 701 |
+
"""
|
| 702 |
+
Find the latest VC version
|
| 703 |
+
|
| 704 |
+
Return
|
| 705 |
+
------
|
| 706 |
+
float
|
| 707 |
+
version
|
| 708 |
+
"""
|
| 709 |
+
reg_vc_vers = self.find_reg_vs_vers()
|
| 710 |
+
|
| 711 |
+
if not (reg_vc_vers or self.known_vs_paths):
|
| 712 |
+
raise distutils.errors.DistutilsPlatformError(
|
| 713 |
+
'No Microsoft Visual C++ version found')
|
| 714 |
+
|
| 715 |
+
vc_vers = set(reg_vc_vers)
|
| 716 |
+
vc_vers.update(self.known_vs_paths)
|
| 717 |
+
return sorted(vc_vers)[-1]
|
| 718 |
+
|
| 719 |
+
def find_reg_vs_vers(self):
|
| 720 |
+
"""
|
| 721 |
+
Find Microsoft Visual Studio versions available in registry.
|
| 722 |
+
|
| 723 |
+
Return
|
| 724 |
+
------
|
| 725 |
+
list of float
|
| 726 |
+
Versions
|
| 727 |
+
"""
|
| 728 |
+
ms = self.ri.microsoft
|
| 729 |
+
vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
|
| 730 |
+
vs_vers = []
|
| 731 |
+
for hkey, key in itertools.product(self.ri.HKEYS, vckeys):
|
| 732 |
+
try:
|
| 733 |
+
bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ)
|
| 734 |
+
except (OSError, IOError):
|
| 735 |
+
continue
|
| 736 |
+
with bkey:
|
| 737 |
+
subkeys, values, _ = winreg.QueryInfoKey(bkey)
|
| 738 |
+
for i in range(values):
|
| 739 |
+
with contextlib.suppress(ValueError):
|
| 740 |
+
ver = float(winreg.EnumValue(bkey, i)[0])
|
| 741 |
+
if ver not in vs_vers:
|
| 742 |
+
vs_vers.append(ver)
|
| 743 |
+
for i in range(subkeys):
|
| 744 |
+
with contextlib.suppress(ValueError):
|
| 745 |
+
ver = float(winreg.EnumKey(bkey, i))
|
| 746 |
+
if ver not in vs_vers:
|
| 747 |
+
vs_vers.append(ver)
|
| 748 |
+
return sorted(vs_vers)
|
| 749 |
+
|
| 750 |
+
def find_programdata_vs_vers(self):
|
| 751 |
+
r"""
|
| 752 |
+
Find Visual studio 2017+ versions from information in
|
| 753 |
+
"C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
|
| 754 |
+
|
| 755 |
+
Return
|
| 756 |
+
------
|
| 757 |
+
dict
|
| 758 |
+
float version as key, path as value.
|
| 759 |
+
"""
|
| 760 |
+
vs_versions = {}
|
| 761 |
+
instances_dir = \
|
| 762 |
+
r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
|
| 763 |
+
|
| 764 |
+
try:
|
| 765 |
+
hashed_names = listdir(instances_dir)
|
| 766 |
+
|
| 767 |
+
except (OSError, IOError):
|
| 768 |
+
# Directory not exists with all Visual Studio versions
|
| 769 |
+
return vs_versions
|
| 770 |
+
|
| 771 |
+
for name in hashed_names:
|
| 772 |
+
try:
|
| 773 |
+
# Get VS installation path from "state.json" file
|
| 774 |
+
state_path = join(instances_dir, name, 'state.json')
|
| 775 |
+
with open(state_path, 'rt', encoding='utf-8') as state_file:
|
| 776 |
+
state = json.load(state_file)
|
| 777 |
+
vs_path = state['installationPath']
|
| 778 |
+
|
| 779 |
+
# Raises OSError if this VS installation does not contain VC
|
| 780 |
+
listdir(join(vs_path, r'VC\Tools\MSVC'))
|
| 781 |
+
|
| 782 |
+
# Store version and path
|
| 783 |
+
vs_versions[self._as_float_version(
|
| 784 |
+
state['installationVersion'])] = vs_path
|
| 785 |
+
|
| 786 |
+
except (OSError, IOError, KeyError):
|
| 787 |
+
# Skip if "state.json" file is missing or bad format
|
| 788 |
+
continue
|
| 789 |
+
|
| 790 |
+
return vs_versions
|
| 791 |
+
|
| 792 |
+
@staticmethod
|
| 793 |
+
def _as_float_version(version):
|
| 794 |
+
"""
|
| 795 |
+
Return a string version as a simplified float version (major.minor)
|
| 796 |
+
|
| 797 |
+
Parameters
|
| 798 |
+
----------
|
| 799 |
+
version: str
|
| 800 |
+
Version.
|
| 801 |
+
|
| 802 |
+
Return
|
| 803 |
+
------
|
| 804 |
+
float
|
| 805 |
+
version
|
| 806 |
+
"""
|
| 807 |
+
return float('.'.join(version.split('.')[:2]))
|
| 808 |
+
|
| 809 |
+
@property
|
| 810 |
+
def VSInstallDir(self):
|
| 811 |
+
"""
|
| 812 |
+
Microsoft Visual Studio directory.
|
| 813 |
+
|
| 814 |
+
Return
|
| 815 |
+
------
|
| 816 |
+
str
|
| 817 |
+
path
|
| 818 |
+
"""
|
| 819 |
+
# Default path
|
| 820 |
+
default = join(self.ProgramFilesx86,
|
| 821 |
+
'Microsoft Visual Studio %0.1f' % self.vs_ver)
|
| 822 |
+
|
| 823 |
+
# Try to get path from registry, if fail use default path
|
| 824 |
+
return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default
|
| 825 |
+
|
| 826 |
+
@property
|
| 827 |
+
def VCInstallDir(self):
|
| 828 |
+
"""
|
| 829 |
+
Microsoft Visual C++ directory.
|
| 830 |
+
|
| 831 |
+
Return
|
| 832 |
+
------
|
| 833 |
+
str
|
| 834 |
+
path
|
| 835 |
+
"""
|
| 836 |
+
path = self._guess_vc() or self._guess_vc_legacy()
|
| 837 |
+
|
| 838 |
+
if not isdir(path):
|
| 839 |
+
msg = 'Microsoft Visual C++ directory not found'
|
| 840 |
+
raise distutils.errors.DistutilsPlatformError(msg)
|
| 841 |
+
|
| 842 |
+
return path
|
| 843 |
+
|
| 844 |
+
def _guess_vc(self):
|
| 845 |
+
"""
|
| 846 |
+
Locate Visual C++ for VS2017+.
|
| 847 |
+
|
| 848 |
+
Return
|
| 849 |
+
------
|
| 850 |
+
str
|
| 851 |
+
path
|
| 852 |
+
"""
|
| 853 |
+
if self.vs_ver <= 14.0:
|
| 854 |
+
return ''
|
| 855 |
+
|
| 856 |
+
try:
|
| 857 |
+
# First search in known VS paths
|
| 858 |
+
vs_dir = self.known_vs_paths[self.vs_ver]
|
| 859 |
+
except KeyError:
|
| 860 |
+
# Else, search with path from registry
|
| 861 |
+
vs_dir = self.VSInstallDir
|
| 862 |
+
|
| 863 |
+
guess_vc = join(vs_dir, r'VC\Tools\MSVC')
|
| 864 |
+
|
| 865 |
+
# Subdir with VC exact version as name
|
| 866 |
+
try:
|
| 867 |
+
# Update the VC version with real one instead of VS version
|
| 868 |
+
vc_ver = listdir(guess_vc)[-1]
|
| 869 |
+
self.vc_ver = self._as_float_version(vc_ver)
|
| 870 |
+
return join(guess_vc, vc_ver)
|
| 871 |
+
except (OSError, IOError, IndexError):
|
| 872 |
+
return ''
|
| 873 |
+
|
| 874 |
+
def _guess_vc_legacy(self):
|
| 875 |
+
"""
|
| 876 |
+
Locate Visual C++ for versions prior to 2017.
|
| 877 |
+
|
| 878 |
+
Return
|
| 879 |
+
------
|
| 880 |
+
str
|
| 881 |
+
path
|
| 882 |
+
"""
|
| 883 |
+
default = join(self.ProgramFilesx86,
|
| 884 |
+
r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver)
|
| 885 |
+
|
| 886 |
+
# Try to get "VC++ for Python" path from registry as default path
|
| 887 |
+
reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver)
|
| 888 |
+
python_vc = self.ri.lookup(reg_path, 'installdir')
|
| 889 |
+
default_vc = join(python_vc, 'VC') if python_vc else default
|
| 890 |
+
|
| 891 |
+
# Try to get path from registry, if fail use default path
|
| 892 |
+
return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc
|
| 893 |
+
|
| 894 |
+
@property
|
| 895 |
+
def WindowsSdkVersion(self):
|
| 896 |
+
"""
|
| 897 |
+
Microsoft Windows SDK versions for specified MSVC++ version.
|
| 898 |
+
|
| 899 |
+
Return
|
| 900 |
+
------
|
| 901 |
+
tuple of str
|
| 902 |
+
versions
|
| 903 |
+
"""
|
| 904 |
+
if self.vs_ver <= 9.0:
|
| 905 |
+
return '7.0', '6.1', '6.0a'
|
| 906 |
+
elif self.vs_ver == 10.0:
|
| 907 |
+
return '7.1', '7.0a'
|
| 908 |
+
elif self.vs_ver == 11.0:
|
| 909 |
+
return '8.0', '8.0a'
|
| 910 |
+
elif self.vs_ver == 12.0:
|
| 911 |
+
return '8.1', '8.1a'
|
| 912 |
+
elif self.vs_ver >= 14.0:
|
| 913 |
+
return '10.0', '8.1'
|
| 914 |
+
|
| 915 |
+
@property
|
| 916 |
+
def WindowsSdkLastVersion(self):
|
| 917 |
+
"""
|
| 918 |
+
Microsoft Windows SDK last version.
|
| 919 |
+
|
| 920 |
+
Return
|
| 921 |
+
------
|
| 922 |
+
str
|
| 923 |
+
version
|
| 924 |
+
"""
|
| 925 |
+
return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
|
| 926 |
+
|
| 927 |
+
@property # noqa: C901
|
| 928 |
+
def WindowsSdkDir(self): # noqa: C901 # is too complex (12) # FIXME
|
| 929 |
+
"""
|
| 930 |
+
Microsoft Windows SDK directory.
|
| 931 |
+
|
| 932 |
+
Return
|
| 933 |
+
------
|
| 934 |
+
str
|
| 935 |
+
path
|
| 936 |
+
"""
|
| 937 |
+
sdkdir = ''
|
| 938 |
+
for ver in self.WindowsSdkVersion:
|
| 939 |
+
# Try to get it from registry
|
| 940 |
+
loc = join(self.ri.windows_sdk, 'v%s' % ver)
|
| 941 |
+
sdkdir = self.ri.lookup(loc, 'installationfolder')
|
| 942 |
+
if sdkdir:
|
| 943 |
+
break
|
| 944 |
+
if not sdkdir or not isdir(sdkdir):
|
| 945 |
+
# Try to get "VC++ for Python" version from registry
|
| 946 |
+
path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
|
| 947 |
+
install_base = self.ri.lookup(path, 'installdir')
|
| 948 |
+
if install_base:
|
| 949 |
+
sdkdir = join(install_base, 'WinSDK')
|
| 950 |
+
if not sdkdir or not isdir(sdkdir):
|
| 951 |
+
# If fail, use default new path
|
| 952 |
+
for ver in self.WindowsSdkVersion:
|
| 953 |
+
intver = ver[:ver.rfind('.')]
|
| 954 |
+
path = r'Microsoft SDKs\Windows Kits\%s' % intver
|
| 955 |
+
d = join(self.ProgramFiles, path)
|
| 956 |
+
if isdir(d):
|
| 957 |
+
sdkdir = d
|
| 958 |
+
if not sdkdir or not isdir(sdkdir):
|
| 959 |
+
# If fail, use default old path
|
| 960 |
+
for ver in self.WindowsSdkVersion:
|
| 961 |
+
path = r'Microsoft SDKs\Windows\v%s' % ver
|
| 962 |
+
d = join(self.ProgramFiles, path)
|
| 963 |
+
if isdir(d):
|
| 964 |
+
sdkdir = d
|
| 965 |
+
if not sdkdir:
|
| 966 |
+
# If fail, use Platform SDK
|
| 967 |
+
sdkdir = join(self.VCInstallDir, 'PlatformSDK')
|
| 968 |
+
return sdkdir
|
| 969 |
+
|
| 970 |
+
@property
|
| 971 |
+
def WindowsSDKExecutablePath(self):
|
| 972 |
+
"""
|
| 973 |
+
Microsoft Windows SDK executable directory.
|
| 974 |
+
|
| 975 |
+
Return
|
| 976 |
+
------
|
| 977 |
+
str
|
| 978 |
+
path
|
| 979 |
+
"""
|
| 980 |
+
# Find WinSDK NetFx Tools registry dir name
|
| 981 |
+
if self.vs_ver <= 11.0:
|
| 982 |
+
netfxver = 35
|
| 983 |
+
arch = ''
|
| 984 |
+
else:
|
| 985 |
+
netfxver = 40
|
| 986 |
+
hidex86 = True if self.vs_ver <= 12.0 else False
|
| 987 |
+
arch = self.pi.current_dir(x64=True, hidex86=hidex86)
|
| 988 |
+
fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-'))
|
| 989 |
+
|
| 990 |
+
# list all possibles registry paths
|
| 991 |
+
regpaths = []
|
| 992 |
+
if self.vs_ver >= 14.0:
|
| 993 |
+
for ver in self.NetFxSdkVersion:
|
| 994 |
+
regpaths += [join(self.ri.netfx_sdk, ver, fx)]
|
| 995 |
+
|
| 996 |
+
for ver in self.WindowsSdkVersion:
|
| 997 |
+
regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
|
| 998 |
+
|
| 999 |
+
# Return installation folder from the more recent path
|
| 1000 |
+
for path in regpaths:
|
| 1001 |
+
execpath = self.ri.lookup(path, 'installationfolder')
|
| 1002 |
+
if execpath:
|
| 1003 |
+
return execpath
|
| 1004 |
+
|
| 1005 |
+
@property
|
| 1006 |
+
def FSharpInstallDir(self):
|
| 1007 |
+
"""
|
| 1008 |
+
Microsoft Visual F# directory.
|
| 1009 |
+
|
| 1010 |
+
Return
|
| 1011 |
+
------
|
| 1012 |
+
str
|
| 1013 |
+
path
|
| 1014 |
+
"""
|
| 1015 |
+
path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver)
|
| 1016 |
+
return self.ri.lookup(path, 'productdir') or ''
|
| 1017 |
+
|
| 1018 |
+
@property
|
| 1019 |
+
def UniversalCRTSdkDir(self):
|
| 1020 |
+
"""
|
| 1021 |
+
Microsoft Universal CRT SDK directory.
|
| 1022 |
+
|
| 1023 |
+
Return
|
| 1024 |
+
------
|
| 1025 |
+
str
|
| 1026 |
+
path
|
| 1027 |
+
"""
|
| 1028 |
+
# Set Kit Roots versions for specified MSVC++ version
|
| 1029 |
+
vers = ('10', '81') if self.vs_ver >= 14.0 else ()
|
| 1030 |
+
|
| 1031 |
+
# Find path of the more recent Kit
|
| 1032 |
+
for ver in vers:
|
| 1033 |
+
sdkdir = self.ri.lookup(self.ri.windows_kits_roots,
|
| 1034 |
+
'kitsroot%s' % ver)
|
| 1035 |
+
if sdkdir:
|
| 1036 |
+
return sdkdir or ''
|
| 1037 |
+
|
| 1038 |
+
@property
|
| 1039 |
+
def UniversalCRTSdkLastVersion(self):
|
| 1040 |
+
"""
|
| 1041 |
+
Microsoft Universal C Runtime SDK last version.
|
| 1042 |
+
|
| 1043 |
+
Return
|
| 1044 |
+
------
|
| 1045 |
+
str
|
| 1046 |
+
version
|
| 1047 |
+
"""
|
| 1048 |
+
return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib'))
|
| 1049 |
+
|
| 1050 |
+
@property
|
| 1051 |
+
def NetFxSdkVersion(self):
|
| 1052 |
+
"""
|
| 1053 |
+
Microsoft .NET Framework SDK versions.
|
| 1054 |
+
|
| 1055 |
+
Return
|
| 1056 |
+
------
|
| 1057 |
+
tuple of str
|
| 1058 |
+
versions
|
| 1059 |
+
"""
|
| 1060 |
+
# Set FxSdk versions for specified VS version
|
| 1061 |
+
return (('4.7.2', '4.7.1', '4.7',
|
| 1062 |
+
'4.6.2', '4.6.1', '4.6',
|
| 1063 |
+
'4.5.2', '4.5.1', '4.5')
|
| 1064 |
+
if self.vs_ver >= 14.0 else ())
|
| 1065 |
+
|
| 1066 |
+
@property
|
| 1067 |
+
def NetFxSdkDir(self):
|
| 1068 |
+
"""
|
| 1069 |
+
Microsoft .NET Framework SDK directory.
|
| 1070 |
+
|
| 1071 |
+
Return
|
| 1072 |
+
------
|
| 1073 |
+
str
|
| 1074 |
+
path
|
| 1075 |
+
"""
|
| 1076 |
+
sdkdir = ''
|
| 1077 |
+
for ver in self.NetFxSdkVersion:
|
| 1078 |
+
loc = join(self.ri.netfx_sdk, ver)
|
| 1079 |
+
sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
|
| 1080 |
+
if sdkdir:
|
| 1081 |
+
break
|
| 1082 |
+
return sdkdir
|
| 1083 |
+
|
| 1084 |
+
@property
|
| 1085 |
+
def FrameworkDir32(self):
|
| 1086 |
+
"""
|
| 1087 |
+
Microsoft .NET Framework 32bit directory.
|
| 1088 |
+
|
| 1089 |
+
Return
|
| 1090 |
+
------
|
| 1091 |
+
str
|
| 1092 |
+
path
|
| 1093 |
+
"""
|
| 1094 |
+
# Default path
|
| 1095 |
+
guess_fw = join(self.WinDir, r'Microsoft.NET\Framework')
|
| 1096 |
+
|
| 1097 |
+
# Try to get path from registry, if fail use default path
|
| 1098 |
+
return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
|
| 1099 |
+
|
| 1100 |
+
@property
|
| 1101 |
+
def FrameworkDir64(self):
|
| 1102 |
+
"""
|
| 1103 |
+
Microsoft .NET Framework 64bit directory.
|
| 1104 |
+
|
| 1105 |
+
Return
|
| 1106 |
+
------
|
| 1107 |
+
str
|
| 1108 |
+
path
|
| 1109 |
+
"""
|
| 1110 |
+
# Default path
|
| 1111 |
+
guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64')
|
| 1112 |
+
|
| 1113 |
+
# Try to get path from registry, if fail use default path
|
| 1114 |
+
return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
|
| 1115 |
+
|
| 1116 |
+
@property
|
| 1117 |
+
def FrameworkVersion32(self):
|
| 1118 |
+
"""
|
| 1119 |
+
Microsoft .NET Framework 32bit versions.
|
| 1120 |
+
|
| 1121 |
+
Return
|
| 1122 |
+
------
|
| 1123 |
+
tuple of str
|
| 1124 |
+
versions
|
| 1125 |
+
"""
|
| 1126 |
+
return self._find_dot_net_versions(32)
|
| 1127 |
+
|
| 1128 |
+
@property
|
| 1129 |
+
def FrameworkVersion64(self):
|
| 1130 |
+
"""
|
| 1131 |
+
Microsoft .NET Framework 64bit versions.
|
| 1132 |
+
|
| 1133 |
+
Return
|
| 1134 |
+
------
|
| 1135 |
+
tuple of str
|
| 1136 |
+
versions
|
| 1137 |
+
"""
|
| 1138 |
+
return self._find_dot_net_versions(64)
|
| 1139 |
+
|
| 1140 |
+
def _find_dot_net_versions(self, bits):
|
| 1141 |
+
"""
|
| 1142 |
+
Find Microsoft .NET Framework versions.
|
| 1143 |
+
|
| 1144 |
+
Parameters
|
| 1145 |
+
----------
|
| 1146 |
+
bits: int
|
| 1147 |
+
Platform number of bits: 32 or 64.
|
| 1148 |
+
|
| 1149 |
+
Return
|
| 1150 |
+
------
|
| 1151 |
+
tuple of str
|
| 1152 |
+
versions
|
| 1153 |
+
"""
|
| 1154 |
+
# Find actual .NET version in registry
|
| 1155 |
+
reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits)
|
| 1156 |
+
dot_net_dir = getattr(self, 'FrameworkDir%d' % bits)
|
| 1157 |
+
ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
|
| 1158 |
+
|
| 1159 |
+
# Set .NET versions for specified MSVC++ version
|
| 1160 |
+
if self.vs_ver >= 12.0:
|
| 1161 |
+
return ver, 'v4.0'
|
| 1162 |
+
elif self.vs_ver >= 10.0:
|
| 1163 |
+
return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'
|
| 1164 |
+
elif self.vs_ver == 9.0:
|
| 1165 |
+
return 'v3.5', 'v2.0.50727'
|
| 1166 |
+
elif self.vs_ver == 8.0:
|
| 1167 |
+
return 'v3.0', 'v2.0.50727'
|
| 1168 |
+
|
| 1169 |
+
@staticmethod
|
| 1170 |
+
def _use_last_dir_name(path, prefix=''):
|
| 1171 |
+
"""
|
| 1172 |
+
Return name of the last dir in path or '' if no dir found.
|
| 1173 |
+
|
| 1174 |
+
Parameters
|
| 1175 |
+
----------
|
| 1176 |
+
path: str
|
| 1177 |
+
Use dirs in this path
|
| 1178 |
+
prefix: str
|
| 1179 |
+
Use only dirs starting by this prefix
|
| 1180 |
+
|
| 1181 |
+
Return
|
| 1182 |
+
------
|
| 1183 |
+
str
|
| 1184 |
+
name
|
| 1185 |
+
"""
|
| 1186 |
+
matching_dirs = (
|
| 1187 |
+
dir_name
|
| 1188 |
+
for dir_name in reversed(listdir(path))
|
| 1189 |
+
if isdir(join(path, dir_name)) and
|
| 1190 |
+
dir_name.startswith(prefix)
|
| 1191 |
+
)
|
| 1192 |
+
return next(matching_dirs, None) or ''
|
| 1193 |
+
|
| 1194 |
+
|
| 1195 |
+
class EnvironmentInfo:
|
| 1196 |
+
"""
|
| 1197 |
+
Return environment variables for specified Microsoft Visual C++ version
|
| 1198 |
+
and platform : Lib, Include, Path and libpath.
|
| 1199 |
+
|
| 1200 |
+
This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
|
| 1201 |
+
|
| 1202 |
+
Script created by analysing Microsoft environment configuration files like
|
| 1203 |
+
"vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
|
| 1204 |
+
|
| 1205 |
+
Parameters
|
| 1206 |
+
----------
|
| 1207 |
+
arch: str
|
| 1208 |
+
Target architecture.
|
| 1209 |
+
vc_ver: float
|
| 1210 |
+
Required Microsoft Visual C++ version. If not set, autodetect the last
|
| 1211 |
+
version.
|
| 1212 |
+
vc_min_ver: float
|
| 1213 |
+
Minimum Microsoft Visual C++ version.
|
| 1214 |
+
"""
|
| 1215 |
+
|
| 1216 |
+
# Variables and properties in this class use originals CamelCase variables
|
| 1217 |
+
# names from Microsoft source files for more easy comparison.
|
| 1218 |
+
|
| 1219 |
+
def __init__(self, arch, vc_ver=None, vc_min_ver=0):
|
| 1220 |
+
self.pi = PlatformInfo(arch)
|
| 1221 |
+
self.ri = RegistryInfo(self.pi)
|
| 1222 |
+
self.si = SystemInfo(self.ri, vc_ver)
|
| 1223 |
+
|
| 1224 |
+
if self.vc_ver < vc_min_ver:
|
| 1225 |
+
err = 'No suitable Microsoft Visual C++ version found'
|
| 1226 |
+
raise distutils.errors.DistutilsPlatformError(err)
|
| 1227 |
+
|
| 1228 |
+
@property
|
| 1229 |
+
def vs_ver(self):
|
| 1230 |
+
"""
|
| 1231 |
+
Microsoft Visual Studio.
|
| 1232 |
+
|
| 1233 |
+
Return
|
| 1234 |
+
------
|
| 1235 |
+
float
|
| 1236 |
+
version
|
| 1237 |
+
"""
|
| 1238 |
+
return self.si.vs_ver
|
| 1239 |
+
|
| 1240 |
+
@property
|
| 1241 |
+
def vc_ver(self):
|
| 1242 |
+
"""
|
| 1243 |
+
Microsoft Visual C++ version.
|
| 1244 |
+
|
| 1245 |
+
Return
|
| 1246 |
+
------
|
| 1247 |
+
float
|
| 1248 |
+
version
|
| 1249 |
+
"""
|
| 1250 |
+
return self.si.vc_ver
|
| 1251 |
+
|
| 1252 |
+
@property
|
| 1253 |
+
def VSTools(self):
|
| 1254 |
+
"""
|
| 1255 |
+
Microsoft Visual Studio Tools.
|
| 1256 |
+
|
| 1257 |
+
Return
|
| 1258 |
+
------
|
| 1259 |
+
list of str
|
| 1260 |
+
paths
|
| 1261 |
+
"""
|
| 1262 |
+
paths = [r'Common7\IDE', r'Common7\Tools']
|
| 1263 |
+
|
| 1264 |
+
if self.vs_ver >= 14.0:
|
| 1265 |
+
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
|
| 1266 |
+
paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
|
| 1267 |
+
paths += [r'Team Tools\Performance Tools']
|
| 1268 |
+
paths += [r'Team Tools\Performance Tools%s' % arch_subdir]
|
| 1269 |
+
|
| 1270 |
+
return [join(self.si.VSInstallDir, path) for path in paths]
|
| 1271 |
+
|
| 1272 |
+
@property
|
| 1273 |
+
def VCIncludes(self):
|
| 1274 |
+
"""
|
| 1275 |
+
Microsoft Visual C++ & Microsoft Foundation Class Includes.
|
| 1276 |
+
|
| 1277 |
+
Return
|
| 1278 |
+
------
|
| 1279 |
+
list of str
|
| 1280 |
+
paths
|
| 1281 |
+
"""
|
| 1282 |
+
return [join(self.si.VCInstallDir, 'Include'),
|
| 1283 |
+
join(self.si.VCInstallDir, r'ATLMFC\Include')]
|
| 1284 |
+
|
| 1285 |
+
@property
|
| 1286 |
+
def VCLibraries(self):
|
| 1287 |
+
"""
|
| 1288 |
+
Microsoft Visual C++ & Microsoft Foundation Class Libraries.
|
| 1289 |
+
|
| 1290 |
+
Return
|
| 1291 |
+
------
|
| 1292 |
+
list of str
|
| 1293 |
+
paths
|
| 1294 |
+
"""
|
| 1295 |
+
if self.vs_ver >= 15.0:
|
| 1296 |
+
arch_subdir = self.pi.target_dir(x64=True)
|
| 1297 |
+
else:
|
| 1298 |
+
arch_subdir = self.pi.target_dir(hidex86=True)
|
| 1299 |
+
paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir]
|
| 1300 |
+
|
| 1301 |
+
if self.vs_ver >= 14.0:
|
| 1302 |
+
paths += [r'Lib\store%s' % arch_subdir]
|
| 1303 |
+
|
| 1304 |
+
return [join(self.si.VCInstallDir, path) for path in paths]
|
| 1305 |
+
|
| 1306 |
+
@property
|
| 1307 |
+
def VCStoreRefs(self):
|
| 1308 |
+
"""
|
| 1309 |
+
Microsoft Visual C++ store references Libraries.
|
| 1310 |
+
|
| 1311 |
+
Return
|
| 1312 |
+
------
|
| 1313 |
+
list of str
|
| 1314 |
+
paths
|
| 1315 |
+
"""
|
| 1316 |
+
if self.vs_ver < 14.0:
|
| 1317 |
+
return []
|
| 1318 |
+
return [join(self.si.VCInstallDir, r'Lib\store\references')]
|
| 1319 |
+
|
| 1320 |
+
@property
|
| 1321 |
+
def VCTools(self):
|
| 1322 |
+
"""
|
| 1323 |
+
Microsoft Visual C++ Tools.
|
| 1324 |
+
|
| 1325 |
+
Return
|
| 1326 |
+
------
|
| 1327 |
+
list of str
|
| 1328 |
+
paths
|
| 1329 |
+
"""
|
| 1330 |
+
si = self.si
|
| 1331 |
+
tools = [join(si.VCInstallDir, 'VCPackages')]
|
| 1332 |
+
|
| 1333 |
+
forcex86 = True if self.vs_ver <= 10.0 else False
|
| 1334 |
+
arch_subdir = self.pi.cross_dir(forcex86)
|
| 1335 |
+
if arch_subdir:
|
| 1336 |
+
tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
|
| 1337 |
+
|
| 1338 |
+
if self.vs_ver == 14.0:
|
| 1339 |
+
path = 'Bin%s' % self.pi.current_dir(hidex86=True)
|
| 1340 |
+
tools += [join(si.VCInstallDir, path)]
|
| 1341 |
+
|
| 1342 |
+
elif self.vs_ver >= 15.0:
|
| 1343 |
+
host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else
|
| 1344 |
+
r'bin\HostX64%s')
|
| 1345 |
+
tools += [join(
|
| 1346 |
+
si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))]
|
| 1347 |
+
|
| 1348 |
+
if self.pi.current_cpu != self.pi.target_cpu:
|
| 1349 |
+
tools += [join(
|
| 1350 |
+
si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))]
|
| 1351 |
+
|
| 1352 |
+
else:
|
| 1353 |
+
tools += [join(si.VCInstallDir, 'Bin')]
|
| 1354 |
+
|
| 1355 |
+
return tools
|
| 1356 |
+
|
| 1357 |
+
@property
|
| 1358 |
+
def OSLibraries(self):
|
| 1359 |
+
"""
|
| 1360 |
+
Microsoft Windows SDK Libraries.
|
| 1361 |
+
|
| 1362 |
+
Return
|
| 1363 |
+
------
|
| 1364 |
+
list of str
|
| 1365 |
+
paths
|
| 1366 |
+
"""
|
| 1367 |
+
if self.vs_ver <= 10.0:
|
| 1368 |
+
arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
|
| 1369 |
+
return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
|
| 1370 |
+
|
| 1371 |
+
else:
|
| 1372 |
+
arch_subdir = self.pi.target_dir(x64=True)
|
| 1373 |
+
lib = join(self.si.WindowsSdkDir, 'lib')
|
| 1374 |
+
libver = self._sdk_subdir
|
| 1375 |
+
return [join(lib, '%sum%s' % (libver, arch_subdir))]
|
| 1376 |
+
|
| 1377 |
+
@property
|
| 1378 |
+
def OSIncludes(self):
|
| 1379 |
+
"""
|
| 1380 |
+
Microsoft Windows SDK Include.
|
| 1381 |
+
|
| 1382 |
+
Return
|
| 1383 |
+
------
|
| 1384 |
+
list of str
|
| 1385 |
+
paths
|
| 1386 |
+
"""
|
| 1387 |
+
include = join(self.si.WindowsSdkDir, 'include')
|
| 1388 |
+
|
| 1389 |
+
if self.vs_ver <= 10.0:
|
| 1390 |
+
return [include, join(include, 'gl')]
|
| 1391 |
+
|
| 1392 |
+
else:
|
| 1393 |
+
if self.vs_ver >= 14.0:
|
| 1394 |
+
sdkver = self._sdk_subdir
|
| 1395 |
+
else:
|
| 1396 |
+
sdkver = ''
|
| 1397 |
+
return [join(include, '%sshared' % sdkver),
|
| 1398 |
+
join(include, '%sum' % sdkver),
|
| 1399 |
+
join(include, '%swinrt' % sdkver)]
|
| 1400 |
+
|
| 1401 |
+
@property
|
| 1402 |
+
def OSLibpath(self):
|
| 1403 |
+
"""
|
| 1404 |
+
Microsoft Windows SDK Libraries Paths.
|
| 1405 |
+
|
| 1406 |
+
Return
|
| 1407 |
+
------
|
| 1408 |
+
list of str
|
| 1409 |
+
paths
|
| 1410 |
+
"""
|
| 1411 |
+
ref = join(self.si.WindowsSdkDir, 'References')
|
| 1412 |
+
libpath = []
|
| 1413 |
+
|
| 1414 |
+
if self.vs_ver <= 9.0:
|
| 1415 |
+
libpath += self.OSLibraries
|
| 1416 |
+
|
| 1417 |
+
if self.vs_ver >= 11.0:
|
| 1418 |
+
libpath += [join(ref, r'CommonConfiguration\Neutral')]
|
| 1419 |
+
|
| 1420 |
+
if self.vs_ver >= 14.0:
|
| 1421 |
+
libpath += [
|
| 1422 |
+
ref,
|
| 1423 |
+
join(self.si.WindowsSdkDir, 'UnionMetadata'),
|
| 1424 |
+
join(
|
| 1425 |
+
ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
|
| 1426 |
+
join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
|
| 1427 |
+
join(
|
| 1428 |
+
ref, 'Windows.Networking.Connectivity.WwanContract',
|
| 1429 |
+
'1.0.0.0'),
|
| 1430 |
+
join(
|
| 1431 |
+
self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs',
|
| 1432 |
+
'%0.1f' % self.vs_ver, 'References', 'CommonConfiguration',
|
| 1433 |
+
'neutral'),
|
| 1434 |
+
]
|
| 1435 |
+
return libpath
|
| 1436 |
+
|
| 1437 |
+
@property
|
| 1438 |
+
def SdkTools(self):
|
| 1439 |
+
"""
|
| 1440 |
+
Microsoft Windows SDK Tools.
|
| 1441 |
+
|
| 1442 |
+
Return
|
| 1443 |
+
------
|
| 1444 |
+
list of str
|
| 1445 |
+
paths
|
| 1446 |
+
"""
|
| 1447 |
+
return list(self._sdk_tools())
|
| 1448 |
+
|
| 1449 |
+
def _sdk_tools(self):
|
| 1450 |
+
"""
|
| 1451 |
+
Microsoft Windows SDK Tools paths generator.
|
| 1452 |
+
|
| 1453 |
+
Return
|
| 1454 |
+
------
|
| 1455 |
+
generator of str
|
| 1456 |
+
paths
|
| 1457 |
+
"""
|
| 1458 |
+
if self.vs_ver < 15.0:
|
| 1459 |
+
bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
|
| 1460 |
+
yield join(self.si.WindowsSdkDir, bin_dir)
|
| 1461 |
+
|
| 1462 |
+
if not self.pi.current_is_x86():
|
| 1463 |
+
arch_subdir = self.pi.current_dir(x64=True)
|
| 1464 |
+
path = 'Bin%s' % arch_subdir
|
| 1465 |
+
yield join(self.si.WindowsSdkDir, path)
|
| 1466 |
+
|
| 1467 |
+
if self.vs_ver in (10.0, 11.0):
|
| 1468 |
+
if self.pi.target_is_x86():
|
| 1469 |
+
arch_subdir = ''
|
| 1470 |
+
else:
|
| 1471 |
+
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
|
| 1472 |
+
path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir
|
| 1473 |
+
yield join(self.si.WindowsSdkDir, path)
|
| 1474 |
+
|
| 1475 |
+
elif self.vs_ver >= 15.0:
|
| 1476 |
+
path = join(self.si.WindowsSdkDir, 'Bin')
|
| 1477 |
+
arch_subdir = self.pi.current_dir(x64=True)
|
| 1478 |
+
sdkver = self.si.WindowsSdkLastVersion
|
| 1479 |
+
yield join(path, '%s%s' % (sdkver, arch_subdir))
|
| 1480 |
+
|
| 1481 |
+
if self.si.WindowsSDKExecutablePath:
|
| 1482 |
+
yield self.si.WindowsSDKExecutablePath
|
| 1483 |
+
|
| 1484 |
+
@property
|
| 1485 |
+
def _sdk_subdir(self):
|
| 1486 |
+
"""
|
| 1487 |
+
Microsoft Windows SDK version subdir.
|
| 1488 |
+
|
| 1489 |
+
Return
|
| 1490 |
+
------
|
| 1491 |
+
str
|
| 1492 |
+
subdir
|
| 1493 |
+
"""
|
| 1494 |
+
ucrtver = self.si.WindowsSdkLastVersion
|
| 1495 |
+
return ('%s\\' % ucrtver) if ucrtver else ''
|
| 1496 |
+
|
| 1497 |
+
@property
|
| 1498 |
+
def SdkSetup(self):
|
| 1499 |
+
"""
|
| 1500 |
+
Microsoft Windows SDK Setup.
|
| 1501 |
+
|
| 1502 |
+
Return
|
| 1503 |
+
------
|
| 1504 |
+
list of str
|
| 1505 |
+
paths
|
| 1506 |
+
"""
|
| 1507 |
+
if self.vs_ver > 9.0:
|
| 1508 |
+
return []
|
| 1509 |
+
|
| 1510 |
+
return [join(self.si.WindowsSdkDir, 'Setup')]
|
| 1511 |
+
|
| 1512 |
+
@property
|
| 1513 |
+
def FxTools(self):
|
| 1514 |
+
"""
|
| 1515 |
+
Microsoft .NET Framework Tools.
|
| 1516 |
+
|
| 1517 |
+
Return
|
| 1518 |
+
------
|
| 1519 |
+
list of str
|
| 1520 |
+
paths
|
| 1521 |
+
"""
|
| 1522 |
+
pi = self.pi
|
| 1523 |
+
si = self.si
|
| 1524 |
+
|
| 1525 |
+
if self.vs_ver <= 10.0:
|
| 1526 |
+
include32 = True
|
| 1527 |
+
include64 = not pi.target_is_x86() and not pi.current_is_x86()
|
| 1528 |
+
else:
|
| 1529 |
+
include32 = pi.target_is_x86() or pi.current_is_x86()
|
| 1530 |
+
include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64'
|
| 1531 |
+
|
| 1532 |
+
tools = []
|
| 1533 |
+
if include32:
|
| 1534 |
+
tools += [join(si.FrameworkDir32, ver)
|
| 1535 |
+
for ver in si.FrameworkVersion32]
|
| 1536 |
+
if include64:
|
| 1537 |
+
tools += [join(si.FrameworkDir64, ver)
|
| 1538 |
+
for ver in si.FrameworkVersion64]
|
| 1539 |
+
return tools
|
| 1540 |
+
|
| 1541 |
+
@property
|
| 1542 |
+
def NetFxSDKLibraries(self):
|
| 1543 |
+
"""
|
| 1544 |
+
Microsoft .Net Framework SDK Libraries.
|
| 1545 |
+
|
| 1546 |
+
Return
|
| 1547 |
+
------
|
| 1548 |
+
list of str
|
| 1549 |
+
paths
|
| 1550 |
+
"""
|
| 1551 |
+
if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
|
| 1552 |
+
return []
|
| 1553 |
+
|
| 1554 |
+
arch_subdir = self.pi.target_dir(x64=True)
|
| 1555 |
+
return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
|
| 1556 |
+
|
| 1557 |
+
@property
|
| 1558 |
+
def NetFxSDKIncludes(self):
|
| 1559 |
+
"""
|
| 1560 |
+
Microsoft .Net Framework SDK Includes.
|
| 1561 |
+
|
| 1562 |
+
Return
|
| 1563 |
+
------
|
| 1564 |
+
list of str
|
| 1565 |
+
paths
|
| 1566 |
+
"""
|
| 1567 |
+
if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
|
| 1568 |
+
return []
|
| 1569 |
+
|
| 1570 |
+
return [join(self.si.NetFxSdkDir, r'include\um')]
|
| 1571 |
+
|
| 1572 |
+
@property
|
| 1573 |
+
def VsTDb(self):
|
| 1574 |
+
"""
|
| 1575 |
+
Microsoft Visual Studio Team System Database.
|
| 1576 |
+
|
| 1577 |
+
Return
|
| 1578 |
+
------
|
| 1579 |
+
list of str
|
| 1580 |
+
paths
|
| 1581 |
+
"""
|
| 1582 |
+
return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
|
| 1583 |
+
|
| 1584 |
+
@property
|
| 1585 |
+
def MSBuild(self):
|
| 1586 |
+
"""
|
| 1587 |
+
Microsoft Build Engine.
|
| 1588 |
+
|
| 1589 |
+
Return
|
| 1590 |
+
------
|
| 1591 |
+
list of str
|
| 1592 |
+
paths
|
| 1593 |
+
"""
|
| 1594 |
+
if self.vs_ver < 12.0:
|
| 1595 |
+
return []
|
| 1596 |
+
elif self.vs_ver < 15.0:
|
| 1597 |
+
base_path = self.si.ProgramFilesx86
|
| 1598 |
+
arch_subdir = self.pi.current_dir(hidex86=True)
|
| 1599 |
+
else:
|
| 1600 |
+
base_path = self.si.VSInstallDir
|
| 1601 |
+
arch_subdir = ''
|
| 1602 |
+
|
| 1603 |
+
path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir)
|
| 1604 |
+
build = [join(base_path, path)]
|
| 1605 |
+
|
| 1606 |
+
if self.vs_ver >= 15.0:
|
| 1607 |
+
# Add Roslyn C# & Visual Basic Compiler
|
| 1608 |
+
build += [join(base_path, path, 'Roslyn')]
|
| 1609 |
+
|
| 1610 |
+
return build
|
| 1611 |
+
|
| 1612 |
+
@property
|
| 1613 |
+
def HTMLHelpWorkshop(self):
|
| 1614 |
+
"""
|
| 1615 |
+
Microsoft HTML Help Workshop.
|
| 1616 |
+
|
| 1617 |
+
Return
|
| 1618 |
+
------
|
| 1619 |
+
list of str
|
| 1620 |
+
paths
|
| 1621 |
+
"""
|
| 1622 |
+
if self.vs_ver < 11.0:
|
| 1623 |
+
return []
|
| 1624 |
+
|
| 1625 |
+
return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
|
| 1626 |
+
|
| 1627 |
+
@property
|
| 1628 |
+
def UCRTLibraries(self):
|
| 1629 |
+
"""
|
| 1630 |
+
Microsoft Universal C Runtime SDK Libraries.
|
| 1631 |
+
|
| 1632 |
+
Return
|
| 1633 |
+
------
|
| 1634 |
+
list of str
|
| 1635 |
+
paths
|
| 1636 |
+
"""
|
| 1637 |
+
if self.vs_ver < 14.0:
|
| 1638 |
+
return []
|
| 1639 |
+
|
| 1640 |
+
arch_subdir = self.pi.target_dir(x64=True)
|
| 1641 |
+
lib = join(self.si.UniversalCRTSdkDir, 'lib')
|
| 1642 |
+
ucrtver = self._ucrt_subdir
|
| 1643 |
+
return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
|
| 1644 |
+
|
| 1645 |
+
@property
|
| 1646 |
+
def UCRTIncludes(self):
|
| 1647 |
+
"""
|
| 1648 |
+
Microsoft Universal C Runtime SDK Include.
|
| 1649 |
+
|
| 1650 |
+
Return
|
| 1651 |
+
------
|
| 1652 |
+
list of str
|
| 1653 |
+
paths
|
| 1654 |
+
"""
|
| 1655 |
+
if self.vs_ver < 14.0:
|
| 1656 |
+
return []
|
| 1657 |
+
|
| 1658 |
+
include = join(self.si.UniversalCRTSdkDir, 'include')
|
| 1659 |
+
return [join(include, '%sucrt' % self._ucrt_subdir)]
|
| 1660 |
+
|
| 1661 |
+
@property
|
| 1662 |
+
def _ucrt_subdir(self):
|
| 1663 |
+
"""
|
| 1664 |
+
Microsoft Universal C Runtime SDK version subdir.
|
| 1665 |
+
|
| 1666 |
+
Return
|
| 1667 |
+
------
|
| 1668 |
+
str
|
| 1669 |
+
subdir
|
| 1670 |
+
"""
|
| 1671 |
+
ucrtver = self.si.UniversalCRTSdkLastVersion
|
| 1672 |
+
return ('%s\\' % ucrtver) if ucrtver else ''
|
| 1673 |
+
|
| 1674 |
+
@property
|
| 1675 |
+
def FSharp(self):
|
| 1676 |
+
"""
|
| 1677 |
+
Microsoft Visual F#.
|
| 1678 |
+
|
| 1679 |
+
Return
|
| 1680 |
+
------
|
| 1681 |
+
list of str
|
| 1682 |
+
paths
|
| 1683 |
+
"""
|
| 1684 |
+
if 11.0 > self.vs_ver > 12.0:
|
| 1685 |
+
return []
|
| 1686 |
+
|
| 1687 |
+
return [self.si.FSharpInstallDir]
|
| 1688 |
+
|
| 1689 |
+
@property
|
| 1690 |
+
def VCRuntimeRedist(self):
|
| 1691 |
+
"""
|
| 1692 |
+
Microsoft Visual C++ runtime redistributable dll.
|
| 1693 |
+
|
| 1694 |
+
Return
|
| 1695 |
+
------
|
| 1696 |
+
str
|
| 1697 |
+
path
|
| 1698 |
+
"""
|
| 1699 |
+
vcruntime = 'vcruntime%d0.dll' % self.vc_ver
|
| 1700 |
+
arch_subdir = self.pi.target_dir(x64=True).strip('\\')
|
| 1701 |
+
|
| 1702 |
+
# Installation prefixes candidates
|
| 1703 |
+
prefixes = []
|
| 1704 |
+
tools_path = self.si.VCInstallDir
|
| 1705 |
+
redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist'))
|
| 1706 |
+
if isdir(redist_path):
|
| 1707 |
+
# Redist version may not be exactly the same as tools
|
| 1708 |
+
redist_path = join(redist_path, listdir(redist_path)[-1])
|
| 1709 |
+
prefixes += [redist_path, join(redist_path, 'onecore')]
|
| 1710 |
+
|
| 1711 |
+
prefixes += [join(tools_path, 'redist')] # VS14 legacy path
|
| 1712 |
+
|
| 1713 |
+
# CRT directory
|
| 1714 |
+
crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10),
|
| 1715 |
+
# Sometime store in directory with VS version instead of VC
|
| 1716 |
+
'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10))
|
| 1717 |
+
|
| 1718 |
+
# vcruntime path
|
| 1719 |
+
for prefix, crt_dir in itertools.product(prefixes, crt_dirs):
|
| 1720 |
+
path = join(prefix, arch_subdir, crt_dir, vcruntime)
|
| 1721 |
+
if isfile(path):
|
| 1722 |
+
return path
|
| 1723 |
+
|
| 1724 |
+
def return_env(self, exists=True):
|
| 1725 |
+
"""
|
| 1726 |
+
Return environment dict.
|
| 1727 |
+
|
| 1728 |
+
Parameters
|
| 1729 |
+
----------
|
| 1730 |
+
exists: bool
|
| 1731 |
+
It True, only return existing paths.
|
| 1732 |
+
|
| 1733 |
+
Return
|
| 1734 |
+
------
|
| 1735 |
+
dict
|
| 1736 |
+
environment
|
| 1737 |
+
"""
|
| 1738 |
+
env = dict(
|
| 1739 |
+
include=self._build_paths('include',
|
| 1740 |
+
[self.VCIncludes,
|
| 1741 |
+
self.OSIncludes,
|
| 1742 |
+
self.UCRTIncludes,
|
| 1743 |
+
self.NetFxSDKIncludes],
|
| 1744 |
+
exists),
|
| 1745 |
+
lib=self._build_paths('lib',
|
| 1746 |
+
[self.VCLibraries,
|
| 1747 |
+
self.OSLibraries,
|
| 1748 |
+
self.FxTools,
|
| 1749 |
+
self.UCRTLibraries,
|
| 1750 |
+
self.NetFxSDKLibraries],
|
| 1751 |
+
exists),
|
| 1752 |
+
libpath=self._build_paths('libpath',
|
| 1753 |
+
[self.VCLibraries,
|
| 1754 |
+
self.FxTools,
|
| 1755 |
+
self.VCStoreRefs,
|
| 1756 |
+
self.OSLibpath],
|
| 1757 |
+
exists),
|
| 1758 |
+
path=self._build_paths('path',
|
| 1759 |
+
[self.VCTools,
|
| 1760 |
+
self.VSTools,
|
| 1761 |
+
self.VsTDb,
|
| 1762 |
+
self.SdkTools,
|
| 1763 |
+
self.SdkSetup,
|
| 1764 |
+
self.FxTools,
|
| 1765 |
+
self.MSBuild,
|
| 1766 |
+
self.HTMLHelpWorkshop,
|
| 1767 |
+
self.FSharp],
|
| 1768 |
+
exists),
|
| 1769 |
+
)
|
| 1770 |
+
if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist):
|
| 1771 |
+
env['py_vcruntime_redist'] = self.VCRuntimeRedist
|
| 1772 |
+
return env
|
| 1773 |
+
|
| 1774 |
+
def _build_paths(self, name, spec_path_lists, exists):
|
| 1775 |
+
"""
|
| 1776 |
+
Given an environment variable name and specified paths,
|
| 1777 |
+
return a pathsep-separated string of paths containing
|
| 1778 |
+
unique, extant, directories from those paths and from
|
| 1779 |
+
the environment variable. Raise an error if no paths
|
| 1780 |
+
are resolved.
|
| 1781 |
+
|
| 1782 |
+
Parameters
|
| 1783 |
+
----------
|
| 1784 |
+
name: str
|
| 1785 |
+
Environment variable name
|
| 1786 |
+
spec_path_lists: list of str
|
| 1787 |
+
Paths
|
| 1788 |
+
exists: bool
|
| 1789 |
+
It True, only return existing paths.
|
| 1790 |
+
|
| 1791 |
+
Return
|
| 1792 |
+
------
|
| 1793 |
+
str
|
| 1794 |
+
Pathsep-separated paths
|
| 1795 |
+
"""
|
| 1796 |
+
# flatten spec_path_lists
|
| 1797 |
+
spec_paths = itertools.chain.from_iterable(spec_path_lists)
|
| 1798 |
+
env_paths = environ.get(name, '').split(pathsep)
|
| 1799 |
+
paths = itertools.chain(spec_paths, env_paths)
|
| 1800 |
+
extant_paths = list(filter(isdir, paths)) if exists else paths
|
| 1801 |
+
if not extant_paths:
|
| 1802 |
+
msg = "%s environment variable is empty" % name.upper()
|
| 1803 |
+
raise distutils.errors.DistutilsPlatformError(msg)
|
| 1804 |
+
unique_paths = unique_everseen(extant_paths)
|
| 1805 |
+
return pathsep.join(unique_paths)
|
venv/lib/python3.10/site-packages/setuptools/namespaces.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from distutils import log
|
| 3 |
+
import itertools
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
flatten = itertools.chain.from_iterable
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Installer:
|
| 10 |
+
|
| 11 |
+
nspkg_ext = '-nspkg.pth'
|
| 12 |
+
|
| 13 |
+
def install_namespaces(self):
|
| 14 |
+
nsp = self._get_all_ns_packages()
|
| 15 |
+
if not nsp:
|
| 16 |
+
return
|
| 17 |
+
filename, ext = os.path.splitext(self._get_target())
|
| 18 |
+
filename += self.nspkg_ext
|
| 19 |
+
self.outputs.append(filename)
|
| 20 |
+
log.info("Installing %s", filename)
|
| 21 |
+
lines = map(self._gen_nspkg_line, nsp)
|
| 22 |
+
|
| 23 |
+
if self.dry_run:
|
| 24 |
+
# always generate the lines, even in dry run
|
| 25 |
+
list(lines)
|
| 26 |
+
return
|
| 27 |
+
|
| 28 |
+
with open(filename, 'wt') as f:
|
| 29 |
+
f.writelines(lines)
|
| 30 |
+
|
| 31 |
+
def uninstall_namespaces(self):
|
| 32 |
+
filename, ext = os.path.splitext(self._get_target())
|
| 33 |
+
filename += self.nspkg_ext
|
| 34 |
+
if not os.path.exists(filename):
|
| 35 |
+
return
|
| 36 |
+
log.info("Removing %s", filename)
|
| 37 |
+
os.remove(filename)
|
| 38 |
+
|
| 39 |
+
def _get_target(self):
|
| 40 |
+
return self.target
|
| 41 |
+
|
| 42 |
+
_nspkg_tmpl = (
|
| 43 |
+
"import sys, types, os",
|
| 44 |
+
"has_mfs = sys.version_info > (3, 5)",
|
| 45 |
+
"p = os.path.join(%(root)s, *%(pth)r)",
|
| 46 |
+
"importlib = has_mfs and __import__('importlib.util')",
|
| 47 |
+
"has_mfs and __import__('importlib.machinery')",
|
| 48 |
+
(
|
| 49 |
+
"m = has_mfs and "
|
| 50 |
+
"sys.modules.setdefault(%(pkg)r, "
|
| 51 |
+
"importlib.util.module_from_spec("
|
| 52 |
+
"importlib.machinery.PathFinder.find_spec(%(pkg)r, "
|
| 53 |
+
"[os.path.dirname(p)])))"
|
| 54 |
+
),
|
| 55 |
+
(
|
| 56 |
+
"m = m or "
|
| 57 |
+
"sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))"
|
| 58 |
+
),
|
| 59 |
+
"mp = (m or []) and m.__dict__.setdefault('__path__',[])",
|
| 60 |
+
"(p not in mp) and mp.append(p)",
|
| 61 |
+
)
|
| 62 |
+
"lines for the namespace installer"
|
| 63 |
+
|
| 64 |
+
_nspkg_tmpl_multi = (
|
| 65 |
+
'm and setattr(sys.modules[%(parent)r], %(child)r, m)',
|
| 66 |
+
)
|
| 67 |
+
"additional line(s) when a parent package is indicated"
|
| 68 |
+
|
| 69 |
+
def _get_root(self):
|
| 70 |
+
return "sys._getframe(1).f_locals['sitedir']"
|
| 71 |
+
|
| 72 |
+
def _gen_nspkg_line(self, pkg):
|
| 73 |
+
pth = tuple(pkg.split('.'))
|
| 74 |
+
root = self._get_root()
|
| 75 |
+
tmpl_lines = self._nspkg_tmpl
|
| 76 |
+
parent, sep, child = pkg.rpartition('.')
|
| 77 |
+
if parent:
|
| 78 |
+
tmpl_lines += self._nspkg_tmpl_multi
|
| 79 |
+
return ';'.join(tmpl_lines) % locals() + '\n'
|
| 80 |
+
|
| 81 |
+
def _get_all_ns_packages(self):
|
| 82 |
+
"""Return sorted list of all package namespaces"""
|
| 83 |
+
pkgs = self.distribution.namespace_packages or []
|
| 84 |
+
return sorted(flatten(map(self._pkg_names, pkgs)))
|
| 85 |
+
|
| 86 |
+
@staticmethod
|
| 87 |
+
def _pkg_names(pkg):
|
| 88 |
+
"""
|
| 89 |
+
Given a namespace package, yield the components of that
|
| 90 |
+
package.
|
| 91 |
+
|
| 92 |
+
>>> names = Installer._pkg_names('a.b.c')
|
| 93 |
+
>>> set(names) == set(['a', 'a.b', 'a.b.c'])
|
| 94 |
+
True
|
| 95 |
+
"""
|
| 96 |
+
parts = pkg.split('.')
|
| 97 |
+
while parts:
|
| 98 |
+
yield '.'.join(parts)
|
| 99 |
+
parts.pop()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class DevelopInstaller(Installer):
|
| 103 |
+
def _get_root(self):
|
| 104 |
+
return repr(str(self.egg_path))
|
| 105 |
+
|
| 106 |
+
def _get_target(self):
|
| 107 |
+
return self.egg_link
|
venv/lib/python3.10/site-packages/setuptools/package_index.py
ADDED
|
@@ -0,0 +1,1150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PyPI and direct package downloading"""
|
| 2 |
+
import sys
|
| 3 |
+
import subprocess
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import io
|
| 7 |
+
import shutil
|
| 8 |
+
import socket
|
| 9 |
+
import base64
|
| 10 |
+
import hashlib
|
| 11 |
+
import itertools
|
| 12 |
+
import warnings
|
| 13 |
+
import configparser
|
| 14 |
+
import html
|
| 15 |
+
import http.client
|
| 16 |
+
import urllib.parse
|
| 17 |
+
import urllib.request
|
| 18 |
+
import urllib.error
|
| 19 |
+
from functools import wraps
|
| 20 |
+
|
| 21 |
+
import setuptools
|
| 22 |
+
from pkg_resources import (
|
| 23 |
+
CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST,
|
| 24 |
+
Environment, find_distributions, safe_name, safe_version,
|
| 25 |
+
to_filename, Requirement, DEVELOP_DIST, EGG_DIST, parse_version,
|
| 26 |
+
)
|
| 27 |
+
from distutils import log
|
| 28 |
+
from distutils.errors import DistutilsError
|
| 29 |
+
from fnmatch import translate
|
| 30 |
+
from setuptools.wheel import Wheel
|
| 31 |
+
from setuptools.extern.more_itertools import unique_everseen
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$')
|
| 35 |
+
HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I)
|
| 36 |
+
PYPI_MD5 = re.compile(
|
| 37 |
+
r'<a href="([^"#]+)">([^<]+)</a>\n\s+\(<a (?:title="MD5 hash"\n\s+)'
|
| 38 |
+
r'href="[^?]+\?:action=show_md5&digest=([0-9a-f]{32})">md5</a>\)'
|
| 39 |
+
)
|
| 40 |
+
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match
|
| 41 |
+
EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
|
| 42 |
+
|
| 43 |
+
__all__ = [
|
| 44 |
+
'PackageIndex', 'distros_for_url', 'parse_bdist_wininst',
|
| 45 |
+
'interpret_distro_name',
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
_SOCKET_TIMEOUT = 15
|
| 49 |
+
|
| 50 |
+
_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}"
|
| 51 |
+
user_agent = _tmpl.format(
|
| 52 |
+
py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def parse_requirement_arg(spec):
|
| 56 |
+
try:
|
| 57 |
+
return Requirement.parse(spec)
|
| 58 |
+
except ValueError as e:
|
| 59 |
+
raise DistutilsError(
|
| 60 |
+
"Not a URL, existing file, or requirement spec: %r" % (spec,)
|
| 61 |
+
) from e
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def parse_bdist_wininst(name):
|
| 65 |
+
"""Return (base,pyversion) or (None,None) for possible .exe name"""
|
| 66 |
+
|
| 67 |
+
lower = name.lower()
|
| 68 |
+
base, py_ver, plat = None, None, None
|
| 69 |
+
|
| 70 |
+
if lower.endswith('.exe'):
|
| 71 |
+
if lower.endswith('.win32.exe'):
|
| 72 |
+
base = name[:-10]
|
| 73 |
+
plat = 'win32'
|
| 74 |
+
elif lower.startswith('.win32-py', -16):
|
| 75 |
+
py_ver = name[-7:-4]
|
| 76 |
+
base = name[:-16]
|
| 77 |
+
plat = 'win32'
|
| 78 |
+
elif lower.endswith('.win-amd64.exe'):
|
| 79 |
+
base = name[:-14]
|
| 80 |
+
plat = 'win-amd64'
|
| 81 |
+
elif lower.startswith('.win-amd64-py', -20):
|
| 82 |
+
py_ver = name[-7:-4]
|
| 83 |
+
base = name[:-20]
|
| 84 |
+
plat = 'win-amd64'
|
| 85 |
+
return base, py_ver, plat
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def egg_info_for_url(url):
|
| 89 |
+
parts = urllib.parse.urlparse(url)
|
| 90 |
+
scheme, server, path, parameters, query, fragment = parts
|
| 91 |
+
base = urllib.parse.unquote(path.split('/')[-1])
|
| 92 |
+
if server == 'sourceforge.net' and base == 'download': # XXX Yuck
|
| 93 |
+
base = urllib.parse.unquote(path.split('/')[-2])
|
| 94 |
+
if '#' in base:
|
| 95 |
+
base, fragment = base.split('#', 1)
|
| 96 |
+
return base, fragment
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def distros_for_url(url, metadata=None):
|
| 100 |
+
"""Yield egg or source distribution objects that might be found at a URL"""
|
| 101 |
+
base, fragment = egg_info_for_url(url)
|
| 102 |
+
for dist in distros_for_location(url, base, metadata):
|
| 103 |
+
yield dist
|
| 104 |
+
if fragment:
|
| 105 |
+
match = EGG_FRAGMENT.match(fragment)
|
| 106 |
+
if match:
|
| 107 |
+
for dist in interpret_distro_name(
|
| 108 |
+
url, match.group(1), metadata, precedence=CHECKOUT_DIST
|
| 109 |
+
):
|
| 110 |
+
yield dist
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def distros_for_location(location, basename, metadata=None):
|
| 114 |
+
"""Yield egg or source distribution objects based on basename"""
|
| 115 |
+
if basename.endswith('.egg.zip'):
|
| 116 |
+
basename = basename[:-4] # strip the .zip
|
| 117 |
+
if basename.endswith('.egg') and '-' in basename:
|
| 118 |
+
# only one, unambiguous interpretation
|
| 119 |
+
return [Distribution.from_location(location, basename, metadata)]
|
| 120 |
+
if basename.endswith('.whl') and '-' in basename:
|
| 121 |
+
wheel = Wheel(basename)
|
| 122 |
+
if not wheel.is_compatible():
|
| 123 |
+
return []
|
| 124 |
+
return [Distribution(
|
| 125 |
+
location=location,
|
| 126 |
+
project_name=wheel.project_name,
|
| 127 |
+
version=wheel.version,
|
| 128 |
+
# Increase priority over eggs.
|
| 129 |
+
precedence=EGG_DIST + 1,
|
| 130 |
+
)]
|
| 131 |
+
if basename.endswith('.exe'):
|
| 132 |
+
win_base, py_ver, platform = parse_bdist_wininst(basename)
|
| 133 |
+
if win_base is not None:
|
| 134 |
+
return interpret_distro_name(
|
| 135 |
+
location, win_base, metadata, py_ver, BINARY_DIST, platform
|
| 136 |
+
)
|
| 137 |
+
# Try source distro extensions (.zip, .tgz, etc.)
|
| 138 |
+
#
|
| 139 |
+
for ext in EXTENSIONS:
|
| 140 |
+
if basename.endswith(ext):
|
| 141 |
+
basename = basename[:-len(ext)]
|
| 142 |
+
return interpret_distro_name(location, basename, metadata)
|
| 143 |
+
return [] # no extension matched
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def distros_for_filename(filename, metadata=None):
|
| 147 |
+
"""Yield possible egg or source distribution objects based on a filename"""
|
| 148 |
+
return distros_for_location(
|
| 149 |
+
normalize_path(filename), os.path.basename(filename), metadata
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def interpret_distro_name(
|
| 154 |
+
location, basename, metadata, py_version=None, precedence=SOURCE_DIST,
|
| 155 |
+
platform=None
|
| 156 |
+
):
|
| 157 |
+
"""Generate alternative interpretations of a source distro name
|
| 158 |
+
|
| 159 |
+
Note: if `location` is a filesystem filename, you should call
|
| 160 |
+
``pkg_resources.normalize_path()`` on it before passing it to this
|
| 161 |
+
routine!
|
| 162 |
+
"""
|
| 163 |
+
# Generate alternative interpretations of a source distro name
|
| 164 |
+
# Because some packages are ambiguous as to name/versions split
|
| 165 |
+
# e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc.
|
| 166 |
+
# So, we generate each possible interpretation (e.g. "adns, python-1.1.0"
|
| 167 |
+
# "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice,
|
| 168 |
+
# the spurious interpretations should be ignored, because in the event
|
| 169 |
+
# there's also an "adns" package, the spurious "python-1.1.0" version will
|
| 170 |
+
# compare lower than any numeric version number, and is therefore unlikely
|
| 171 |
+
# to match a request for it. It's still a potential problem, though, and
|
| 172 |
+
# in the long run PyPI and the distutils should go for "safe" names and
|
| 173 |
+
# versions in distribution archive names (sdist and bdist).
|
| 174 |
+
|
| 175 |
+
parts = basename.split('-')
|
| 176 |
+
if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]):
|
| 177 |
+
# it is a bdist_dumb, not an sdist -- bail out
|
| 178 |
+
return
|
| 179 |
+
|
| 180 |
+
for p in range(1, len(parts) + 1):
|
| 181 |
+
yield Distribution(
|
| 182 |
+
location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]),
|
| 183 |
+
py_version=py_version, precedence=precedence,
|
| 184 |
+
platform=platform
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def unique_values(func):
|
| 189 |
+
"""
|
| 190 |
+
Wrap a function returning an iterable such that the resulting iterable
|
| 191 |
+
only ever yields unique items.
|
| 192 |
+
"""
|
| 193 |
+
|
| 194 |
+
@wraps(func)
|
| 195 |
+
def wrapper(*args, **kwargs):
|
| 196 |
+
return unique_everseen(func(*args, **kwargs))
|
| 197 |
+
|
| 198 |
+
return wrapper
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
REL = re.compile(r"""<([^>]*\srel\s{0,10}=\s{0,10}['"]?([^'" >]+)[^>]*)>""", re.I)
|
| 202 |
+
# this line is here to fix emacs' cruddy broken syntax highlighting
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
@unique_values
|
| 206 |
+
def find_external_links(url, page):
|
| 207 |
+
"""Find rel="homepage" and rel="download" links in `page`, yielding URLs"""
|
| 208 |
+
|
| 209 |
+
for match in REL.finditer(page):
|
| 210 |
+
tag, rel = match.groups()
|
| 211 |
+
rels = set(map(str.strip, rel.lower().split(',')))
|
| 212 |
+
if 'homepage' in rels or 'download' in rels:
|
| 213 |
+
for match in HREF.finditer(tag):
|
| 214 |
+
yield urllib.parse.urljoin(url, htmldecode(match.group(1)))
|
| 215 |
+
|
| 216 |
+
for tag in ("<th>Home Page", "<th>Download URL"):
|
| 217 |
+
pos = page.find(tag)
|
| 218 |
+
if pos != -1:
|
| 219 |
+
match = HREF.search(page, pos)
|
| 220 |
+
if match:
|
| 221 |
+
yield urllib.parse.urljoin(url, htmldecode(match.group(1)))
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class ContentChecker:
|
| 225 |
+
"""
|
| 226 |
+
A null content checker that defines the interface for checking content
|
| 227 |
+
"""
|
| 228 |
+
|
| 229 |
+
def feed(self, block):
|
| 230 |
+
"""
|
| 231 |
+
Feed a block of data to the hash.
|
| 232 |
+
"""
|
| 233 |
+
return
|
| 234 |
+
|
| 235 |
+
def is_valid(self):
|
| 236 |
+
"""
|
| 237 |
+
Check the hash. Return False if validation fails.
|
| 238 |
+
"""
|
| 239 |
+
return True
|
| 240 |
+
|
| 241 |
+
def report(self, reporter, template):
|
| 242 |
+
"""
|
| 243 |
+
Call reporter with information about the checker (hash name)
|
| 244 |
+
substituted into the template.
|
| 245 |
+
"""
|
| 246 |
+
return
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
class HashChecker(ContentChecker):
|
| 250 |
+
pattern = re.compile(
|
| 251 |
+
r'(?P<hash_name>sha1|sha224|sha384|sha256|sha512|md5)='
|
| 252 |
+
r'(?P<expected>[a-f0-9]+)'
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
def __init__(self, hash_name, expected):
|
| 256 |
+
self.hash_name = hash_name
|
| 257 |
+
self.hash = hashlib.new(hash_name)
|
| 258 |
+
self.expected = expected
|
| 259 |
+
|
| 260 |
+
@classmethod
|
| 261 |
+
def from_url(cls, url):
|
| 262 |
+
"Construct a (possibly null) ContentChecker from a URL"
|
| 263 |
+
fragment = urllib.parse.urlparse(url)[-1]
|
| 264 |
+
if not fragment:
|
| 265 |
+
return ContentChecker()
|
| 266 |
+
match = cls.pattern.search(fragment)
|
| 267 |
+
if not match:
|
| 268 |
+
return ContentChecker()
|
| 269 |
+
return cls(**match.groupdict())
|
| 270 |
+
|
| 271 |
+
def feed(self, block):
|
| 272 |
+
self.hash.update(block)
|
| 273 |
+
|
| 274 |
+
def is_valid(self):
|
| 275 |
+
return self.hash.hexdigest() == self.expected
|
| 276 |
+
|
| 277 |
+
def report(self, reporter, template):
|
| 278 |
+
msg = template % self.hash_name
|
| 279 |
+
return reporter(msg)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
class PackageIndex(Environment):
|
| 283 |
+
"""A distribution index that scans web pages for download URLs"""
|
| 284 |
+
|
| 285 |
+
def __init__(
|
| 286 |
+
self, index_url="https://pypi.org/simple/", hosts=('*',),
|
| 287 |
+
ca_bundle=None, verify_ssl=True, *args, **kw
|
| 288 |
+
):
|
| 289 |
+
Environment.__init__(self, *args, **kw)
|
| 290 |
+
self.index_url = index_url + "/" [:not index_url.endswith('/')]
|
| 291 |
+
self.scanned_urls = {}
|
| 292 |
+
self.fetched_urls = {}
|
| 293 |
+
self.package_pages = {}
|
| 294 |
+
self.allows = re.compile('|'.join(map(translate, hosts))).match
|
| 295 |
+
self.to_scan = []
|
| 296 |
+
self.opener = urllib.request.urlopen
|
| 297 |
+
|
| 298 |
+
def add(self, dist):
|
| 299 |
+
# ignore invalid versions
|
| 300 |
+
try:
|
| 301 |
+
parse_version(dist.version)
|
| 302 |
+
except Exception:
|
| 303 |
+
return
|
| 304 |
+
return super().add(dist)
|
| 305 |
+
|
| 306 |
+
# FIXME: 'PackageIndex.process_url' is too complex (14)
|
| 307 |
+
def process_url(self, url, retrieve=False): # noqa: C901
|
| 308 |
+
"""Evaluate a URL as a possible download, and maybe retrieve it"""
|
| 309 |
+
if url in self.scanned_urls and not retrieve:
|
| 310 |
+
return
|
| 311 |
+
self.scanned_urls[url] = True
|
| 312 |
+
if not URL_SCHEME(url):
|
| 313 |
+
self.process_filename(url)
|
| 314 |
+
return
|
| 315 |
+
else:
|
| 316 |
+
dists = list(distros_for_url(url))
|
| 317 |
+
if dists:
|
| 318 |
+
if not self.url_ok(url):
|
| 319 |
+
return
|
| 320 |
+
self.debug("Found link: %s", url)
|
| 321 |
+
|
| 322 |
+
if dists or not retrieve or url in self.fetched_urls:
|
| 323 |
+
list(map(self.add, dists))
|
| 324 |
+
return # don't need the actual page
|
| 325 |
+
|
| 326 |
+
if not self.url_ok(url):
|
| 327 |
+
self.fetched_urls[url] = True
|
| 328 |
+
return
|
| 329 |
+
|
| 330 |
+
self.info("Reading %s", url)
|
| 331 |
+
self.fetched_urls[url] = True # prevent multiple fetch attempts
|
| 332 |
+
tmpl = "Download error on %s: %%s -- Some packages may not be found!"
|
| 333 |
+
f = self.open_url(url, tmpl % url)
|
| 334 |
+
if f is None:
|
| 335 |
+
return
|
| 336 |
+
if isinstance(f, urllib.error.HTTPError) and f.code == 401:
|
| 337 |
+
self.info("Authentication error: %s" % f.msg)
|
| 338 |
+
self.fetched_urls[f.url] = True
|
| 339 |
+
if 'html' not in f.headers.get('content-type', '').lower():
|
| 340 |
+
f.close() # not html, we can't process it
|
| 341 |
+
return
|
| 342 |
+
|
| 343 |
+
base = f.url # handle redirects
|
| 344 |
+
page = f.read()
|
| 345 |
+
if not isinstance(page, str):
|
| 346 |
+
# In Python 3 and got bytes but want str.
|
| 347 |
+
if isinstance(f, urllib.error.HTTPError):
|
| 348 |
+
# Errors have no charset, assume latin1:
|
| 349 |
+
charset = 'latin-1'
|
| 350 |
+
else:
|
| 351 |
+
charset = f.headers.get_param('charset') or 'latin-1'
|
| 352 |
+
page = page.decode(charset, "ignore")
|
| 353 |
+
f.close()
|
| 354 |
+
for match in HREF.finditer(page):
|
| 355 |
+
link = urllib.parse.urljoin(base, htmldecode(match.group(1)))
|
| 356 |
+
self.process_url(link)
|
| 357 |
+
if url.startswith(self.index_url) and getattr(f, 'code', None) != 404:
|
| 358 |
+
page = self.process_index(url, page)
|
| 359 |
+
|
| 360 |
+
def process_filename(self, fn, nested=False):
|
| 361 |
+
# process filenames or directories
|
| 362 |
+
if not os.path.exists(fn):
|
| 363 |
+
self.warn("Not found: %s", fn)
|
| 364 |
+
return
|
| 365 |
+
|
| 366 |
+
if os.path.isdir(fn) and not nested:
|
| 367 |
+
path = os.path.realpath(fn)
|
| 368 |
+
for item in os.listdir(path):
|
| 369 |
+
self.process_filename(os.path.join(path, item), True)
|
| 370 |
+
|
| 371 |
+
dists = distros_for_filename(fn)
|
| 372 |
+
if dists:
|
| 373 |
+
self.debug("Found: %s", fn)
|
| 374 |
+
list(map(self.add, dists))
|
| 375 |
+
|
| 376 |
+
def url_ok(self, url, fatal=False):
|
| 377 |
+
s = URL_SCHEME(url)
|
| 378 |
+
is_file = s and s.group(1).lower() == 'file'
|
| 379 |
+
if is_file or self.allows(urllib.parse.urlparse(url)[1]):
|
| 380 |
+
return True
|
| 381 |
+
msg = (
|
| 382 |
+
"\nNote: Bypassing %s (disallowed host; see "
|
| 383 |
+
"http://bit.ly/2hrImnY for details).\n")
|
| 384 |
+
if fatal:
|
| 385 |
+
raise DistutilsError(msg % url)
|
| 386 |
+
else:
|
| 387 |
+
self.warn(msg, url)
|
| 388 |
+
|
| 389 |
+
def scan_egg_links(self, search_path):
|
| 390 |
+
dirs = filter(os.path.isdir, search_path)
|
| 391 |
+
egg_links = (
|
| 392 |
+
(path, entry)
|
| 393 |
+
for path in dirs
|
| 394 |
+
for entry in os.listdir(path)
|
| 395 |
+
if entry.endswith('.egg-link')
|
| 396 |
+
)
|
| 397 |
+
list(itertools.starmap(self.scan_egg_link, egg_links))
|
| 398 |
+
|
| 399 |
+
def scan_egg_link(self, path, entry):
|
| 400 |
+
with open(os.path.join(path, entry)) as raw_lines:
|
| 401 |
+
# filter non-empty lines
|
| 402 |
+
lines = list(filter(None, map(str.strip, raw_lines)))
|
| 403 |
+
|
| 404 |
+
if len(lines) != 2:
|
| 405 |
+
# format is not recognized; punt
|
| 406 |
+
return
|
| 407 |
+
|
| 408 |
+
egg_path, setup_path = lines
|
| 409 |
+
|
| 410 |
+
for dist in find_distributions(os.path.join(path, egg_path)):
|
| 411 |
+
dist.location = os.path.join(path, *lines)
|
| 412 |
+
dist.precedence = SOURCE_DIST
|
| 413 |
+
self.add(dist)
|
| 414 |
+
|
| 415 |
+
def _scan(self, link):
|
| 416 |
+
# Process a URL to see if it's for a package page
|
| 417 |
+
NO_MATCH_SENTINEL = None, None
|
| 418 |
+
if not link.startswith(self.index_url):
|
| 419 |
+
return NO_MATCH_SENTINEL
|
| 420 |
+
|
| 421 |
+
parts = list(map(
|
| 422 |
+
urllib.parse.unquote, link[len(self.index_url):].split('/')
|
| 423 |
+
))
|
| 424 |
+
if len(parts) != 2 or '#' in parts[1]:
|
| 425 |
+
return NO_MATCH_SENTINEL
|
| 426 |
+
|
| 427 |
+
# it's a package page, sanitize and index it
|
| 428 |
+
pkg = safe_name(parts[0])
|
| 429 |
+
ver = safe_version(parts[1])
|
| 430 |
+
self.package_pages.setdefault(pkg.lower(), {})[link] = True
|
| 431 |
+
return to_filename(pkg), to_filename(ver)
|
| 432 |
+
|
| 433 |
+
def process_index(self, url, page):
|
| 434 |
+
"""Process the contents of a PyPI page"""
|
| 435 |
+
|
| 436 |
+
# process an index page into the package-page index
|
| 437 |
+
for match in HREF.finditer(page):
|
| 438 |
+
try:
|
| 439 |
+
self._scan(urllib.parse.urljoin(url, htmldecode(match.group(1))))
|
| 440 |
+
except ValueError:
|
| 441 |
+
pass
|
| 442 |
+
|
| 443 |
+
pkg, ver = self._scan(url) # ensure this page is in the page index
|
| 444 |
+
if not pkg:
|
| 445 |
+
return "" # no sense double-scanning non-package pages
|
| 446 |
+
|
| 447 |
+
# process individual package page
|
| 448 |
+
for new_url in find_external_links(url, page):
|
| 449 |
+
# Process the found URL
|
| 450 |
+
base, frag = egg_info_for_url(new_url)
|
| 451 |
+
if base.endswith('.py') and not frag:
|
| 452 |
+
if ver:
|
| 453 |
+
new_url += '#egg=%s-%s' % (pkg, ver)
|
| 454 |
+
else:
|
| 455 |
+
self.need_version_info(url)
|
| 456 |
+
self.scan_url(new_url)
|
| 457 |
+
|
| 458 |
+
return PYPI_MD5.sub(
|
| 459 |
+
lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2), page
|
| 460 |
+
)
|
| 461 |
+
|
| 462 |
+
def need_version_info(self, url):
|
| 463 |
+
self.scan_all(
|
| 464 |
+
"Page at %s links to .py file(s) without version info; an index "
|
| 465 |
+
"scan is required.", url
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
def scan_all(self, msg=None, *args):
|
| 469 |
+
if self.index_url not in self.fetched_urls:
|
| 470 |
+
if msg:
|
| 471 |
+
self.warn(msg, *args)
|
| 472 |
+
self.info(
|
| 473 |
+
"Scanning index of all packages (this may take a while)"
|
| 474 |
+
)
|
| 475 |
+
self.scan_url(self.index_url)
|
| 476 |
+
|
| 477 |
+
def find_packages(self, requirement):
|
| 478 |
+
self.scan_url(self.index_url + requirement.unsafe_name + '/')
|
| 479 |
+
|
| 480 |
+
if not self.package_pages.get(requirement.key):
|
| 481 |
+
# Fall back to safe version of the name
|
| 482 |
+
self.scan_url(self.index_url + requirement.project_name + '/')
|
| 483 |
+
|
| 484 |
+
if not self.package_pages.get(requirement.key):
|
| 485 |
+
# We couldn't find the target package, so search the index page too
|
| 486 |
+
self.not_found_in_index(requirement)
|
| 487 |
+
|
| 488 |
+
for url in list(self.package_pages.get(requirement.key, ())):
|
| 489 |
+
# scan each page that might be related to the desired package
|
| 490 |
+
self.scan_url(url)
|
| 491 |
+
|
| 492 |
+
def obtain(self, requirement, installer=None):
|
| 493 |
+
self.prescan()
|
| 494 |
+
self.find_packages(requirement)
|
| 495 |
+
for dist in self[requirement.key]:
|
| 496 |
+
if dist in requirement:
|
| 497 |
+
return dist
|
| 498 |
+
self.debug("%s does not match %s", requirement, dist)
|
| 499 |
+
return super(PackageIndex, self).obtain(requirement, installer)
|
| 500 |
+
|
| 501 |
+
def check_hash(self, checker, filename, tfp):
|
| 502 |
+
"""
|
| 503 |
+
checker is a ContentChecker
|
| 504 |
+
"""
|
| 505 |
+
checker.report(
|
| 506 |
+
self.debug,
|
| 507 |
+
"Validating %%s checksum for %s" % filename)
|
| 508 |
+
if not checker.is_valid():
|
| 509 |
+
tfp.close()
|
| 510 |
+
os.unlink(filename)
|
| 511 |
+
raise DistutilsError(
|
| 512 |
+
"%s validation failed for %s; "
|
| 513 |
+
"possible download problem?"
|
| 514 |
+
% (checker.hash.name, os.path.basename(filename))
|
| 515 |
+
)
|
| 516 |
+
|
| 517 |
+
def add_find_links(self, urls):
|
| 518 |
+
"""Add `urls` to the list that will be prescanned for searches"""
|
| 519 |
+
for url in urls:
|
| 520 |
+
if (
|
| 521 |
+
self.to_scan is None # if we have already "gone online"
|
| 522 |
+
or not URL_SCHEME(url) # or it's a local file/directory
|
| 523 |
+
or url.startswith('file:')
|
| 524 |
+
or list(distros_for_url(url)) # or a direct package link
|
| 525 |
+
):
|
| 526 |
+
# then go ahead and process it now
|
| 527 |
+
self.scan_url(url)
|
| 528 |
+
else:
|
| 529 |
+
# otherwise, defer retrieval till later
|
| 530 |
+
self.to_scan.append(url)
|
| 531 |
+
|
| 532 |
+
def prescan(self):
|
| 533 |
+
"""Scan urls scheduled for prescanning (e.g. --find-links)"""
|
| 534 |
+
if self.to_scan:
|
| 535 |
+
list(map(self.scan_url, self.to_scan))
|
| 536 |
+
self.to_scan = None # from now on, go ahead and process immediately
|
| 537 |
+
|
| 538 |
+
def not_found_in_index(self, requirement):
|
| 539 |
+
if self[requirement.key]: # we've seen at least one distro
|
| 540 |
+
meth, msg = self.info, "Couldn't retrieve index page for %r"
|
| 541 |
+
else: # no distros seen for this name, might be misspelled
|
| 542 |
+
meth, msg = (
|
| 543 |
+
self.warn,
|
| 544 |
+
"Couldn't find index page for %r (maybe misspelled?)")
|
| 545 |
+
meth(msg, requirement.unsafe_name)
|
| 546 |
+
self.scan_all()
|
| 547 |
+
|
| 548 |
+
def download(self, spec, tmpdir):
|
| 549 |
+
"""Locate and/or download `spec` to `tmpdir`, returning a local path
|
| 550 |
+
|
| 551 |
+
`spec` may be a ``Requirement`` object, or a string containing a URL,
|
| 552 |
+
an existing local filename, or a project/version requirement spec
|
| 553 |
+
(i.e. the string form of a ``Requirement`` object). If it is the URL
|
| 554 |
+
of a .py file with an unambiguous ``#egg=name-version`` tag (i.e., one
|
| 555 |
+
that escapes ``-`` as ``_`` throughout), a trivial ``setup.py`` is
|
| 556 |
+
automatically created alongside the downloaded file.
|
| 557 |
+
|
| 558 |
+
If `spec` is a ``Requirement`` object or a string containing a
|
| 559 |
+
project/version requirement spec, this method returns the location of
|
| 560 |
+
a matching distribution (possibly after downloading it to `tmpdir`).
|
| 561 |
+
If `spec` is a locally existing file or directory name, it is simply
|
| 562 |
+
returned unchanged. If `spec` is a URL, it is downloaded to a subpath
|
| 563 |
+
of `tmpdir`, and the local filename is returned. Various errors may be
|
| 564 |
+
raised if a problem occurs during downloading.
|
| 565 |
+
"""
|
| 566 |
+
if not isinstance(spec, Requirement):
|
| 567 |
+
scheme = URL_SCHEME(spec)
|
| 568 |
+
if scheme:
|
| 569 |
+
# It's a url, download it to tmpdir
|
| 570 |
+
found = self._download_url(spec, tmpdir)
|
| 571 |
+
base, fragment = egg_info_for_url(spec)
|
| 572 |
+
if base.endswith('.py'):
|
| 573 |
+
found = self.gen_setup(found, fragment, tmpdir)
|
| 574 |
+
return found
|
| 575 |
+
elif os.path.exists(spec):
|
| 576 |
+
# Existing file or directory, just return it
|
| 577 |
+
return spec
|
| 578 |
+
else:
|
| 579 |
+
spec = parse_requirement_arg(spec)
|
| 580 |
+
return getattr(self.fetch_distribution(spec, tmpdir), 'location', None)
|
| 581 |
+
|
| 582 |
+
def fetch_distribution( # noqa: C901 # is too complex (14) # FIXME
|
| 583 |
+
self, requirement, tmpdir, force_scan=False, source=False,
|
| 584 |
+
develop_ok=False, local_index=None):
|
| 585 |
+
"""Obtain a distribution suitable for fulfilling `requirement`
|
| 586 |
+
|
| 587 |
+
`requirement` must be a ``pkg_resources.Requirement`` instance.
|
| 588 |
+
If necessary, or if the `force_scan` flag is set, the requirement is
|
| 589 |
+
searched for in the (online) package index as well as the locally
|
| 590 |
+
installed packages. If a distribution matching `requirement` is found,
|
| 591 |
+
the returned distribution's ``location`` is the value you would have
|
| 592 |
+
gotten from calling the ``download()`` method with the matching
|
| 593 |
+
distribution's URL or filename. If no matching distribution is found,
|
| 594 |
+
``None`` is returned.
|
| 595 |
+
|
| 596 |
+
If the `source` flag is set, only source distributions and source
|
| 597 |
+
checkout links will be considered. Unless the `develop_ok` flag is
|
| 598 |
+
set, development and system eggs (i.e., those using the ``.egg-info``
|
| 599 |
+
format) will be ignored.
|
| 600 |
+
"""
|
| 601 |
+
# process a Requirement
|
| 602 |
+
self.info("Searching for %s", requirement)
|
| 603 |
+
skipped = {}
|
| 604 |
+
dist = None
|
| 605 |
+
|
| 606 |
+
def find(req, env=None):
|
| 607 |
+
if env is None:
|
| 608 |
+
env = self
|
| 609 |
+
# Find a matching distribution; may be called more than once
|
| 610 |
+
|
| 611 |
+
for dist in env[req.key]:
|
| 612 |
+
|
| 613 |
+
if dist.precedence == DEVELOP_DIST and not develop_ok:
|
| 614 |
+
if dist not in skipped:
|
| 615 |
+
self.warn(
|
| 616 |
+
"Skipping development or system egg: %s", dist,
|
| 617 |
+
)
|
| 618 |
+
skipped[dist] = 1
|
| 619 |
+
continue
|
| 620 |
+
|
| 621 |
+
test = (
|
| 622 |
+
dist in req
|
| 623 |
+
and (dist.precedence <= SOURCE_DIST or not source)
|
| 624 |
+
)
|
| 625 |
+
if test:
|
| 626 |
+
loc = self.download(dist.location, tmpdir)
|
| 627 |
+
dist.download_location = loc
|
| 628 |
+
if os.path.exists(dist.download_location):
|
| 629 |
+
return dist
|
| 630 |
+
|
| 631 |
+
if force_scan:
|
| 632 |
+
self.prescan()
|
| 633 |
+
self.find_packages(requirement)
|
| 634 |
+
dist = find(requirement)
|
| 635 |
+
|
| 636 |
+
if not dist and local_index is not None:
|
| 637 |
+
dist = find(requirement, local_index)
|
| 638 |
+
|
| 639 |
+
if dist is None:
|
| 640 |
+
if self.to_scan is not None:
|
| 641 |
+
self.prescan()
|
| 642 |
+
dist = find(requirement)
|
| 643 |
+
|
| 644 |
+
if dist is None and not force_scan:
|
| 645 |
+
self.find_packages(requirement)
|
| 646 |
+
dist = find(requirement)
|
| 647 |
+
|
| 648 |
+
if dist is None:
|
| 649 |
+
self.warn(
|
| 650 |
+
"No local packages or working download links found for %s%s",
|
| 651 |
+
(source and "a source distribution of " or ""),
|
| 652 |
+
requirement,
|
| 653 |
+
)
|
| 654 |
+
else:
|
| 655 |
+
self.info("Best match: %s", dist)
|
| 656 |
+
return dist.clone(location=dist.download_location)
|
| 657 |
+
|
| 658 |
+
def fetch(self, requirement, tmpdir, force_scan=False, source=False):
|
| 659 |
+
"""Obtain a file suitable for fulfilling `requirement`
|
| 660 |
+
|
| 661 |
+
DEPRECATED; use the ``fetch_distribution()`` method now instead. For
|
| 662 |
+
backward compatibility, this routine is identical but returns the
|
| 663 |
+
``location`` of the downloaded distribution instead of a distribution
|
| 664 |
+
object.
|
| 665 |
+
"""
|
| 666 |
+
dist = self.fetch_distribution(requirement, tmpdir, force_scan, source)
|
| 667 |
+
if dist is not None:
|
| 668 |
+
return dist.location
|
| 669 |
+
return None
|
| 670 |
+
|
| 671 |
+
def gen_setup(self, filename, fragment, tmpdir):
|
| 672 |
+
match = EGG_FRAGMENT.match(fragment)
|
| 673 |
+
dists = match and [
|
| 674 |
+
d for d in
|
| 675 |
+
interpret_distro_name(filename, match.group(1), None) if d.version
|
| 676 |
+
] or []
|
| 677 |
+
|
| 678 |
+
if len(dists) == 1: # unambiguous ``#egg`` fragment
|
| 679 |
+
basename = os.path.basename(filename)
|
| 680 |
+
|
| 681 |
+
# Make sure the file has been downloaded to the temp dir.
|
| 682 |
+
if os.path.dirname(filename) != tmpdir:
|
| 683 |
+
dst = os.path.join(tmpdir, basename)
|
| 684 |
+
from setuptools.command.easy_install import samefile
|
| 685 |
+
if not samefile(filename, dst):
|
| 686 |
+
shutil.copy2(filename, dst)
|
| 687 |
+
filename = dst
|
| 688 |
+
|
| 689 |
+
with open(os.path.join(tmpdir, 'setup.py'), 'w') as file:
|
| 690 |
+
file.write(
|
| 691 |
+
"from setuptools import setup\n"
|
| 692 |
+
"setup(name=%r, version=%r, py_modules=[%r])\n"
|
| 693 |
+
% (
|
| 694 |
+
dists[0].project_name, dists[0].version,
|
| 695 |
+
os.path.splitext(basename)[0]
|
| 696 |
+
)
|
| 697 |
+
)
|
| 698 |
+
return filename
|
| 699 |
+
|
| 700 |
+
elif match:
|
| 701 |
+
raise DistutilsError(
|
| 702 |
+
"Can't unambiguously interpret project/version identifier %r; "
|
| 703 |
+
"any dashes in the name or version should be escaped using "
|
| 704 |
+
"underscores. %r" % (fragment, dists)
|
| 705 |
+
)
|
| 706 |
+
else:
|
| 707 |
+
raise DistutilsError(
|
| 708 |
+
"Can't process plain .py files without an '#egg=name-version'"
|
| 709 |
+
" suffix to enable automatic setup script generation."
|
| 710 |
+
)
|
| 711 |
+
|
| 712 |
+
dl_blocksize = 8192
|
| 713 |
+
|
| 714 |
+
def _download_to(self, url, filename):
|
| 715 |
+
self.info("Downloading %s", url)
|
| 716 |
+
# Download the file
|
| 717 |
+
fp = None
|
| 718 |
+
try:
|
| 719 |
+
checker = HashChecker.from_url(url)
|
| 720 |
+
fp = self.open_url(url)
|
| 721 |
+
if isinstance(fp, urllib.error.HTTPError):
|
| 722 |
+
raise DistutilsError(
|
| 723 |
+
"Can't download %s: %s %s" % (url, fp.code, fp.msg)
|
| 724 |
+
)
|
| 725 |
+
headers = fp.info()
|
| 726 |
+
blocknum = 0
|
| 727 |
+
bs = self.dl_blocksize
|
| 728 |
+
size = -1
|
| 729 |
+
if "content-length" in headers:
|
| 730 |
+
# Some servers return multiple Content-Length headers :(
|
| 731 |
+
sizes = headers.get_all('Content-Length')
|
| 732 |
+
size = max(map(int, sizes))
|
| 733 |
+
self.reporthook(url, filename, blocknum, bs, size)
|
| 734 |
+
with open(filename, 'wb') as tfp:
|
| 735 |
+
while True:
|
| 736 |
+
block = fp.read(bs)
|
| 737 |
+
if block:
|
| 738 |
+
checker.feed(block)
|
| 739 |
+
tfp.write(block)
|
| 740 |
+
blocknum += 1
|
| 741 |
+
self.reporthook(url, filename, blocknum, bs, size)
|
| 742 |
+
else:
|
| 743 |
+
break
|
| 744 |
+
self.check_hash(checker, filename, tfp)
|
| 745 |
+
return headers
|
| 746 |
+
finally:
|
| 747 |
+
if fp:
|
| 748 |
+
fp.close()
|
| 749 |
+
|
| 750 |
+
def reporthook(self, url, filename, blocknum, blksize, size):
|
| 751 |
+
pass # no-op
|
| 752 |
+
|
| 753 |
+
# FIXME:
|
| 754 |
+
def open_url(self, url, warning=None): # noqa: C901 # is too complex (12)
|
| 755 |
+
if url.startswith('file:'):
|
| 756 |
+
return local_open(url)
|
| 757 |
+
try:
|
| 758 |
+
return open_with_auth(url, self.opener)
|
| 759 |
+
except (ValueError, http.client.InvalidURL) as v:
|
| 760 |
+
msg = ' '.join([str(arg) for arg in v.args])
|
| 761 |
+
if warning:
|
| 762 |
+
self.warn(warning, msg)
|
| 763 |
+
else:
|
| 764 |
+
raise DistutilsError('%s %s' % (url, msg)) from v
|
| 765 |
+
except urllib.error.HTTPError as v:
|
| 766 |
+
return v
|
| 767 |
+
except urllib.error.URLError as v:
|
| 768 |
+
if warning:
|
| 769 |
+
self.warn(warning, v.reason)
|
| 770 |
+
else:
|
| 771 |
+
raise DistutilsError("Download error for %s: %s"
|
| 772 |
+
% (url, v.reason)) from v
|
| 773 |
+
except http.client.BadStatusLine as v:
|
| 774 |
+
if warning:
|
| 775 |
+
self.warn(warning, v.line)
|
| 776 |
+
else:
|
| 777 |
+
raise DistutilsError(
|
| 778 |
+
'%s returned a bad status line. The server might be '
|
| 779 |
+
'down, %s' %
|
| 780 |
+
(url, v.line)
|
| 781 |
+
) from v
|
| 782 |
+
except (http.client.HTTPException, socket.error) as v:
|
| 783 |
+
if warning:
|
| 784 |
+
self.warn(warning, v)
|
| 785 |
+
else:
|
| 786 |
+
raise DistutilsError("Download error for %s: %s"
|
| 787 |
+
% (url, v)) from v
|
| 788 |
+
|
| 789 |
+
def _download_url(self, url, tmpdir):
|
| 790 |
+
# Determine download filename
|
| 791 |
+
#
|
| 792 |
+
name, fragment = egg_info_for_url(url)
|
| 793 |
+
if name:
|
| 794 |
+
while '..' in name:
|
| 795 |
+
name = name.replace('..', '.').replace('\\', '_')
|
| 796 |
+
else:
|
| 797 |
+
name = "__downloaded__" # default if URL has no path contents
|
| 798 |
+
|
| 799 |
+
if name.endswith('.egg.zip'):
|
| 800 |
+
name = name[:-4] # strip the extra .zip before download
|
| 801 |
+
|
| 802 |
+
filename = os.path.join(tmpdir, name)
|
| 803 |
+
|
| 804 |
+
return self._download_vcs(url, filename) or self._download_other(url, filename)
|
| 805 |
+
|
| 806 |
+
@staticmethod
|
| 807 |
+
def _resolve_vcs(url):
|
| 808 |
+
"""
|
| 809 |
+
>>> rvcs = PackageIndex._resolve_vcs
|
| 810 |
+
>>> rvcs('git+http://foo/bar')
|
| 811 |
+
'git'
|
| 812 |
+
>>> rvcs('hg+https://foo/bar')
|
| 813 |
+
'hg'
|
| 814 |
+
>>> rvcs('git:myhost')
|
| 815 |
+
'git'
|
| 816 |
+
>>> rvcs('hg:myhost')
|
| 817 |
+
>>> rvcs('http://foo/bar')
|
| 818 |
+
"""
|
| 819 |
+
scheme = urllib.parse.urlsplit(url).scheme
|
| 820 |
+
pre, sep, post = scheme.partition('+')
|
| 821 |
+
# svn and git have their own protocol; hg does not
|
| 822 |
+
allowed = set(['svn', 'git'] + ['hg'] * bool(sep))
|
| 823 |
+
return next(iter({pre} & allowed), None)
|
| 824 |
+
|
| 825 |
+
def _download_vcs(self, url, spec_filename):
|
| 826 |
+
vcs = self._resolve_vcs(url)
|
| 827 |
+
if not vcs:
|
| 828 |
+
return
|
| 829 |
+
if vcs == 'svn':
|
| 830 |
+
return self._download_svn(url, spec_filename)
|
| 831 |
+
|
| 832 |
+
filename, _, _ = spec_filename.partition('#')
|
| 833 |
+
url, rev = self._vcs_split_rev_from_url(url)
|
| 834 |
+
|
| 835 |
+
self.info(f"Doing {vcs} clone from {url} to {filename}")
|
| 836 |
+
subprocess.check_call([vcs, 'clone', '--quiet', url, filename])
|
| 837 |
+
|
| 838 |
+
co_commands = dict(
|
| 839 |
+
git=[vcs, '-C', filename, 'checkout', '--quiet', rev],
|
| 840 |
+
hg=[vcs, '--cwd', filename, 'up', '-C', '-r', rev, '-q'],
|
| 841 |
+
)
|
| 842 |
+
if rev is not None:
|
| 843 |
+
self.info(f"Checking out {rev}")
|
| 844 |
+
subprocess.check_call(co_commands[vcs])
|
| 845 |
+
|
| 846 |
+
return filename
|
| 847 |
+
|
| 848 |
+
def _download_other(self, url, filename):
|
| 849 |
+
scheme = urllib.parse.urlsplit(url).scheme
|
| 850 |
+
if scheme == 'file': # pragma: no cover
|
| 851 |
+
return urllib.request.url2pathname(urllib.parse.urlparse(url).path)
|
| 852 |
+
# raise error if not allowed
|
| 853 |
+
self.url_ok(url, True)
|
| 854 |
+
return self._attempt_download(url, filename)
|
| 855 |
+
|
| 856 |
+
def scan_url(self, url):
|
| 857 |
+
self.process_url(url, True)
|
| 858 |
+
|
| 859 |
+
def _attempt_download(self, url, filename):
|
| 860 |
+
headers = self._download_to(url, filename)
|
| 861 |
+
if 'html' in headers.get('content-type', '').lower():
|
| 862 |
+
return self._download_html(url, headers, filename)
|
| 863 |
+
else:
|
| 864 |
+
return filename
|
| 865 |
+
|
| 866 |
+
def _download_html(self, url, headers, filename):
|
| 867 |
+
file = open(filename)
|
| 868 |
+
for line in file:
|
| 869 |
+
if line.strip():
|
| 870 |
+
# Check for a subversion index page
|
| 871 |
+
if re.search(r'<title>([^- ]+ - )?Revision \d+:', line):
|
| 872 |
+
# it's a subversion index page:
|
| 873 |
+
file.close()
|
| 874 |
+
os.unlink(filename)
|
| 875 |
+
return self._download_svn(url, filename)
|
| 876 |
+
break # not an index page
|
| 877 |
+
file.close()
|
| 878 |
+
os.unlink(filename)
|
| 879 |
+
raise DistutilsError("Unexpected HTML page found at " + url)
|
| 880 |
+
|
| 881 |
+
def _download_svn(self, url, filename):
|
| 882 |
+
warnings.warn("SVN download support is deprecated", UserWarning)
|
| 883 |
+
url = url.split('#', 1)[0] # remove any fragment for svn's sake
|
| 884 |
+
creds = []
|
| 885 |
+
if url.lower().startswith('svn:') and '@' in url:
|
| 886 |
+
scheme, netloc, path, p, q, f = urllib.parse.urlparse(url)
|
| 887 |
+
if not netloc and path.startswith('//') and '/' in path[2:]:
|
| 888 |
+
netloc, path = path[2:].split('/', 1)
|
| 889 |
+
auth, host = _splituser(netloc)
|
| 890 |
+
if auth:
|
| 891 |
+
if ':' in auth:
|
| 892 |
+
user, pw = auth.split(':', 1)
|
| 893 |
+
creds.extend(["--username", user, "--password", pw])
|
| 894 |
+
else:
|
| 895 |
+
creds.extend(["--username", auth])
|
| 896 |
+
netloc = host
|
| 897 |
+
parts = scheme, netloc, url, p, q, f
|
| 898 |
+
url = urllib.parse.urlunparse(parts)
|
| 899 |
+
self.info("Doing subversion checkout from %s to %s", url, filename)
|
| 900 |
+
cmd = ["svn", "checkout", "-q"] + creds + [url, filename]
|
| 901 |
+
subprocess.check_call(cmd)
|
| 902 |
+
|
| 903 |
+
return filename
|
| 904 |
+
|
| 905 |
+
@staticmethod
|
| 906 |
+
def _vcs_split_rev_from_url(url):
|
| 907 |
+
"""
|
| 908 |
+
Given a possible VCS URL, return a clean URL and resolved revision if any.
|
| 909 |
+
|
| 910 |
+
>>> vsrfu = PackageIndex._vcs_split_rev_from_url
|
| 911 |
+
>>> vsrfu('git+https://github.com/pypa/setuptools@v69.0.0#egg-info=setuptools')
|
| 912 |
+
('https://github.com/pypa/setuptools', 'v69.0.0')
|
| 913 |
+
>>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools')
|
| 914 |
+
('https://github.com/pypa/setuptools', None)
|
| 915 |
+
>>> vsrfu('http://foo/bar')
|
| 916 |
+
('http://foo/bar', None)
|
| 917 |
+
"""
|
| 918 |
+
parts = urllib.parse.urlsplit(url)
|
| 919 |
+
|
| 920 |
+
clean_scheme = parts.scheme.split('+', 1)[-1]
|
| 921 |
+
|
| 922 |
+
# Some fragment identification fails
|
| 923 |
+
no_fragment_path, _, _ = parts.path.partition('#')
|
| 924 |
+
|
| 925 |
+
pre, sep, post = no_fragment_path.rpartition('@')
|
| 926 |
+
clean_path, rev = (pre, post) if sep else (post, None)
|
| 927 |
+
|
| 928 |
+
resolved = parts._replace(
|
| 929 |
+
scheme=clean_scheme,
|
| 930 |
+
path=clean_path,
|
| 931 |
+
# discard the fragment
|
| 932 |
+
fragment='',
|
| 933 |
+
).geturl()
|
| 934 |
+
|
| 935 |
+
return resolved, rev
|
| 936 |
+
|
| 937 |
+
def debug(self, msg, *args):
|
| 938 |
+
log.debug(msg, *args)
|
| 939 |
+
|
| 940 |
+
def info(self, msg, *args):
|
| 941 |
+
log.info(msg, *args)
|
| 942 |
+
|
| 943 |
+
def warn(self, msg, *args):
|
| 944 |
+
log.warn(msg, *args)
|
| 945 |
+
|
| 946 |
+
|
| 947 |
+
# This pattern matches a character entity reference (a decimal numeric
|
| 948 |
+
# references, a hexadecimal numeric reference, or a named reference).
|
| 949 |
+
entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub
|
| 950 |
+
|
| 951 |
+
|
| 952 |
+
def decode_entity(match):
|
| 953 |
+
what = match.group(0)
|
| 954 |
+
return html.unescape(what)
|
| 955 |
+
|
| 956 |
+
|
| 957 |
+
def htmldecode(text):
|
| 958 |
+
"""
|
| 959 |
+
Decode HTML entities in the given text.
|
| 960 |
+
|
| 961 |
+
>>> htmldecode(
|
| 962 |
+
... 'https://../package_name-0.1.2.tar.gz'
|
| 963 |
+
... '?tokena=A&tokenb=B">package_name-0.1.2.tar.gz')
|
| 964 |
+
'https://../package_name-0.1.2.tar.gz?tokena=A&tokenb=B">package_name-0.1.2.tar.gz'
|
| 965 |
+
"""
|
| 966 |
+
return entity_sub(decode_entity, text)
|
| 967 |
+
|
| 968 |
+
|
| 969 |
+
def socket_timeout(timeout=15):
|
| 970 |
+
def _socket_timeout(func):
|
| 971 |
+
def _socket_timeout(*args, **kwargs):
|
| 972 |
+
old_timeout = socket.getdefaulttimeout()
|
| 973 |
+
socket.setdefaulttimeout(timeout)
|
| 974 |
+
try:
|
| 975 |
+
return func(*args, **kwargs)
|
| 976 |
+
finally:
|
| 977 |
+
socket.setdefaulttimeout(old_timeout)
|
| 978 |
+
|
| 979 |
+
return _socket_timeout
|
| 980 |
+
|
| 981 |
+
return _socket_timeout
|
| 982 |
+
|
| 983 |
+
|
| 984 |
+
def _encode_auth(auth):
|
| 985 |
+
"""
|
| 986 |
+
Encode auth from a URL suitable for an HTTP header.
|
| 987 |
+
>>> str(_encode_auth('username%3Apassword'))
|
| 988 |
+
'dXNlcm5hbWU6cGFzc3dvcmQ='
|
| 989 |
+
|
| 990 |
+
Long auth strings should not cause a newline to be inserted.
|
| 991 |
+
>>> long_auth = 'username:' + 'password'*10
|
| 992 |
+
>>> chr(10) in str(_encode_auth(long_auth))
|
| 993 |
+
False
|
| 994 |
+
"""
|
| 995 |
+
auth_s = urllib.parse.unquote(auth)
|
| 996 |
+
# convert to bytes
|
| 997 |
+
auth_bytes = auth_s.encode()
|
| 998 |
+
encoded_bytes = base64.b64encode(auth_bytes)
|
| 999 |
+
# convert back to a string
|
| 1000 |
+
encoded = encoded_bytes.decode()
|
| 1001 |
+
# strip the trailing carriage return
|
| 1002 |
+
return encoded.replace('\n', '')
|
| 1003 |
+
|
| 1004 |
+
|
| 1005 |
+
class Credential:
|
| 1006 |
+
"""
|
| 1007 |
+
A username/password pair. Use like a namedtuple.
|
| 1008 |
+
"""
|
| 1009 |
+
|
| 1010 |
+
def __init__(self, username, password):
|
| 1011 |
+
self.username = username
|
| 1012 |
+
self.password = password
|
| 1013 |
+
|
| 1014 |
+
def __iter__(self):
|
| 1015 |
+
yield self.username
|
| 1016 |
+
yield self.password
|
| 1017 |
+
|
| 1018 |
+
def __str__(self):
|
| 1019 |
+
return '%(username)s:%(password)s' % vars(self)
|
| 1020 |
+
|
| 1021 |
+
|
| 1022 |
+
class PyPIConfig(configparser.RawConfigParser):
|
| 1023 |
+
def __init__(self):
|
| 1024 |
+
"""
|
| 1025 |
+
Load from ~/.pypirc
|
| 1026 |
+
"""
|
| 1027 |
+
defaults = dict.fromkeys(['username', 'password', 'repository'], '')
|
| 1028 |
+
configparser.RawConfigParser.__init__(self, defaults)
|
| 1029 |
+
|
| 1030 |
+
rc = os.path.join(os.path.expanduser('~'), '.pypirc')
|
| 1031 |
+
if os.path.exists(rc):
|
| 1032 |
+
self.read(rc)
|
| 1033 |
+
|
| 1034 |
+
@property
|
| 1035 |
+
def creds_by_repository(self):
|
| 1036 |
+
sections_with_repositories = [
|
| 1037 |
+
section for section in self.sections()
|
| 1038 |
+
if self.get(section, 'repository').strip()
|
| 1039 |
+
]
|
| 1040 |
+
|
| 1041 |
+
return dict(map(self._get_repo_cred, sections_with_repositories))
|
| 1042 |
+
|
| 1043 |
+
def _get_repo_cred(self, section):
|
| 1044 |
+
repo = self.get(section, 'repository').strip()
|
| 1045 |
+
return repo, Credential(
|
| 1046 |
+
self.get(section, 'username').strip(),
|
| 1047 |
+
self.get(section, 'password').strip(),
|
| 1048 |
+
)
|
| 1049 |
+
|
| 1050 |
+
def find_credential(self, url):
|
| 1051 |
+
"""
|
| 1052 |
+
If the URL indicated appears to be a repository defined in this
|
| 1053 |
+
config, return the credential for that repository.
|
| 1054 |
+
"""
|
| 1055 |
+
for repository, cred in self.creds_by_repository.items():
|
| 1056 |
+
if url.startswith(repository):
|
| 1057 |
+
return cred
|
| 1058 |
+
|
| 1059 |
+
|
| 1060 |
+
def open_with_auth(url, opener=urllib.request.urlopen):
|
| 1061 |
+
"""Open a urllib2 request, handling HTTP authentication"""
|
| 1062 |
+
|
| 1063 |
+
parsed = urllib.parse.urlparse(url)
|
| 1064 |
+
scheme, netloc, path, params, query, frag = parsed
|
| 1065 |
+
|
| 1066 |
+
# Double scheme does not raise on macOS as revealed by a
|
| 1067 |
+
# failing test. We would expect "nonnumeric port". Refs #20.
|
| 1068 |
+
if netloc.endswith(':'):
|
| 1069 |
+
raise http.client.InvalidURL("nonnumeric port: ''")
|
| 1070 |
+
|
| 1071 |
+
if scheme in ('http', 'https'):
|
| 1072 |
+
auth, address = _splituser(netloc)
|
| 1073 |
+
else:
|
| 1074 |
+
auth = None
|
| 1075 |
+
|
| 1076 |
+
if not auth:
|
| 1077 |
+
cred = PyPIConfig().find_credential(url)
|
| 1078 |
+
if cred:
|
| 1079 |
+
auth = str(cred)
|
| 1080 |
+
info = cred.username, url
|
| 1081 |
+
log.info('Authenticating as %s for %s (from .pypirc)', *info)
|
| 1082 |
+
|
| 1083 |
+
if auth:
|
| 1084 |
+
auth = "Basic " + _encode_auth(auth)
|
| 1085 |
+
parts = scheme, address, path, params, query, frag
|
| 1086 |
+
new_url = urllib.parse.urlunparse(parts)
|
| 1087 |
+
request = urllib.request.Request(new_url)
|
| 1088 |
+
request.add_header("Authorization", auth)
|
| 1089 |
+
else:
|
| 1090 |
+
request = urllib.request.Request(url)
|
| 1091 |
+
|
| 1092 |
+
request.add_header('User-Agent', user_agent)
|
| 1093 |
+
fp = opener(request)
|
| 1094 |
+
|
| 1095 |
+
if auth:
|
| 1096 |
+
# Put authentication info back into request URL if same host,
|
| 1097 |
+
# so that links found on the page will work
|
| 1098 |
+
s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url)
|
| 1099 |
+
if s2 == scheme and h2 == address:
|
| 1100 |
+
parts = s2, netloc, path2, param2, query2, frag2
|
| 1101 |
+
fp.url = urllib.parse.urlunparse(parts)
|
| 1102 |
+
|
| 1103 |
+
return fp
|
| 1104 |
+
|
| 1105 |
+
|
| 1106 |
+
# copy of urllib.parse._splituser from Python 3.8
|
| 1107 |
+
def _splituser(host):
|
| 1108 |
+
"""splituser('user[:passwd]@host[:port]')
|
| 1109 |
+
--> 'user[:passwd]', 'host[:port]'."""
|
| 1110 |
+
user, delim, host = host.rpartition('@')
|
| 1111 |
+
return (user if delim else None), host
|
| 1112 |
+
|
| 1113 |
+
|
| 1114 |
+
# adding a timeout to avoid freezing package_index
|
| 1115 |
+
open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth)
|
| 1116 |
+
|
| 1117 |
+
|
| 1118 |
+
def fix_sf_url(url):
|
| 1119 |
+
return url # backward compatibility
|
| 1120 |
+
|
| 1121 |
+
|
| 1122 |
+
def local_open(url):
|
| 1123 |
+
"""Read a local path, with special support for directories"""
|
| 1124 |
+
scheme, server, path, param, query, frag = urllib.parse.urlparse(url)
|
| 1125 |
+
filename = urllib.request.url2pathname(path)
|
| 1126 |
+
if os.path.isfile(filename):
|
| 1127 |
+
return urllib.request.urlopen(url)
|
| 1128 |
+
elif path.endswith('/') and os.path.isdir(filename):
|
| 1129 |
+
files = []
|
| 1130 |
+
for f in os.listdir(filename):
|
| 1131 |
+
filepath = os.path.join(filename, f)
|
| 1132 |
+
if f == 'index.html':
|
| 1133 |
+
with open(filepath, 'r') as fp:
|
| 1134 |
+
body = fp.read()
|
| 1135 |
+
break
|
| 1136 |
+
elif os.path.isdir(filepath):
|
| 1137 |
+
f += '/'
|
| 1138 |
+
files.append('<a href="{name}">{name}</a>'.format(name=f))
|
| 1139 |
+
else:
|
| 1140 |
+
tmpl = (
|
| 1141 |
+
"<html><head><title>{url}</title>"
|
| 1142 |
+
"</head><body>{files}</body></html>")
|
| 1143 |
+
body = tmpl.format(url=url, files='\n'.join(files))
|
| 1144 |
+
status, message = 200, "OK"
|
| 1145 |
+
else:
|
| 1146 |
+
status, message, body = 404, "Path not found", "Not found"
|
| 1147 |
+
|
| 1148 |
+
headers = {'content-type': 'text/html'}
|
| 1149 |
+
body_stream = io.StringIO(body)
|
| 1150 |
+
return urllib.error.HTTPError(url, status, message, headers, body_stream)
|
venv/lib/python3.10/site-packages/setuptools/py34compat.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import importlib
|
| 2 |
+
|
| 3 |
+
try:
|
| 4 |
+
import importlib.util
|
| 5 |
+
except ImportError:
|
| 6 |
+
pass
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
module_from_spec = importlib.util.module_from_spec
|
| 11 |
+
except AttributeError:
|
| 12 |
+
def module_from_spec(spec):
|
| 13 |
+
return spec.loader.load_module(spec.name)
|
venv/lib/python3.10/site-packages/setuptools/sandbox.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import tempfile
|
| 4 |
+
import operator
|
| 5 |
+
import functools
|
| 6 |
+
import itertools
|
| 7 |
+
import re
|
| 8 |
+
import contextlib
|
| 9 |
+
import pickle
|
| 10 |
+
import textwrap
|
| 11 |
+
import builtins
|
| 12 |
+
|
| 13 |
+
import pkg_resources
|
| 14 |
+
from distutils.errors import DistutilsError
|
| 15 |
+
from pkg_resources import working_set
|
| 16 |
+
|
| 17 |
+
if sys.platform.startswith('java'):
|
| 18 |
+
import org.python.modules.posix.PosixModule as _os
|
| 19 |
+
else:
|
| 20 |
+
_os = sys.modules[os.name]
|
| 21 |
+
try:
|
| 22 |
+
_file = file
|
| 23 |
+
except NameError:
|
| 24 |
+
_file = None
|
| 25 |
+
_open = open
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
__all__ = [
|
| 29 |
+
"AbstractSandbox",
|
| 30 |
+
"DirectorySandbox",
|
| 31 |
+
"SandboxViolation",
|
| 32 |
+
"run_setup",
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _execfile(filename, globals, locals=None):
|
| 37 |
+
"""
|
| 38 |
+
Python 3 implementation of execfile.
|
| 39 |
+
"""
|
| 40 |
+
mode = 'rb'
|
| 41 |
+
with open(filename, mode) as stream:
|
| 42 |
+
script = stream.read()
|
| 43 |
+
if locals is None:
|
| 44 |
+
locals = globals
|
| 45 |
+
code = compile(script, filename, 'exec')
|
| 46 |
+
exec(code, globals, locals)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@contextlib.contextmanager
|
| 50 |
+
def save_argv(repl=None):
|
| 51 |
+
saved = sys.argv[:]
|
| 52 |
+
if repl is not None:
|
| 53 |
+
sys.argv[:] = repl
|
| 54 |
+
try:
|
| 55 |
+
yield saved
|
| 56 |
+
finally:
|
| 57 |
+
sys.argv[:] = saved
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@contextlib.contextmanager
|
| 61 |
+
def save_path():
|
| 62 |
+
saved = sys.path[:]
|
| 63 |
+
try:
|
| 64 |
+
yield saved
|
| 65 |
+
finally:
|
| 66 |
+
sys.path[:] = saved
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
@contextlib.contextmanager
|
| 70 |
+
def override_temp(replacement):
|
| 71 |
+
"""
|
| 72 |
+
Monkey-patch tempfile.tempdir with replacement, ensuring it exists
|
| 73 |
+
"""
|
| 74 |
+
os.makedirs(replacement, exist_ok=True)
|
| 75 |
+
|
| 76 |
+
saved = tempfile.tempdir
|
| 77 |
+
|
| 78 |
+
tempfile.tempdir = replacement
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
yield
|
| 82 |
+
finally:
|
| 83 |
+
tempfile.tempdir = saved
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@contextlib.contextmanager
|
| 87 |
+
def pushd(target):
|
| 88 |
+
saved = os.getcwd()
|
| 89 |
+
os.chdir(target)
|
| 90 |
+
try:
|
| 91 |
+
yield saved
|
| 92 |
+
finally:
|
| 93 |
+
os.chdir(saved)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class UnpickleableException(Exception):
|
| 97 |
+
"""
|
| 98 |
+
An exception representing another Exception that could not be pickled.
|
| 99 |
+
"""
|
| 100 |
+
|
| 101 |
+
@staticmethod
|
| 102 |
+
def dump(type, exc):
|
| 103 |
+
"""
|
| 104 |
+
Always return a dumped (pickled) type and exc. If exc can't be pickled,
|
| 105 |
+
wrap it in UnpickleableException first.
|
| 106 |
+
"""
|
| 107 |
+
try:
|
| 108 |
+
return pickle.dumps(type), pickle.dumps(exc)
|
| 109 |
+
except Exception:
|
| 110 |
+
# get UnpickleableException inside the sandbox
|
| 111 |
+
from setuptools.sandbox import UnpickleableException as cls
|
| 112 |
+
|
| 113 |
+
return cls.dump(cls, cls(repr(exc)))
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class ExceptionSaver:
|
| 117 |
+
"""
|
| 118 |
+
A Context Manager that will save an exception, serialized, and restore it
|
| 119 |
+
later.
|
| 120 |
+
"""
|
| 121 |
+
|
| 122 |
+
def __enter__(self):
|
| 123 |
+
return self
|
| 124 |
+
|
| 125 |
+
def __exit__(self, type, exc, tb):
|
| 126 |
+
if not exc:
|
| 127 |
+
return
|
| 128 |
+
|
| 129 |
+
# dump the exception
|
| 130 |
+
self._saved = UnpickleableException.dump(type, exc)
|
| 131 |
+
self._tb = tb
|
| 132 |
+
|
| 133 |
+
# suppress the exception
|
| 134 |
+
return True
|
| 135 |
+
|
| 136 |
+
def resume(self):
|
| 137 |
+
"restore and re-raise any exception"
|
| 138 |
+
|
| 139 |
+
if '_saved' not in vars(self):
|
| 140 |
+
return
|
| 141 |
+
|
| 142 |
+
type, exc = map(pickle.loads, self._saved)
|
| 143 |
+
raise exc.with_traceback(self._tb)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
@contextlib.contextmanager
|
| 147 |
+
def save_modules():
|
| 148 |
+
"""
|
| 149 |
+
Context in which imported modules are saved.
|
| 150 |
+
|
| 151 |
+
Translates exceptions internal to the context into the equivalent exception
|
| 152 |
+
outside the context.
|
| 153 |
+
"""
|
| 154 |
+
saved = sys.modules.copy()
|
| 155 |
+
with ExceptionSaver() as saved_exc:
|
| 156 |
+
yield saved
|
| 157 |
+
|
| 158 |
+
sys.modules.update(saved)
|
| 159 |
+
# remove any modules imported since
|
| 160 |
+
del_modules = (
|
| 161 |
+
mod_name
|
| 162 |
+
for mod_name in sys.modules
|
| 163 |
+
if mod_name not in saved
|
| 164 |
+
# exclude any encodings modules. See #285
|
| 165 |
+
and not mod_name.startswith('encodings.')
|
| 166 |
+
)
|
| 167 |
+
_clear_modules(del_modules)
|
| 168 |
+
|
| 169 |
+
saved_exc.resume()
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def _clear_modules(module_names):
|
| 173 |
+
for mod_name in list(module_names):
|
| 174 |
+
del sys.modules[mod_name]
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@contextlib.contextmanager
|
| 178 |
+
def save_pkg_resources_state():
|
| 179 |
+
saved = pkg_resources.__getstate__()
|
| 180 |
+
try:
|
| 181 |
+
yield saved
|
| 182 |
+
finally:
|
| 183 |
+
pkg_resources.__setstate__(saved)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
@contextlib.contextmanager
|
| 187 |
+
def setup_context(setup_dir):
|
| 188 |
+
temp_dir = os.path.join(setup_dir, 'temp')
|
| 189 |
+
with save_pkg_resources_state():
|
| 190 |
+
with save_modules():
|
| 191 |
+
with save_path():
|
| 192 |
+
hide_setuptools()
|
| 193 |
+
with save_argv():
|
| 194 |
+
with override_temp(temp_dir):
|
| 195 |
+
with pushd(setup_dir):
|
| 196 |
+
# ensure setuptools commands are available
|
| 197 |
+
__import__('setuptools')
|
| 198 |
+
yield
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
_MODULES_TO_HIDE = {
|
| 202 |
+
'setuptools',
|
| 203 |
+
'distutils',
|
| 204 |
+
'pkg_resources',
|
| 205 |
+
'Cython',
|
| 206 |
+
'_distutils_hack',
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def _needs_hiding(mod_name):
|
| 211 |
+
"""
|
| 212 |
+
>>> _needs_hiding('setuptools')
|
| 213 |
+
True
|
| 214 |
+
>>> _needs_hiding('pkg_resources')
|
| 215 |
+
True
|
| 216 |
+
>>> _needs_hiding('setuptools_plugin')
|
| 217 |
+
False
|
| 218 |
+
>>> _needs_hiding('setuptools.__init__')
|
| 219 |
+
True
|
| 220 |
+
>>> _needs_hiding('distutils')
|
| 221 |
+
True
|
| 222 |
+
>>> _needs_hiding('os')
|
| 223 |
+
False
|
| 224 |
+
>>> _needs_hiding('Cython')
|
| 225 |
+
True
|
| 226 |
+
"""
|
| 227 |
+
base_module = mod_name.split('.', 1)[0]
|
| 228 |
+
return base_module in _MODULES_TO_HIDE
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def hide_setuptools():
|
| 232 |
+
"""
|
| 233 |
+
Remove references to setuptools' modules from sys.modules to allow the
|
| 234 |
+
invocation to import the most appropriate setuptools. This technique is
|
| 235 |
+
necessary to avoid issues such as #315 where setuptools upgrading itself
|
| 236 |
+
would fail to find a function declared in the metadata.
|
| 237 |
+
"""
|
| 238 |
+
_distutils_hack = sys.modules.get('_distutils_hack', None)
|
| 239 |
+
if _distutils_hack is not None:
|
| 240 |
+
_distutils_hack.remove_shim()
|
| 241 |
+
|
| 242 |
+
modules = filter(_needs_hiding, sys.modules)
|
| 243 |
+
_clear_modules(modules)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def run_setup(setup_script, args):
|
| 247 |
+
"""Run a distutils setup script, sandboxed in its directory"""
|
| 248 |
+
setup_dir = os.path.abspath(os.path.dirname(setup_script))
|
| 249 |
+
with setup_context(setup_dir):
|
| 250 |
+
try:
|
| 251 |
+
sys.argv[:] = [setup_script] + list(args)
|
| 252 |
+
sys.path.insert(0, setup_dir)
|
| 253 |
+
# reset to include setup dir, w/clean callback list
|
| 254 |
+
working_set.__init__()
|
| 255 |
+
working_set.callbacks.append(lambda dist: dist.activate())
|
| 256 |
+
|
| 257 |
+
with DirectorySandbox(setup_dir):
|
| 258 |
+
ns = dict(__file__=setup_script, __name__='__main__')
|
| 259 |
+
_execfile(setup_script, ns)
|
| 260 |
+
except SystemExit as v:
|
| 261 |
+
if v.args and v.args[0]:
|
| 262 |
+
raise
|
| 263 |
+
# Normal exit, just return
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
class AbstractSandbox:
|
| 267 |
+
"""Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
|
| 268 |
+
|
| 269 |
+
_active = False
|
| 270 |
+
|
| 271 |
+
def __init__(self):
|
| 272 |
+
self._attrs = [
|
| 273 |
+
name
|
| 274 |
+
for name in dir(_os)
|
| 275 |
+
if not name.startswith('_') and hasattr(self, name)
|
| 276 |
+
]
|
| 277 |
+
|
| 278 |
+
def _copy(self, source):
|
| 279 |
+
for name in self._attrs:
|
| 280 |
+
setattr(os, name, getattr(source, name))
|
| 281 |
+
|
| 282 |
+
def __enter__(self):
|
| 283 |
+
self._copy(self)
|
| 284 |
+
if _file:
|
| 285 |
+
builtins.file = self._file
|
| 286 |
+
builtins.open = self._open
|
| 287 |
+
self._active = True
|
| 288 |
+
|
| 289 |
+
def __exit__(self, exc_type, exc_value, traceback):
|
| 290 |
+
self._active = False
|
| 291 |
+
if _file:
|
| 292 |
+
builtins.file = _file
|
| 293 |
+
builtins.open = _open
|
| 294 |
+
self._copy(_os)
|
| 295 |
+
|
| 296 |
+
def run(self, func):
|
| 297 |
+
"""Run 'func' under os sandboxing"""
|
| 298 |
+
with self:
|
| 299 |
+
return func()
|
| 300 |
+
|
| 301 |
+
def _mk_dual_path_wrapper(name):
|
| 302 |
+
original = getattr(_os, name)
|
| 303 |
+
|
| 304 |
+
def wrap(self, src, dst, *args, **kw):
|
| 305 |
+
if self._active:
|
| 306 |
+
src, dst = self._remap_pair(name, src, dst, *args, **kw)
|
| 307 |
+
return original(src, dst, *args, **kw)
|
| 308 |
+
|
| 309 |
+
return wrap
|
| 310 |
+
|
| 311 |
+
for name in ["rename", "link", "symlink"]:
|
| 312 |
+
if hasattr(_os, name):
|
| 313 |
+
locals()[name] = _mk_dual_path_wrapper(name)
|
| 314 |
+
|
| 315 |
+
def _mk_single_path_wrapper(name, original=None):
|
| 316 |
+
original = original or getattr(_os, name)
|
| 317 |
+
|
| 318 |
+
def wrap(self, path, *args, **kw):
|
| 319 |
+
if self._active:
|
| 320 |
+
path = self._remap_input(name, path, *args, **kw)
|
| 321 |
+
return original(path, *args, **kw)
|
| 322 |
+
|
| 323 |
+
return wrap
|
| 324 |
+
|
| 325 |
+
if _file:
|
| 326 |
+
_file = _mk_single_path_wrapper('file', _file)
|
| 327 |
+
_open = _mk_single_path_wrapper('open', _open)
|
| 328 |
+
for name in [
|
| 329 |
+
"stat",
|
| 330 |
+
"listdir",
|
| 331 |
+
"chdir",
|
| 332 |
+
"open",
|
| 333 |
+
"chmod",
|
| 334 |
+
"chown",
|
| 335 |
+
"mkdir",
|
| 336 |
+
"remove",
|
| 337 |
+
"unlink",
|
| 338 |
+
"rmdir",
|
| 339 |
+
"utime",
|
| 340 |
+
"lchown",
|
| 341 |
+
"chroot",
|
| 342 |
+
"lstat",
|
| 343 |
+
"startfile",
|
| 344 |
+
"mkfifo",
|
| 345 |
+
"mknod",
|
| 346 |
+
"pathconf",
|
| 347 |
+
"access",
|
| 348 |
+
]:
|
| 349 |
+
if hasattr(_os, name):
|
| 350 |
+
locals()[name] = _mk_single_path_wrapper(name)
|
| 351 |
+
|
| 352 |
+
def _mk_single_with_return(name):
|
| 353 |
+
original = getattr(_os, name)
|
| 354 |
+
|
| 355 |
+
def wrap(self, path, *args, **kw):
|
| 356 |
+
if self._active:
|
| 357 |
+
path = self._remap_input(name, path, *args, **kw)
|
| 358 |
+
return self._remap_output(name, original(path, *args, **kw))
|
| 359 |
+
return original(path, *args, **kw)
|
| 360 |
+
|
| 361 |
+
return wrap
|
| 362 |
+
|
| 363 |
+
for name in ['readlink', 'tempnam']:
|
| 364 |
+
if hasattr(_os, name):
|
| 365 |
+
locals()[name] = _mk_single_with_return(name)
|
| 366 |
+
|
| 367 |
+
def _mk_query(name):
|
| 368 |
+
original = getattr(_os, name)
|
| 369 |
+
|
| 370 |
+
def wrap(self, *args, **kw):
|
| 371 |
+
retval = original(*args, **kw)
|
| 372 |
+
if self._active:
|
| 373 |
+
return self._remap_output(name, retval)
|
| 374 |
+
return retval
|
| 375 |
+
|
| 376 |
+
return wrap
|
| 377 |
+
|
| 378 |
+
for name in ['getcwd', 'tmpnam']:
|
| 379 |
+
if hasattr(_os, name):
|
| 380 |
+
locals()[name] = _mk_query(name)
|
| 381 |
+
|
| 382 |
+
def _validate_path(self, path):
|
| 383 |
+
"""Called to remap or validate any path, whether input or output"""
|
| 384 |
+
return path
|
| 385 |
+
|
| 386 |
+
def _remap_input(self, operation, path, *args, **kw):
|
| 387 |
+
"""Called for path inputs"""
|
| 388 |
+
return self._validate_path(path)
|
| 389 |
+
|
| 390 |
+
def _remap_output(self, operation, path):
|
| 391 |
+
"""Called for path outputs"""
|
| 392 |
+
return self._validate_path(path)
|
| 393 |
+
|
| 394 |
+
def _remap_pair(self, operation, src, dst, *args, **kw):
|
| 395 |
+
"""Called for path pairs like rename, link, and symlink operations"""
|
| 396 |
+
return (
|
| 397 |
+
self._remap_input(operation + '-from', src, *args, **kw),
|
| 398 |
+
self._remap_input(operation + '-to', dst, *args, **kw),
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
if hasattr(os, 'devnull'):
|
| 403 |
+
_EXCEPTIONS = [os.devnull]
|
| 404 |
+
else:
|
| 405 |
+
_EXCEPTIONS = []
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
class DirectorySandbox(AbstractSandbox):
|
| 409 |
+
"""Restrict operations to a single subdirectory - pseudo-chroot"""
|
| 410 |
+
|
| 411 |
+
write_ops = dict.fromkeys(
|
| 412 |
+
[
|
| 413 |
+
"open",
|
| 414 |
+
"chmod",
|
| 415 |
+
"chown",
|
| 416 |
+
"mkdir",
|
| 417 |
+
"remove",
|
| 418 |
+
"unlink",
|
| 419 |
+
"rmdir",
|
| 420 |
+
"utime",
|
| 421 |
+
"lchown",
|
| 422 |
+
"chroot",
|
| 423 |
+
"mkfifo",
|
| 424 |
+
"mknod",
|
| 425 |
+
"tempnam",
|
| 426 |
+
]
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
_exception_patterns = []
|
| 430 |
+
"exempt writing to paths that match the pattern"
|
| 431 |
+
|
| 432 |
+
def __init__(self, sandbox, exceptions=_EXCEPTIONS):
|
| 433 |
+
self._sandbox = os.path.normcase(os.path.realpath(sandbox))
|
| 434 |
+
self._prefix = os.path.join(self._sandbox, '')
|
| 435 |
+
self._exceptions = [
|
| 436 |
+
os.path.normcase(os.path.realpath(path)) for path in exceptions
|
| 437 |
+
]
|
| 438 |
+
AbstractSandbox.__init__(self)
|
| 439 |
+
|
| 440 |
+
def _violation(self, operation, *args, **kw):
|
| 441 |
+
from setuptools.sandbox import SandboxViolation
|
| 442 |
+
|
| 443 |
+
raise SandboxViolation(operation, args, kw)
|
| 444 |
+
|
| 445 |
+
if _file:
|
| 446 |
+
|
| 447 |
+
def _file(self, path, mode='r', *args, **kw):
|
| 448 |
+
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
|
| 449 |
+
self._violation("file", path, mode, *args, **kw)
|
| 450 |
+
return _file(path, mode, *args, **kw)
|
| 451 |
+
|
| 452 |
+
def _open(self, path, mode='r', *args, **kw):
|
| 453 |
+
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
|
| 454 |
+
self._violation("open", path, mode, *args, **kw)
|
| 455 |
+
return _open(path, mode, *args, **kw)
|
| 456 |
+
|
| 457 |
+
def tmpnam(self):
|
| 458 |
+
self._violation("tmpnam")
|
| 459 |
+
|
| 460 |
+
def _ok(self, path):
|
| 461 |
+
active = self._active
|
| 462 |
+
try:
|
| 463 |
+
self._active = False
|
| 464 |
+
realpath = os.path.normcase(os.path.realpath(path))
|
| 465 |
+
return (
|
| 466 |
+
self._exempted(realpath)
|
| 467 |
+
or realpath == self._sandbox
|
| 468 |
+
or realpath.startswith(self._prefix)
|
| 469 |
+
)
|
| 470 |
+
finally:
|
| 471 |
+
self._active = active
|
| 472 |
+
|
| 473 |
+
def _exempted(self, filepath):
|
| 474 |
+
start_matches = (
|
| 475 |
+
filepath.startswith(exception) for exception in self._exceptions
|
| 476 |
+
)
|
| 477 |
+
pattern_matches = (
|
| 478 |
+
re.match(pattern, filepath) for pattern in self._exception_patterns
|
| 479 |
+
)
|
| 480 |
+
candidates = itertools.chain(start_matches, pattern_matches)
|
| 481 |
+
return any(candidates)
|
| 482 |
+
|
| 483 |
+
def _remap_input(self, operation, path, *args, **kw):
|
| 484 |
+
"""Called for path inputs"""
|
| 485 |
+
if operation in self.write_ops and not self._ok(path):
|
| 486 |
+
self._violation(operation, os.path.realpath(path), *args, **kw)
|
| 487 |
+
return path
|
| 488 |
+
|
| 489 |
+
def _remap_pair(self, operation, src, dst, *args, **kw):
|
| 490 |
+
"""Called for path pairs like rename, link, and symlink operations"""
|
| 491 |
+
if not self._ok(src) or not self._ok(dst):
|
| 492 |
+
self._violation(operation, src, dst, *args, **kw)
|
| 493 |
+
return (src, dst)
|
| 494 |
+
|
| 495 |
+
def open(self, file, flags, mode=0o777, *args, **kw):
|
| 496 |
+
"""Called for low-level os.open()"""
|
| 497 |
+
if flags & WRITE_FLAGS and not self._ok(file):
|
| 498 |
+
self._violation("os.open", file, flags, mode, *args, **kw)
|
| 499 |
+
return _os.open(file, flags, mode, *args, **kw)
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
WRITE_FLAGS = functools.reduce(
|
| 503 |
+
operator.or_,
|
| 504 |
+
[
|
| 505 |
+
getattr(_os, a, 0)
|
| 506 |
+
for a in "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()
|
| 507 |
+
],
|
| 508 |
+
)
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
class SandboxViolation(DistutilsError):
|
| 512 |
+
"""A setup script attempted to modify the filesystem outside the sandbox"""
|
| 513 |
+
|
| 514 |
+
tmpl = textwrap.dedent(
|
| 515 |
+
"""
|
| 516 |
+
SandboxViolation: {cmd}{args!r} {kwargs}
|
| 517 |
+
|
| 518 |
+
The package setup script has attempted to modify files on your system
|
| 519 |
+
that are not within the EasyInstall build area, and has been aborted.
|
| 520 |
+
|
| 521 |
+
This package cannot be safely installed by EasyInstall, and may not
|
| 522 |
+
support alternate installation locations even if you run its setup
|
| 523 |
+
script by hand. Please inform the package's author and the EasyInstall
|
| 524 |
+
maintainers to find out if a fix or workaround is available.
|
| 525 |
+
"""
|
| 526 |
+
).lstrip()
|
| 527 |
+
|
| 528 |
+
def __str__(self):
|
| 529 |
+
cmd, args, kwargs = self.args
|
| 530 |
+
return self.tmpl.format(**locals())
|
venv/lib/python3.10/site-packages/setuptools/script (dev).tmpl
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r
|
| 2 |
+
__requires__ = %(spec)r
|
| 3 |
+
__import__('pkg_resources').require(%(spec)r)
|
| 4 |
+
__file__ = %(dev_path)r
|
| 5 |
+
with open(__file__) as f:
|
| 6 |
+
exec(compile(f.read(), __file__, 'exec'))
|
venv/lib/python3.10/site-packages/setuptools/script.tmpl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r
|
| 2 |
+
__requires__ = %(spec)r
|
| 3 |
+
__import__('pkg_resources').run_script(%(spec)r, %(script_name)r)
|
venv/lib/python3.10/site-packages/setuptools/unicode_utils.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import unicodedata
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# HFS Plus uses decomposed UTF-8
|
| 6 |
+
def decompose(path):
|
| 7 |
+
if isinstance(path, str):
|
| 8 |
+
return unicodedata.normalize('NFD', path)
|
| 9 |
+
try:
|
| 10 |
+
path = path.decode('utf-8')
|
| 11 |
+
path = unicodedata.normalize('NFD', path)
|
| 12 |
+
path = path.encode('utf-8')
|
| 13 |
+
except UnicodeError:
|
| 14 |
+
pass # Not UTF-8
|
| 15 |
+
return path
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def filesys_decode(path):
|
| 19 |
+
"""
|
| 20 |
+
Ensure that the given path is decoded,
|
| 21 |
+
NONE when no expected encoding works
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
if isinstance(path, str):
|
| 25 |
+
return path
|
| 26 |
+
|
| 27 |
+
fs_enc = sys.getfilesystemencoding() or 'utf-8'
|
| 28 |
+
candidates = fs_enc, 'utf-8'
|
| 29 |
+
|
| 30 |
+
for enc in candidates:
|
| 31 |
+
try:
|
| 32 |
+
return path.decode(enc)
|
| 33 |
+
except UnicodeDecodeError:
|
| 34 |
+
continue
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def try_encode(string, enc):
|
| 38 |
+
"turn unicode encoding into a functional routine"
|
| 39 |
+
try:
|
| 40 |
+
return string.encode(enc)
|
| 41 |
+
except UnicodeEncodeError:
|
| 42 |
+
return None
|
venv/lib/python3.10/site-packages/setuptools/version.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pkg_resources
|
| 2 |
+
|
| 3 |
+
try:
|
| 4 |
+
__version__ = pkg_resources.get_distribution('setuptools').version
|
| 5 |
+
except Exception:
|
| 6 |
+
__version__ = 'unknown'
|
venv/lib/python3.10/site-packages/setuptools/wheel.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Wheels support."""
|
| 2 |
+
|
| 3 |
+
from distutils.util import get_platform
|
| 4 |
+
from distutils import log
|
| 5 |
+
import email
|
| 6 |
+
import itertools
|
| 7 |
+
import os
|
| 8 |
+
import posixpath
|
| 9 |
+
import re
|
| 10 |
+
import zipfile
|
| 11 |
+
|
| 12 |
+
import pkg_resources
|
| 13 |
+
import setuptools
|
| 14 |
+
from pkg_resources import parse_version
|
| 15 |
+
from setuptools.extern.packaging.tags import sys_tags
|
| 16 |
+
from setuptools.extern.packaging.utils import canonicalize_name
|
| 17 |
+
from setuptools.command.egg_info import write_requirements
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
WHEEL_NAME = re.compile(
|
| 21 |
+
r"""^(?P<project_name>.+?)-(?P<version>\d.*?)
|
| 22 |
+
((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)
|
| 23 |
+
)\.whl$""",
|
| 24 |
+
re.VERBOSE).match
|
| 25 |
+
|
| 26 |
+
NAMESPACE_PACKAGE_INIT = \
|
| 27 |
+
"__import__('pkg_resources').declare_namespace(__name__)\n"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def unpack(src_dir, dst_dir):
|
| 31 |
+
'''Move everything under `src_dir` to `dst_dir`, and delete the former.'''
|
| 32 |
+
for dirpath, dirnames, filenames in os.walk(src_dir):
|
| 33 |
+
subdir = os.path.relpath(dirpath, src_dir)
|
| 34 |
+
for f in filenames:
|
| 35 |
+
src = os.path.join(dirpath, f)
|
| 36 |
+
dst = os.path.join(dst_dir, subdir, f)
|
| 37 |
+
os.renames(src, dst)
|
| 38 |
+
for n, d in reversed(list(enumerate(dirnames))):
|
| 39 |
+
src = os.path.join(dirpath, d)
|
| 40 |
+
dst = os.path.join(dst_dir, subdir, d)
|
| 41 |
+
if not os.path.exists(dst):
|
| 42 |
+
# Directory does not exist in destination,
|
| 43 |
+
# rename it and prune it from os.walk list.
|
| 44 |
+
os.renames(src, dst)
|
| 45 |
+
del dirnames[n]
|
| 46 |
+
# Cleanup.
|
| 47 |
+
for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True):
|
| 48 |
+
assert not filenames
|
| 49 |
+
os.rmdir(dirpath)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class Wheel:
|
| 53 |
+
|
| 54 |
+
def __init__(self, filename):
|
| 55 |
+
match = WHEEL_NAME(os.path.basename(filename))
|
| 56 |
+
if match is None:
|
| 57 |
+
raise ValueError('invalid wheel name: %r' % filename)
|
| 58 |
+
self.filename = filename
|
| 59 |
+
for k, v in match.groupdict().items():
|
| 60 |
+
setattr(self, k, v)
|
| 61 |
+
|
| 62 |
+
def tags(self):
|
| 63 |
+
'''List tags (py_version, abi, platform) supported by this wheel.'''
|
| 64 |
+
return itertools.product(
|
| 65 |
+
self.py_version.split('.'),
|
| 66 |
+
self.abi.split('.'),
|
| 67 |
+
self.platform.split('.'),
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
def is_compatible(self):
|
| 71 |
+
'''Is the wheel is compatible with the current platform?'''
|
| 72 |
+
supported_tags = set(
|
| 73 |
+
(t.interpreter, t.abi, t.platform) for t in sys_tags())
|
| 74 |
+
return next((True for t in self.tags() if t in supported_tags), False)
|
| 75 |
+
|
| 76 |
+
def egg_name(self):
|
| 77 |
+
return pkg_resources.Distribution(
|
| 78 |
+
project_name=self.project_name, version=self.version,
|
| 79 |
+
platform=(None if self.platform == 'any' else get_platform()),
|
| 80 |
+
).egg_name() + '.egg'
|
| 81 |
+
|
| 82 |
+
def get_dist_info(self, zf):
|
| 83 |
+
# find the correct name of the .dist-info dir in the wheel file
|
| 84 |
+
for member in zf.namelist():
|
| 85 |
+
dirname = posixpath.dirname(member)
|
| 86 |
+
if (dirname.endswith('.dist-info') and
|
| 87 |
+
canonicalize_name(dirname).startswith(
|
| 88 |
+
canonicalize_name(self.project_name))):
|
| 89 |
+
return dirname
|
| 90 |
+
raise ValueError("unsupported wheel format. .dist-info not found")
|
| 91 |
+
|
| 92 |
+
def install_as_egg(self, destination_eggdir):
|
| 93 |
+
'''Install wheel as an egg directory.'''
|
| 94 |
+
with zipfile.ZipFile(self.filename) as zf:
|
| 95 |
+
self._install_as_egg(destination_eggdir, zf)
|
| 96 |
+
|
| 97 |
+
def _install_as_egg(self, destination_eggdir, zf):
|
| 98 |
+
dist_basename = '%s-%s' % (self.project_name, self.version)
|
| 99 |
+
dist_info = self.get_dist_info(zf)
|
| 100 |
+
dist_data = '%s.data' % dist_basename
|
| 101 |
+
egg_info = os.path.join(destination_eggdir, 'EGG-INFO')
|
| 102 |
+
|
| 103 |
+
self._convert_metadata(zf, destination_eggdir, dist_info, egg_info)
|
| 104 |
+
self._move_data_entries(destination_eggdir, dist_data)
|
| 105 |
+
self._fix_namespace_packages(egg_info, destination_eggdir)
|
| 106 |
+
|
| 107 |
+
@staticmethod
|
| 108 |
+
def _convert_metadata(zf, destination_eggdir, dist_info, egg_info):
|
| 109 |
+
def get_metadata(name):
|
| 110 |
+
with zf.open(posixpath.join(dist_info, name)) as fp:
|
| 111 |
+
value = fp.read().decode('utf-8')
|
| 112 |
+
return email.parser.Parser().parsestr(value)
|
| 113 |
+
|
| 114 |
+
wheel_metadata = get_metadata('WHEEL')
|
| 115 |
+
# Check wheel format version is supported.
|
| 116 |
+
wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))
|
| 117 |
+
wheel_v1 = (
|
| 118 |
+
parse_version('1.0') <= wheel_version < parse_version('2.0dev0')
|
| 119 |
+
)
|
| 120 |
+
if not wheel_v1:
|
| 121 |
+
raise ValueError(
|
| 122 |
+
'unsupported wheel format version: %s' % wheel_version)
|
| 123 |
+
# Extract to target directory.
|
| 124 |
+
os.mkdir(destination_eggdir)
|
| 125 |
+
zf.extractall(destination_eggdir)
|
| 126 |
+
# Convert metadata.
|
| 127 |
+
dist_info = os.path.join(destination_eggdir, dist_info)
|
| 128 |
+
dist = pkg_resources.Distribution.from_location(
|
| 129 |
+
destination_eggdir, dist_info,
|
| 130 |
+
metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info),
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# Note: Evaluate and strip markers now,
|
| 134 |
+
# as it's difficult to convert back from the syntax:
|
| 135 |
+
# foobar; "linux" in sys_platform and extra == 'test'
|
| 136 |
+
def raw_req(req):
|
| 137 |
+
req.marker = None
|
| 138 |
+
return str(req)
|
| 139 |
+
install_requires = list(sorted(map(raw_req, dist.requires())))
|
| 140 |
+
extras_require = {
|
| 141 |
+
extra: sorted(
|
| 142 |
+
req
|
| 143 |
+
for req in map(raw_req, dist.requires((extra,)))
|
| 144 |
+
if req not in install_requires
|
| 145 |
+
)
|
| 146 |
+
for extra in dist.extras
|
| 147 |
+
}
|
| 148 |
+
os.rename(dist_info, egg_info)
|
| 149 |
+
os.rename(
|
| 150 |
+
os.path.join(egg_info, 'METADATA'),
|
| 151 |
+
os.path.join(egg_info, 'PKG-INFO'),
|
| 152 |
+
)
|
| 153 |
+
setup_dist = setuptools.Distribution(
|
| 154 |
+
attrs=dict(
|
| 155 |
+
install_requires=install_requires,
|
| 156 |
+
extras_require=extras_require,
|
| 157 |
+
),
|
| 158 |
+
)
|
| 159 |
+
# Temporarily disable info traces.
|
| 160 |
+
log_threshold = log._global_log.threshold
|
| 161 |
+
log.set_threshold(log.WARN)
|
| 162 |
+
try:
|
| 163 |
+
write_requirements(
|
| 164 |
+
setup_dist.get_command_obj('egg_info'),
|
| 165 |
+
None,
|
| 166 |
+
os.path.join(egg_info, 'requires.txt'),
|
| 167 |
+
)
|
| 168 |
+
finally:
|
| 169 |
+
log.set_threshold(log_threshold)
|
| 170 |
+
|
| 171 |
+
@staticmethod
|
| 172 |
+
def _move_data_entries(destination_eggdir, dist_data):
|
| 173 |
+
"""Move data entries to their correct location."""
|
| 174 |
+
dist_data = os.path.join(destination_eggdir, dist_data)
|
| 175 |
+
dist_data_scripts = os.path.join(dist_data, 'scripts')
|
| 176 |
+
if os.path.exists(dist_data_scripts):
|
| 177 |
+
egg_info_scripts = os.path.join(
|
| 178 |
+
destination_eggdir, 'EGG-INFO', 'scripts')
|
| 179 |
+
os.mkdir(egg_info_scripts)
|
| 180 |
+
for entry in os.listdir(dist_data_scripts):
|
| 181 |
+
# Remove bytecode, as it's not properly handled
|
| 182 |
+
# during easy_install scripts install phase.
|
| 183 |
+
if entry.endswith('.pyc'):
|
| 184 |
+
os.unlink(os.path.join(dist_data_scripts, entry))
|
| 185 |
+
else:
|
| 186 |
+
os.rename(
|
| 187 |
+
os.path.join(dist_data_scripts, entry),
|
| 188 |
+
os.path.join(egg_info_scripts, entry),
|
| 189 |
+
)
|
| 190 |
+
os.rmdir(dist_data_scripts)
|
| 191 |
+
for subdir in filter(os.path.exists, (
|
| 192 |
+
os.path.join(dist_data, d)
|
| 193 |
+
for d in ('data', 'headers', 'purelib', 'platlib')
|
| 194 |
+
)):
|
| 195 |
+
unpack(subdir, destination_eggdir)
|
| 196 |
+
if os.path.exists(dist_data):
|
| 197 |
+
os.rmdir(dist_data)
|
| 198 |
+
|
| 199 |
+
@staticmethod
|
| 200 |
+
def _fix_namespace_packages(egg_info, destination_eggdir):
|
| 201 |
+
namespace_packages = os.path.join(
|
| 202 |
+
egg_info, 'namespace_packages.txt')
|
| 203 |
+
if os.path.exists(namespace_packages):
|
| 204 |
+
with open(namespace_packages) as fp:
|
| 205 |
+
namespace_packages = fp.read().split()
|
| 206 |
+
for mod in namespace_packages:
|
| 207 |
+
mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
|
| 208 |
+
mod_init = os.path.join(mod_dir, '__init__.py')
|
| 209 |
+
if not os.path.exists(mod_dir):
|
| 210 |
+
os.mkdir(mod_dir)
|
| 211 |
+
if not os.path.exists(mod_init):
|
| 212 |
+
with open(mod_init, 'w') as fp:
|
| 213 |
+
fp.write(NAMESPACE_PACKAGE_INIT)
|
venv/lib/python3.10/site-packages/setuptools/windows_support.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import platform
|
| 2 |
+
import ctypes
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def windows_only(func):
|
| 6 |
+
if platform.system() != 'Windows':
|
| 7 |
+
return lambda *args, **kwargs: None
|
| 8 |
+
return func
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@windows_only
|
| 12 |
+
def hide_file(path):
|
| 13 |
+
"""
|
| 14 |
+
Set the hidden attribute on a file or directory.
|
| 15 |
+
|
| 16 |
+
From http://stackoverflow.com/questions/19622133/
|
| 17 |
+
|
| 18 |
+
`path` must be text.
|
| 19 |
+
"""
|
| 20 |
+
__import__('ctypes.wintypes')
|
| 21 |
+
SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW
|
| 22 |
+
SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD
|
| 23 |
+
SetFileAttributes.restype = ctypes.wintypes.BOOL
|
| 24 |
+
|
| 25 |
+
FILE_ATTRIBUTE_HIDDEN = 0x02
|
| 26 |
+
|
| 27 |
+
ret = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN)
|
| 28 |
+
if not ret:
|
| 29 |
+
raise ctypes.WinError()
|