Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/pip/req/req_install.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author | bcclaywell |
---|---|
date | Mon, 12 Oct 2015 17:43:33 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d67268158946 |
---|---|
1 from __future__ import absolute_import | |
2 | |
3 import logging | |
4 import os | |
5 import re | |
6 import shutil | |
7 import sys | |
8 import tempfile | |
9 import warnings | |
10 import zipfile | |
11 | |
12 from distutils.util import change_root | |
13 from distutils import sysconfig | |
14 from email.parser import FeedParser | |
15 | |
16 from pip._vendor import pkg_resources, six | |
17 from pip._vendor.distlib.markers import interpret as markers_interpret | |
18 from pip._vendor.six.moves import configparser | |
19 | |
20 import pip.wheel | |
21 | |
22 from pip.compat import native_str, WINDOWS | |
23 from pip.download import is_url, url_to_path, path_to_url, is_archive_file | |
24 from pip.exceptions import ( | |
25 InstallationError, UninstallationError, UnsupportedWheel, | |
26 ) | |
27 from pip.locations import ( | |
28 bin_py, running_under_virtualenv, PIP_DELETE_MARKER_FILENAME, bin_user, | |
29 ) | |
30 from pip.utils import ( | |
31 display_path, rmtree, ask_path_exists, backup_dir, is_installable_dir, | |
32 dist_in_usersite, dist_in_site_packages, egg_link_path, make_path_relative, | |
33 call_subprocess, read_text_file, FakeFile, _make_build_dir, | |
34 ) | |
35 from pip.utils.deprecation import RemovedInPip7Warning, RemovedInPip8Warning | |
36 from pip.utils.logging import indent_log | |
37 from pip.req.req_uninstall import UninstallPathSet | |
38 from pip.vcs import vcs | |
39 from pip.wheel import move_wheel_files, Wheel | |
40 from pip._vendor.packaging.version import Version | |
41 | |
42 | |
43 _FILTER_INSTALL_OUTPUT_REGEX = re.compile(r""" | |
44 (?:^running\s.*) | | |
45 (?:^writing\s.*) | | |
46 (?:creating\s.*) | | |
47 (?:[Cc]opying\s.*) | | |
48 (?:^reading\s.*') | | |
49 (?:^removing\s.*\.egg-info'\s\(and\severything\sunder\sit\)$) | | |
50 (?:^byte-compiling) | | |
51 (?:^SyntaxError:) | | |
52 (?:^SyntaxWarning:) | | |
53 (?:^\s*Skipping\simplicit\sfixer:) | | |
54 (?:^\s*(warning:\s)?no\spreviously-included\s(files|directories)) | | |
55 (?:^\s*warning:\sno\sfiles\sfound matching\s\'.*\') | | |
56 (?:^\s*changing\smode\sof) | | |
57 # Not sure what this warning is, but it seems harmless: | |
58 (?:^warning:\smanifest_maker:\sstandard\sfile\s'-c'\snot found$) | |
59 """, re.VERBOSE) | |
60 | |
61 | |
62 logger = logging.getLogger(__name__) | |
63 | |
64 | |
65 def _filter_install(line): | |
66 level = logging.INFO | |
67 if _FILTER_INSTALL_OUTPUT_REGEX.search(line.strip()): | |
68 level = logging.DEBUG | |
69 return (level, line) | |
70 | |
71 | |
72 class InstallRequirement(object): | |
73 | |
74 def __init__(self, req, comes_from, source_dir=None, editable=False, | |
75 link=None, as_egg=False, update=True, editable_options=None, | |
76 pycompile=True, markers=None, isolated=False): | |
77 self.extras = () | |
78 if isinstance(req, six.string_types): | |
79 req = pkg_resources.Requirement.parse(req) | |
80 self.extras = req.extras | |
81 | |
82 self.req = req | |
83 self.comes_from = comes_from | |
84 self.source_dir = source_dir | |
85 self.editable = editable | |
86 | |
87 if editable_options is None: | |
88 editable_options = {} | |
89 | |
90 self.editable_options = editable_options | |
91 self.link = link | |
92 self.as_egg = as_egg | |
93 self.markers = markers | |
94 self._egg_info_path = None | |
95 # This holds the pkg_resources.Distribution object if this requirement | |
96 # is already available: | |
97 self.satisfied_by = None | |
98 # This hold the pkg_resources.Distribution object if this requirement | |
99 # conflicts with another installed distribution: | |
100 self.conflicts_with = None | |
101 # Temporary build location | |
102 self._temp_build_dir = None | |
103 # Used to store the global directory where the _temp_build_dir should | |
104 # have been created. Cf _correct_build_location method. | |
105 self._ideal_global_dir = None | |
106 # True if the editable should be updated: | |
107 self.update = update | |
108 # Set to True after successful installation | |
109 self.install_succeeded = None | |
110 # UninstallPathSet of uninstalled distribution (for possible rollback) | |
111 self.uninstalled = None | |
112 self.use_user_site = False | |
113 self.target_dir = None | |
114 | |
115 self.pycompile = pycompile | |
116 | |
117 self.isolated = isolated | |
118 | |
119 @property | |
120 def url(self): | |
121 warnings.warn( | |
122 "The InstallRequirement.url attribute has been removed and should " | |
123 "not be used. It was temporary left here as a shim for projects " | |
124 "which used it even though it was not a public API.", | |
125 RemovedInPip7Warning, | |
126 ) | |
127 | |
128 return self.link.url | |
129 | |
130 @classmethod | |
131 def from_editable(cls, editable_req, comes_from=None, default_vcs=None, | |
132 isolated=False): | |
133 from pip.index import Link | |
134 | |
135 name, url, extras_override, editable_options = parse_editable( | |
136 editable_req, default_vcs) | |
137 if url.startswith('file:'): | |
138 source_dir = url_to_path(url) | |
139 else: | |
140 source_dir = None | |
141 | |
142 res = cls(name, comes_from, source_dir=source_dir, | |
143 editable=True, | |
144 link=Link(url), | |
145 editable_options=editable_options, | |
146 isolated=isolated) | |
147 | |
148 if extras_override is not None: | |
149 res.extras = extras_override | |
150 | |
151 return res | |
152 | |
153 @classmethod | |
154 def from_line(cls, name, comes_from=None, isolated=False): | |
155 """Creates an InstallRequirement from a name, which might be a | |
156 requirement, directory containing 'setup.py', filename, or URL. | |
157 """ | |
158 from pip.index import Link | |
159 | |
160 if is_url(name): | |
161 marker_sep = '; ' | |
162 else: | |
163 marker_sep = ';' | |
164 if marker_sep in name: | |
165 name, markers = name.split(marker_sep, 1) | |
166 markers = markers.strip() | |
167 if not markers: | |
168 markers = None | |
169 else: | |
170 markers = None | |
171 name = name.strip() | |
172 req = None | |
173 path = os.path.normpath(os.path.abspath(name)) | |
174 link = None | |
175 | |
176 if is_url(name): | |
177 link = Link(name) | |
178 elif (os.path.isdir(path) and | |
179 (os.path.sep in name or name.startswith('.'))): | |
180 if not is_installable_dir(path): | |
181 raise InstallationError( | |
182 "Directory %r is not installable. File 'setup.py' not " | |
183 "found." % name | |
184 ) | |
185 link = Link(path_to_url(name)) | |
186 elif is_archive_file(path): | |
187 if not os.path.isfile(path): | |
188 logger.warning( | |
189 'Requirement %r looks like a filename, but the file does ' | |
190 'not exist', | |
191 name | |
192 ) | |
193 link = Link(path_to_url(name)) | |
194 | |
195 # it's a local file, dir, or url | |
196 if link: | |
197 # Handle relative file URLs | |
198 if link.scheme == 'file' and re.search(r'\.\./', link.url): | |
199 link = Link( | |
200 path_to_url(os.path.normpath(os.path.abspath(link.path)))) | |
201 # wheel file | |
202 if link.is_wheel: | |
203 wheel = Wheel(link.filename) # can raise InvalidWheelFilename | |
204 if not wheel.supported(): | |
205 raise UnsupportedWheel( | |
206 "%s is not a supported wheel on this platform." % | |
207 wheel.filename | |
208 ) | |
209 req = "%s==%s" % (wheel.name, wheel.version) | |
210 else: | |
211 # set the req to the egg fragment. when it's not there, this | |
212 # will become an 'unnamed' requirement | |
213 req = link.egg_fragment | |
214 | |
215 # a requirement specifier | |
216 else: | |
217 req = name | |
218 | |
219 return cls(req, comes_from, link=link, markers=markers, | |
220 isolated=isolated) | |
221 | |
222 def __str__(self): | |
223 if self.req: | |
224 s = str(self.req) | |
225 if self.link: | |
226 s += ' from %s' % self.link.url | |
227 else: | |
228 s = self.link.url if self.link else None | |
229 if self.satisfied_by is not None: | |
230 s += ' in %s' % display_path(self.satisfied_by.location) | |
231 if self.comes_from: | |
232 if isinstance(self.comes_from, six.string_types): | |
233 comes_from = self.comes_from | |
234 else: | |
235 comes_from = self.comes_from.from_path() | |
236 if comes_from: | |
237 s += ' (from %s)' % comes_from | |
238 return s | |
239 | |
240 def __repr__(self): | |
241 return '<%s object: %s editable=%r>' % ( | |
242 self.__class__.__name__, str(self), self.editable) | |
243 | |
244 def populate_link(self, finder, upgrade): | |
245 """Ensure that if a link can be found for this, that it is found. | |
246 | |
247 Note that self.link may still be None - if Upgrade is False and the | |
248 requirement is already installed. | |
249 """ | |
250 if self.link is None: | |
251 self.link = finder.find_requirement(self, upgrade) | |
252 | |
253 @property | |
254 def specifier(self): | |
255 return self.req.specifier | |
256 | |
257 def from_path(self): | |
258 if self.req is None: | |
259 return None | |
260 s = str(self.req) | |
261 if self.comes_from: | |
262 if isinstance(self.comes_from, six.string_types): | |
263 comes_from = self.comes_from | |
264 else: | |
265 comes_from = self.comes_from.from_path() | |
266 if comes_from: | |
267 s += '->' + comes_from | |
268 return s | |
269 | |
270 def build_location(self, build_dir): | |
271 if self._temp_build_dir is not None: | |
272 return self._temp_build_dir | |
273 if self.req is None: | |
274 # for requirement via a path to a directory: the name of the | |
275 # package is not available yet so we create a temp directory | |
276 # Once run_egg_info will have run, we'll be able | |
277 # to fix it via _correct_build_location | |
278 self._temp_build_dir = tempfile.mkdtemp('-build', 'pip-') | |
279 self._ideal_build_dir = build_dir | |
280 return self._temp_build_dir | |
281 if self.editable: | |
282 name = self.name.lower() | |
283 else: | |
284 name = self.name | |
285 # FIXME: Is there a better place to create the build_dir? (hg and bzr | |
286 # need this) | |
287 if not os.path.exists(build_dir): | |
288 logger.debug('Creating directory %s', build_dir) | |
289 _make_build_dir(build_dir) | |
290 return os.path.join(build_dir, name) | |
291 | |
292 def _correct_build_location(self): | |
293 """Move self._temp_build_dir to self._ideal_build_dir/self.req.name | |
294 | |
295 For some requirements (e.g. a path to a directory), the name of the | |
296 package is not available until we run egg_info, so the build_location | |
297 will return a temporary directory and store the _ideal_build_dir. | |
298 | |
299 This is only called by self.egg_info_path to fix the temporary build | |
300 directory. | |
301 """ | |
302 if self.source_dir is not None: | |
303 return | |
304 assert self.req is not None | |
305 assert self._temp_build_dir | |
306 assert self._ideal_build_dir | |
307 old_location = self._temp_build_dir | |
308 self._temp_build_dir = None | |
309 new_location = self.build_location(self._ideal_build_dir) | |
310 if os.path.exists(new_location): | |
311 raise InstallationError( | |
312 'A package already exists in %s; please remove it to continue' | |
313 % display_path(new_location)) | |
314 logger.debug( | |
315 'Moving package %s from %s to new location %s', | |
316 self, display_path(old_location), display_path(new_location), | |
317 ) | |
318 shutil.move(old_location, new_location) | |
319 self._temp_build_dir = new_location | |
320 self._ideal_build_dir = None | |
321 self.source_dir = new_location | |
322 self._egg_info_path = None | |
323 | |
324 @property | |
325 def name(self): | |
326 if self.req is None: | |
327 return None | |
328 return native_str(self.req.project_name) | |
329 | |
330 @property | |
331 def setup_py(self): | |
332 assert self.source_dir, "No source dir for %s" % self | |
333 try: | |
334 import setuptools # noqa | |
335 except ImportError: | |
336 # Setuptools is not available | |
337 raise InstallationError( | |
338 "setuptools must be installed to install from a source " | |
339 "distribution" | |
340 ) | |
341 | |
342 setup_file = 'setup.py' | |
343 | |
344 if self.editable_options and 'subdirectory' in self.editable_options: | |
345 setup_py = os.path.join(self.source_dir, | |
346 self.editable_options['subdirectory'], | |
347 setup_file) | |
348 | |
349 else: | |
350 setup_py = os.path.join(self.source_dir, setup_file) | |
351 | |
352 # Python2 __file__ should not be unicode | |
353 if six.PY2 and isinstance(setup_py, six.text_type): | |
354 setup_py = setup_py.encode(sys.getfilesystemencoding()) | |
355 | |
356 return setup_py | |
357 | |
358 def run_egg_info(self): | |
359 assert self.source_dir | |
360 if self.name: | |
361 logger.debug( | |
362 'Running setup.py (path:%s) egg_info for package %s', | |
363 self.setup_py, self.name, | |
364 ) | |
365 else: | |
366 logger.debug( | |
367 'Running setup.py (path:%s) egg_info for package from %s', | |
368 self.setup_py, self.link, | |
369 ) | |
370 | |
371 with indent_log(): | |
372 # if it's distribute>=0.7, it won't contain an importable | |
373 # setuptools, and having an egg-info dir blocks the ability of | |
374 # setup.py to find setuptools plugins, so delete the egg-info dir | |
375 # if no setuptools. it will get recreated by the run of egg_info | |
376 # NOTE: this self.name check only works when installing from a | |
377 # specifier (not archive path/urls) | |
378 # TODO: take this out later | |
379 if (self.name == 'distribute' and not | |
380 os.path.isdir( | |
381 os.path.join(self.source_dir, 'setuptools'))): | |
382 rmtree(os.path.join(self.source_dir, 'distribute.egg-info')) | |
383 | |
384 script = self._run_setup_py | |
385 script = script.replace('__SETUP_PY__', repr(self.setup_py)) | |
386 script = script.replace('__PKG_NAME__', repr(self.name)) | |
387 base_cmd = [sys.executable, '-c', script] | |
388 if self.isolated: | |
389 base_cmd += ["--no-user-cfg"] | |
390 egg_info_cmd = base_cmd + ['egg_info'] | |
391 # We can't put the .egg-info files at the root, because then the | |
392 # source code will be mistaken for an installed egg, causing | |
393 # problems | |
394 if self.editable: | |
395 egg_base_option = [] | |
396 else: | |
397 egg_info_dir = os.path.join(self.source_dir, 'pip-egg-info') | |
398 if not os.path.exists(egg_info_dir): | |
399 os.makedirs(egg_info_dir) | |
400 egg_base_option = ['--egg-base', 'pip-egg-info'] | |
401 cwd = self.source_dir | |
402 if self.editable_options and \ | |
403 'subdirectory' in self.editable_options: | |
404 cwd = os.path.join(cwd, self.editable_options['subdirectory']) | |
405 call_subprocess( | |
406 egg_info_cmd + egg_base_option, | |
407 cwd=cwd, | |
408 filter_stdout=_filter_install, | |
409 show_stdout=False, | |
410 command_level=logging.DEBUG, | |
411 command_desc='python setup.py egg_info') | |
412 | |
413 if not self.req: | |
414 if isinstance( | |
415 pkg_resources.parse_version(self.pkg_info()["Version"]), | |
416 Version): | |
417 op = "==" | |
418 else: | |
419 op = "===" | |
420 self.req = pkg_resources.Requirement.parse( | |
421 "".join([ | |
422 self.pkg_info()["Name"], | |
423 op, | |
424 self.pkg_info()["Version"], | |
425 ])) | |
426 self._correct_build_location() | |
427 | |
428 # FIXME: This is a lame hack, entirely for PasteScript which has | |
429 # a self-provided entry point that causes this awkwardness | |
430 _run_setup_py = """ | |
431 __file__ = __SETUP_PY__ | |
432 from setuptools.command import egg_info | |
433 import pkg_resources | |
434 import os | |
435 import tokenize | |
436 def replacement_run(self): | |
437 self.mkpath(self.egg_info) | |
438 installer = self.distribution.fetch_build_egg | |
439 for ep in pkg_resources.iter_entry_points('egg_info.writers'): | |
440 # require=False is the change we're making: | |
441 writer = ep.load(require=False) | |
442 if writer: | |
443 writer(self, ep.name, os.path.join(self.egg_info,ep.name)) | |
444 self.find_sources() | |
445 egg_info.egg_info.run = replacement_run | |
446 exec(compile( | |
447 getattr(tokenize, 'open', open)(__file__).read().replace('\\r\\n', '\\n'), | |
448 __file__, | |
449 'exec' | |
450 )) | |
451 """ | |
452 | |
453 def egg_info_data(self, filename): | |
454 if self.satisfied_by is not None: | |
455 if not self.satisfied_by.has_metadata(filename): | |
456 return None | |
457 return self.satisfied_by.get_metadata(filename) | |
458 assert self.source_dir | |
459 filename = self.egg_info_path(filename) | |
460 if not os.path.exists(filename): | |
461 return None | |
462 data = read_text_file(filename) | |
463 return data | |
464 | |
465 def egg_info_path(self, filename): | |
466 if self._egg_info_path is None: | |
467 if self.editable: | |
468 base = self.source_dir | |
469 else: | |
470 base = os.path.join(self.source_dir, 'pip-egg-info') | |
471 filenames = os.listdir(base) | |
472 if self.editable: | |
473 filenames = [] | |
474 for root, dirs, files in os.walk(base): | |
475 for dir in vcs.dirnames: | |
476 if dir in dirs: | |
477 dirs.remove(dir) | |
478 # Iterate over a copy of ``dirs``, since mutating | |
479 # a list while iterating over it can cause trouble. | |
480 # (See https://github.com/pypa/pip/pull/462.) | |
481 for dir in list(dirs): | |
482 # Don't search in anything that looks like a virtualenv | |
483 # environment | |
484 if ( | |
485 os.path.exists( | |
486 os.path.join(root, dir, 'bin', 'python') | |
487 ) or | |
488 os.path.exists( | |
489 os.path.join( | |
490 root, dir, 'Scripts', 'Python.exe' | |
491 ) | |
492 )): | |
493 dirs.remove(dir) | |
494 # Also don't search through tests | |
495 elif dir == 'test' or dir == 'tests': | |
496 dirs.remove(dir) | |
497 filenames.extend([os.path.join(root, dir) | |
498 for dir in dirs]) | |
499 filenames = [f for f in filenames if f.endswith('.egg-info')] | |
500 | |
501 if not filenames: | |
502 raise InstallationError( | |
503 'No files/directories in %s (from %s)' % (base, filename) | |
504 ) | |
505 assert filenames, \ | |
506 "No files/directories in %s (from %s)" % (base, filename) | |
507 | |
508 # if we have more than one match, we pick the toplevel one. This | |
509 # can easily be the case if there is a dist folder which contains | |
510 # an extracted tarball for testing purposes. | |
511 if len(filenames) > 1: | |
512 filenames.sort( | |
513 key=lambda x: x.count(os.path.sep) + | |
514 (os.path.altsep and x.count(os.path.altsep) or 0) | |
515 ) | |
516 self._egg_info_path = os.path.join(base, filenames[0]) | |
517 return os.path.join(self._egg_info_path, filename) | |
518 | |
519 def pkg_info(self): | |
520 p = FeedParser() | |
521 data = self.egg_info_data('PKG-INFO') | |
522 if not data: | |
523 logger.warning( | |
524 'No PKG-INFO file found in %s', | |
525 display_path(self.egg_info_path('PKG-INFO')), | |
526 ) | |
527 p.feed(data or '') | |
528 return p.close() | |
529 | |
530 _requirements_section_re = re.compile(r'\[(.*?)\]') | |
531 | |
532 @property | |
533 def installed_version(self): | |
534 # Create a requirement that we'll look for inside of setuptools. | |
535 req = pkg_resources.Requirement.parse(self.name) | |
536 | |
537 # We want to avoid having this cached, so we need to construct a new | |
538 # working set each time. | |
539 working_set = pkg_resources.WorkingSet() | |
540 | |
541 # Get the installed distribution from our working set | |
542 dist = working_set.find(req) | |
543 | |
544 # Check to see if we got an installed distribution or not, if we did | |
545 # we want to return it's version. | |
546 if dist: | |
547 return dist.version | |
548 | |
549 def assert_source_matches_version(self): | |
550 assert self.source_dir | |
551 version = self.pkg_info()['version'] | |
552 if version not in self.req: | |
553 logger.warning( | |
554 'Requested %s, but installing version %s', | |
555 self, | |
556 self.installed_version, | |
557 ) | |
558 else: | |
559 logger.debug( | |
560 'Source in %s has version %s, which satisfies requirement %s', | |
561 display_path(self.source_dir), | |
562 version, | |
563 self, | |
564 ) | |
565 | |
566 def update_editable(self, obtain=True): | |
567 if not self.link: | |
568 logger.debug( | |
569 "Cannot update repository at %s; repository location is " | |
570 "unknown", | |
571 self.source_dir, | |
572 ) | |
573 return | |
574 assert self.editable | |
575 assert self.source_dir | |
576 if self.link.scheme == 'file': | |
577 # Static paths don't get updated | |
578 return | |
579 assert '+' in self.link.url, "bad url: %r" % self.link.url | |
580 if not self.update: | |
581 return | |
582 vc_type, url = self.link.url.split('+', 1) | |
583 backend = vcs.get_backend(vc_type) | |
584 if backend: | |
585 vcs_backend = backend(self.link.url) | |
586 if obtain: | |
587 vcs_backend.obtain(self.source_dir) | |
588 else: | |
589 vcs_backend.export(self.source_dir) | |
590 else: | |
591 assert 0, ( | |
592 'Unexpected version control type (in %s): %s' | |
593 % (self.link, vc_type)) | |
594 | |
595 def uninstall(self, auto_confirm=False): | |
596 """ | |
597 Uninstall the distribution currently satisfying this requirement. | |
598 | |
599 Prompts before removing or modifying files unless | |
600 ``auto_confirm`` is True. | |
601 | |
602 Refuses to delete or modify files outside of ``sys.prefix`` - | |
603 thus uninstallation within a virtual environment can only | |
604 modify that virtual environment, even if the virtualenv is | |
605 linked to global site-packages. | |
606 | |
607 """ | |
608 if not self.check_if_exists(): | |
609 raise UninstallationError( | |
610 "Cannot uninstall requirement %s, not installed" % (self.name,) | |
611 ) | |
612 dist = self.satisfied_by or self.conflicts_with | |
613 | |
614 paths_to_remove = UninstallPathSet(dist) | |
615 develop_egg_link = egg_link_path(dist) | |
616 develop_egg_link_egg_info = '{0}.egg-info'.format( | |
617 pkg_resources.to_filename(dist.project_name)) | |
618 egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info) | |
619 # Special case for distutils installed package | |
620 distutils_egg_info = getattr(dist._provider, 'path', None) | |
621 | |
622 # Uninstall cases order do matter as in the case of 2 installs of the | |
623 # same package, pip needs to uninstall the currently detected version | |
624 if (egg_info_exists and dist.egg_info.endswith('.egg-info') and | |
625 not dist.egg_info.endswith(develop_egg_link_egg_info)): | |
626 # if dist.egg_info.endswith(develop_egg_link_egg_info), we | |
627 # are in fact in the develop_egg_link case | |
628 paths_to_remove.add(dist.egg_info) | |
629 if dist.has_metadata('installed-files.txt'): | |
630 for installed_file in dist.get_metadata( | |
631 'installed-files.txt').splitlines(): | |
632 path = os.path.normpath( | |
633 os.path.join(dist.egg_info, installed_file) | |
634 ) | |
635 paths_to_remove.add(path) | |
636 # FIXME: need a test for this elif block | |
637 # occurs with --single-version-externally-managed/--record outside | |
638 # of pip | |
639 elif dist.has_metadata('top_level.txt'): | |
640 if dist.has_metadata('namespace_packages.txt'): | |
641 namespaces = dist.get_metadata('namespace_packages.txt') | |
642 else: | |
643 namespaces = [] | |
644 for top_level_pkg in [ | |
645 p for p | |
646 in dist.get_metadata('top_level.txt').splitlines() | |
647 if p and p not in namespaces]: | |
648 path = os.path.join(dist.location, top_level_pkg) | |
649 paths_to_remove.add(path) | |
650 paths_to_remove.add(path + '.py') | |
651 paths_to_remove.add(path + '.pyc') | |
652 | |
653 elif distutils_egg_info: | |
654 warnings.warn( | |
655 "Uninstalling a distutils installed project ({0}) has been " | |
656 "deprecated and will be removed in a future version. This is " | |
657 "due to the fact that uninstalling a distutils project will " | |
658 "only partially uninstall the project.".format(self.name), | |
659 RemovedInPip8Warning, | |
660 ) | |
661 paths_to_remove.add(distutils_egg_info) | |
662 | |
663 elif dist.location.endswith('.egg'): | |
664 # package installed by easy_install | |
665 # We cannot match on dist.egg_name because it can slightly vary | |
666 # i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg | |
667 paths_to_remove.add(dist.location) | |
668 easy_install_egg = os.path.split(dist.location)[1] | |
669 easy_install_pth = os.path.join(os.path.dirname(dist.location), | |
670 'easy-install.pth') | |
671 paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg) | |
672 | |
673 elif develop_egg_link: | |
674 # develop egg | |
675 with open(develop_egg_link, 'r') as fh: | |
676 link_pointer = os.path.normcase(fh.readline().strip()) | |
677 assert (link_pointer == dist.location), ( | |
678 'Egg-link %s does not match installed location of %s ' | |
679 '(at %s)' % (link_pointer, self.name, dist.location) | |
680 ) | |
681 paths_to_remove.add(develop_egg_link) | |
682 easy_install_pth = os.path.join(os.path.dirname(develop_egg_link), | |
683 'easy-install.pth') | |
684 paths_to_remove.add_pth(easy_install_pth, dist.location) | |
685 | |
686 elif egg_info_exists and dist.egg_info.endswith('.dist-info'): | |
687 for path in pip.wheel.uninstallation_paths(dist): | |
688 paths_to_remove.add(path) | |
689 | |
690 else: | |
691 logger.debug( | |
692 'Not sure how to uninstall: %s - Check: %s', | |
693 dist, dist.location) | |
694 | |
695 # find distutils scripts= scripts | |
696 if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'): | |
697 for script in dist.metadata_listdir('scripts'): | |
698 if dist_in_usersite(dist): | |
699 bin_dir = bin_user | |
700 else: | |
701 bin_dir = bin_py | |
702 paths_to_remove.add(os.path.join(bin_dir, script)) | |
703 if WINDOWS: | |
704 paths_to_remove.add(os.path.join(bin_dir, script) + '.bat') | |
705 | |
706 # find console_scripts | |
707 if dist.has_metadata('entry_points.txt'): | |
708 config = configparser.SafeConfigParser() | |
709 config.readfp( | |
710 FakeFile(dist.get_metadata_lines('entry_points.txt')) | |
711 ) | |
712 if config.has_section('console_scripts'): | |
713 for name, value in config.items('console_scripts'): | |
714 if dist_in_usersite(dist): | |
715 bin_dir = bin_user | |
716 else: | |
717 bin_dir = bin_py | |
718 paths_to_remove.add(os.path.join(bin_dir, name)) | |
719 if WINDOWS: | |
720 paths_to_remove.add( | |
721 os.path.join(bin_dir, name) + '.exe' | |
722 ) | |
723 paths_to_remove.add( | |
724 os.path.join(bin_dir, name) + '.exe.manifest' | |
725 ) | |
726 paths_to_remove.add( | |
727 os.path.join(bin_dir, name) + '-script.py' | |
728 ) | |
729 | |
730 paths_to_remove.remove(auto_confirm) | |
731 self.uninstalled = paths_to_remove | |
732 | |
733 def rollback_uninstall(self): | |
734 if self.uninstalled: | |
735 self.uninstalled.rollback() | |
736 else: | |
737 logger.error( | |
738 "Can't rollback %s, nothing uninstalled.", self.project_name, | |
739 ) | |
740 | |
741 def commit_uninstall(self): | |
742 if self.uninstalled: | |
743 self.uninstalled.commit() | |
744 else: | |
745 logger.error( | |
746 "Can't commit %s, nothing uninstalled.", self.project_name, | |
747 ) | |
748 | |
749 def archive(self, build_dir): | |
750 assert self.source_dir | |
751 create_archive = True | |
752 archive_name = '%s-%s.zip' % (self.name, self.pkg_info()["version"]) | |
753 archive_path = os.path.join(build_dir, archive_name) | |
754 if os.path.exists(archive_path): | |
755 response = ask_path_exists( | |
756 'The file %s exists. (i)gnore, (w)ipe, (b)ackup ' % | |
757 display_path(archive_path), ('i', 'w', 'b')) | |
758 if response == 'i': | |
759 create_archive = False | |
760 elif response == 'w': | |
761 logger.warning('Deleting %s', display_path(archive_path)) | |
762 os.remove(archive_path) | |
763 elif response == 'b': | |
764 dest_file = backup_dir(archive_path) | |
765 logger.warning( | |
766 'Backing up %s to %s', | |
767 display_path(archive_path), | |
768 display_path(dest_file), | |
769 ) | |
770 shutil.move(archive_path, dest_file) | |
771 if create_archive: | |
772 zip = zipfile.ZipFile( | |
773 archive_path, 'w', zipfile.ZIP_DEFLATED, | |
774 allowZip64=True | |
775 ) | |
776 dir = os.path.normcase(os.path.abspath(self.source_dir)) | |
777 for dirpath, dirnames, filenames in os.walk(dir): | |
778 if 'pip-egg-info' in dirnames: | |
779 dirnames.remove('pip-egg-info') | |
780 for dirname in dirnames: | |
781 dirname = os.path.join(dirpath, dirname) | |
782 name = self._clean_zip_name(dirname, dir) | |
783 zipdir = zipfile.ZipInfo(self.name + '/' + name + '/') | |
784 zipdir.external_attr = 0x1ED << 16 # 0o755 | |
785 zip.writestr(zipdir, '') | |
786 for filename in filenames: | |
787 if filename == PIP_DELETE_MARKER_FILENAME: | |
788 continue | |
789 filename = os.path.join(dirpath, filename) | |
790 name = self._clean_zip_name(filename, dir) | |
791 zip.write(filename, self.name + '/' + name) | |
792 zip.close() | |
793 logger.info('Saved %s', display_path(archive_path)) | |
794 | |
795 def _clean_zip_name(self, name, prefix): | |
796 assert name.startswith(prefix + os.path.sep), ( | |
797 "name %r doesn't start with prefix %r" % (name, prefix) | |
798 ) | |
799 name = name[len(prefix) + 1:] | |
800 name = name.replace(os.path.sep, '/') | |
801 return name | |
802 | |
803 def match_markers(self): | |
804 if self.markers is not None: | |
805 return markers_interpret(self.markers) | |
806 else: | |
807 return True | |
808 | |
809 def install(self, install_options, global_options=(), root=None): | |
810 if self.editable: | |
811 self.install_editable(install_options, global_options) | |
812 return | |
813 if self.is_wheel: | |
814 version = pip.wheel.wheel_version(self.source_dir) | |
815 pip.wheel.check_compatibility(version, self.name) | |
816 | |
817 self.move_wheel_files(self.source_dir, root=root) | |
818 self.install_succeeded = True | |
819 return | |
820 | |
821 if self.isolated: | |
822 global_options = list(global_options) + ["--no-user-cfg"] | |
823 | |
824 temp_location = tempfile.mkdtemp('-record', 'pip-') | |
825 record_filename = os.path.join(temp_location, 'install-record.txt') | |
826 try: | |
827 install_args = [sys.executable] | |
828 install_args.append('-c') | |
829 install_args.append( | |
830 "import setuptools, tokenize;__file__=%r;" | |
831 "exec(compile(getattr(tokenize, 'open', open)(__file__).read()" | |
832 ".replace('\\r\\n', '\\n'), __file__, 'exec'))" % self.setup_py | |
833 ) | |
834 install_args += list(global_options) + \ | |
835 ['install', '--record', record_filename] | |
836 | |
837 if not self.as_egg: | |
838 install_args += ['--single-version-externally-managed'] | |
839 | |
840 if root is not None: | |
841 install_args += ['--root', root] | |
842 | |
843 if self.pycompile: | |
844 install_args += ["--compile"] | |
845 else: | |
846 install_args += ["--no-compile"] | |
847 | |
848 if running_under_virtualenv(): | |
849 py_ver_str = 'python' + sysconfig.get_python_version() | |
850 install_args += ['--install-headers', | |
851 os.path.join(sys.prefix, 'include', 'site', | |
852 py_ver_str, self.name)] | |
853 logger.info('Running setup.py install for %s', self.name) | |
854 with indent_log(): | |
855 call_subprocess( | |
856 install_args + install_options, | |
857 cwd=self.source_dir, | |
858 filter_stdout=_filter_install, | |
859 show_stdout=False, | |
860 ) | |
861 | |
862 if not os.path.exists(record_filename): | |
863 logger.debug('Record file %s not found', record_filename) | |
864 return | |
865 self.install_succeeded = True | |
866 if self.as_egg: | |
867 # there's no --always-unzip option we can pass to install | |
868 # command so we unable to save the installed-files.txt | |
869 return | |
870 | |
871 def prepend_root(path): | |
872 if root is None or not os.path.isabs(path): | |
873 return path | |
874 else: | |
875 return change_root(root, path) | |
876 | |
877 with open(record_filename) as f: | |
878 for line in f: | |
879 directory = os.path.dirname(line) | |
880 if directory.endswith('.egg-info'): | |
881 egg_info_dir = prepend_root(directory) | |
882 break | |
883 else: | |
884 logger.warning( | |
885 'Could not find .egg-info directory in install record' | |
886 ' for %s', | |
887 self, | |
888 ) | |
889 # FIXME: put the record somewhere | |
890 # FIXME: should this be an error? | |
891 return | |
892 new_lines = [] | |
893 with open(record_filename) as f: | |
894 for line in f: | |
895 filename = line.strip() | |
896 if os.path.isdir(filename): | |
897 filename += os.path.sep | |
898 new_lines.append( | |
899 make_path_relative( | |
900 prepend_root(filename), egg_info_dir) | |
901 ) | |
902 inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt') | |
903 with open(inst_files_path, 'w') as f: | |
904 f.write('\n'.join(new_lines) + '\n') | |
905 finally: | |
906 if os.path.exists(record_filename): | |
907 os.remove(record_filename) | |
908 rmtree(temp_location) | |
909 | |
910 def ensure_has_source_dir(self, parent_dir): | |
911 """Ensure that a source_dir is set. | |
912 | |
913 This will create a temporary build dir if the name of the requirement | |
914 isn't known yet. | |
915 | |
916 :param parent_dir: The ideal pip parent_dir for the source_dir. | |
917 Generally src_dir for editables and build_dir for sdists. | |
918 :return: self.source_dir | |
919 """ | |
920 if self.source_dir is None: | |
921 self.source_dir = self.build_location(parent_dir) | |
922 return self.source_dir | |
923 | |
924 def remove_temporary_source(self): | |
925 """Remove the source files from this requirement, if they are marked | |
926 for deletion""" | |
927 if self.source_dir and os.path.exists( | |
928 os.path.join(self.source_dir, PIP_DELETE_MARKER_FILENAME)): | |
929 logger.debug('Removing source in %s', self.source_dir) | |
930 rmtree(self.source_dir) | |
931 self.source_dir = None | |
932 if self._temp_build_dir and os.path.exists(self._temp_build_dir): | |
933 rmtree(self._temp_build_dir) | |
934 self._temp_build_dir = None | |
935 | |
936 def install_editable(self, install_options, global_options=()): | |
937 logger.info('Running setup.py develop for %s', self.name) | |
938 | |
939 if self.isolated: | |
940 global_options = list(global_options) + ["--no-user-cfg"] | |
941 | |
942 with indent_log(): | |
943 # FIXME: should we do --install-headers here too? | |
944 cwd = self.source_dir | |
945 if self.editable_options and \ | |
946 'subdirectory' in self.editable_options: | |
947 cwd = os.path.join(cwd, self.editable_options['subdirectory']) | |
948 call_subprocess( | |
949 [ | |
950 sys.executable, | |
951 '-c', | |
952 "import setuptools, tokenize; __file__=%r; exec(compile(" | |
953 "getattr(tokenize, 'open', open)(__file__).read().replace" | |
954 "('\\r\\n', '\\n'), __file__, 'exec'))" % self.setup_py | |
955 ] + | |
956 list(global_options) + | |
957 ['develop', '--no-deps'] + | |
958 list(install_options), | |
959 | |
960 cwd=cwd, filter_stdout=self._filter_install, | |
961 show_stdout=False) | |
962 | |
963 self.install_succeeded = True | |
964 | |
965 def _filter_install(self, line): | |
966 return _filter_install(line) | |
967 | |
968 def check_if_exists(self): | |
969 """Find an installed distribution that satisfies or conflicts | |
970 with this requirement, and set self.satisfied_by or | |
971 self.conflicts_with appropriately. | |
972 """ | |
973 if self.req is None: | |
974 return False | |
975 try: | |
976 # DISTRIBUTE TO SETUPTOOLS UPGRADE HACK (1 of 3 parts) | |
977 # if we've already set distribute as a conflict to setuptools | |
978 # then this check has already run before. we don't want it to | |
979 # run again, and return False, since it would block the uninstall | |
980 # TODO: remove this later | |
981 if (self.req.project_name == 'setuptools' and | |
982 self.conflicts_with and | |
983 self.conflicts_with.project_name == 'distribute'): | |
984 return True | |
985 else: | |
986 self.satisfied_by = pkg_resources.get_distribution(self.req) | |
987 except pkg_resources.DistributionNotFound: | |
988 return False | |
989 except pkg_resources.VersionConflict: | |
990 existing_dist = pkg_resources.get_distribution( | |
991 self.req.project_name | |
992 ) | |
993 if self.use_user_site: | |
994 if dist_in_usersite(existing_dist): | |
995 self.conflicts_with = existing_dist | |
996 elif (running_under_virtualenv() and | |
997 dist_in_site_packages(existing_dist)): | |
998 raise InstallationError( | |
999 "Will not install to the user site because it will " | |
1000 "lack sys.path precedence to %s in %s" % | |
1001 (existing_dist.project_name, existing_dist.location) | |
1002 ) | |
1003 else: | |
1004 self.conflicts_with = existing_dist | |
1005 return True | |
1006 | |
1007 @property | |
1008 def is_wheel(self): | |
1009 return self.link and self.link.is_wheel | |
1010 | |
1011 def move_wheel_files(self, wheeldir, root=None): | |
1012 move_wheel_files( | |
1013 self.name, self.req, wheeldir, | |
1014 user=self.use_user_site, | |
1015 home=self.target_dir, | |
1016 root=root, | |
1017 pycompile=self.pycompile, | |
1018 isolated=self.isolated, | |
1019 ) | |
1020 | |
1021 def get_dist(self): | |
1022 """Return a pkg_resources.Distribution built from self.egg_info_path""" | |
1023 egg_info = self.egg_info_path('').rstrip('/') | |
1024 base_dir = os.path.dirname(egg_info) | |
1025 metadata = pkg_resources.PathMetadata(base_dir, egg_info) | |
1026 dist_name = os.path.splitext(os.path.basename(egg_info))[0] | |
1027 return pkg_resources.Distribution( | |
1028 os.path.dirname(egg_info), | |
1029 project_name=dist_name, | |
1030 metadata=metadata) | |
1031 | |
1032 | |
1033 def _strip_postfix(req): | |
1034 """ | |
1035 Strip req postfix ( -dev, 0.2, etc ) | |
1036 """ | |
1037 # FIXME: use package_to_requirement? | |
1038 match = re.search(r'^(.*?)(?:-dev|-\d.*)$', req) | |
1039 if match: | |
1040 # Strip off -dev, -0.2, etc. | |
1041 req = match.group(1) | |
1042 return req | |
1043 | |
1044 | |
1045 def _build_req_from_url(url): | |
1046 | |
1047 parts = [p for p in url.split('#', 1)[0].split('/') if p] | |
1048 | |
1049 req = None | |
1050 if parts[-2] in ('tags', 'branches', 'tag', 'branch'): | |
1051 req = parts[-3] | |
1052 elif parts[-1] == 'trunk': | |
1053 req = parts[-2] | |
1054 return req | |
1055 | |
1056 | |
1057 def _build_editable_options(req): | |
1058 | |
1059 """ | |
1060 This method generates a dictionary of the query string | |
1061 parameters contained in a given editable URL. | |
1062 """ | |
1063 regexp = re.compile(r"[\?#&](?P<name>[^&=]+)=(?P<value>[^&=]+)") | |
1064 matched = regexp.findall(req) | |
1065 | |
1066 if matched: | |
1067 ret = dict() | |
1068 for option in matched: | |
1069 (name, value) = option | |
1070 if name in ret: | |
1071 raise Exception("%s option already defined" % name) | |
1072 ret[name] = value | |
1073 return ret | |
1074 return None | |
1075 | |
1076 | |
1077 def parse_editable(editable_req, default_vcs=None): | |
1078 """Parses an editable requirement into: | |
1079 - a requirement name | |
1080 - an URL | |
1081 - extras | |
1082 - editable options | |
1083 Accepted requirements: | |
1084 svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir | |
1085 .[some_extra] | |
1086 """ | |
1087 | |
1088 url = editable_req | |
1089 extras = None | |
1090 | |
1091 # If a file path is specified with extras, strip off the extras. | |
1092 m = re.match(r'^(.+)(\[[^\]]+\])$', url) | |
1093 if m: | |
1094 url_no_extras = m.group(1) | |
1095 extras = m.group(2) | |
1096 else: | |
1097 url_no_extras = url | |
1098 | |
1099 if os.path.isdir(url_no_extras): | |
1100 if not os.path.exists(os.path.join(url_no_extras, 'setup.py')): | |
1101 raise InstallationError( | |
1102 "Directory %r is not installable. File 'setup.py' not found." % | |
1103 url_no_extras | |
1104 ) | |
1105 # Treating it as code that has already been checked out | |
1106 url_no_extras = path_to_url(url_no_extras) | |
1107 | |
1108 if url_no_extras.lower().startswith('file:'): | |
1109 if extras: | |
1110 return ( | |
1111 None, | |
1112 url_no_extras, | |
1113 pkg_resources.Requirement.parse( | |
1114 '__placeholder__' + extras | |
1115 ).extras, | |
1116 {}, | |
1117 ) | |
1118 else: | |
1119 return None, url_no_extras, None, {} | |
1120 | |
1121 for version_control in vcs: | |
1122 if url.lower().startswith('%s:' % version_control): | |
1123 url = '%s+%s' % (version_control, url) | |
1124 break | |
1125 | |
1126 if '+' not in url: | |
1127 if default_vcs: | |
1128 url = default_vcs + '+' + url | |
1129 else: | |
1130 raise InstallationError( | |
1131 '%s should either be a path to a local project or a VCS url ' | |
1132 'beginning with svn+, git+, hg+, or bzr+' % | |
1133 editable_req | |
1134 ) | |
1135 | |
1136 vc_type = url.split('+', 1)[0].lower() | |
1137 | |
1138 if not vcs.get_backend(vc_type): | |
1139 error_message = 'For --editable=%s only ' % editable_req + \ | |
1140 ', '.join([backend.name + '+URL' for backend in vcs.backends]) + \ | |
1141 ' is currently supported' | |
1142 raise InstallationError(error_message) | |
1143 | |
1144 try: | |
1145 options = _build_editable_options(editable_req) | |
1146 except Exception as exc: | |
1147 raise InstallationError( | |
1148 '--editable=%s error in editable options:%s' % (editable_req, exc) | |
1149 ) | |
1150 if not options or 'egg' not in options: | |
1151 req = _build_req_from_url(editable_req) | |
1152 if not req: | |
1153 raise InstallationError( | |
1154 '--editable=%s is not the right format; it must have ' | |
1155 '#egg=Package' % editable_req | |
1156 ) | |
1157 else: | |
1158 req = options['egg'] | |
1159 | |
1160 package = _strip_postfix(req) | |
1161 return package, url, None, options |