File size: 5,093 Bytes
b4740c6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
from __future__ import annotations
import glob
import itertools
import os
import subprocess
import sys
import tempfile
import packaging.requirements
import packaging.utils
from . import _reqs
from ._importlib import metadata
from .warnings import SetuptoolsDeprecationWarning
from .wheel import Wheel
from distutils import log
from distutils.errors import DistutilsError
def _fixup_find_links(find_links):
"""Ensure find-links option end-up being a list of strings."""
if isinstance(find_links, str):
return find_links.split()
assert isinstance(find_links, (tuple, list))
return find_links
def fetch_build_egg(dist, req):
"""Fetch an egg needed for building.
Use pip/wheel to fetch/build a wheel."""
_DeprecatedInstaller.emit()
_warn_wheel_not_available(dist)
return _fetch_build_egg_no_warn(dist, req)
def _present(req):
return any(_dist_matches_req(dist, req) for dist in metadata.distributions())
def _fetch_build_eggs(dist, requires: _reqs._StrOrIter) -> list[metadata.Distribution]:
_DeprecatedInstaller.emit(stacklevel=3)
_warn_wheel_not_available(dist)
parsed_reqs = _reqs.parse(requires)
missing_reqs = itertools.filterfalse(_present, parsed_reqs)
needed_reqs = (
req for req in missing_reqs if not req.marker or req.marker.evaluate()
)
resolved_dists = [_fetch_build_egg_no_warn(dist, req) for req in needed_reqs]
for dist in resolved_dists:
# dist.locate_file('') is the directory containing EGG-INFO, where the importabl
# contents can be found.
sys.path.insert(0, str(dist.locate_file('')))
return resolved_dists
def _dist_matches_req(egg_dist, req):
return (
packaging.utils.canonicalize_name(egg_dist.name)
== packaging.utils.canonicalize_name(req.name)
and egg_dist.version in req.specifier
)
def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex (16) # FIXME
# Ignore environment markers; if supplied, it is required.
req = strip_marker(req)
# Take easy_install options into account, but do not override relevant
# pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll
# take precedence.
opts = dist.get_option_dict('easy_install')
if 'allow_hosts' in opts:
raise DistutilsError(
'the `allow-hosts` option is not supported '
'when using pip to install requirements.'
)
quiet = 'PIP_QUIET' not in os.environ and 'PIP_VERBOSE' not in os.environ
if 'PIP_INDEX_URL' in os.environ:
index_url = None
elif 'index_url' in opts:
index_url = opts['index_url'][1]
else:
index_url = None
find_links = (
_fixup_find_links(opts['find_links'][1])[:] if 'find_links' in opts else []
)
if dist.dependency_links:
find_links.extend(dist.dependency_links)
eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
cached_dists = metadata.Distribution.discover(path=glob.glob(f'{eggs_dir}/*.egg'))
for egg_dist in cached_dists:
if _dist_matches_req(egg_dist, req):
return egg_dist
with tempfile.TemporaryDirectory() as tmpdir:
cmd = [
sys.executable,
'-m',
'pip',
'--disable-pip-version-check',
'wheel',
'--no-deps',
'-w',
tmpdir,
]
if quiet:
cmd.append('--quiet')
if index_url is not None:
cmd.extend(('--index-url', index_url))
for link in find_links or []:
cmd.extend(('--find-links', link))
# If requirement is a PEP 508 direct URL, directly pass
# the URL to pip, as `req @ url` does not work on the
# command line.
cmd.append(req.url or str(req))
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
raise DistutilsError(str(e)) from e
wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
dist_location = os.path.join(eggs_dir, wheel.egg_name())
wheel.install_as_egg(dist_location)
return metadata.Distribution.at(dist_location + '/EGG-INFO')
def strip_marker(req):
"""
Return a new requirement without the environment marker to avoid
calling pip with something like `babel; extra == "i18n"`, which
would always be ignored.
"""
# create a copy to avoid mutating the input
req = packaging.requirements.Requirement(str(req))
req.marker = None
return req
def _warn_wheel_not_available(dist):
try:
metadata.distribution('wheel')
except metadata.PackageNotFoundError:
dist.announce('WARNING: The wheel package is not available.', log.WARN)
class _DeprecatedInstaller(SetuptoolsDeprecationWarning):
_SUMMARY = "setuptools.installer and fetch_build_eggs are deprecated."
_DETAILS = """
Requirements should be satisfied by a PEP 517 installer.
If you are using pip, you can try `pip install --use-pep517`.
"""
_DUE_DATE = 2025, 10, 31
|