1import glob 2import os 3import subprocess 4import sys 5import tempfile 6from distutils import log 7from distutils.errors import DistutilsError 8 9import pkg_resources 10from setuptools.command.easy_install import easy_install 11from setuptools.wheel import Wheel 12 13 14def _fixup_find_links(find_links): 15 """Ensure find-links option end-up being a list of strings.""" 16 if isinstance(find_links, str): 17 return find_links.split() 18 assert isinstance(find_links, (tuple, list)) 19 return find_links 20 21 22def _legacy_fetch_build_egg(dist, req): 23 """Fetch an egg needed for building. 24 25 Legacy path using EasyInstall. 26 """ 27 tmp_dist = dist.__class__({'script_args': ['easy_install']}) 28 opts = tmp_dist.get_option_dict('easy_install') 29 opts.clear() 30 opts.update( 31 (k, v) 32 for k, v in dist.get_option_dict('easy_install').items() 33 if k in ( 34 # don't use any other settings 35 'find_links', 'site_dirs', 'index_url', 36 'optimize', 'site_dirs', 'allow_hosts', 37 )) 38 if dist.dependency_links: 39 links = dist.dependency_links[:] 40 if 'find_links' in opts: 41 links = _fixup_find_links(opts['find_links'][1]) + links 42 opts['find_links'] = ('setup', links) 43 install_dir = dist.get_egg_cache_dir() 44 cmd = easy_install( 45 tmp_dist, args=["x"], install_dir=install_dir, 46 exclude_scripts=True, 47 always_copy=False, build_directory=None, editable=False, 48 upgrade=False, multi_version=True, no_report=True, user=False 49 ) 50 cmd.ensure_finalized() 51 return cmd.easy_install(req) 52 53 54def fetch_build_egg(dist, req): 55 """Fetch an egg needed for building. 56 57 Use pip/wheel to fetch/build a wheel.""" 58 # Check pip is available. 59 try: 60 pkg_resources.get_distribution('pip') 61 except pkg_resources.DistributionNotFound: 62 dist.announce( 63 'WARNING: The pip package is not available, falling back ' 64 'to EasyInstall for handling setup_requires/test_requires; ' 65 'this is deprecated and will be removed in a future version.', 66 log.WARN 67 ) 68 return _legacy_fetch_build_egg(dist, req) 69 # Warn if wheel is not. 70 try: 71 pkg_resources.get_distribution('wheel') 72 except pkg_resources.DistributionNotFound: 73 dist.announce('WARNING: The wheel package is not available.', log.WARN) 74 # Ignore environment markers; if supplied, it is required. 75 req = strip_marker(req) 76 # Take easy_install options into account, but do not override relevant 77 # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll 78 # take precedence. 79 opts = dist.get_option_dict('easy_install') 80 if 'allow_hosts' in opts: 81 raise DistutilsError('the `allow-hosts` option is not supported ' 82 'when using pip to install requirements.') 83 if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ: 84 quiet = False 85 else: 86 quiet = True 87 if 'PIP_INDEX_URL' in os.environ: 88 index_url = None 89 elif 'index_url' in opts: 90 index_url = opts['index_url'][1] 91 else: 92 index_url = None 93 if 'find_links' in opts: 94 find_links = _fixup_find_links(opts['find_links'][1])[:] 95 else: 96 find_links = [] 97 if dist.dependency_links: 98 find_links.extend(dist.dependency_links) 99 eggs_dir = os.path.realpath(dist.get_egg_cache_dir()) 100 environment = pkg_resources.Environment() 101 for egg_dist in pkg_resources.find_distributions(eggs_dir): 102 if egg_dist in req and environment.can_add(egg_dist): 103 return egg_dist 104 with tempfile.TemporaryDirectory() as tmpdir: 105 cmd = [ 106 sys.executable, '-m', 'pip', 107 '--disable-pip-version-check', 108 'wheel', '--no-deps', 109 '-w', tmpdir, 110 ] 111 if quiet: 112 cmd.append('--quiet') 113 if index_url is not None: 114 cmd.extend(('--index-url', index_url)) 115 if find_links is not None: 116 for link in find_links: 117 cmd.extend(('--find-links', link)) 118 # If requirement is a PEP 508 direct URL, directly pass 119 # the URL to pip, as `req @ url` does not work on the 120 # command line. 121 if req.url: 122 cmd.append(req.url) 123 else: 124 cmd.append(str(req)) 125 try: 126 subprocess.check_call(cmd) 127 except subprocess.CalledProcessError as e: 128 raise DistutilsError(str(e)) from e 129 wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0]) 130 dist_location = os.path.join(eggs_dir, wheel.egg_name()) 131 wheel.install_as_egg(dist_location) 132 dist_metadata = pkg_resources.PathMetadata( 133 dist_location, os.path.join(dist_location, 'EGG-INFO')) 134 dist = pkg_resources.Distribution.from_filename( 135 dist_location, metadata=dist_metadata) 136 return dist 137 138 139def strip_marker(req): 140 """ 141 Return a new requirement without the environment marker to avoid 142 calling pip with something like `babel; extra == "i18n"`, which 143 would always be ignored. 144 """ 145 # create a copy to avoid mutating the input 146 req = pkg_resources.Requirement.parse(str(req)) 147 req.marker = None 148 return req 149