comparison lib/python3.8/site-packages/pip/_internal/wheel_builder.py @ 0:9e54283cc701 draft

"planemo upload commit d12c32a45bcd441307e632fca6d9af7d60289d44"
author guerler
date Mon, 27 Jul 2020 03:47:31 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:9e54283cc701
1 """Orchestrator for building wheels from InstallRequirements.
2 """
3
4 # The following comment should be removed at some point in the future.
5 # mypy: strict-optional=False
6
7 import logging
8 import os.path
9 import re
10 import shutil
11
12 from pip._internal.models.link import Link
13 from pip._internal.operations.build.wheel import build_wheel_pep517
14 from pip._internal.operations.build.wheel_legacy import build_wheel_legacy
15 from pip._internal.utils.logging import indent_log
16 from pip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed
17 from pip._internal.utils.setuptools_build import make_setuptools_clean_args
18 from pip._internal.utils.subprocess import call_subprocess
19 from pip._internal.utils.temp_dir import TempDirectory
20 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
21 from pip._internal.utils.urls import path_to_url
22 from pip._internal.vcs import vcs
23
24 if MYPY_CHECK_RUNNING:
25 from typing import (
26 Any, Callable, Iterable, List, Optional, Pattern, Tuple,
27 )
28
29 from pip._internal.cache import WheelCache
30 from pip._internal.req.req_install import InstallRequirement
31
32 BinaryAllowedPredicate = Callable[[InstallRequirement], bool]
33 BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]]
34
35 logger = logging.getLogger(__name__)
36
37
38 def _contains_egg_info(
39 s, _egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)):
40 # type: (str, Pattern[str]) -> bool
41 """Determine whether the string looks like an egg_info.
42
43 :param s: The string to parse. E.g. foo-2.1
44 """
45 return bool(_egg_info_re.search(s))
46
47
48 def _should_build(
49 req, # type: InstallRequirement
50 need_wheel, # type: bool
51 check_binary_allowed, # type: BinaryAllowedPredicate
52 ):
53 # type: (...) -> bool
54 """Return whether an InstallRequirement should be built into a wheel."""
55 if req.constraint:
56 # never build requirements that are merely constraints
57 return False
58 if req.is_wheel:
59 if need_wheel:
60 logger.info(
61 'Skipping %s, due to already being wheel.', req.name,
62 )
63 return False
64
65 if need_wheel:
66 # i.e. pip wheel, not pip install
67 return True
68
69 # From this point, this concerns the pip install command only
70 # (need_wheel=False).
71
72 if not req.use_pep517 and not is_wheel_installed():
73 # we don't build legacy requirements if wheel is not installed
74 return False
75
76 if req.editable or not req.source_dir:
77 return False
78
79 if not check_binary_allowed(req):
80 logger.info(
81 "Skipping wheel build for %s, due to binaries "
82 "being disabled for it.", req.name,
83 )
84 return False
85
86 return True
87
88
89 def should_build_for_wheel_command(
90 req, # type: InstallRequirement
91 ):
92 # type: (...) -> bool
93 return _should_build(
94 req, need_wheel=True, check_binary_allowed=_always_true
95 )
96
97
98 def should_build_for_install_command(
99 req, # type: InstallRequirement
100 check_binary_allowed, # type: BinaryAllowedPredicate
101 ):
102 # type: (...) -> bool
103 return _should_build(
104 req, need_wheel=False, check_binary_allowed=check_binary_allowed
105 )
106
107
108 def _should_cache(
109 req, # type: InstallRequirement
110 ):
111 # type: (...) -> Optional[bool]
112 """
113 Return whether a built InstallRequirement can be stored in the persistent
114 wheel cache, assuming the wheel cache is available, and _should_build()
115 has determined a wheel needs to be built.
116 """
117 if not should_build_for_install_command(
118 req, check_binary_allowed=_always_true
119 ):
120 # never cache if pip install would not have built
121 # (editable mode, etc)
122 return False
123
124 if req.link and req.link.is_vcs:
125 # VCS checkout. Do not cache
126 # unless it points to an immutable commit hash.
127 assert not req.editable
128 assert req.source_dir
129 vcs_backend = vcs.get_backend_for_scheme(req.link.scheme)
130 assert vcs_backend
131 if vcs_backend.is_immutable_rev_checkout(req.link.url, req.source_dir):
132 return True
133 return False
134
135 base, ext = req.link.splitext()
136 if _contains_egg_info(base):
137 return True
138
139 # Otherwise, do not cache.
140 return False
141
142
143 def _get_cache_dir(
144 req, # type: InstallRequirement
145 wheel_cache, # type: WheelCache
146 ):
147 # type: (...) -> str
148 """Return the persistent or temporary cache directory where the built
149 wheel need to be stored.
150 """
151 cache_available = bool(wheel_cache.cache_dir)
152 if cache_available and _should_cache(req):
153 cache_dir = wheel_cache.get_path_for_link(req.link)
154 else:
155 cache_dir = wheel_cache.get_ephem_path_for_link(req.link)
156 return cache_dir
157
158
159 def _always_true(_):
160 # type: (Any) -> bool
161 return True
162
163
164 def _build_one(
165 req, # type: InstallRequirement
166 output_dir, # type: str
167 build_options, # type: List[str]
168 global_options, # type: List[str]
169 ):
170 # type: (...) -> Optional[str]
171 """Build one wheel.
172
173 :return: The filename of the built wheel, or None if the build failed.
174 """
175 try:
176 ensure_dir(output_dir)
177 except OSError as e:
178 logger.warning(
179 "Building wheel for %s failed: %s",
180 req.name, e,
181 )
182 return None
183
184 # Install build deps into temporary directory (PEP 518)
185 with req.build_env:
186 return _build_one_inside_env(
187 req, output_dir, build_options, global_options
188 )
189
190
191 def _build_one_inside_env(
192 req, # type: InstallRequirement
193 output_dir, # type: str
194 build_options, # type: List[str]
195 global_options, # type: List[str]
196 ):
197 # type: (...) -> Optional[str]
198 with TempDirectory(kind="wheel") as temp_dir:
199 if req.use_pep517:
200 wheel_path = build_wheel_pep517(
201 name=req.name,
202 backend=req.pep517_backend,
203 metadata_directory=req.metadata_directory,
204 build_options=build_options,
205 tempd=temp_dir.path,
206 )
207 else:
208 wheel_path = build_wheel_legacy(
209 name=req.name,
210 setup_py_path=req.setup_py_path,
211 source_dir=req.unpacked_source_directory,
212 global_options=global_options,
213 build_options=build_options,
214 tempd=temp_dir.path,
215 )
216
217 if wheel_path is not None:
218 wheel_name = os.path.basename(wheel_path)
219 dest_path = os.path.join(output_dir, wheel_name)
220 try:
221 wheel_hash, length = hash_file(wheel_path)
222 shutil.move(wheel_path, dest_path)
223 logger.info('Created wheel for %s: '
224 'filename=%s size=%d sha256=%s',
225 req.name, wheel_name, length,
226 wheel_hash.hexdigest())
227 logger.info('Stored in directory: %s', output_dir)
228 return dest_path
229 except Exception as e:
230 logger.warning(
231 "Building wheel for %s failed: %s",
232 req.name, e,
233 )
234 # Ignore return, we can't do anything else useful.
235 if not req.use_pep517:
236 _clean_one_legacy(req, global_options)
237 return None
238
239
240 def _clean_one_legacy(req, global_options):
241 # type: (InstallRequirement, List[str]) -> bool
242 clean_args = make_setuptools_clean_args(
243 req.setup_py_path,
244 global_options=global_options,
245 )
246
247 logger.info('Running setup.py clean for %s', req.name)
248 try:
249 call_subprocess(clean_args, cwd=req.source_dir)
250 return True
251 except Exception:
252 logger.error('Failed cleaning build dir for %s', req.name)
253 return False
254
255
256 def build(
257 requirements, # type: Iterable[InstallRequirement]
258 wheel_cache, # type: WheelCache
259 build_options, # type: List[str]
260 global_options, # type: List[str]
261 ):
262 # type: (...) -> BuildResult
263 """Build wheels.
264
265 :return: The list of InstallRequirement that succeeded to build and
266 the list of InstallRequirement that failed to build.
267 """
268 if not requirements:
269 return [], []
270
271 # Build the wheels.
272 logger.info(
273 'Building wheels for collected packages: %s',
274 ', '.join(req.name for req in requirements),
275 )
276
277 with indent_log():
278 build_successes, build_failures = [], []
279 for req in requirements:
280 cache_dir = _get_cache_dir(req, wheel_cache)
281 wheel_file = _build_one(
282 req, cache_dir, build_options, global_options
283 )
284 if wheel_file:
285 # Update the link for this.
286 req.link = Link(path_to_url(wheel_file))
287 req.local_file_path = req.link.file_path
288 assert req.link.is_wheel
289 build_successes.append(req)
290 else:
291 build_failures.append(req)
292
293 # notify success/failure
294 if build_successes:
295 logger.info(
296 'Successfully built %s',
297 ' '.join([req.name for req in build_successes]),
298 )
299 if build_failures:
300 logger.info(
301 'Failed to build %s',
302 ' '.join([req.name for req in build_failures]),
303 )
304 # Return a list of requirements that failed to build
305 return build_successes, build_failures