diff options
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_install.py')
-rw-r--r-- | venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_install.py | 1115 |
1 files changed, 1115 insertions, 0 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_install.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_install.py new file mode 100644 index 0000000..9dd1523 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_install.py | |||
@@ -0,0 +1,1115 @@ | |||
1 | from __future__ import absolute_import | ||
2 | |||
3 | import logging | ||
4 | import os | ||
5 | import re | ||
6 | import shutil | ||
7 | import sys | ||
8 | import sysconfig | ||
9 | import traceback | ||
10 | import warnings | ||
11 | import zipfile | ||
12 | from distutils.util import change_root | ||
13 | from email.parser import FeedParser # type: ignore | ||
14 | |||
15 | from pip._vendor import pkg_resources, pytoml, six | ||
16 | from pip._vendor.packaging import specifiers | ||
17 | from pip._vendor.packaging.markers import Marker | ||
18 | from pip._vendor.packaging.requirements import InvalidRequirement, Requirement | ||
19 | from pip._vendor.packaging.utils import canonicalize_name | ||
20 | from pip._vendor.packaging.version import parse as parse_version | ||
21 | from pip._vendor.packaging.version import Version | ||
22 | from pip._vendor.pkg_resources import RequirementParseError, parse_requirements | ||
23 | |||
24 | from pip._internal import wheel | ||
25 | from pip._internal.build_env import BuildEnvironment | ||
26 | from pip._internal.compat import native_str | ||
27 | from pip._internal.download import ( | ||
28 | is_archive_file, is_url, path_to_url, url_to_path, | ||
29 | ) | ||
30 | from pip._internal.exceptions import InstallationError, UninstallationError | ||
31 | from pip._internal.locations import ( | ||
32 | PIP_DELETE_MARKER_FILENAME, running_under_virtualenv, | ||
33 | ) | ||
34 | from pip._internal.req.req_uninstall import UninstallPathSet | ||
35 | from pip._internal.utils.deprecation import RemovedInPip11Warning | ||
36 | from pip._internal.utils.hashes import Hashes | ||
37 | from pip._internal.utils.logging import indent_log | ||
38 | from pip._internal.utils.misc import ( | ||
39 | _make_build_dir, ask_path_exists, backup_dir, call_subprocess, | ||
40 | display_path, dist_in_site_packages, dist_in_usersite, ensure_dir, | ||
41 | get_installed_version, is_installable_dir, read_text_file, rmtree, | ||
42 | ) | ||
43 | from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM | ||
44 | from pip._internal.utils.temp_dir import TempDirectory | ||
45 | from pip._internal.utils.ui import open_spinner | ||
46 | from pip._internal.vcs import vcs | ||
47 | from pip._internal.wheel import Wheel, move_wheel_files | ||
48 | |||
49 | logger = logging.getLogger(__name__) | ||
50 | |||
51 | operators = specifiers.Specifier._operators.keys() | ||
52 | |||
53 | |||
54 | def _strip_extras(path): | ||
55 | m = re.match(r'^(.+)(\[[^\]]+\])$', path) | ||
56 | extras = None | ||
57 | if m: | ||
58 | path_no_extras = m.group(1) | ||
59 | extras = m.group(2) | ||
60 | else: | ||
61 | path_no_extras = path | ||
62 | |||
63 | return path_no_extras, extras | ||
64 | |||
65 | |||
66 | class InstallRequirement(object): | ||
67 | """ | ||
68 | Represents something that may be installed later on, may have information | ||
69 | about where to fetch the relavant requirement and also contains logic for | ||
70 | installing the said requirement. | ||
71 | """ | ||
72 | |||
73 | def __init__(self, req, comes_from, source_dir=None, editable=False, | ||
74 | link=None, update=True, markers=None, | ||
75 | isolated=False, options=None, wheel_cache=None, | ||
76 | constraint=False, extras=()): | ||
77 | assert req is None or isinstance(req, Requirement), req | ||
78 | self.req = req | ||
79 | self.comes_from = comes_from | ||
80 | self.constraint = constraint | ||
81 | if source_dir is not None: | ||
82 | self.source_dir = os.path.normpath(os.path.abspath(source_dir)) | ||
83 | else: | ||
84 | self.source_dir = None | ||
85 | self.editable = editable | ||
86 | |||
87 | self._wheel_cache = wheel_cache | ||
88 | if link is not None: | ||
89 | self.link = self.original_link = link | ||
90 | else: | ||
91 | from pip._internal.index import Link | ||
92 | self.link = self.original_link = req and req.url and Link(req.url) | ||
93 | |||
94 | if extras: | ||
95 | self.extras = extras | ||
96 | elif req: | ||
97 | self.extras = { | ||
98 | pkg_resources.safe_extra(extra) for extra in req.extras | ||
99 | } | ||
100 | else: | ||
101 | self.extras = set() | ||
102 | if markers is not None: | ||
103 | self.markers = markers | ||
104 | else: | ||
105 | self.markers = req and req.marker | ||
106 | self._egg_info_path = None | ||
107 | # This holds the pkg_resources.Distribution object if this requirement | ||
108 | # is already available: | ||
109 | self.satisfied_by = None | ||
110 | # This hold the pkg_resources.Distribution object if this requirement | ||
111 | # conflicts with another installed distribution: | ||
112 | self.conflicts_with = None | ||
113 | # Temporary build location | ||
114 | self._temp_build_dir = TempDirectory(kind="req-build") | ||
115 | # Used to store the global directory where the _temp_build_dir should | ||
116 | # have been created. Cf _correct_build_location method. | ||
117 | self._ideal_build_dir = None | ||
118 | # True if the editable should be updated: | ||
119 | self.update = update | ||
120 | # Set to True after successful installation | ||
121 | self.install_succeeded = None | ||
122 | # UninstallPathSet of uninstalled distribution (for possible rollback) | ||
123 | self.uninstalled_pathset = None | ||
124 | self.options = options if options else {} | ||
125 | # Set to True after successful preparation of this requirement | ||
126 | self.prepared = False | ||
127 | self.is_direct = False | ||
128 | |||
129 | self.isolated = isolated | ||
130 | self.build_env = BuildEnvironment(no_clean=True) | ||
131 | |||
132 | @classmethod | ||
133 | def from_editable(cls, editable_req, comes_from=None, isolated=False, | ||
134 | options=None, wheel_cache=None, constraint=False): | ||
135 | from pip._internal.index import Link | ||
136 | |||
137 | name, url, extras_override = parse_editable(editable_req) | ||
138 | if url.startswith('file:'): | ||
139 | source_dir = url_to_path(url) | ||
140 | else: | ||
141 | source_dir = None | ||
142 | |||
143 | if name is not None: | ||
144 | try: | ||
145 | req = Requirement(name) | ||
146 | except InvalidRequirement: | ||
147 | raise InstallationError("Invalid requirement: '%s'" % name) | ||
148 | else: | ||
149 | req = None | ||
150 | return cls( | ||
151 | req, comes_from, source_dir=source_dir, | ||
152 | editable=True, | ||
153 | link=Link(url), | ||
154 | constraint=constraint, | ||
155 | isolated=isolated, | ||
156 | options=options if options else {}, | ||
157 | wheel_cache=wheel_cache, | ||
158 | extras=extras_override or (), | ||
159 | ) | ||
160 | |||
161 | @classmethod | ||
162 | def from_req(cls, req, comes_from=None, isolated=False, wheel_cache=None): | ||
163 | try: | ||
164 | req = Requirement(req) | ||
165 | except InvalidRequirement: | ||
166 | raise InstallationError("Invalid requirement: '%s'" % req) | ||
167 | if req.url: | ||
168 | raise InstallationError( | ||
169 | "Direct url requirement (like %s) are not allowed for " | ||
170 | "dependencies" % req | ||
171 | ) | ||
172 | return cls(req, comes_from, isolated=isolated, wheel_cache=wheel_cache) | ||
173 | |||
174 | @classmethod | ||
175 | def from_line( | ||
176 | cls, name, comes_from=None, isolated=False, options=None, | ||
177 | wheel_cache=None, constraint=False): | ||
178 | """Creates an InstallRequirement from a name, which might be a | ||
179 | requirement, directory containing 'setup.py', filename, or URL. | ||
180 | """ | ||
181 | from pip._internal.index import Link | ||
182 | |||
183 | if is_url(name): | ||
184 | marker_sep = '; ' | ||
185 | else: | ||
186 | marker_sep = ';' | ||
187 | if marker_sep in name: | ||
188 | name, markers = name.split(marker_sep, 1) | ||
189 | markers = markers.strip() | ||
190 | if not markers: | ||
191 | markers = None | ||
192 | else: | ||
193 | markers = Marker(markers) | ||
194 | else: | ||
195 | markers = None | ||
196 | name = name.strip() | ||
197 | req = None | ||
198 | path = os.path.normpath(os.path.abspath(name)) | ||
199 | link = None | ||
200 | extras = None | ||
201 | |||
202 | if is_url(name): | ||
203 | link = Link(name) | ||
204 | else: | ||
205 | p, extras = _strip_extras(path) | ||
206 | looks_like_dir = os.path.isdir(p) and ( | ||
207 | os.path.sep in name or | ||
208 | (os.path.altsep is not None and os.path.altsep in name) or | ||
209 | name.startswith('.') | ||
210 | ) | ||
211 | if looks_like_dir: | ||
212 | if not is_installable_dir(p): | ||
213 | raise InstallationError( | ||
214 | "Directory %r is not installable. File 'setup.py' " | ||
215 | "not found." % name | ||
216 | ) | ||
217 | link = Link(path_to_url(p)) | ||
218 | elif is_archive_file(p): | ||
219 | if not os.path.isfile(p): | ||
220 | logger.warning( | ||
221 | 'Requirement %r looks like a filename, but the ' | ||
222 | 'file does not exist', | ||
223 | name | ||
224 | ) | ||
225 | link = Link(path_to_url(p)) | ||
226 | |||
227 | # it's a local file, dir, or url | ||
228 | if link: | ||
229 | # Handle relative file URLs | ||
230 | if link.scheme == 'file' and re.search(r'\.\./', link.url): | ||
231 | link = Link( | ||
232 | path_to_url(os.path.normpath(os.path.abspath(link.path)))) | ||
233 | # wheel file | ||
234 | if link.is_wheel: | ||
235 | wheel = Wheel(link.filename) # can raise InvalidWheelFilename | ||
236 | req = "%s==%s" % (wheel.name, wheel.version) | ||
237 | else: | ||
238 | # set the req to the egg fragment. when it's not there, this | ||
239 | # will become an 'unnamed' requirement | ||
240 | req = link.egg_fragment | ||
241 | |||
242 | # a requirement specifier | ||
243 | else: | ||
244 | req = name | ||
245 | |||
246 | if extras: | ||
247 | extras = Requirement("placeholder" + extras.lower()).extras | ||
248 | else: | ||
249 | extras = () | ||
250 | if req is not None: | ||
251 | try: | ||
252 | req = Requirement(req) | ||
253 | except InvalidRequirement: | ||
254 | if os.path.sep in req: | ||
255 | add_msg = "It looks like a path." | ||
256 | add_msg += deduce_helpful_msg(req) | ||
257 | elif '=' in req and not any(op in req for op in operators): | ||
258 | add_msg = "= is not a valid operator. Did you mean == ?" | ||
259 | else: | ||
260 | add_msg = traceback.format_exc() | ||
261 | raise InstallationError( | ||
262 | "Invalid requirement: '%s'\n%s" % (req, add_msg)) | ||
263 | return cls( | ||
264 | req, comes_from, link=link, markers=markers, | ||
265 | isolated=isolated, | ||
266 | options=options if options else {}, | ||
267 | wheel_cache=wheel_cache, | ||
268 | constraint=constraint, | ||
269 | extras=extras, | ||
270 | ) | ||
271 | |||
272 | def __str__(self): | ||
273 | if self.req: | ||
274 | s = str(self.req) | ||
275 | if self.link: | ||
276 | s += ' from %s' % self.link.url | ||
277 | else: | ||
278 | s = self.link.url if self.link else None | ||
279 | if self.satisfied_by is not None: | ||
280 | s += ' in %s' % display_path(self.satisfied_by.location) | ||
281 | if self.comes_from: | ||
282 | if isinstance(self.comes_from, six.string_types): | ||
283 | comes_from = self.comes_from | ||
284 | else: | ||
285 | comes_from = self.comes_from.from_path() | ||
286 | if comes_from: | ||
287 | s += ' (from %s)' % comes_from | ||
288 | return s | ||
289 | |||
290 | def __repr__(self): | ||
291 | return '<%s object: %s editable=%r>' % ( | ||
292 | self.__class__.__name__, str(self), self.editable) | ||
293 | |||
294 | def populate_link(self, finder, upgrade, require_hashes): | ||
295 | """Ensure that if a link can be found for this, that it is found. | ||
296 | |||
297 | Note that self.link may still be None - if Upgrade is False and the | ||
298 | requirement is already installed. | ||
299 | |||
300 | If require_hashes is True, don't use the wheel cache, because cached | ||
301 | wheels, always built locally, have different hashes than the files | ||
302 | downloaded from the index server and thus throw false hash mismatches. | ||
303 | Furthermore, cached wheels at present have undeterministic contents due | ||
304 | to file modification times. | ||
305 | """ | ||
306 | if self.link is None: | ||
307 | self.link = finder.find_requirement(self, upgrade) | ||
308 | if self._wheel_cache is not None and not require_hashes: | ||
309 | old_link = self.link | ||
310 | self.link = self._wheel_cache.get(self.link, self.name) | ||
311 | if old_link != self.link: | ||
312 | logger.debug('Using cached wheel link: %s', self.link) | ||
313 | |||
314 | @property | ||
315 | def specifier(self): | ||
316 | return self.req.specifier | ||
317 | |||
318 | @property | ||
319 | def is_pinned(self): | ||
320 | """Return whether I am pinned to an exact version. | ||
321 | |||
322 | For example, some-package==1.2 is pinned; some-package>1.2 is not. | ||
323 | """ | ||
324 | specifiers = self.specifier | ||
325 | return (len(specifiers) == 1 and | ||
326 | next(iter(specifiers)).operator in {'==', '==='}) | ||
327 | |||
328 | def from_path(self): | ||
329 | if self.req is None: | ||
330 | return None | ||
331 | s = str(self.req) | ||
332 | if self.comes_from: | ||
333 | if isinstance(self.comes_from, six.string_types): | ||
334 | comes_from = self.comes_from | ||
335 | else: | ||
336 | comes_from = self.comes_from.from_path() | ||
337 | if comes_from: | ||
338 | s += '->' + comes_from | ||
339 | return s | ||
340 | |||
341 | def build_location(self, build_dir): | ||
342 | assert build_dir is not None | ||
343 | if self._temp_build_dir.path is not None: | ||
344 | return self._temp_build_dir.path | ||
345 | if self.req is None: | ||
346 | # for requirement via a path to a directory: the name of the | ||
347 | # package is not available yet so we create a temp directory | ||
348 | # Once run_egg_info will have run, we'll be able | ||
349 | # to fix it via _correct_build_location | ||
350 | # Some systems have /tmp as a symlink which confuses custom | ||
351 | # builds (such as numpy). Thus, we ensure that the real path | ||
352 | # is returned. | ||
353 | self._temp_build_dir.create() | ||
354 | self._ideal_build_dir = build_dir | ||
355 | |||
356 | return self._temp_build_dir.path | ||
357 | if self.editable: | ||
358 | name = self.name.lower() | ||
359 | else: | ||
360 | name = self.name | ||
361 | # FIXME: Is there a better place to create the build_dir? (hg and bzr | ||
362 | # need this) | ||
363 | if not os.path.exists(build_dir): | ||
364 | logger.debug('Creating directory %s', build_dir) | ||
365 | _make_build_dir(build_dir) | ||
366 | return os.path.join(build_dir, name) | ||
367 | |||
368 | def _correct_build_location(self): | ||
369 | """Move self._temp_build_dir to self._ideal_build_dir/self.req.name | ||
370 | |||
371 | For some requirements (e.g. a path to a directory), the name of the | ||
372 | package is not available until we run egg_info, so the build_location | ||
373 | will return a temporary directory and store the _ideal_build_dir. | ||
374 | |||
375 | This is only called by self.egg_info_path to fix the temporary build | ||
376 | directory. | ||
377 | """ | ||
378 | if self.source_dir is not None: | ||
379 | return | ||
380 | assert self.req is not None | ||
381 | assert self._temp_build_dir.path | ||
382 | assert self._ideal_build_dir.path | ||
383 | old_location = self._temp_build_dir.path | ||
384 | self._temp_build_dir.path = None | ||
385 | |||
386 | new_location = self.build_location(self._ideal_build_dir) | ||
387 | if os.path.exists(new_location): | ||
388 | raise InstallationError( | ||
389 | 'A package already exists in %s; please remove it to continue' | ||
390 | % display_path(new_location)) | ||
391 | logger.debug( | ||
392 | 'Moving package %s from %s to new location %s', | ||
393 | self, display_path(old_location), display_path(new_location), | ||
394 | ) | ||
395 | shutil.move(old_location, new_location) | ||
396 | self._temp_build_dir.path = new_location | ||
397 | self._ideal_build_dir = None | ||
398 | self.source_dir = os.path.normpath(os.path.abspath(new_location)) | ||
399 | self._egg_info_path = None | ||
400 | |||
401 | @property | ||
402 | def name(self): | ||
403 | if self.req is None: | ||
404 | return None | ||
405 | return native_str(pkg_resources.safe_name(self.req.name)) | ||
406 | |||
407 | @property | ||
408 | def setup_py_dir(self): | ||
409 | return os.path.join( | ||
410 | self.source_dir, | ||
411 | self.link and self.link.subdirectory_fragment or '') | ||
412 | |||
413 | @property | ||
414 | def setup_py(self): | ||
415 | assert self.source_dir, "No source dir for %s" % self | ||
416 | |||
417 | setup_py = os.path.join(self.setup_py_dir, 'setup.py') | ||
418 | |||
419 | # Python2 __file__ should not be unicode | ||
420 | if six.PY2 and isinstance(setup_py, six.text_type): | ||
421 | setup_py = setup_py.encode(sys.getfilesystemencoding()) | ||
422 | |||
423 | return setup_py | ||
424 | |||
425 | @property | ||
426 | def pyproject_toml(self): | ||
427 | assert self.source_dir, "No source dir for %s" % self | ||
428 | |||
429 | pp_toml = os.path.join(self.setup_py_dir, 'pyproject.toml') | ||
430 | |||
431 | # Python2 __file__ should not be unicode | ||
432 | if six.PY2 and isinstance(pp_toml, six.text_type): | ||
433 | pp_toml = pp_toml.encode(sys.getfilesystemencoding()) | ||
434 | |||
435 | return pp_toml | ||
436 | |||
437 | def get_pep_518_info(self): | ||
438 | """Get a list of the packages required to build the project, if any, | ||
439 | and a flag indicating whether pyproject.toml is present, indicating | ||
440 | that the build should be isolated. | ||
441 | |||
442 | Build requirements can be specified in a pyproject.toml, as described | ||
443 | in PEP 518. If this file exists but doesn't specify build | ||
444 | requirements, pip will default to installing setuptools and wheel. | ||
445 | """ | ||
446 | if os.path.isfile(self.pyproject_toml): | ||
447 | with open(self.pyproject_toml) as f: | ||
448 | pp_toml = pytoml.load(f) | ||
449 | build_sys = pp_toml.get('build-system', {}) | ||
450 | return (build_sys.get('requires', ['setuptools', 'wheel']), True) | ||
451 | return (['setuptools', 'wheel'], False) | ||
452 | |||
453 | def run_egg_info(self): | ||
454 | assert self.source_dir | ||
455 | if self.name: | ||
456 | logger.debug( | ||
457 | 'Running setup.py (path:%s) egg_info for package %s', | ||
458 | self.setup_py, self.name, | ||
459 | ) | ||
460 | else: | ||
461 | logger.debug( | ||
462 | 'Running setup.py (path:%s) egg_info for package from %s', | ||
463 | self.setup_py, self.link, | ||
464 | ) | ||
465 | |||
466 | with indent_log(): | ||
467 | script = SETUPTOOLS_SHIM % self.setup_py | ||
468 | base_cmd = [sys.executable, '-c', script] | ||
469 | if self.isolated: | ||
470 | base_cmd += ["--no-user-cfg"] | ||
471 | egg_info_cmd = base_cmd + ['egg_info'] | ||
472 | # We can't put the .egg-info files at the root, because then the | ||
473 | # source code will be mistaken for an installed egg, causing | ||
474 | # problems | ||
475 | if self.editable: | ||
476 | egg_base_option = [] | ||
477 | else: | ||
478 | egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info') | ||
479 | ensure_dir(egg_info_dir) | ||
480 | egg_base_option = ['--egg-base', 'pip-egg-info'] | ||
481 | with self.build_env: | ||
482 | call_subprocess( | ||
483 | egg_info_cmd + egg_base_option, | ||
484 | cwd=self.setup_py_dir, | ||
485 | show_stdout=False, | ||
486 | command_desc='python setup.py egg_info') | ||
487 | |||
488 | if not self.req: | ||
489 | if isinstance(parse_version(self.pkg_info()["Version"]), Version): | ||
490 | op = "==" | ||
491 | else: | ||
492 | op = "===" | ||
493 | self.req = Requirement( | ||
494 | "".join([ | ||
495 | self.pkg_info()["Name"], | ||
496 | op, | ||
497 | self.pkg_info()["Version"], | ||
498 | ]) | ||
499 | ) | ||
500 | self._correct_build_location() | ||
501 | else: | ||
502 | metadata_name = canonicalize_name(self.pkg_info()["Name"]) | ||
503 | if canonicalize_name(self.req.name) != metadata_name: | ||
504 | logger.warning( | ||
505 | 'Running setup.py (path:%s) egg_info for package %s ' | ||
506 | 'produced metadata for project name %s. Fix your ' | ||
507 | '#egg=%s fragments.', | ||
508 | self.setup_py, self.name, metadata_name, self.name | ||
509 | ) | ||
510 | self.req = Requirement(metadata_name) | ||
511 | |||
512 | def egg_info_data(self, filename): | ||
513 | if self.satisfied_by is not None: | ||
514 | if not self.satisfied_by.has_metadata(filename): | ||
515 | return None | ||
516 | return self.satisfied_by.get_metadata(filename) | ||
517 | assert self.source_dir | ||
518 | filename = self.egg_info_path(filename) | ||
519 | if not os.path.exists(filename): | ||
520 | return None | ||
521 | data = read_text_file(filename) | ||
522 | return data | ||
523 | |||
524 | def egg_info_path(self, filename): | ||
525 | if self._egg_info_path is None: | ||
526 | if self.editable: | ||
527 | base = self.source_dir | ||
528 | else: | ||
529 | base = os.path.join(self.setup_py_dir, 'pip-egg-info') | ||
530 | filenames = os.listdir(base) | ||
531 | if self.editable: | ||
532 | filenames = [] | ||
533 | for root, dirs, files in os.walk(base): | ||
534 | for dir in vcs.dirnames: | ||
535 | if dir in dirs: | ||
536 | dirs.remove(dir) | ||
537 | # Iterate over a copy of ``dirs``, since mutating | ||
538 | # a list while iterating over it can cause trouble. | ||
539 | # (See https://github.com/pypa/pip/pull/462.) | ||
540 | for dir in list(dirs): | ||
541 | # Don't search in anything that looks like a virtualenv | ||
542 | # environment | ||
543 | if ( | ||
544 | os.path.lexists( | ||
545 | os.path.join(root, dir, 'bin', 'python') | ||
546 | ) or | ||
547 | os.path.exists( | ||
548 | os.path.join( | ||
549 | root, dir, 'Scripts', 'Python.exe' | ||
550 | ) | ||
551 | )): | ||
552 | dirs.remove(dir) | ||
553 | # Also don't search through tests | ||
554 | elif dir == 'test' or dir == 'tests': | ||
555 | dirs.remove(dir) | ||
556 | filenames.extend([os.path.join(root, dir) | ||
557 | for dir in dirs]) | ||
558 | filenames = [f for f in filenames if f.endswith('.egg-info')] | ||
559 | |||
560 | if not filenames: | ||
561 | raise InstallationError( | ||
562 | 'No files/directories in %s (from %s)' % (base, filename) | ||
563 | ) | ||
564 | assert filenames, \ | ||
565 | "No files/directories in %s (from %s)" % (base, filename) | ||
566 | |||
567 | # if we have more than one match, we pick the toplevel one. This | ||
568 | # can easily be the case if there is a dist folder which contains | ||
569 | # an extracted tarball for testing purposes. | ||
570 | if len(filenames) > 1: | ||
571 | filenames.sort( | ||
572 | key=lambda x: x.count(os.path.sep) + | ||
573 | (os.path.altsep and x.count(os.path.altsep) or 0) | ||
574 | ) | ||
575 | self._egg_info_path = os.path.join(base, filenames[0]) | ||
576 | return os.path.join(self._egg_info_path, filename) | ||
577 | |||
578 | def pkg_info(self): | ||
579 | p = FeedParser() | ||
580 | data = self.egg_info_data('PKG-INFO') | ||
581 | if not data: | ||
582 | logger.warning( | ||
583 | 'No PKG-INFO file found in %s', | ||
584 | display_path(self.egg_info_path('PKG-INFO')), | ||
585 | ) | ||
586 | p.feed(data or '') | ||
587 | return p.close() | ||
588 | |||
589 | _requirements_section_re = re.compile(r'\[(.*?)\]') | ||
590 | |||
591 | @property | ||
592 | def installed_version(self): | ||
593 | return get_installed_version(self.name) | ||
594 | |||
595 | def assert_source_matches_version(self): | ||
596 | assert self.source_dir | ||
597 | version = self.pkg_info()['version'] | ||
598 | if self.req.specifier and version not in self.req.specifier: | ||
599 | logger.warning( | ||
600 | 'Requested %s, but installing version %s', | ||
601 | self, | ||
602 | version, | ||
603 | ) | ||
604 | else: | ||
605 | logger.debug( | ||
606 | 'Source in %s has version %s, which satisfies requirement %s', | ||
607 | display_path(self.source_dir), | ||
608 | version, | ||
609 | self, | ||
610 | ) | ||
611 | |||
612 | def update_editable(self, obtain=True): | ||
613 | if not self.link: | ||
614 | logger.debug( | ||
615 | "Cannot update repository at %s; repository location is " | ||
616 | "unknown", | ||
617 | self.source_dir, | ||
618 | ) | ||
619 | return | ||
620 | assert self.editable | ||
621 | assert self.source_dir | ||
622 | if self.link.scheme == 'file': | ||
623 | # Static paths don't get updated | ||
624 | return | ||
625 | assert '+' in self.link.url, "bad url: %r" % self.link.url | ||
626 | if not self.update: | ||
627 | return | ||
628 | vc_type, url = self.link.url.split('+', 1) | ||
629 | backend = vcs.get_backend(vc_type) | ||
630 | if backend: | ||
631 | vcs_backend = backend(self.link.url) | ||
632 | if obtain: | ||
633 | vcs_backend.obtain(self.source_dir) | ||
634 | else: | ||
635 | vcs_backend.export(self.source_dir) | ||
636 | else: | ||
637 | assert 0, ( | ||
638 | 'Unexpected version control type (in %s): %s' | ||
639 | % (self.link, vc_type)) | ||
640 | |||
641 | def uninstall(self, auto_confirm=False, verbose=False, | ||
642 | use_user_site=False): | ||
643 | """ | ||
644 | Uninstall the distribution currently satisfying this requirement. | ||
645 | |||
646 | Prompts before removing or modifying files unless | ||
647 | ``auto_confirm`` is True. | ||
648 | |||
649 | Refuses to delete or modify files outside of ``sys.prefix`` - | ||
650 | thus uninstallation within a virtual environment can only | ||
651 | modify that virtual environment, even if the virtualenv is | ||
652 | linked to global site-packages. | ||
653 | |||
654 | """ | ||
655 | if not self.check_if_exists(use_user_site): | ||
656 | logger.warning("Skipping %s as it is not installed.", self.name) | ||
657 | return | ||
658 | dist = self.satisfied_by or self.conflicts_with | ||
659 | |||
660 | uninstalled_pathset = UninstallPathSet.from_dist(dist) | ||
661 | uninstalled_pathset.remove(auto_confirm, verbose) | ||
662 | return uninstalled_pathset | ||
663 | |||
664 | def archive(self, build_dir): | ||
665 | assert self.source_dir | ||
666 | create_archive = True | ||
667 | archive_name = '%s-%s.zip' % (self.name, self.pkg_info()["version"]) | ||
668 | archive_path = os.path.join(build_dir, archive_name) | ||
669 | if os.path.exists(archive_path): | ||
670 | response = ask_path_exists( | ||
671 | 'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)bort ' % | ||
672 | display_path(archive_path), ('i', 'w', 'b', 'a')) | ||
673 | if response == 'i': | ||
674 | create_archive = False | ||
675 | elif response == 'w': | ||
676 | logger.warning('Deleting %s', display_path(archive_path)) | ||
677 | os.remove(archive_path) | ||
678 | elif response == 'b': | ||
679 | dest_file = backup_dir(archive_path) | ||
680 | logger.warning( | ||
681 | 'Backing up %s to %s', | ||
682 | display_path(archive_path), | ||
683 | display_path(dest_file), | ||
684 | ) | ||
685 | shutil.move(archive_path, dest_file) | ||
686 | elif response == 'a': | ||
687 | sys.exit(-1) | ||
688 | if create_archive: | ||
689 | zip = zipfile.ZipFile( | ||
690 | archive_path, 'w', zipfile.ZIP_DEFLATED, | ||
691 | allowZip64=True | ||
692 | ) | ||
693 | dir = os.path.normcase(os.path.abspath(self.setup_py_dir)) | ||
694 | for dirpath, dirnames, filenames in os.walk(dir): | ||
695 | if 'pip-egg-info' in dirnames: | ||
696 | dirnames.remove('pip-egg-info') | ||
697 | for dirname in dirnames: | ||
698 | dirname = os.path.join(dirpath, dirname) | ||
699 | name = self._clean_zip_name(dirname, dir) | ||
700 | zipdir = zipfile.ZipInfo(self.name + '/' + name + '/') | ||
701 | zipdir.external_attr = 0x1ED << 16 # 0o755 | ||
702 | zip.writestr(zipdir, '') | ||
703 | for filename in filenames: | ||
704 | if filename == PIP_DELETE_MARKER_FILENAME: | ||
705 | continue | ||
706 | filename = os.path.join(dirpath, filename) | ||
707 | name = self._clean_zip_name(filename, dir) | ||
708 | zip.write(filename, self.name + '/' + name) | ||
709 | zip.close() | ||
710 | logger.info('Saved %s', display_path(archive_path)) | ||
711 | |||
712 | def _clean_zip_name(self, name, prefix): | ||
713 | assert name.startswith(prefix + os.path.sep), ( | ||
714 | "name %r doesn't start with prefix %r" % (name, prefix) | ||
715 | ) | ||
716 | name = name[len(prefix) + 1:] | ||
717 | name = name.replace(os.path.sep, '/') | ||
718 | return name | ||
719 | |||
720 | def match_markers(self, extras_requested=None): | ||
721 | if not extras_requested: | ||
722 | # Provide an extra to safely evaluate the markers | ||
723 | # without matching any extra | ||
724 | extras_requested = ('',) | ||
725 | if self.markers is not None: | ||
726 | return any( | ||
727 | self.markers.evaluate({'extra': extra}) | ||
728 | for extra in extras_requested) | ||
729 | else: | ||
730 | return True | ||
731 | |||
732 | def install(self, install_options, global_options=None, root=None, | ||
733 | home=None, prefix=None, warn_script_location=True, | ||
734 | use_user_site=False, pycompile=True): | ||
735 | global_options = global_options if global_options is not None else [] | ||
736 | if self.editable: | ||
737 | self.install_editable( | ||
738 | install_options, global_options, prefix=prefix, | ||
739 | ) | ||
740 | return | ||
741 | if self.is_wheel: | ||
742 | version = wheel.wheel_version(self.source_dir) | ||
743 | wheel.check_compatibility(version, self.name) | ||
744 | |||
745 | self.move_wheel_files( | ||
746 | self.source_dir, root=root, prefix=prefix, home=home, | ||
747 | warn_script_location=warn_script_location, | ||
748 | use_user_site=use_user_site, pycompile=pycompile, | ||
749 | ) | ||
750 | self.install_succeeded = True | ||
751 | return | ||
752 | |||
753 | # Extend the list of global and install options passed on to | ||
754 | # the setup.py call with the ones from the requirements file. | ||
755 | # Options specified in requirements file override those | ||
756 | # specified on the command line, since the last option given | ||
757 | # to setup.py is the one that is used. | ||
758 | global_options = list(global_options) + \ | ||
759 | self.options.get('global_options', []) | ||
760 | install_options = list(install_options) + \ | ||
761 | self.options.get('install_options', []) | ||
762 | |||
763 | if self.isolated: | ||
764 | global_options = global_options + ["--no-user-cfg"] | ||
765 | |||
766 | with TempDirectory(kind="record") as temp_dir: | ||
767 | record_filename = os.path.join(temp_dir.path, 'install-record.txt') | ||
768 | install_args = self.get_install_args( | ||
769 | global_options, record_filename, root, prefix, pycompile, | ||
770 | ) | ||
771 | msg = 'Running setup.py install for %s' % (self.name,) | ||
772 | with open_spinner(msg) as spinner: | ||
773 | with indent_log(): | ||
774 | with self.build_env: | ||
775 | call_subprocess( | ||
776 | install_args + install_options, | ||
777 | cwd=self.setup_py_dir, | ||
778 | show_stdout=False, | ||
779 | spinner=spinner, | ||
780 | ) | ||
781 | |||
782 | if not os.path.exists(record_filename): | ||
783 | logger.debug('Record file %s not found', record_filename) | ||
784 | return | ||
785 | self.install_succeeded = True | ||
786 | |||
787 | def prepend_root(path): | ||
788 | if root is None or not os.path.isabs(path): | ||
789 | return path | ||
790 | else: | ||
791 | return change_root(root, path) | ||
792 | |||
793 | with open(record_filename) as f: | ||
794 | for line in f: | ||
795 | directory = os.path.dirname(line) | ||
796 | if directory.endswith('.egg-info'): | ||
797 | egg_info_dir = prepend_root(directory) | ||
798 | break | ||
799 | else: | ||
800 | logger.warning( | ||
801 | 'Could not find .egg-info directory in install record' | ||
802 | ' for %s', | ||
803 | self, | ||
804 | ) | ||
805 | # FIXME: put the record somewhere | ||
806 | # FIXME: should this be an error? | ||
807 | return | ||
808 | new_lines = [] | ||
809 | with open(record_filename) as f: | ||
810 | for line in f: | ||
811 | filename = line.strip() | ||
812 | if os.path.isdir(filename): | ||
813 | filename += os.path.sep | ||
814 | new_lines.append( | ||
815 | os.path.relpath(prepend_root(filename), egg_info_dir) | ||
816 | ) | ||
817 | new_lines.sort() | ||
818 | ensure_dir(egg_info_dir) | ||
819 | inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt') | ||
820 | with open(inst_files_path, 'w') as f: | ||
821 | f.write('\n'.join(new_lines) + '\n') | ||
822 | |||
823 | def ensure_has_source_dir(self, parent_dir): | ||
824 | """Ensure that a source_dir is set. | ||
825 | |||
826 | This will create a temporary build dir if the name of the requirement | ||
827 | isn't known yet. | ||
828 | |||
829 | :param parent_dir: The ideal pip parent_dir for the source_dir. | ||
830 | Generally src_dir for editables and build_dir for sdists. | ||
831 | :return: self.source_dir | ||
832 | """ | ||
833 | if self.source_dir is None: | ||
834 | self.source_dir = self.build_location(parent_dir) | ||
835 | return self.source_dir | ||
836 | |||
837 | def get_install_args(self, global_options, record_filename, root, prefix, | ||
838 | pycompile): | ||
839 | install_args = [sys.executable, "-u"] | ||
840 | install_args.append('-c') | ||
841 | install_args.append(SETUPTOOLS_SHIM % self.setup_py) | ||
842 | install_args += list(global_options) + \ | ||
843 | ['install', '--record', record_filename] | ||
844 | install_args += ['--single-version-externally-managed'] | ||
845 | |||
846 | if root is not None: | ||
847 | install_args += ['--root', root] | ||
848 | if prefix is not None: | ||
849 | install_args += ['--prefix', prefix] | ||
850 | |||
851 | if pycompile: | ||
852 | install_args += ["--compile"] | ||
853 | else: | ||
854 | install_args += ["--no-compile"] | ||
855 | |||
856 | if running_under_virtualenv(): | ||
857 | py_ver_str = 'python' + sysconfig.get_python_version() | ||
858 | install_args += ['--install-headers', | ||
859 | os.path.join(sys.prefix, 'include', 'site', | ||
860 | py_ver_str, self.name)] | ||
861 | |||
862 | return install_args | ||
863 | |||
864 | def remove_temporary_source(self): | ||
865 | """Remove the source files from this requirement, if they are marked | ||
866 | for deletion""" | ||
867 | if self.source_dir and os.path.exists( | ||
868 | os.path.join(self.source_dir, PIP_DELETE_MARKER_FILENAME)): | ||
869 | logger.debug('Removing source in %s', self.source_dir) | ||
870 | rmtree(self.source_dir) | ||
871 | self.source_dir = None | ||
872 | self._temp_build_dir.cleanup() | ||
873 | self.build_env.cleanup() | ||
874 | |||
875 | def install_editable(self, install_options, | ||
876 | global_options=(), prefix=None): | ||
877 | logger.info('Running setup.py develop for %s', self.name) | ||
878 | |||
879 | if self.isolated: | ||
880 | global_options = list(global_options) + ["--no-user-cfg"] | ||
881 | |||
882 | if prefix: | ||
883 | prefix_param = ['--prefix={}'.format(prefix)] | ||
884 | install_options = list(install_options) + prefix_param | ||
885 | |||
886 | with indent_log(): | ||
887 | # FIXME: should we do --install-headers here too? | ||
888 | with self.build_env: | ||
889 | call_subprocess( | ||
890 | [ | ||
891 | sys.executable, | ||
892 | '-c', | ||
893 | SETUPTOOLS_SHIM % self.setup_py | ||
894 | ] + | ||
895 | list(global_options) + | ||
896 | ['develop', '--no-deps'] + | ||
897 | list(install_options), | ||
898 | |||
899 | cwd=self.setup_py_dir, | ||
900 | show_stdout=False, | ||
901 | ) | ||
902 | |||
903 | self.install_succeeded = True | ||
904 | |||
905 | def check_if_exists(self, use_user_site): | ||
906 | """Find an installed distribution that satisfies or conflicts | ||
907 | with this requirement, and set self.satisfied_by or | ||
908 | self.conflicts_with appropriately. | ||
909 | """ | ||
910 | if self.req is None: | ||
911 | return False | ||
912 | try: | ||
913 | # get_distribution() will resolve the entire list of requirements | ||
914 | # anyway, and we've already determined that we need the requirement | ||
915 | # in question, so strip the marker so that we don't try to | ||
916 | # evaluate it. | ||
917 | no_marker = Requirement(str(self.req)) | ||
918 | no_marker.marker = None | ||
919 | self.satisfied_by = pkg_resources.get_distribution(str(no_marker)) | ||
920 | if self.editable and self.satisfied_by: | ||
921 | self.conflicts_with = self.satisfied_by | ||
922 | # when installing editables, nothing pre-existing should ever | ||
923 | # satisfy | ||
924 | self.satisfied_by = None | ||
925 | return True | ||
926 | except pkg_resources.DistributionNotFound: | ||
927 | return False | ||
928 | except pkg_resources.VersionConflict: | ||
929 | existing_dist = pkg_resources.get_distribution( | ||
930 | self.req.name | ||
931 | ) | ||
932 | if use_user_site: | ||
933 | if dist_in_usersite(existing_dist): | ||
934 | self.conflicts_with = existing_dist | ||
935 | elif (running_under_virtualenv() and | ||
936 | dist_in_site_packages(existing_dist)): | ||
937 | raise InstallationError( | ||
938 | "Will not install to the user site because it will " | ||
939 | "lack sys.path precedence to %s in %s" % | ||
940 | (existing_dist.project_name, existing_dist.location) | ||
941 | ) | ||
942 | else: | ||
943 | self.conflicts_with = existing_dist | ||
944 | return True | ||
945 | |||
946 | @property | ||
947 | def is_wheel(self): | ||
948 | return self.link and self.link.is_wheel | ||
949 | |||
950 | def move_wheel_files(self, wheeldir, root=None, home=None, prefix=None, | ||
951 | warn_script_location=True, use_user_site=False, | ||
952 | pycompile=True): | ||
953 | move_wheel_files( | ||
954 | self.name, self.req, wheeldir, | ||
955 | user=use_user_site, | ||
956 | home=home, | ||
957 | root=root, | ||
958 | prefix=prefix, | ||
959 | pycompile=pycompile, | ||
960 | isolated=self.isolated, | ||
961 | warn_script_location=warn_script_location, | ||
962 | ) | ||
963 | |||
964 | def get_dist(self): | ||
965 | """Return a pkg_resources.Distribution built from self.egg_info_path""" | ||
966 | egg_info = self.egg_info_path('').rstrip(os.path.sep) | ||
967 | base_dir = os.path.dirname(egg_info) | ||
968 | metadata = pkg_resources.PathMetadata(base_dir, egg_info) | ||
969 | dist_name = os.path.splitext(os.path.basename(egg_info))[0] | ||
970 | return pkg_resources.Distribution( | ||
971 | os.path.dirname(egg_info), | ||
972 | project_name=dist_name, | ||
973 | metadata=metadata, | ||
974 | ) | ||
975 | |||
976 | @property | ||
977 | def has_hash_options(self): | ||
978 | """Return whether any known-good hashes are specified as options. | ||
979 | |||
980 | These activate --require-hashes mode; hashes specified as part of a | ||
981 | URL do not. | ||
982 | |||
983 | """ | ||
984 | return bool(self.options.get('hashes', {})) | ||
985 | |||
986 | def hashes(self, trust_internet=True): | ||
987 | """Return a hash-comparer that considers my option- and URL-based | ||
988 | hashes to be known-good. | ||
989 | |||
990 | Hashes in URLs--ones embedded in the requirements file, not ones | ||
991 | downloaded from an index server--are almost peers with ones from | ||
992 | flags. They satisfy --require-hashes (whether it was implicitly or | ||
993 | explicitly activated) but do not activate it. md5 and sha224 are not | ||
994 | allowed in flags, which should nudge people toward good algos. We | ||
995 | always OR all hashes together, even ones from URLs. | ||
996 | |||
997 | :param trust_internet: Whether to trust URL-based (#md5=...) hashes | ||
998 | downloaded from the internet, as by populate_link() | ||
999 | |||
1000 | """ | ||
1001 | good_hashes = self.options.get('hashes', {}).copy() | ||
1002 | link = self.link if trust_internet else self.original_link | ||
1003 | if link and link.hash: | ||
1004 | good_hashes.setdefault(link.hash_name, []).append(link.hash) | ||
1005 | return Hashes(good_hashes) | ||
1006 | |||
1007 | |||
1008 | def _strip_postfix(req): | ||
1009 | """ | ||
1010 | Strip req postfix ( -dev, 0.2, etc ) | ||
1011 | """ | ||
1012 | # FIXME: use package_to_requirement? | ||
1013 | match = re.search(r'^(.*?)(?:-dev|-\d.*)$', req) | ||
1014 | if match: | ||
1015 | # Strip off -dev, -0.2, etc. | ||
1016 | warnings.warn( | ||
1017 | "#egg cleanup for editable urls will be dropped in the future", | ||
1018 | RemovedInPip11Warning, | ||
1019 | ) | ||
1020 | req = match.group(1) | ||
1021 | return req | ||
1022 | |||
1023 | |||
1024 | def parse_editable(editable_req): | ||
1025 | """Parses an editable requirement into: | ||
1026 | - a requirement name | ||
1027 | - an URL | ||
1028 | - extras | ||
1029 | - editable options | ||
1030 | Accepted requirements: | ||
1031 | svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir | ||
1032 | .[some_extra] | ||
1033 | """ | ||
1034 | |||
1035 | from pip._internal.index import Link | ||
1036 | |||
1037 | url = editable_req | ||
1038 | |||
1039 | # If a file path is specified with extras, strip off the extras. | ||
1040 | url_no_extras, extras = _strip_extras(url) | ||
1041 | |||
1042 | if os.path.isdir(url_no_extras): | ||
1043 | if not os.path.exists(os.path.join(url_no_extras, 'setup.py')): | ||
1044 | raise InstallationError( | ||
1045 | "Directory %r is not installable. File 'setup.py' not found." % | ||
1046 | url_no_extras | ||
1047 | ) | ||
1048 | # Treating it as code that has already been checked out | ||
1049 | url_no_extras = path_to_url(url_no_extras) | ||
1050 | |||
1051 | if url_no_extras.lower().startswith('file:'): | ||
1052 | package_name = Link(url_no_extras).egg_fragment | ||
1053 | if extras: | ||
1054 | return ( | ||
1055 | package_name, | ||
1056 | url_no_extras, | ||
1057 | Requirement("placeholder" + extras.lower()).extras, | ||
1058 | ) | ||
1059 | else: | ||
1060 | return package_name, url_no_extras, None | ||
1061 | |||
1062 | for version_control in vcs: | ||
1063 | if url.lower().startswith('%s:' % version_control): | ||
1064 | url = '%s+%s' % (version_control, url) | ||
1065 | break | ||
1066 | |||
1067 | if '+' not in url: | ||
1068 | raise InstallationError( | ||
1069 | '%s should either be a path to a local project or a VCS url ' | ||
1070 | 'beginning with svn+, git+, hg+, or bzr+' % | ||
1071 | editable_req | ||
1072 | ) | ||
1073 | |||
1074 | vc_type = url.split('+', 1)[0].lower() | ||
1075 | |||
1076 | if not vcs.get_backend(vc_type): | ||
1077 | error_message = 'For --editable=%s only ' % editable_req + \ | ||
1078 | ', '.join([backend.name + '+URL' for backend in vcs.backends]) + \ | ||
1079 | ' is currently supported' | ||
1080 | raise InstallationError(error_message) | ||
1081 | |||
1082 | package_name = Link(url).egg_fragment | ||
1083 | if not package_name: | ||
1084 | raise InstallationError( | ||
1085 | "Could not detect requirement name for '%s', please specify one " | ||
1086 | "with #egg=your_package_name" % editable_req | ||
1087 | ) | ||
1088 | return _strip_postfix(package_name), url, None | ||
1089 | |||
1090 | |||
1091 | def deduce_helpful_msg(req): | ||
1092 | """Returns helpful msg in case requirements file does not exist, | ||
1093 | or cannot be parsed. | ||
1094 | |||
1095 | :params req: Requirements file path | ||
1096 | """ | ||
1097 | msg = "" | ||
1098 | if os.path.exists(req): | ||
1099 | msg = " It does exist." | ||
1100 | # Try to parse and check if it is a requirements file. | ||
1101 | try: | ||
1102 | with open(req, 'r') as fp: | ||
1103 | # parse first line only | ||
1104 | next(parse_requirements(fp.read())) | ||
1105 | msg += " The argument you provided " + \ | ||
1106 | "(%s) appears to be a" % (req) + \ | ||
1107 | " requirements file. If that is the" + \ | ||
1108 | " case, use the '-r' flag to install" + \ | ||
1109 | " the packages specified within it." | ||
1110 | except RequirementParseError: | ||
1111 | logger.debug("Cannot parse '%s' as requirements \ | ||
1112 | file" % (req), exc_info=1) | ||
1113 | else: | ||
1114 | msg += " File '%s' does not exist." % (req) | ||
1115 | return msg | ||