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