PageRenderTime 93ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/pip.py

https://bitbucket.org/kozo2/pip
Python | 4728 lines | 4467 code | 132 blank | 129 comment | 456 complexity | 6d82f4e0a9f55aac4a78ae8a9e2c3553 MD5 | raw file
  1. #!/usr/bin/env python
  2. import sys
  3. import os
  4. import errno
  5. import stat
  6. import optparse
  7. import pkg_resources
  8. import urllib2
  9. import urllib
  10. import mimetypes
  11. import zipfile
  12. import tarfile
  13. import tempfile
  14. import subprocess
  15. import posixpath
  16. import re
  17. import shutil
  18. import fnmatch
  19. import operator
  20. import copy
  21. try:
  22. from hashlib import md5
  23. except ImportError:
  24. import md5 as md5_module
  25. md5 = md5_module.new
  26. import urlparse
  27. from email.FeedParser import FeedParser
  28. import traceback
  29. from cStringIO import StringIO
  30. import socket
  31. from Queue import Queue
  32. from Queue import Empty as QueueEmpty
  33. import threading
  34. import httplib
  35. import time
  36. import logging
  37. import ConfigParser
  38. from distutils.util import strtobool
  39. from distutils import sysconfig
  40. class InstallationError(Exception):
  41. """General exception during installation"""
  42. class UninstallationError(Exception):
  43. """General exception during uninstallation"""
  44. class DistributionNotFound(InstallationError):
  45. """Raised when a distribution cannot be found to satisfy a requirement"""
  46. class BadCommand(Exception):
  47. """Raised when virtualenv or a command is not found"""
  48. try:
  49. any
  50. except NameError:
  51. def any(seq):
  52. for item in seq:
  53. if item:
  54. return True
  55. return False
  56. if getattr(sys, 'real_prefix', None):
  57. ## FIXME: is build/ a good name?
  58. build_prefix = os.path.join(sys.prefix, 'build')
  59. src_prefix = os.path.join(sys.prefix, 'src')
  60. else:
  61. ## FIXME: this isn't a very good default
  62. build_prefix = os.path.join(os.getcwd(), 'build')
  63. src_prefix = os.path.join(os.getcwd(), 'src')
  64. # FIXME doesn't account for venv linked to global site-packages
  65. site_packages = sysconfig.get_python_lib()
  66. if sys.platform == 'win32':
  67. bin_py = os.path.join(sys.prefix, 'Scripts')
  68. # buildout uses 'bin' on Windows too?
  69. if not os.path.exists(bin_py):
  70. bin_py = os.path.join(sys.prefix, 'bin')
  71. config_filename = 'pip.cfg'
  72. else:
  73. bin_py = os.path.join(sys.prefix, 'bin')
  74. config_filename = '.pip.cfg'
  75. # Forcing to use /usr/local/bin for standard Mac OS X framework installs
  76. if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/':
  77. bin_py = '/usr/local/bin'
  78. class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter):
  79. def expand_default(self, option):
  80. if self.parser is not None:
  81. self.parser.update_defaults(self.parser.defaults)
  82. return optparse.IndentedHelpFormatter.expand_default(self, option)
  83. class ConfigOptionParser(optparse.OptionParser):
  84. """Custom option parser which updates its defaults by by checking the
  85. configuration files and environmental variables"""
  86. def __init__(self, *args, **kwargs):
  87. self.config = ConfigParser.RawConfigParser()
  88. self.name = kwargs.pop('name')
  89. self.files = self.get_config_files()
  90. self.config.read(self.files)
  91. assert self.name
  92. optparse.OptionParser.__init__(self, *args, **kwargs)
  93. def get_config_files(self):
  94. config_file = os.environ.get('PIP_CONFIG_FILE', False)
  95. if config_file and os.path.exists(config_file):
  96. return [config_file]
  97. # FIXME: add ~/.python/pip.cfg or whatever Python core decides here
  98. return [os.path.join(os.path.expanduser('~'), config_filename)]
  99. def update_defaults(self, defaults):
  100. """Updates the given defaults with values from the config files and
  101. the environ. Does a little special handling for certain types of
  102. options (lists)."""
  103. # Then go and look for the other sources of configuration:
  104. config = {}
  105. # 1. config files
  106. for section in ('global', self.name):
  107. config.update(dict(self.get_config_section(section)))
  108. # 2. environmental variables
  109. config.update(dict(self.get_environ_vars()))
  110. # Then set the options with those values
  111. for key, val in config.iteritems():
  112. key = key.replace('_', '-')
  113. if not key.startswith('--'):
  114. key = '--%s' % key # only prefer long opts
  115. option = self.get_option(key)
  116. if option is not None:
  117. # ignore empty values
  118. if not val:
  119. continue
  120. # handle multiline configs
  121. if option.action == 'append':
  122. val = val.split()
  123. else:
  124. option.nargs = 1
  125. if option.action in ('store_true', 'store_false', 'count'):
  126. val = strtobool(val)
  127. try:
  128. val = option.convert_value(key, val)
  129. except optparse.OptionValueError, e:
  130. print ("An error occured during configuration: %s" % e)
  131. sys.exit(3)
  132. defaults[option.dest] = val
  133. return defaults
  134. def get_config_section(self, name):
  135. """Get a section of a configuration"""
  136. if self.config.has_section(name):
  137. return self.config.items(name)
  138. return []
  139. def get_environ_vars(self, prefix='PIP_'):
  140. """Returns a generator with all environmental vars with prefix PIP_"""
  141. for key, val in os.environ.iteritems():
  142. if key.startswith(prefix):
  143. yield (key.replace(prefix, '').lower(), val)
  144. def get_default_values(self):
  145. """Overridding to make updating the defaults after instantiation of
  146. the option parser possible, update_defaults() does the dirty work."""
  147. if not self.process_default_values:
  148. # Old, pre-Optik 1.5 behaviour.
  149. return optparse.Values(self.defaults)
  150. defaults = self.update_defaults(self.defaults.copy()) # ours
  151. for option in self._get_all_options():
  152. default = defaults.get(option.dest)
  153. if isinstance(default, basestring):
  154. opt_str = option.get_opt_string()
  155. defaults[option.dest] = option.check_value(opt_str, default)
  156. return optparse.Values(defaults)
  157. try:
  158. pip_dist = pkg_resources.get_distribution('pip')
  159. version = '%s from %s (python %s)' % (
  160. pip_dist, pip_dist.location, sys.version[:3])
  161. except pkg_resources.DistributionNotFound:
  162. # when running pip.py without installing
  163. version=None
  164. def rmtree_errorhandler(func, path, exc_info):
  165. """On Windows, the files in .svn are read-only, so when rmtree() tries to
  166. remove them, an exception is thrown. We catch that here, remove the
  167. read-only attribute, and hopefully continue without problems."""
  168. exctype, value = exc_info[:2]
  169. # lookin for a windows error
  170. if exctype is not WindowsError or 'Access is denied' not in str(value):
  171. raise
  172. # file type should currently be read only
  173. if ((os.stat(path).st_mode & stat.S_IREAD) != stat.S_IREAD):
  174. raise
  175. # convert to read/write
  176. os.chmod(path, stat.S_IWRITE)
  177. # use the original function to repeat the operation
  178. func(path)
  179. class VcsSupport(object):
  180. _registry = {}
  181. schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp']
  182. def __init__(self):
  183. # Register more schemes with urlparse for various version control systems
  184. urlparse.uses_netloc.extend(self.schemes)
  185. urlparse.uses_fragment.extend(self.schemes)
  186. super(VcsSupport, self).__init__()
  187. def __iter__(self):
  188. return self._registry.__iter__()
  189. @property
  190. def backends(self):
  191. return self._registry.values()
  192. @property
  193. def dirnames(self):
  194. return [backend.dirname for backend in self.backends]
  195. @property
  196. def all_schemes(self):
  197. schemes = []
  198. for backend in self.backends:
  199. schemes.extend(backend.schemes)
  200. return schemes
  201. def register(self, cls):
  202. if not hasattr(cls, 'name'):
  203. logger.warn('Cannot register VCS %s' % cls.__name__)
  204. return
  205. if cls.name not in self._registry:
  206. self._registry[cls.name] = cls
  207. def unregister(self, cls=None, name=None):
  208. if name in self._registry:
  209. del self._registry[name]
  210. elif cls in self._registry.values():
  211. del self._registry[cls.name]
  212. else:
  213. logger.warn('Cannot unregister because no class or name given')
  214. def get_backend_name(self, location):
  215. """
  216. Return the name of the version control backend if found at given
  217. location, e.g. vcs.get_backend_name('/path/to/vcs/checkout')
  218. """
  219. for vc_type in self._registry.values():
  220. path = os.path.join(location, vc_type.dirname)
  221. if os.path.exists(path):
  222. return vc_type.name
  223. return None
  224. def get_backend(self, name):
  225. name = name.lower()
  226. if name in self._registry:
  227. return self._registry[name]
  228. def get_backend_from_location(self, location):
  229. vc_type = self.get_backend_name(location)
  230. if vc_type:
  231. return self.get_backend(vc_type)
  232. return None
  233. vcs = VcsSupport()
  234. parser = ConfigOptionParser(
  235. usage='%prog COMMAND [OPTIONS]',
  236. version=version,
  237. add_help_option=False,
  238. formatter=UpdatingDefaultsHelpFormatter(),
  239. name='global')
  240. parser.add_option(
  241. '-h', '--help',
  242. dest='help',
  243. action='store_true',
  244. help='Show help')
  245. parser.add_option(
  246. '-E', '--environment',
  247. dest='venv',
  248. metavar='DIR',
  249. help='virtualenv environment to run pip in (either give the '
  250. 'interpreter or the environment base directory)')
  251. parser.add_option(
  252. '-s', '--enable-site-packages',
  253. dest='site_packages',
  254. action='store_true',
  255. help='Include site-packages in virtualenv if one is to be '
  256. 'created. Ignored if --environment is not used or '
  257. 'the virtualenv already exists.')
  258. parser.add_option(
  259. # Defines a default root directory for virtualenvs, relative
  260. # virtualenvs names/paths are considered relative to it.
  261. '--virtualenv-base',
  262. dest='venv_base',
  263. type='str',
  264. default='',
  265. help=optparse.SUPPRESS_HELP)
  266. parser.add_option(
  267. # Run only if inside a virtualenv, bail if not.
  268. '--require-virtualenv', '--require-venv',
  269. dest='require_venv',
  270. action='store_true',
  271. default=False,
  272. help=optparse.SUPPRESS_HELP)
  273. parser.add_option(
  274. # Use automatically an activated virtualenv instead of installing
  275. # globally. -E will be ignored if used.
  276. '--respect-virtualenv', '--respect-venv',
  277. dest='respect_venv',
  278. action='store_true',
  279. default=False,
  280. help=optparse.SUPPRESS_HELP)
  281. parser.add_option(
  282. '-v', '--verbose',
  283. dest='verbose',
  284. action='count',
  285. default=0,
  286. help='Give more output')
  287. parser.add_option(
  288. '-q', '--quiet',
  289. dest='quiet',
  290. action='count',
  291. default=0,
  292. help='Give less output')
  293. parser.add_option(
  294. '--log',
  295. dest='log',
  296. metavar='FILENAME',
  297. help='Log file where a complete (maximum verbosity) record will be kept')
  298. parser.add_option(
  299. # Writes the log levels explicitely to the log'
  300. '--log-explicit-levels',
  301. dest='log_explicit_levels',
  302. action='store_true',
  303. default=False,
  304. help=optparse.SUPPRESS_HELP)
  305. parser.add_option(
  306. # The default log file
  307. '--local-log', '--log-file',
  308. dest='log_file',
  309. metavar='FILENAME',
  310. default='./pip-log.txt',
  311. help=optparse.SUPPRESS_HELP)
  312. parser.add_option(
  313. '--proxy',
  314. dest='proxy',
  315. type='str',
  316. default='',
  317. help="Specify a proxy in the form user:passwd@proxy.server:port. "
  318. "Note that the user:password@ is optional and required only if you "
  319. "are behind an authenticated proxy. If you provide "
  320. "user@proxy.server:port then you will be prompted for a password.")
  321. parser.add_option(
  322. '--timeout', '--default-timeout',
  323. metavar='SECONDS',
  324. dest='timeout',
  325. type='float',
  326. default=15,
  327. help='Set the socket timeout (default %default seconds)')
  328. parser.add_option(
  329. # The default version control system for editables, e.g. 'svn'
  330. '--default-vcs',
  331. dest='default_vcs',
  332. type='str',
  333. default='',
  334. help=optparse.SUPPRESS_HELP)
  335. parser.add_option(
  336. # A regex to be used to skip requirements
  337. '--skip-requirements-regex',
  338. dest='skip_requirements_regex',
  339. type='str',
  340. default='',
  341. help=optparse.SUPPRESS_HELP)
  342. parser.disable_interspersed_args()
  343. _commands = {}
  344. class Command(object):
  345. name = None
  346. usage = None
  347. def __init__(self):
  348. assert self.name
  349. self.parser = ConfigOptionParser(
  350. usage=self.usage,
  351. prog='%s %s' % (sys.argv[0], self.name),
  352. version=parser.version,
  353. formatter=UpdatingDefaultsHelpFormatter(),
  354. name=self.name)
  355. for option in parser.option_list:
  356. if not option.dest or option.dest == 'help':
  357. # -h, --version, etc
  358. continue
  359. self.parser.add_option(option)
  360. _commands[self.name] = self
  361. def merge_options(self, initial_options, options):
  362. # Make sure we have all global options carried over
  363. for attr in ['log', 'venv', 'proxy', 'venv_base', 'require_venv',
  364. 'respect_venv', 'log_explicit_levels', 'log_file',
  365. 'timeout', 'default_vcs', 'skip_requirements_regex']:
  366. setattr(options, attr, getattr(initial_options, attr) or getattr(options, attr))
  367. options.quiet += initial_options.quiet
  368. options.verbose += initial_options.verbose
  369. def main(self, complete_args, args, initial_options):
  370. global logger
  371. options, args = self.parser.parse_args(args)
  372. self.merge_options(initial_options, options)
  373. if options.require_venv and not options.venv:
  374. # If a venv is required check if it can really be found
  375. if not os.environ.get('VIRTUAL_ENV'):
  376. print 'Could not find an activated virtualenv (required).'
  377. sys.exit(3)
  378. # Automatically install in currently activated venv if required
  379. options.respect_venv = True
  380. if args and args[-1] == '___VENV_RESTART___':
  381. ## FIXME: We don't do anything this this value yet:
  382. venv_location = args[-2]
  383. args = args[:-2]
  384. options.venv = None
  385. else:
  386. # If given the option to respect the activated environment
  387. # check if no venv is given as a command line parameter
  388. if options.respect_venv and os.environ.get('VIRTUAL_ENV'):
  389. if options.venv and os.path.exists(options.venv):
  390. # Make sure command line venv and environmental are the same
  391. if (os.path.realpath(os.path.expanduser(options.venv)) !=
  392. os.path.realpath(os.environ.get('VIRTUAL_ENV'))):
  393. print ("Given virtualenv (%s) doesn't match "
  394. "currently activated virtualenv (%s)."
  395. % (options.venv, os.environ.get('VIRTUAL_ENV')))
  396. sys.exit(3)
  397. else:
  398. options.venv = os.environ.get('VIRTUAL_ENV')
  399. print 'Using already activated environment %s' % options.venv
  400. level = 1 # Notify
  401. level += options.verbose
  402. level -= options.quiet
  403. level = Logger.level_for_integer(4-level)
  404. complete_log = []
  405. logger = Logger([(level, sys.stdout),
  406. (Logger.DEBUG, complete_log.append)])
  407. if options.log_explicit_levels:
  408. logger.explicit_levels = True
  409. if options.venv:
  410. if options.verbose > 0:
  411. # The logger isn't setup yet
  412. print 'Running in environment %s' % options.venv
  413. site_packages=False
  414. if options.site_packages:
  415. site_packages=True
  416. restart_in_venv(options.venv, options.venv_base, site_packages,
  417. complete_args)
  418. # restart_in_venv should actually never return, but for clarity...
  419. return
  420. ## FIXME: not sure if this sure come before or after venv restart
  421. if options.log:
  422. log_fp = open_logfile_append(options.log)
  423. logger.consumers.append((logger.DEBUG, log_fp))
  424. else:
  425. log_fp = None
  426. socket.setdefaulttimeout(options.timeout or None)
  427. setup_proxy_handler(options.proxy)
  428. exit = 0
  429. try:
  430. self.run(options, args)
  431. except (InstallationError, UninstallationError), e:
  432. logger.fatal(str(e))
  433. logger.info('Exception information:\n%s' % format_exc())
  434. exit = 1
  435. except:
  436. logger.fatal('Exception:\n%s' % format_exc())
  437. exit = 2
  438. if log_fp is not None:
  439. log_fp.close()
  440. if exit:
  441. log_fn = options.log_file
  442. text = '\n'.join(complete_log)
  443. logger.fatal('Storing complete log in %s' % log_fn)
  444. log_fp = open_logfile_append(log_fn)
  445. log_fp.write(text)
  446. log_fp.close()
  447. return exit
  448. class HelpCommand(Command):
  449. name = 'help'
  450. usage = '%prog'
  451. summary = 'Show available commands'
  452. def run(self, options, args):
  453. if args:
  454. ## FIXME: handle errors better here
  455. command = args[0]
  456. if command not in _commands:
  457. raise InstallationError('No command with the name: %s' % command)
  458. command = _commands[command]
  459. command.parser.print_help()
  460. return
  461. parser.print_help()
  462. print
  463. print 'Commands available:'
  464. commands = list(set(_commands.values()))
  465. commands.sort(key=lambda x: x.name)
  466. for command in commands:
  467. print ' %s: %s' % (command.name, command.summary)
  468. HelpCommand()
  469. class InstallCommand(Command):
  470. name = 'install'
  471. usage = '%prog [OPTIONS] PACKAGE_NAMES...'
  472. summary = 'Install packages'
  473. bundle = False
  474. def __init__(self):
  475. super(InstallCommand, self).__init__()
  476. self.parser.add_option(
  477. '-e', '--editable',
  478. dest='editables',
  479. action='append',
  480. default=[],
  481. metavar='VCS+REPOS_URL[@REV]#egg=PACKAGE',
  482. help='Install a package directly from a checkout. Source will be checked '
  483. 'out into src/PACKAGE (lower-case) and installed in-place (using '
  484. 'setup.py develop). You can run this on an existing directory/checkout (like '
  485. 'pip install -e src/mycheckout). This option may be provided multiple times. '
  486. 'Possible values for VCS are: svn, git, hg and bzr.')
  487. self.parser.add_option(
  488. '-r', '--requirement',
  489. dest='requirements',
  490. action='append',
  491. default=[],
  492. metavar='FILENAME',
  493. help='Install all the packages listed in the given requirements file. '
  494. 'This option can be used multiple times.')
  495. self.parser.add_option(
  496. '-f', '--find-links',
  497. dest='find_links',
  498. action='append',
  499. default=[],
  500. metavar='URL',
  501. help='URL to look for packages at')
  502. self.parser.add_option(
  503. '-i', '--index-url', '--pypi-url',
  504. dest='index_url',
  505. metavar='URL',
  506. default='http://pypi.python.org/simple',
  507. help='Base URL of Python Package Index (default %default)')
  508. self.parser.add_option(
  509. '--extra-index-url',
  510. dest='extra_index_urls',
  511. metavar='URL',
  512. action='append',
  513. default=[],
  514. help='Extra URLs of package indexes to use in addition to --index-url')
  515. self.parser.add_option(
  516. '--no-index',
  517. dest='no_index',
  518. action='store_true',
  519. default=False,
  520. help='Ignore package index (only looking at --find-links URLs instead)')
  521. self.parser.add_option(
  522. '-b', '--build', '--build-dir', '--build-directory',
  523. dest='build_dir',
  524. metavar='DIR',
  525. default=None,
  526. help='Unpack packages into DIR (default %s) and build from there' % build_prefix)
  527. self.parser.add_option(
  528. '-d', '--download', '--download-dir', '--download-directory',
  529. dest='download_dir',
  530. metavar='DIR',
  531. default=None,
  532. help='Download packages into DIR instead of installing them')
  533. self.parser.add_option(
  534. '--download-cache',
  535. dest='download_cache',
  536. metavar='DIR',
  537. default=None,
  538. help='Cache downloaded packages in DIR')
  539. self.parser.add_option(
  540. '--src', '--source', '--source-dir', '--source-directory',
  541. dest='src_dir',
  542. metavar='DIR',
  543. default=None,
  544. help='Check out --editable packages into DIR (default %s)' % src_prefix)
  545. self.parser.add_option(
  546. '-U', '--upgrade',
  547. dest='upgrade',
  548. action='store_true',
  549. help='Upgrade all packages to the newest available version')
  550. self.parser.add_option(
  551. '-I', '--ignore-installed',
  552. dest='ignore_installed',
  553. action='store_true',
  554. help='Ignore the installed packages (reinstalling instead)')
  555. self.parser.add_option(
  556. '--no-deps', '--no-dependencies',
  557. dest='ignore_dependencies',
  558. action='store_true',
  559. default=False,
  560. help='Ignore package dependencies')
  561. self.parser.add_option(
  562. '--no-install',
  563. dest='no_install',
  564. action='store_true',
  565. help="Download and unpack all packages, but don't actually install them")
  566. self.parser.add_option(
  567. '--install-option',
  568. dest='install_options',
  569. action='append',
  570. help="Extra arguments to be supplied to the setup.py install "
  571. "command (use like --install-option=\"--install-scripts=/usr/local/bin\"). "
  572. "Use multiple --install-option options to pass multiple options to setup.py install. "
  573. "If you are using an option with a directory path, be sure to use absolute path.")
  574. def run(self, options, args):
  575. if not options.build_dir:
  576. options.build_dir = build_prefix
  577. if not options.src_dir:
  578. options.src_dir = src_prefix
  579. if options.download_dir:
  580. options.no_install = True
  581. options.ignore_installed = True
  582. else:
  583. options.build_dir = os.path.abspath(options.build_dir)
  584. options.src_dir = os.path.abspath(options.src_dir)
  585. install_options = options.install_options or []
  586. index_urls = [options.index_url] + options.extra_index_urls
  587. if options.no_index:
  588. logger.notify('Ignoring indexes: %s' % ','.join(index_urls))
  589. index_urls = []
  590. finder = PackageFinder(
  591. find_links=options.find_links,
  592. index_urls=index_urls)
  593. requirement_set = RequirementSet(
  594. build_dir=options.build_dir,
  595. src_dir=options.src_dir,
  596. download_dir=options.download_dir,
  597. download_cache=options.download_cache,
  598. upgrade=options.upgrade,
  599. ignore_installed=options.ignore_installed,
  600. ignore_dependencies=options.ignore_dependencies)
  601. for name in args:
  602. requirement_set.add_requirement(
  603. InstallRequirement.from_line(name, None))
  604. for name in options.editables:
  605. requirement_set.add_requirement(
  606. InstallRequirement.from_editable(name, default_vcs=options.default_vcs))
  607. for filename in options.requirements:
  608. for req in parse_requirements(filename, finder=finder, options=options):
  609. requirement_set.add_requirement(req)
  610. requirement_set.install_files(finder, force_root_egg_info=self.bundle)
  611. if not options.no_install and not self.bundle:
  612. requirement_set.install(install_options)
  613. installed = ' '.join([req.name for req in
  614. requirement_set.successfully_installed])
  615. if installed:
  616. logger.notify('Successfully installed %s' % installed)
  617. elif not self.bundle:
  618. downloaded = ' '.join([req.name for req in
  619. requirement_set.successfully_downloaded])
  620. if downloaded:
  621. logger.notify('Successfully downloaded %s' % downloaded)
  622. return requirement_set
  623. InstallCommand()
  624. class UninstallCommand(Command):
  625. name = 'uninstall'
  626. usage = '%prog [OPTIONS] PACKAGE_NAMES ...'
  627. summary = 'Uninstall packages'
  628. def __init__(self):
  629. super(UninstallCommand, self).__init__()
  630. self.parser.add_option(
  631. '-r', '--requirement',
  632. dest='requirements',
  633. action='append',
  634. default=[],
  635. metavar='FILENAME',
  636. help='Uninstall all the packages listed in the given requirements file. '
  637. 'This option can be used multiple times.')
  638. self.parser.add_option(
  639. '-y', '--yes',
  640. dest='yes',
  641. action='store_true',
  642. help="Don't ask for confirmation of uninstall deletions.")
  643. def run(self, options, args):
  644. requirement_set = RequirementSet(
  645. build_dir=None,
  646. src_dir=None,
  647. download_dir=None)
  648. for name in args:
  649. requirement_set.add_requirement(
  650. InstallRequirement.from_line(name))
  651. for filename in options.requirements:
  652. for req in parse_requirements(filename, options=options):
  653. requirement_set.add_requirement(req)
  654. requirement_set.uninstall(auto_confirm=options.yes)
  655. UninstallCommand()
  656. class BundleCommand(InstallCommand):
  657. name = 'bundle'
  658. usage = '%prog [OPTIONS] BUNDLE_NAME.pybundle PACKAGE_NAMES...'
  659. summary = 'Create pybundles (archives containing multiple packages)'
  660. bundle = True
  661. def __init__(self):
  662. super(BundleCommand, self).__init__()
  663. def run(self, options, args):
  664. if not args:
  665. raise InstallationError('You must give a bundle filename')
  666. if not options.build_dir:
  667. options.build_dir = backup_dir(build_prefix, '-bundle')
  668. if not options.src_dir:
  669. options.src_dir = backup_dir(src_prefix, '-bundle')
  670. # We have to get everything when creating a bundle:
  671. options.ignore_installed = True
  672. logger.notify('Putting temporary build files in %s and source/develop files in %s'
  673. % (display_path(options.build_dir), display_path(options.src_dir)))
  674. bundle_filename = args[0]
  675. args = args[1:]
  676. requirement_set = super(BundleCommand, self).run(options, args)
  677. # FIXME: here it has to do something
  678. requirement_set.create_bundle(bundle_filename)
  679. logger.notify('Created bundle in %s' % bundle_filename)
  680. return requirement_set
  681. BundleCommand()
  682. class FreezeCommand(Command):
  683. name = 'freeze'
  684. usage = '%prog [OPTIONS]'
  685. summary = 'Output all currently installed packages (exact versions) to stdout'
  686. def __init__(self):
  687. super(FreezeCommand, self).__init__()
  688. self.parser.add_option(
  689. '-r', '--requirement',
  690. dest='requirement',
  691. action='store',
  692. default=None,
  693. metavar='FILENAME',
  694. help='Use the given requirements file as a hint about how to generate the new frozen requirements')
  695. self.parser.add_option(
  696. '-f', '--find-links',
  697. dest='find_links',
  698. action='append',
  699. default=[],
  700. metavar='URL',
  701. help='URL for finding packages, which will be added to the frozen requirements file')
  702. def run(self, options, args):
  703. requirement = options.requirement
  704. find_links = options.find_links or []
  705. ## FIXME: Obviously this should be settable:
  706. find_tags = False
  707. skip_match = None
  708. skip_regex = options.skip_requirements_regex
  709. if skip_regex:
  710. skip_match = re.compile(skip_regex)
  711. logger.move_stdout_to_stderr()
  712. dependency_links = []
  713. f = sys.stdout
  714. for dist in pkg_resources.working_set:
  715. if dist.has_metadata('dependency_links.txt'):
  716. dependency_links.extend(dist.get_metadata_lines('dependency_links.txt'))
  717. for link in find_links:
  718. if '#egg=' in link:
  719. dependency_links.append(link)
  720. for link in find_links:
  721. f.write('-f %s\n' % link)
  722. installations = {}
  723. for dist in pkg_resources.working_set:
  724. if dist.key in ('setuptools', 'pip', 'python'):
  725. ## FIXME: also skip virtualenv?
  726. continue
  727. req = FrozenRequirement.from_dist(dist, dependency_links, find_tags=find_tags)
  728. installations[req.name] = req
  729. if requirement:
  730. req_f = open(requirement)
  731. for line in req_f:
  732. if not line.strip() or line.strip().startswith('#'):
  733. f.write(line)
  734. continue
  735. if skip_match and skip_match.search(line):
  736. f.write(line)
  737. continue
  738. elif line.startswith('-e') or line.startswith('--editable'):
  739. if line.startswith('-e'):
  740. line = line[2:].strip()
  741. else:
  742. line = line[len('--editable'):].strip().lstrip('=')
  743. line_req = InstallRequirement.from_editable(line, default_vcs=options.default_vcs)
  744. elif (line.startswith('-r') or line.startswith('--requirement')
  745. or line.startswith('-Z') or line.startswith('--always-unzip')):
  746. logger.debug('Skipping line %r' % line.strip())
  747. continue
  748. else:
  749. line_req = InstallRequirement.from_line(line)
  750. if not line_req.name:
  751. logger.notify("Skipping line because it's not clear what it would install: %s"
  752. % line.strip())
  753. logger.notify(" (add #egg=PackageName to the URL to avoid this warning)")
  754. continue
  755. if line_req.name not in installations:
  756. logger.warn("Requirement file contains %s, but that package is not installed"
  757. % line.strip())
  758. continue
  759. f.write(str(installations[line_req.name]))
  760. del installations[line_req.name]
  761. f.write('## The following requirements were added by pip --freeze:\n')
  762. for installation in sorted(installations.values(), key=lambda x: x.name):
  763. f.write(str(installation))
  764. FreezeCommand()
  765. class ZipCommand(Command):
  766. name = 'zip'
  767. usage = '%prog [OPTIONS] PACKAGE_NAMES...'
  768. summary = 'Zip individual packages'
  769. def __init__(self):
  770. super(ZipCommand, self).__init__()
  771. if self.name == 'zip':
  772. self.parser.add_option(
  773. '--unzip',
  774. action='store_true',
  775. dest='unzip',
  776. help='Unzip (rather than zip) a package')
  777. else:
  778. self.parser.add_option(
  779. '--zip',
  780. action='store_false',
  781. dest='unzip',
  782. default=True,
  783. help='Zip (rather than unzip) a package')
  784. self.parser.add_option(
  785. '--no-pyc',
  786. action='store_true',
  787. dest='no_pyc',
  788. help='Do not include .pyc files in zip files (useful on Google App Engine)')
  789. self.parser.add_option(
  790. '-l', '--list',
  791. action='store_true',
  792. dest='list',
  793. help='List the packages available, and their zip status')
  794. self.parser.add_option(
  795. '--sort-files',
  796. action='store_true',
  797. dest='sort_files',
  798. help='With --list, sort packages according to how many files they contain')
  799. self.parser.add_option(
  800. '--path',
  801. action='append',
  802. dest='paths',
  803. help='Restrict operations to the given paths (may include wildcards)')
  804. self.parser.add_option(
  805. '-n', '--simulate',
  806. action='store_true',
  807. help='Do not actually perform the zip/unzip operation')
  808. def paths(self):
  809. """All the entries of sys.path, possibly restricted by --path"""
  810. if not self.select_paths:
  811. return sys.path
  812. result = []
  813. match_any = set()
  814. for path in sys.path:
  815. path = os.path.normcase(os.path.abspath(path))
  816. for match in self.select_paths:
  817. match = os.path.normcase(os.path.abspath(match))
  818. if '*' in match:
  819. if re.search(fnmatch.translate(match+'*'), path):
  820. result.append(path)
  821. match_any.add(match)
  822. break
  823. else:
  824. if path.startswith(match):
  825. result.append(path)
  826. match_any.add(match)
  827. break
  828. else:
  829. logger.debug("Skipping path %s because it doesn't match %s"
  830. % (path, ', '.join(self.select_paths)))
  831. for match in self.select_paths:
  832. if match not in match_any and '*' not in match:
  833. result.append(match)
  834. logger.debug("Adding path %s because it doesn't match anything already on sys.path"
  835. % match)
  836. return result
  837. def run(self, options, args):
  838. self.select_paths = options.paths
  839. self.simulate = options.simulate
  840. if options.list:
  841. return self.list(options, args)
  842. if not args:
  843. raise InstallationError(
  844. 'You must give at least one package to zip or unzip')
  845. packages = []
  846. for arg in args:
  847. module_name, filename = self.find_package(arg)
  848. if options.unzip and os.path.isdir(filename):
  849. raise InstallationError(
  850. 'The module %s (in %s) is not a zip file; cannot be unzipped'
  851. % (module_name, filename))
  852. elif not options.unzip and not os.path.isdir(filename):
  853. raise InstallationError(
  854. 'The module %s (in %s) is not a directory; cannot be zipped'
  855. % (module_name, filename))
  856. packages.append((module_name, filename))
  857. last_status = None
  858. for module_name, filename in packages:
  859. if options.unzip:
  860. last_status = self.unzip_package(module_name, filename)
  861. else:
  862. last_status = self.zip_package(module_name, filename, options.no_pyc)
  863. return last_status
  864. def unzip_package(self, module_name, filename):
  865. zip_filename = os.path.dirname(filename)
  866. if not os.path.isfile(zip_filename) and zipfile.is_zipfile(zip_filename):
  867. raise InstallationError(
  868. 'Module %s (in %s) isn\'t located in a zip file in %s'
  869. % (module_name, filename, zip_filename))
  870. package_path = os.path.dirname(zip_filename)
  871. if not package_path in self.paths():
  872. logger.warn(
  873. 'Unpacking %s into %s, but %s is not on sys.path'
  874. % (display_path(zip_filename), display_path(package_path),
  875. display_path(package_path)))
  876. logger.notify('Unzipping %s (in %s)' % (module_name, display_path(zip_filename)))
  877. if self.simulate:
  878. logger.notify('Skipping remaining operations because of --simulate')
  879. return
  880. logger.indent += 2
  881. try:
  882. ## FIXME: this should be undoable:
  883. zip = zipfile.ZipFile(zip_filename)
  884. to_save = []
  885. for name in zip.namelist():
  886. if name.startswith('%s/' % module_name):
  887. content = zip.read(name)
  888. dest = os.path.join(package_path, name)
  889. if not os.path.exists(os.path.dirname(dest)):
  890. os.makedirs(os.path.dirname(dest))
  891. if not content and dest.endswith('/'):
  892. if not os.path.exists(dest):
  893. os.makedirs(dest)
  894. else:
  895. f = open(dest, 'wb')
  896. f.write(content)
  897. f.close()
  898. else:
  899. to_save.append((name, zip.read(name)))
  900. zip.close()
  901. if not to_save:
  902. logger.info('Removing now-empty zip file %s' % display_path(zip_filename))
  903. os.unlink(zip_filename)
  904. self.remove_filename_from_pth(zip_filename)
  905. else:
  906. logger.info('Removing entries in %s/ from zip file %s' % (module_name, display_path(zip_filename)))
  907. zip = zipfile.ZipFile(zip_filename, 'w')
  908. for name, content in to_save:
  909. zip.writestr(name, content)
  910. zip.close()
  911. finally:
  912. logger.indent -= 2
  913. def zip_package(self, module_name, filename, no_pyc):
  914. orig_filename = filename
  915. logger.notify('Zip %s (in %s)' % (module_name, display_path(filename)))
  916. logger.indent += 2
  917. if filename.endswith('.egg'):
  918. dest_filename = filename
  919. else:
  920. dest_filename = filename + '.zip'
  921. try:
  922. ## FIXME: I think this needs to be undoable:
  923. if filename == dest_filename:
  924. filename = backup_dir(orig_filename)
  925. logger.notify('Moving %s aside to %s' % (orig_filename, filename))
  926. if not self.simulate:
  927. shutil.move(orig_filename, filename)
  928. try:
  929. logger.info('Creating zip file in %s' % display_path(dest_filename))
  930. if not self.simulate:
  931. zip = zipfile.ZipFile(dest_filename, 'w')
  932. zip.writestr(module_name + '/', '')
  933. for dirpath, dirnames, filenames in os.walk(filename):
  934. if no_pyc:
  935. filenames = [f for f in filenames
  936. if not f.lower().endswith('.pyc')]
  937. for fns, is_dir in [(dirnames, True), (filenames, False)]:
  938. for fn in fns:
  939. full = os.path.join(dirpath, fn)
  940. dest = os.path.join(module_name, dirpath[len(filename):].lstrip(os.path.sep), fn)
  941. if is_dir:
  942. zip.writestr(dest+'/', '')
  943. else:
  944. zip.write(full, dest)
  945. zip.close()
  946. logger.info('Removing old directory %s' % display_path(filename))
  947. if not self.simulate:
  948. shutil.rmtree(filename)
  949. except:
  950. ## FIXME: need to do an undo here
  951. raise
  952. ## FIXME: should also be undone:
  953. self.add_filename_to_pth(dest_filename)
  954. finally:
  955. logger.indent -= 2
  956. def remove_filename_from_pth(self, filename):
  957. for pth in self.pth_files():
  958. f = open(pth, 'r')
  959. lines = f.readlines()
  960. f.close()
  961. new_lines = [
  962. l for l in lines if l.strip() != filename]
  963. if lines != new_lines:
  964. logger.info('Removing reference to %s from .pth file %s'
  965. % (display_path(filename), display_path(pth)))
  966. if not filter(None, new_lines):
  967. logger.info('%s file would be empty: deleting' % display_path(pth))
  968. if not self.simulate:
  969. os.unlink(pth)
  970. else:
  971. if not self.simulate:
  972. f = open(pth, 'w')
  973. f.writelines(new_lines)
  974. f.close()
  975. return
  976. logger.warn('Cannot find a reference to %s in any .pth file' % display_path(filename))
  977. def add_filename_to_pth(self, filename):
  978. path = os.path.dirname(filename)
  979. dest = os.path.join(path, filename + '.pth')
  980. if path not in self.paths():
  981. logger.warn('Adding .pth file %s, but it is not on sys.path' % display_path(dest))
  982. if not self.simulate:
  983. if os.path.exists(dest):
  984. f = open(dest)
  985. lines = f.readlines()
  986. f.close()
  987. if lines and not lines[-1].endswith('\n'):
  988. lines[-1] += '\n'
  989. lines.append(filename+'\n')
  990. else:
  991. lines = [filename + '\n']
  992. f = open(dest, 'w')
  993. f.writelines(lines)
  994. f.close()
  995. def pth_files(self):
  996. for path in self.paths():
  997. if not os.path.exists(path) or not os.path.isdir(path):
  998. continue
  999. for filename in os.listdir(path):
  1000. if filename.endswith('.pth'):
  1001. yield os.path.join(path, filename)
  1002. def find_package(self, package):
  1003. for path in self.paths():
  1004. full = os.path.join(path, package)
  1005. if os.path.exists(full):
  1006. return package, full
  1007. if not os.path.isdir(path) and zipfile.is_zipfile(path):
  1008. zip = zipfile.ZipFile(path, 'r')
  1009. try:
  1010. zip.read('%s/__init__.py' % package)
  1011. except KeyError:
  1012. pass
  1013. else:
  1014. zip.close()
  1015. return package, full
  1016. zip.close()
  1017. ## FIXME: need special error for package.py case:
  1018. raise InstallationError(
  1019. 'No package with the name %s found' % package)
  1020. def list(self, options, args):
  1021. if args:
  1022. raise InstallationError(
  1023. 'You cannot give an argument with --list')
  1024. for path in sorted(self.paths()):
  1025. if not os.path.exists(path):
  1026. continue
  1027. basename = os.path.basename(path.rstrip(os.path.sep))
  1028. if os.path.isfile(path) and zipfile.is_zipfile(path):
  1029. if os.path.dirname(path) not in self.paths():
  1030. logger.notify('Zipped egg: %s' % display_path(path))
  1031. continue
  1032. if (basename != 'site-packages'
  1033. and not path.replace('\\', '/').endswith('lib/python')):
  1034. continue
  1035. logger.notify('In %s:' % display_path(path))
  1036. logger.indent += 2
  1037. zipped = []
  1038. unzipped = []
  1039. try:
  1040. for filename in sorted(os.listdir(path)):
  1041. ext = os.path.splitext(filename)[1].lower()
  1042. if ext in ('.pth', '.egg-info', '.egg-link'):
  1043. continue
  1044. if ext == '.py':
  1045. logger.info('Not displaying %s: not a package' % display_path(filename))
  1046. continue
  1047. full = os.path.join(path, filename)
  1048. if os.path.isdir(full):
  1049. unzipped.append((filename, self.count_package(full)))
  1050. elif zipfile.is_zipfile(full):
  1051. zipped.append(filename)
  1052. else:
  1053. logger.info('Unknown file: %s' % display_path(filename))
  1054. if zipped:
  1055. logger.notify('Zipped packages:')
  1056. logger.indent += 2
  1057. try:
  1058. for filename in zipped:
  1059. logger.notify(filename)
  1060. finally:
  1061. logger.indent -= 2
  1062. else:
  1063. logger.notify('No zipped packages.')
  1064. if unzipped:
  1065. if options.sort_files:
  1066. unzipped.sort(key=lambda x: -x[1])
  1067. logger.notify('Unzipped packages:')
  1068. logger.indent += 2
  1069. try:
  1070. for filename, count in unzipped:
  1071. logger.notify('%s (%i files)' % (filename, count))
  1072. finally:
  1073. logger.indent -= 2
  1074. else:
  1075. logger.notify('No unzipped packages.')
  1076. finally:
  1077. logger.indent -= 2
  1078. def count_package(self, path):
  1079. total = 0
  1080. for dirpath, dirnames, filenames in os.walk(path):
  1081. filenames = [f for f in filenames
  1082. if not f.lower().endswith('.pyc')]
  1083. total += len(filenames)
  1084. return total
  1085. ZipCommand()
  1086. class UnzipCommand(ZipCommand):
  1087. name = 'unzip'
  1088. summary = 'Unzip individual packages'
  1089. UnzipCommand()
  1090. def main(initial_args=None):
  1091. if initial_args is None:
  1092. initial_args = sys.argv[1:]
  1093. options, args = parser.parse_args(initial_args)
  1094. if options.help and not args:
  1095. args = ['help']
  1096. if not args:
  1097. parser.error('You must give a command (use "pip help" see a list of commands)')
  1098. command = args[0].lower()
  1099. ## FIXME: search for a command match?
  1100. if command not in _commands:
  1101. parser.error('No command by the name %(script)s %(arg)s\n (maybe you meant "%(script)s install %(arg)s")'
  1102. % dict(script=os.path.basename(sys.argv[0]), arg=command))
  1103. command = _commands[command]
  1104. return command.main(initial_args, args[1:], options)
  1105. def get_proxy(proxystr=''):
  1106. """Get the proxy given the option passed on the command line. If an
  1107. empty string is passed it looks at the HTTP_PROXY environment
  1108. variable."""
  1109. if not proxystr:
  1110. proxystr = os.environ.get('HTTP_PROXY', '')
  1111. if proxystr:
  1112. if '@' in proxystr:
  1113. user_password, server_port = proxystr.split('@', 1)
  1114. if ':' in user_password:
  1115. user, password = user_password.split(':', 1)
  1116. else:
  1117. user = user_password
  1118. import getpass
  1119. prompt = 'Password for %s@%s: ' % (user, server_port)
  1120. password = urllib.quote(getpass.getpass(prompt))
  1121. return '%s:%s@%s' % (user, password, server_port)
  1122. else:
  1123. return proxystr
  1124. else:
  1125. return None
  1126. def setup_proxy_handler(proxystr=''):
  1127. """Set the proxy handler given the option passed on the command
  1128. line. If an empty string is passed it looks at the HTTP_PROXY
  1129. environment variable. """
  1130. proxy = get_proxy(proxystr)
  1131. if proxy:
  1132. proxy_support = urllib2.ProxyHandler({"http": proxy, "ftp": proxy})
  1133. opener = urllib2.build_opener(proxy_support, urllib2.CacheFTPHandler)
  1134. urllib2.install_opener(opener)
  1135. def format_exc(exc_info=None):
  1136. if exc_info is None:
  1137. exc_info = sys.exc_info()
  1138. out = StringIO()
  1139. traceback.print_exception(*exc_info, **dict(file=out))
  1140. return out.getvalue()
  1141. def restart_in_venv(venv, base, site_packages, args):
  1142. """
  1143. Restart this script using the interpreter in the given virtual environment
  1144. """
  1145. if base and not os.path.isabs(venv) and not venv.startswith('~'):
  1146. base = os.path.expanduser(base)
  1147. # ensure we have an abs basepath at this point:
  1148. # a relative one makes no sense (or does it?)
  1149. if os.path.isabs(base):
  1150. venv = os.path.join(base, venv)
  1151. if venv.startswith('~'):
  1152. venv = os.path.expanduser(venv)
  1153. if not os.path.exists(venv):
  1154. try:
  1155. import virtualenv
  1156. except ImportError:
  1157. print 'The virtual environment does not exist: %s' % venv
  1158. print 'and virtualenv is not installed, so a new environment cannot be created'
  1159. sys.exit(3)
  1160. print 'Creating new virtualenv environment in %s' % venv
  1161. virtualenv.logger = logger
  1162. logger.indent += 2
  1163. virtualenv.create_environment(venv, site_packages=site_packages)
  1164. if sys.platform == 'win32':
  1165. python = os.path.join(venv, 'Scripts', 'python.exe')
  1166. # check for bin directory which is used in buildouts
  1167. if not os.path.exists(python):
  1168. python = os.path.join(venv, 'bin', 'python.exe')
  1169. else:
  1170. python = os.path.join(venv, 'bin', 'python')
  1171. if not os.path.exists(python):
  1172. python = venv
  1173. if not os.path.exists(python):
  1174. raise BadCommand('Cannot find virtual environment interpreter at %s' % python)
  1175. base = os.path.dirname(os.path.dirname(python))
  1176. file = __file__
  1177. if file.endswith('.pyc'):
  1178. file = file[:-1]
  1179. proc = subprocess.Popen(
  1180. [python, file] + args + [base, '___VENV_RESTART___'])
  1181. proc.wait()
  1182. sys.exit(proc.returncode)
  1183. class PackageFinder(object):
  1184. """This finds packages.
  1185. This is meant to match easy_install's technique for looking for
  1186. packages, by reading pages and looking for appropriate links
  1187. """
  1188. failure_limit = 3
  1189. def __init__(self, find_links, index_urls):
  1190. self.find_links = find_links
  1191. self.index_urls = index_urls
  1192. self.dependency_links = []
  1193. self.cache = PageCache()
  1194. # These are boring links that have already been logged somehow:
  1195. self.logged_links = set()
  1196. def add_dependency_links(self, links):
  1197. ## FIXME: this shouldn't be global list this, it should only
  1198. ## apply to requirements of the package that specifies the
  1199. ## dependency_links value
  1200. ## FIXME: also, we should track comes_from (i.e., use Link)
  1201. self.dependency_links.extend(links)
  1202. def find_requirement(self, req, upgrade):
  1203. url_name = req.url_name
  1204. # Only check main index if index URL is given:
  1205. main_index_url = None
  1206. if self.index_urls:
  1207. # Check that we have the url_name correctly spelled:
  1208. main_index_url = Link(posixpath.join(self.index_urls[0], url_name))
  1209. # This will also cache the page, so it's okay that we get it again later:
  1210. page = self._get_page(main_index_url, req)
  1211. if page is None:
  1212. url_name = self._find_url_name(Link(self.index_urls[0]), url_name, req) or req.url_name
  1213. def mkurl_pypi_url(url):
  1214. loc = posixpath.join(url, url_name)
  1215. # For maximum compatibility with easy_install, ensure the path
  1216. # ends in a trailing slash. Although this isn't in the spec
  1217. # (and PyPI can handle it without the slash) some other index
  1218. # implementations might break if they relied on easy_install's behavior.
  1219. if not loc.endswith('/'):
  1220. loc = loc + '/'
  1221. return loc
  1222. if url_name is not None:
  1223. locations = [
  1224. mkurl_pypi_url(url)
  1225. for url in self.index_urls] + self.find_links
  1226. else:
  1227. locations = list(self.find_links)
  1228. locations.extend(self.dependency_links)
  1229. for version in req.absolute_versions:
  1230. if url_name is not None and main_index_url is not None:
  1231. locations = [
  1232. posixpath.join(main_index_url.url, version)] + locations
  1233. file_locations = []
  1234. url_locations = []
  1235. for url in locations:
  1236. if url.startswith('file:'):
  1237. fn = url_to_filename(url)
  1238. if os.path.isdir(fn):
  1239. path = os.path.realpath(fn)
  1240. for item in os.listdir(path):
  1241. file_locations.append(
  1242. filename_to_url2(os.path.join(path, item)))
  1243. elif os.path.isfile(fn):
  1244. file_locations.append(filename_to_url2(fn))
  1245. else:
  1246. url_locations.append(url)
  1247. locations = [Link(url) for url in url_locations]
  1248. logger.debug('URLs to search for versions for %s:' % req)
  1249. for location in locations:
  1250. logger.debug('* %s' % location)
  1251. found_versions = []
  1252. found_versions.extend(
  1253. self._package_versions(
  1254. [Link(url, '-f') for url in self.find_links], req.name.lower()))
  1255. page_versions = []
  1256. for page in self._get_pages(locations, req):
  1257. logger.debug('Analyzing links from page %s' % page.url)
  1258. logger.indent += 2
  1259. try:
  1260. page_versions.extend(self._package_versions(page.links, req.name.lower()))
  1261. finally:
  1262. logger.indent -= 2
  1263. dependency_versions = list(self._package_versions(
  1264. [Link(url) for url in self.dependency_links], req.name.lower()))
  1265. if dependency_versions:
  1266. logger.info('dependency_links found: %s' % ', '.join([link.url for parsed, link, version in dependency_versions]))
  1267. file_versions = list(self._package_versions(
  1268. [Link(url) for url in file_locations], req.name.lower()))
  1269. if not found_versions and not page_versions and not dependency_versions and not file_versions:
  1270. logger.fatal('Could not find any downloads that satisfy the requirement %s' % req)
  1271. raise DistributionNotFound('No distributions at all found for %s' % req)
  1272. if req.satisfied_by is not None:
  1273. found_versions.append((req.satisfied_by.parsed_version, Inf, req.satisfied_by.version))
  1274. if file_versions:
  1275. file_versions.sort(reverse=True)
  1276. logger.info('Local files found: %s' % ', '.join([url_to_filename(link.url) for parsed, link, version in file_versions]))
  1277. found_versions = file_versions + found_versions
  1278. all_versions = found_versions + page_versions + dependency_versions
  1279. applicable_versions = []
  1280. for (parsed_version, link, version) in all_versions:
  1281. if version not in req.req:
  1282. logger.info("Ignoring link %s, version %s doesn't match %s"
  1283. % (link, version, ','.join([''.join(s) for s in req.req.specs])))
  1284. continue
  1285. applicable_versions.append((link, version))
  1286. applicable_versions = sorted(applicable_versions, key=operator.itemgetter(1),
  1287. cmp=lambda x, y : cmp(pkg_resources.parse_version(y), pkg_resources.parse_version(x))
  1288. )
  1289. existing_applicable = bool([link for link, version in applicable_versions if link is Inf])
  1290. if not upgrade and existing_applicable:
  1291. if applicable_versions[0][1] is Inf:
  1292. logger.info('Existing installed version (%s) is most up-to-date and satisfies requirement'
  1293. % req.satisfied_by.version)
  1294. else:
  1295. logger.info('Existing installed version (%s) satisfies requirement (most up-to-date version is %s)'
  1296. % (req.satisfied_by.version, applicable_versions[0][1]))
  1297. return None
  1298. if not applicable_versions:
  1299. logger.fatal('Could not find a version that satisfies the requirement %s (from versions: %s)'
  1300. % (req, ', '.join([version for parsed_version, link, version in found_versions])))
  1301. raise DistributionNotFound('No distributions matching the version for %s' % req)
  1302. if applicable_versions[0][0] is Inf:
  1303. # We have an existing version, and its the best version
  1304. logger.info('Installed version (%s) is most up-to-date (past versions: %s)'
  1305. % (req.satisfied_by.version, ', '.join([version for link, version in applicable_versions[1:]]) or 'none'))
  1306. return None
  1307. if len(applicable_versions) > 1:
  1308. logger.info('Using version %s (newest of versions: %s)' %
  1309. (applicable_versions[0][1], ', '.join([version for link, version in applicable_versions])))
  1310. return applicable_versions[0][0]
  1311. def _find_url_name(self, index_url, url_name, req):
  1312. """Finds the true URL name of a package, when the given name isn't quite correct.
  1313. This is usually used to implement case-insensitivity."""
  1314. if not index_url.url.endswith('/'):
  1315. # Vaguely part of the PyPI API... weird but true.
  1316. ## FIXME: bad to modify this?
  1317. index_url.url += '/'
  1318. page = self._get_page(index_url, req)
  1319. if page is None:
  1320. logger.fatal('Cannot fetch index base URL %s' % index_url)
  1321. return
  1322. norm_name = normalize_name(req.url_name)
  1323. for link in page.links:
  1324. base = posixpath.basename(link.path.rstrip('/'))
  1325. if norm_name == normalize_name(base):
  1326. logger.notify('Real name of requirement %s is %s' % (url_name, base))
  1327. return base
  1328. return None
  1329. def _get_pages(self, locations, req):
  1330. """Yields (page, page_url) from the given locations, skipping
  1331. locations that have errors, and adding download/homepage links"""
  1332. pending_queue = Queue()
  1333. for location in locations:
  1334. pending_queue.put(location)
  1335. done = []
  1336. seen = set()
  1337. threads = []
  1338. for i in range(min(10, len(locations))):
  1339. t = threading.Thread(target=self._get_queued_page, args=(req, pending_queue, done, seen))
  1340. t.setDaemon(True)
  1341. threads.append(t)
  1342. t.start()
  1343. for t in threads:
  1344. t.join()
  1345. return done
  1346. _log_lock = threading.Lock()
  1347. def _get_queued_page(self, req, pending_queue, done, seen):
  1348. while 1:
  1349. try:
  1350. location = pending_queue.get(False)
  1351. except QueueEmpty:
  1352. return
  1353. if location in seen:
  1354. continue
  1355. seen.add(location)
  1356. page = self._get_page(location, req)
  1357. if page is None:
  1358. continue
  1359. done.append(page)
  1360. for link in page.rel_links():
  1361. pending_queue.put(link)
  1362. _egg_fragment_re = re.compile(r'#egg=([^&]*)')
  1363. _egg_info_re = re.compile(r'([a-z0-9_.]+)-([a-z0-9_.-]+)', re.I)
  1364. _py_version_re = re.compile(r'-py([123]\.[0-9])$')
  1365. def _sort_links(self, links):
  1366. "Brings links in order, non-egg links first, egg links second"
  1367. eggs, no_eggs = [], []
  1368. for link in links:
  1369. if link.egg_fragment:
  1370. eggs.append(link)
  1371. else:
  1372. no_eggs.append(link)
  1373. return no_eggs + eggs
  1374. def _package_versions(self, links, search_name):
  1375. seen_links = {}
  1376. for link in self._sort_links(links):
  1377. if link.url in seen_links:
  1378. continue
  1379. seen_links[link.url] = None
  1380. if link.egg_fragment:
  1381. egg_info = link.egg_fragment
  1382. else:
  1383. path = link.path
  1384. egg_info, ext = link.splitext()
  1385. if not ext:
  1386. if link not in self.logged_links:
  1387. logger.debug('Skipping link %s; not a file' % link)
  1388. self.logged_links.add(link)
  1389. continue
  1390. if egg_info.endswith('.tar'):
  1391. # Special double-extension case:
  1392. egg_info = egg_info[:-4]
  1393. ext = '.tar' + ext
  1394. if ext not in ('.tar.gz', '.tar.bz2', '.tar', '.tgz', '.zip'):
  1395. if link not in self.logged_links:
  1396. logger.debug('Skipping link %s; unknown archive format: %s' % (link, ext))
  1397. self.logged_links.add(link)
  1398. continue
  1399. version = self._egg_info_matches(egg_info, search_name, link)
  1400. if version is None:
  1401. logger.debug('Skipping link %s; wrong project name (not %s)' % (link, search_name))
  1402. continue
  1403. match = self._py_version_re.search(version)
  1404. if match:
  1405. version = version[:match.start()]
  1406. py_version = match.group(1)
  1407. if py_version != sys.version[:3]:
  1408. logger.debug('Skipping %s because Python version is incorrect' % link)
  1409. continue
  1410. logger.debug('Found link %s, version: %s' % (link, version))
  1411. yield (pkg_resources.parse_version(version),
  1412. link,
  1413. version)
  1414. def _egg_info_matches(self, egg_info, search_name, link):
  1415. match = self._egg_info_re.search(egg_info)
  1416. if not match:
  1417. logger.debug('Could not parse version from link: %s' % link)
  1418. return None
  1419. name = match.group(0).lower()
  1420. # To match the "safe" name that pkg_resources creates:
  1421. name = name.replace('_', '-')
  1422. if name.startswith(search_name.lower()):
  1423. return match.group(0)[len(search_name):].lstrip('-')
  1424. else:
  1425. return None
  1426. def _get_page(self, link, req):
  1427. return HTMLPage.get_page(link, req, cache=self.cache)
  1428. class InstallRequirement(object):
  1429. def __init__(self, req, comes_from, source_dir=None, editable=False,
  1430. url=None, update=True):
  1431. if isinstance(req, basestring):
  1432. req = pkg_resources.Requirement.parse(req)
  1433. self.req = req
  1434. self.comes_from = comes_from
  1435. self.source_dir = source_dir
  1436. self.editable = editable
  1437. self.url = url
  1438. self._egg_info_path = None
  1439. # This holds the pkg_resources.Distribution object if this requirement
  1440. # is already available:
  1441. self.satisfied_by = None
  1442. # This hold the pkg_resources.Distribution object if this requirement
  1443. # conflicts with another installed distribution:
  1444. self.conflicts_with = None
  1445. self._temp_build_dir = None
  1446. self._is_bundle = None
  1447. # True if the editable should be updated:
  1448. self.update = update
  1449. # Set to True after successful installation
  1450. self.install_succeeded = None
  1451. # UninstallPathSet of uninstalled distribution (for possible rollback)
  1452. self.uninstalled = None
  1453. @classmethod
  1454. def from_editable(cls, editable_req, comes_from=None, default_vcs=None):
  1455. name, url = parse_editable(editable_req, default_vcs)
  1456. if url.startswith('file:'):
  1457. source_dir = url_to_filename(url)
  1458. else:
  1459. source_dir = None
  1460. return cls(name, comes_from, source_dir=source_dir, editable=True, url=url)
  1461. @classmethod
  1462. def from_line(cls, name, comes_from=None):
  1463. """Creates an InstallRequirement from a name, which might be a
  1464. requirement, filename, or URL.
  1465. """
  1466. url = None
  1467. name = name.strip()
  1468. req = name
  1469. if is_url(name):
  1470. url = name
  1471. ## FIXME: I think getting the requirement here is a bad idea:
  1472. #req = get_requirement_from_url(url)
  1473. req = None
  1474. elif is_filename(name):
  1475. if not os.path.exists(name):
  1476. logger.warn('Requirement %r looks like a filename, but the file does not exist'
  1477. % name)
  1478. url = filename_to_url(name)
  1479. #req = get_requirement_from_url(url)
  1480. req = None
  1481. return cls(req, comes_from, url=url)
  1482. def __str__(self):
  1483. if self.req:
  1484. s = str(self.req)
  1485. if self.url:
  1486. s += ' from %s' % self.url
  1487. else:
  1488. s = self.url
  1489. if self.satisfied_by is not None:
  1490. s += ' in %s' % display_path(self.satisfied_by.location)
  1491. if self.comes_from:
  1492. if isinstance(self.comes_from, basestring):
  1493. comes_from = self.comes_from
  1494. else:
  1495. comes_from = self.comes_from.from_path()
  1496. if comes_from:
  1497. s += ' (from %s)' % comes_from
  1498. return s
  1499. def from_path(self):
  1500. if self.req is None:
  1501. return None
  1502. s = str(self.req)
  1503. if self.comes_from:
  1504. if isinstance(self.comes_from, basestring):
  1505. comes_from = self.comes_from
  1506. else:
  1507. comes_from = self.comes_from.from_path()
  1508. if comes_from:
  1509. s += '->' + comes_from
  1510. return s
  1511. def build_location(self, build_dir, unpack=True):
  1512. if self._temp_build_dir is not None:
  1513. return self._temp_build_dir
  1514. if self.req is None:
  1515. self._temp_build_dir = tempfile.mkdtemp('-build', 'pip-')
  1516. self._ideal_build_dir = build_dir
  1517. return self._temp_build_dir
  1518. if self.editable:
  1519. name = self.name.lower()
  1520. else:
  1521. name = self.name
  1522. # FIXME: Is there a better place to create the build_dir? (hg and bzr need this)
  1523. if not os.path.exists(build_dir):
  1524. os.makedirs(build_dir)
  1525. return os.path.join(build_dir, name)
  1526. def correct_build_location(self):
  1527. """If the build location was a temporary directory, this will move it
  1528. to a new more permanent location"""
  1529. if self.source_dir is not None:
  1530. return
  1531. assert self.req is not None
  1532. assert self._temp_build_dir
  1533. old_location = self._temp_build_dir
  1534. new_build_dir = self._ideal_build_dir
  1535. del self._ideal_build_dir
  1536. if self.editable:
  1537. name = self.name.lower()
  1538. else:
  1539. name = self.name
  1540. new_location = os.path.join(new_build_dir, name)
  1541. if not os.path.exists(new_build_dir):
  1542. logger.debug('Creating directory %s' % new_build_dir)
  1543. os.makedirs(new_build_dir)
  1544. if os.path.exists(new_location):
  1545. raise InstallationError(
  1546. 'A package already exists in %s; please remove it to continue'
  1547. % display_path(new_location))
  1548. logger.debug('Moving package %s from %s to new location %s'
  1549. % (self, display_path(old_location), display_path(new_location)))
  1550. shutil.move(old_location, new_location)
  1551. self._temp_build_dir = new_location
  1552. self.source_dir = new_location
  1553. self._egg_info_path = None
  1554. @property
  1555. def name(self):
  1556. if self.req is None:
  1557. return None
  1558. return self.req.project_name
  1559. @property
  1560. def url_name(self):
  1561. if self.req is None:
  1562. return None
  1563. return urllib.quote(self.req.unsafe_name)
  1564. @property
  1565. def setup_py(self):
  1566. return os.path.join(self.source_dir, 'setup.py')
  1567. def run_egg_info(self, force_root_egg_info=False):
  1568. assert self.source_dir
  1569. if self.name:
  1570. logger.notify('Running setup.py egg_info for package %s' % self.name)
  1571. else:
  1572. logger.notify('Running setup.py egg_info for package from %s' % self.url)
  1573. logger.indent += 2
  1574. try:
  1575. script = self._run_setup_py
  1576. script = script.replace('__SETUP_PY__', repr(self.setup_py))
  1577. script = script.replace('__PKG_NAME__', repr(self.name))
  1578. # We can't put the .egg-info files at the root, because then the source code will be mistaken
  1579. # for an installed egg, causing problems
  1580. if self.editable or force_root_egg_info:
  1581. egg_base_option = []
  1582. else:
  1583. egg_info_dir = os.path.join(self.source_dir, 'pip-egg-info')
  1584. if not os.path.exists(egg_info_dir):
  1585. os.makedirs(egg_info_dir)
  1586. egg_base_option = ['--egg-base', 'pip-egg-info']
  1587. call_subprocess(
  1588. [sys.executable, '-c', script, 'egg_info'] + egg_base_option,
  1589. cwd=self.source_dir, filter_stdout=self._filter_install, show_stdout=False,
  1590. command_level=Logger.VERBOSE_DEBUG,
  1591. command_desc='python setup.py egg_info')
  1592. finally:
  1593. logger.indent -= 2
  1594. if not self.req:
  1595. self.req = pkg_resources.Requirement.parse(self.pkg_info()['Name'])
  1596. self.correct_build_location()
  1597. ## FIXME: This is a lame hack, entirely for PasteScript which has
  1598. ## a self-provided entry point that causes this awkwardness
  1599. _run_setup_py = """
  1600. __file__ = __SETUP_PY__
  1601. from setuptools.command import egg_info
  1602. def replacement_run(self):
  1603. self.mkpath(self.egg_info)
  1604. installer = self.distribution.fetch_build_egg
  1605. for ep in egg_info.iter_entry_points('egg_info.writers'):
  1606. # require=False is the change we're making:
  1607. writer = ep.load(require=False)
  1608. if writer:
  1609. writer(self, ep.name, egg_info.os.path.join(self.egg_info,ep.name))
  1610. self.find_sources()
  1611. egg_info.egg_info.run = replacement_run
  1612. execfile(__file__)
  1613. """
  1614. def egg_info_data(self, filename):
  1615. if self.satisfied_by is not None:
  1616. if not self.satisfied_by.has_metadata(filename):
  1617. return None
  1618. return self.satisfied_by.get_metadata(filename)
  1619. assert self.source_dir
  1620. filename = self.egg_info_path(filename)
  1621. if not os.path.exists(filename):
  1622. return None
  1623. fp = open(filename, 'r')
  1624. data = fp.read()
  1625. fp.close()
  1626. return data
  1627. def egg_info_path(self, filename):
  1628. if self._egg_info_path is None:
  1629. if self.editable:
  1630. base = self.source_dir
  1631. else:
  1632. base = os.path.join(self.source_dir, 'pip-egg-info')
  1633. filenames = os.listdir(base)
  1634. if self.editable:
  1635. filenames = []
  1636. for root, dirs, files in os.walk(base):
  1637. for dir in vcs.dirnames:
  1638. if dir in dirs:
  1639. dirs.remove(dir)
  1640. filenames.extend([os.path.join(root, dir)
  1641. for dir in dirs])
  1642. filenames = [f for f in filenames if f.endswith('.egg-info')]
  1643. assert filenames, "No files/directories in %s (from %s)" % (base, filename)
  1644. assert len(filenames) == 1, "Unexpected files/directories in %s: %s" % (base, ' '.join(filenames))
  1645. self._egg_info_path = os.path.join(base, filenames[0])
  1646. return os.path.join(self._egg_info_path, filename)
  1647. def egg_info_lines(self, filename):
  1648. data = self.egg_info_data(filename)
  1649. if not data:
  1650. return []
  1651. result = []
  1652. for line in data.splitlines():
  1653. line = line.strip()
  1654. if not line or line.startswith('#'):
  1655. continue
  1656. result.append(line)
  1657. return result
  1658. def pkg_info(self):
  1659. p = FeedParser()
  1660. data = self.egg_info_data('PKG-INFO')
  1661. if not data:
  1662. logger.warn('No PKG-INFO file found in %s' % display_path(self.egg_info_path('PKG-INFO')))
  1663. p.feed(data or '')
  1664. return p.close()
  1665. @property
  1666. def dependency_links(self):
  1667. return self.egg_info_lines('dependency_links.txt')
  1668. _requirements_section_re = re.compile(r'\[(.*?)\]')
  1669. def requirements(self, extras=()):
  1670. in_extra = None
  1671. for line in self.egg_info_lines('requires.txt'):
  1672. match = self._requirements_section_re.match(line)
  1673. if match:
  1674. in_extra = match.group(1)
  1675. continue
  1676. if in_extra and in_extra not in extras:
  1677. # Skip requirement for an extra we aren't requiring
  1678. continue
  1679. yield line
  1680. @property
  1681. def absolute_versions(self):
  1682. for qualifier, version in self.req.specs:
  1683. if qualifier == '==':
  1684. yield version
  1685. @property
  1686. def installed_version(self):
  1687. return self.pkg_info()['version']
  1688. def assert_source_matches_version(self):
  1689. assert self.source_dir
  1690. if self.comes_from is None:
  1691. # We don't check the versions of things explicitly installed.
  1692. # This makes, e.g., "pip Package==dev" possible
  1693. return
  1694. version = self.installed_version
  1695. if version not in self.req:
  1696. logger.fatal(
  1697. 'Source in %s has the version %s, which does not match the requirement %s'
  1698. % (display_path(self.source_dir), version, self))
  1699. raise InstallationError(
  1700. 'Source in %s has version %s that conflicts with %s'
  1701. % (display_path(self.source_dir), version, self))
  1702. else:
  1703. logger.debug('Source in %s has version %s, which satisfies requirement %s'
  1704. % (display_path(self.source_dir), version, self))
  1705. def update_editable(self, obtain=True):
  1706. if not self.url:
  1707. logger.info("Cannot update repository at %s; repository location is unknown" % self.source_dir)
  1708. return
  1709. assert self.editable
  1710. assert self.source_dir
  1711. if self.url.startswith('file:'):
  1712. # Static paths don't get updated
  1713. return
  1714. assert '+' in self.url, "bad url: %r" % self.url
  1715. if not self.update:
  1716. return
  1717. vc_type, url = self.url.split('+', 1)
  1718. backend = vcs.get_backend(vc_type)
  1719. if backend:
  1720. vcs_backend = backend(self.url)
  1721. if obtain:
  1722. vcs_backend.obtain(self.source_dir)
  1723. else:
  1724. vcs_backend.export(self.source_dir)
  1725. else:
  1726. assert 0, (
  1727. 'Unexpected version control type (in %s): %s'
  1728. % (self.url, vc_type))
  1729. def uninstall(self, auto_confirm=False):
  1730. """
  1731. Uninstall the distribution currently satisfying this requirement.
  1732. Prompts before removing or modifying files unless
  1733. ``auto_confirm`` is True.
  1734. Refuses to delete or modify files outside of ``sys.prefix`` -
  1735. thus uninstallation within a virtual environment can only
  1736. modify that virtual environment, even if the virtualenv is
  1737. linked to global site-packages.
  1738. """
  1739. if not self.check_if_exists():
  1740. raise UninstallationError("Cannot uninstall requirement %s, not installed" % (self.name,))
  1741. dist = self.satisfied_by or self.conflicts_with
  1742. paths_to_remove = UninstallPathSet(dist, sys.prefix)
  1743. pip_egg_info_path = os.path.join(dist.location,
  1744. dist.egg_name()) + '.egg-info'
  1745. easy_install_egg = dist.egg_name() + '.egg'
  1746. # This won't find a globally-installed develop egg if
  1747. # we're in a virtualenv.
  1748. # (There doesn't seem to be any metadata in the
  1749. # Distribution object for a develop egg that points back
  1750. # to its .egg-link and easy-install.pth files). That's
  1751. # OK, because we restrict ourselves to making changes
  1752. # within sys.prefix anyway.
  1753. develop_egg_link = os.path.join(site_packages,
  1754. dist.project_name) + '.egg-link'
  1755. if os.path.exists(pip_egg_info_path):
  1756. # package installed by pip
  1757. paths_to_remove.add(pip_egg_info_path)
  1758. if dist.has_metadata('installed-files.txt'):
  1759. for installed_file in dist.get_metadata('installed-files.txt').splitlines():
  1760. path = os.path.normpath(os.path.join(pip_egg_info_path, installed_file))
  1761. if os.path.exists(path):
  1762. paths_to_remove.add(path)
  1763. if dist.has_metadata('top_level.txt'):
  1764. for top_level_pkg in [p for p
  1765. in dist.get_metadata('top_level.txt').splitlines()
  1766. if p]:
  1767. path = os.path.join(dist.location, top_level_pkg)
  1768. if os.path.exists(path):
  1769. paths_to_remove.add(path)
  1770. elif os.path.exists(path + '.py'):
  1771. paths_to_remove.add(path + '.py')
  1772. if os.path.exists(path + '.pyc'):
  1773. paths_to_remove.add(path + '.pyc')
  1774. elif dist.location.endswith(easy_install_egg):
  1775. # package installed by easy_install
  1776. paths_to_remove.add(dist.location)
  1777. easy_install_pth = os.path.join(os.path.dirname(dist.location),
  1778. 'easy-install.pth')
  1779. paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)
  1780. elif os.path.isfile(develop_egg_link):
  1781. # develop egg
  1782. fh = open(develop_egg_link, 'r')
  1783. link_pointer = os.path.normcase(fh.readline().strip())
  1784. fh.close()
  1785. assert (link_pointer == dist.location), 'Egg-link %s does not match installed location of %s (at %s)' % (link_pointer, self.name, dist.location)
  1786. paths_to_remove.add(develop_egg_link)
  1787. easy_install_pth = os.path.join(os.path.dirname(develop_egg_link),
  1788. 'easy-install.pth')
  1789. paths_to_remove.add_pth(easy_install_pth, dist.location)
  1790. # fix location (so we can uninstall links to sources outside venv)
  1791. paths_to_remove.location = develop_egg_link
  1792. # find distutils scripts= scripts
  1793. if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'):
  1794. for script in dist.metadata_listdir('scripts'):
  1795. paths_to_remove.add(os.path.join(bin_py, script))
  1796. if sys.platform == 'win32':
  1797. paths_to_remove.add(os.path.join(bin_py, script) + '.bat')
  1798. # find console_scripts
  1799. if dist.has_metadata('entry_points.txt'):
  1800. config = ConfigParser.SafeConfigParser()
  1801. config.readfp(FakeFile(dist.get_metadata_lines('entry_points.txt')))
  1802. for name, value in config.items('console_scripts'):
  1803. paths_to_remove.add(os.path.join(bin_py, name))
  1804. if sys.platform == 'win32':
  1805. paths_to_remove.add(os.path.join(bin_py, name) + '.exe')
  1806. paths_to_remove.add(os.path.join(bin_py, name) + '-script.py')
  1807. paths_to_remove.remove(auto_confirm)
  1808. self.uninstalled = paths_to_remove
  1809. def rollback_uninstall(self):
  1810. if self.uninstalled:
  1811. self.uninstalled.rollback()
  1812. else:
  1813. logger.error("Can't rollback %s, nothing uninstalled."
  1814. % (self.project_name,))
  1815. def archive(self, build_dir):
  1816. assert self.source_dir
  1817. create_archive = True
  1818. archive_name = '%s-%s.zip' % (self.name, self.installed_version)
  1819. archive_path = os.path.join(build_dir, archive_name)
  1820. if os.path.exists(archive_path):
  1821. response = ask('The file %s exists. (i)gnore, (w)ipe, (b)ackup '
  1822. % display_path(archive_path), ('i', 'w', 'b'))
  1823. if response == 'i':
  1824. create_archive = False
  1825. elif response == 'w':
  1826. logger.warn('Deleting %s' % display_path(archive_path))
  1827. os.remove(archive_path)
  1828. elif response == 'b':
  1829. dest_file = backup_dir(archive_path)
  1830. logger.warn('Backing up %s to %s'
  1831. % (display_path(archive_path), display_path(dest_file)))
  1832. shutil.move(archive_path, dest_file)
  1833. if create_archive:
  1834. zip = zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED)
  1835. dir = os.path.normcase(os.path.abspath(self.source_dir))
  1836. for dirpath, dirnames, filenames in os.walk(dir):
  1837. if 'pip-egg-info' in dirnames:
  1838. dirnames.remove('pip-egg-info')
  1839. for dirname in dirnames:
  1840. dirname = os.path.join(dirpath, dirname)
  1841. name = self._clean_zip_name(dirname, dir)
  1842. zipdir = zipfile.ZipInfo(self.name + '/' + name + '/')
  1843. zipdir.external_attr = 0755 << 16L
  1844. zip.writestr(zipdir, '')
  1845. for filename in filenames:
  1846. if filename == 'pip-delete-this-directory.txt':
  1847. continue
  1848. filename = os.path.join(dirpath, filename)
  1849. name = self._clean_zip_name(filename, dir)
  1850. zip.write(filename, self.name + '/' + name)
  1851. zip.close()
  1852. logger.indent -= 2
  1853. logger.notify('Saved %s' % display_path(archive_path))
  1854. def _clean_zip_name(self, name, prefix):
  1855. assert name.startswith(prefix+'/'), (
  1856. "name %r doesn't start with prefix %r" % (name, prefix))
  1857. name = name[len(prefix)+1:]
  1858. name = name.replace(os.path.sep, '/')
  1859. return name
  1860. def install(self, install_options):
  1861. if self.editable:
  1862. self.install_editable()
  1863. return
  1864. temp_location = tempfile.mkdtemp('-record', 'pip-')
  1865. record_filename = os.path.join(temp_location, 'install-record.txt')
  1866. ## FIXME: I'm not sure if this is a reasonable location; probably not
  1867. ## but we can't put it in the default location, as that is a virtualenv symlink that isn't writable
  1868. header_dir = os.path.join(os.path.dirname(os.path.dirname(self.source_dir)), 'lib', 'include')
  1869. logger.notify('Running setup.py install for %s' % self.name)
  1870. logger.indent += 2
  1871. try:
  1872. call_subprocess(
  1873. [sys.executable, '-c',
  1874. "import setuptools; __file__=%r; execfile(%r)" % (self.setup_py, self.setup_py),
  1875. 'install', '--single-version-externally-managed', '--record', record_filename,
  1876. '--install-headers', header_dir] + install_options,
  1877. cwd=self.source_dir, filter_stdout=self._filter_install, show_stdout=False)
  1878. finally:
  1879. logger.indent -= 2
  1880. self.install_succeeded = True
  1881. f = open(record_filename)
  1882. for line in f:
  1883. line = line.strip()
  1884. if line.endswith('.egg-info'):
  1885. egg_info_dir = line
  1886. break
  1887. else:
  1888. logger.warn('Could not find .egg-info directory in install record for %s' % self)
  1889. ## FIXME: put the record somewhere
  1890. return
  1891. f.close()
  1892. new_lines = []
  1893. f = open(record_filename)
  1894. for line in f:
  1895. filename = line.strip()
  1896. if os.path.isdir(filename):
  1897. filename += os.path.sep
  1898. new_lines.append(make_path_relative(filename, egg_info_dir))
  1899. f.close()
  1900. f = open(os.path.join(egg_info_dir, 'installed-files.txt'), 'w')
  1901. f.write('\n'.join(new_lines)+'\n')
  1902. f.close()
  1903. def remove_temporary_source(self):
  1904. """Remove the source files from this requirement, if they are marked
  1905. for deletion"""
  1906. if self.is_bundle or os.path.exists(self.delete_marker_filename):
  1907. logger.info('Removing source in %s' % self.source_dir)
  1908. if self.source_dir:
  1909. shutil.rmtree(self.source_dir, ignore_errors=True, onerror=rmtree_errorhandler)
  1910. self.source_dir = None
  1911. if self._temp_build_dir and os.path.exists(self._temp_build_dir):
  1912. shutil.rmtree(self._temp_build_dir, ignore_errors=True, onerror=rmtree_errorhandler)
  1913. self._temp_build_dir = None
  1914. def install_editable(self):
  1915. logger.notify('Running setup.py develop for %s' % self.name)
  1916. logger.indent += 2
  1917. try:
  1918. ## FIXME: should we do --install-headers here too?
  1919. call_subprocess(
  1920. [sys.executable, '-c',
  1921. "import setuptools; __file__=%r; execfile(%r)" % (self.setup_py, self.setup_py),
  1922. 'develop', '--no-deps'], cwd=self.source_dir, filter_stdout=self._filter_install,
  1923. show_stdout=False)
  1924. finally:
  1925. logger.indent -= 2
  1926. self.install_succeeded = True
  1927. def _filter_install(self, line):
  1928. level = Logger.NOTIFY
  1929. for regex in [r'^running .*', r'^writing .*', '^creating .*', '^[Cc]opying .*',
  1930. r'^reading .*', r"^removing .*\.egg-info' \(and everything under it\)$",
  1931. r'^byte-compiling ',
  1932. # Not sure what this warning is, but it seems harmless:
  1933. r"^warning: manifest_maker: standard file '-c' not found$"]:
  1934. if re.search(regex, line.strip()):
  1935. level = Logger.INFO
  1936. break
  1937. return (level, line)
  1938. def check_if_exists(self):
  1939. """Find an installed distribution that satisfies or conflicts
  1940. with this requirement, and set self.satisfied_by or
  1941. self.conflicts_with appropriately."""
  1942. if self.req is None:
  1943. return False
  1944. try:
  1945. self.satisfied_by = pkg_resources.get_distribution(self.req)
  1946. except pkg_resources.DistributionNotFound:
  1947. return False
  1948. except pkg_resources.VersionConflict:
  1949. self.conflicts_with = pkg_resources.get_distribution(self.req.project_name)
  1950. return True
  1951. @property
  1952. def is_bundle(self):
  1953. if self._is_bundle is not None:
  1954. return self._is_bundle
  1955. base = self._temp_build_dir
  1956. if not base:
  1957. ## FIXME: this doesn't seem right:
  1958. return False
  1959. self._is_bundle = (os.path.exists(os.path.join(base, 'pip-manifest.txt'))
  1960. or os.path.exists(os.path.join(base, 'pyinstall-manifest.txt')))
  1961. return self._is_bundle
  1962. def bundle_requirements(self):
  1963. for dest_dir in self._bundle_editable_dirs:
  1964. package = os.path.basename(dest_dir)
  1965. ## FIXME: svnism:
  1966. for vcs_backend in vcs.backends:
  1967. url = rev = None
  1968. vcs_bundle_file = os.path.join(
  1969. dest_dir, vcs_backend.bundle_file)
  1970. if os.path.exists(vcs_bundle_file):
  1971. vc_type = vcs_backend.name
  1972. fp = open(vcs_bundle_file)
  1973. content = fp.read()
  1974. fp.close()
  1975. url, rev = vcs_backend().parse_vcs_bundle_file(content)
  1976. break
  1977. if url:
  1978. url = '%s+%s@%s' % (vc_type, url, rev)
  1979. else:
  1980. url = None
  1981. yield InstallRequirement(
  1982. package, self, editable=True, url=url,
  1983. update=False, source_dir=dest_dir)
  1984. for dest_dir in self._bundle_build_dirs:
  1985. package = os.path.basename(dest_dir)
  1986. yield InstallRequirement(
  1987. package, self,
  1988. source_dir=dest_dir)
  1989. def move_bundle_files(self, dest_build_dir, dest_src_dir):
  1990. base = self._temp_build_dir
  1991. assert base
  1992. src_dir = os.path.join(base, 'src')
  1993. build_dir = os.path.join(base, 'build')
  1994. bundle_build_dirs = []
  1995. bundle_editable_dirs = []
  1996. for source_dir, dest_dir, dir_collection in [
  1997. (src_dir, dest_src_dir, bundle_editable_dirs),
  1998. (build_dir, dest_build_dir, bundle_build_dirs)]:
  1999. if os.path.exists(source_dir):
  2000. for dirname in os.listdir(source_dir):
  2001. dest = os.path.join(dest_dir, dirname)
  2002. dir_collection.append(dest)
  2003. if os.path.exists(dest):
  2004. logger.warn('The directory %s (containing package %s) already exists; cannot move source from bundle %s'
  2005. % (dest, dirname, self))
  2006. continue
  2007. if not os.path.exists(dest_dir):
  2008. logger.info('Creating directory %s' % dest_dir)
  2009. os.makedirs(dest_dir)
  2010. shutil.move(os.path.join(source_dir, dirname), dest)
  2011. if not os.listdir(source_dir):
  2012. os.rmdir(source_dir)
  2013. self._temp_build_dir = None
  2014. self._bundle_build_dirs = bundle_build_dirs
  2015. self._bundle_editable_dirs = bundle_editable_dirs
  2016. @property
  2017. def delete_marker_filename(self):
  2018. assert self.source_dir
  2019. return os.path.join(self.source_dir, 'pip-delete-this-directory.txt')
  2020. DELETE_MARKER_MESSAGE = '''\
  2021. This file is placed here by pip to indicate the source was put
  2022. here by pip.
  2023. Once this package is successfully installed this source code will be
  2024. deleted (unless you remove this file).
  2025. '''
  2026. class RequirementSet(object):
  2027. def __init__(self, build_dir, src_dir, download_dir, download_cache=None,
  2028. upgrade=False, ignore_installed=False,
  2029. ignore_dependencies=False):
  2030. self.build_dir = build_dir
  2031. self.src_dir = src_dir
  2032. self.download_dir = download_dir
  2033. self.download_cache = download_cache
  2034. self.upgrade = upgrade
  2035. self.ignore_installed = ignore_installed
  2036. self.requirements = {}
  2037. # Mapping of alias: real_name
  2038. self.requirement_aliases = {}
  2039. self.unnamed_requirements = []
  2040. self.ignore_dependencies = ignore_dependencies
  2041. self.successfully_downloaded = []
  2042. self.successfully_installed = []
  2043. def __str__(self):
  2044. reqs = [req for req in self.requirements.values()
  2045. if not req.comes_from]
  2046. reqs.sort(key=lambda req: req.name.lower())
  2047. return ' '.join([str(req.req) for req in reqs])
  2048. def add_requirement(self, install_req):
  2049. name = install_req.name
  2050. if not name:
  2051. self.unnamed_requirements.append(install_req)
  2052. else:
  2053. if self.has_requirement(name):
  2054. raise InstallationError(
  2055. 'Double requirement given: %s (aready in %s, name=%r)'
  2056. % (install_req, self.get_requirement(name), name))
  2057. self.requirements[name] = install_req
  2058. ## FIXME: what about other normalizations? E.g., _ vs. -?
  2059. if name.lower() != name:
  2060. self.requirement_aliases[name.lower()] = name
  2061. def has_requirement(self, project_name):
  2062. for name in project_name, project_name.lower():
  2063. if name in self.requirements or name in self.requirement_aliases:
  2064. return True
  2065. return False
  2066. @property
  2067. def is_download(self):
  2068. if self.download_dir:
  2069. self.download_dir = os.path.expanduser(self.download_dir)
  2070. if os.path.exists(self.download_dir):
  2071. return True
  2072. else:
  2073. logger.fatal('Could not find download directory')
  2074. raise InstallationError(
  2075. "Could not find or access download directory '%s'"
  2076. % display_path(self.download_dir))
  2077. return False
  2078. def get_requirement(self, project_name):
  2079. for name in project_name, project_name.lower():
  2080. if name in self.requirements:
  2081. return self.requirements[name]
  2082. if name in self.requirement_aliases:
  2083. return self.requirements[self.requirement_aliases[name]]
  2084. raise KeyError("No project with the name %r" % project_name)
  2085. def uninstall(self, auto_confirm=False):
  2086. for req in self.requirements.values():
  2087. req.uninstall(auto_confirm=auto_confirm)
  2088. def install_files(self, finder, force_root_egg_info=False):
  2089. unnamed = list(self.unnamed_requirements)
  2090. reqs = self.requirements.values()
  2091. while reqs or unnamed:
  2092. if unnamed:
  2093. req_to_install = unnamed.pop(0)
  2094. else:
  2095. req_to_install = reqs.pop(0)
  2096. install = True
  2097. if not self.ignore_installed and not req_to_install.editable:
  2098. req_to_install.check_if_exists()
  2099. if req_to_install.satisfied_by:
  2100. if self.upgrade:
  2101. req_to_install.conflicts_with = req_to_install.satisfied_by
  2102. req_to_install.satisfied_by = None
  2103. else:
  2104. install = False
  2105. if req_to_install.satisfied_by:
  2106. logger.notify('Requirement already satisfied '
  2107. '(use --upgrade to upgrade): %s'
  2108. % req_to_install)
  2109. if req_to_install.editable:
  2110. logger.notify('Obtaining %s' % req_to_install)
  2111. elif install:
  2112. if req_to_install.url and req_to_install.url.lower().startswith('file:'):
  2113. logger.notify('Unpacking %s' % display_path(url_to_filename(req_to_install.url)))
  2114. else:
  2115. logger.notify('Downloading/unpacking %s' % req_to_install)
  2116. logger.indent += 2
  2117. is_bundle = False
  2118. try:
  2119. if req_to_install.editable:
  2120. if req_to_install.source_dir is None:
  2121. location = req_to_install.build_location(self.src_dir)
  2122. req_to_install.source_dir = location
  2123. else:
  2124. location = req_to_install.source_dir
  2125. if not os.path.exists(self.build_dir):
  2126. os.makedirs(self.build_dir)
  2127. req_to_install.update_editable(not self.is_download)
  2128. if self.is_download:
  2129. req_to_install.run_egg_info()
  2130. req_to_install.archive(self.download_dir)
  2131. else:
  2132. req_to_install.run_egg_info()
  2133. elif install:
  2134. location = req_to_install.build_location(self.build_dir, not self.is_download)
  2135. ## FIXME: is the existance of the checkout good enough to use it? I don't think so.
  2136. unpack = True
  2137. if not os.path.exists(os.path.join(location, 'setup.py')):
  2138. ## FIXME: this won't upgrade when there's an existing package unpacked in `location`
  2139. if req_to_install.url is None:
  2140. url = finder.find_requirement(req_to_install, upgrade=self.upgrade)
  2141. else:
  2142. ## FIXME: should req_to_install.url already be a link?
  2143. url = Link(req_to_install.url)
  2144. assert url
  2145. if url:
  2146. try:
  2147. self.unpack_url(url, location, self.is_download)
  2148. except urllib2.HTTPError, e:
  2149. logger.fatal('Could not install requirement %s because of error %s'
  2150. % (req_to_install, e))
  2151. raise InstallationError(
  2152. 'Could not install requirement %s because of HTTP error %s for URL %s'
  2153. % (req_to_install, e, url))
  2154. else:
  2155. unpack = False
  2156. if unpack:
  2157. is_bundle = req_to_install.is_bundle
  2158. url = None
  2159. if is_bundle:
  2160. req_to_install.move_bundle_files(self.build_dir, self.src_dir)
  2161. for subreq in req_to_install.bundle_requirements():
  2162. reqs.append(subreq)
  2163. self.add_requirement(subreq)
  2164. elif self.is_download:
  2165. req_to_install.source_dir = location
  2166. if url and url.scheme in vcs.all_schemes:
  2167. req_to_install.run_egg_info()
  2168. req_to_install.archive(self.download_dir)
  2169. else:
  2170. req_to_install.source_dir = location
  2171. req_to_install.run_egg_info()
  2172. if force_root_egg_info:
  2173. # We need to run this to make sure that the .egg-info/
  2174. # directory is created for packing in the bundle
  2175. req_to_install.run_egg_info(force_root_egg_info=True)
  2176. req_to_install.assert_source_matches_version()
  2177. f = open(req_to_install.delete_marker_filename, 'w')
  2178. f.write(DELETE_MARKER_MESSAGE)
  2179. f.close()
  2180. if not is_bundle and not self.is_download:
  2181. ## FIXME: shouldn't be globally added:
  2182. finder.add_dependency_links(req_to_install.dependency_links)
  2183. ## FIXME: add extras in here:
  2184. if not self.ignore_dependencies:
  2185. for req in req_to_install.requirements():
  2186. try:
  2187. name = pkg_resources.Requirement.parse(req).project_name
  2188. except ValueError, e:
  2189. ## FIXME: proper warning
  2190. logger.error('Invalid requirement: %r (%s) in requirement %s' % (req, e, req_to_install))
  2191. continue
  2192. if self.has_requirement(name):
  2193. ## FIXME: check for conflict
  2194. continue
  2195. subreq = InstallRequirement(req, req_to_install)
  2196. reqs.append(subreq)
  2197. self.add_requirement(subreq)
  2198. if req_to_install.name not in self.requirements:
  2199. self.requirements[req_to_install.name] = req_to_install
  2200. else:
  2201. req_to_install.remove_temporary_source()
  2202. if install:
  2203. self.successfully_downloaded.append(req_to_install)
  2204. finally:
  2205. logger.indent -= 2
  2206. def unpack_url(self, link, location, only_download=False):
  2207. if only_download:
  2208. location = self.download_dir
  2209. for backend in vcs.backends:
  2210. if link.scheme in backend.schemes:
  2211. vcs_backend = backend(link.url)
  2212. if only_download:
  2213. vcs_backend.export(location)
  2214. else:
  2215. vcs_backend.unpack(location)
  2216. return
  2217. dir = tempfile.mkdtemp()
  2218. if link.url.lower().startswith('file:'):
  2219. source = url_to_filename(link.url)
  2220. content_type = mimetypes.guess_type(source)
  2221. self.unpack_file(source, location, content_type, link)
  2222. return
  2223. md5_hash = link.md5_hash
  2224. target_url = link.url.split('#', 1)[0]
  2225. target_file = None
  2226. if self.download_cache:
  2227. target_file = os.path.join(self.download_cache,
  2228. urllib.quote(target_url, ''))
  2229. if (target_file and os.path.exists(target_file)
  2230. and os.path.exists(target_file+'.content-type')):
  2231. fp = open(target_file+'.content-type')
  2232. content_type = fp.read().strip()
  2233. fp.close()
  2234. if md5_hash:
  2235. download_hash = md5()
  2236. fp = open(target_file, 'rb')
  2237. while 1:
  2238. chunk = fp.read(4096)
  2239. if not chunk:
  2240. break
  2241. download_hash.update(chunk)
  2242. fp.close()
  2243. temp_location = target_file
  2244. logger.notify('Using download cache from %s' % target_file)
  2245. else:
  2246. try:
  2247. resp = urllib2.urlopen(target_url)
  2248. except urllib2.HTTPError, e:
  2249. logger.fatal("HTTP error %s while getting %s" % (e.code, link))
  2250. raise
  2251. except IOError, e:
  2252. # Typically an FTP error
  2253. logger.fatal("Error %s while getting %s" % (e, link))
  2254. raise
  2255. content_type = resp.info()['content-type']
  2256. filename = link.filename
  2257. ext = splitext(filename)[1]
  2258. if not ext:
  2259. ext = mimetypes.guess_extension(content_type)
  2260. if ext:
  2261. filename += ext
  2262. if not ext and link.url != resp.geturl():
  2263. ext = os.path.splitext(resp.geturl())[1]
  2264. if ext:
  2265. filename += ext
  2266. temp_location = os.path.join(dir, filename)
  2267. fp = open(temp_location, 'wb')
  2268. if md5_hash:
  2269. download_hash = md5()
  2270. try:
  2271. total_length = int(resp.info()['content-length'])
  2272. except (ValueError, KeyError):
  2273. total_length = 0
  2274. downloaded = 0
  2275. show_progress = total_length > 40*1000 or not total_length
  2276. show_url = link.show_url
  2277. try:
  2278. if show_progress:
  2279. ## FIXME: the URL can get really long in this message:
  2280. if total_length:
  2281. logger.start_progress('Downloading %s (%s): ' % (show_url, format_size(total_length)))
  2282. else:
  2283. logger.start_progress('Downloading %s (unknown size): ' % show_url)
  2284. else:
  2285. logger.notify('Downloading %s' % show_url)
  2286. logger.debug('Downloading from URL %s' % link)
  2287. while 1:
  2288. chunk = resp.read(4096)
  2289. if not chunk:
  2290. break
  2291. downloaded += len(chunk)
  2292. if show_progress:
  2293. if not total_length:
  2294. logger.show_progress('%s' % format_size(downloaded))
  2295. else:
  2296. logger.show_progress('%3i%% %s' % (100*downloaded/total_length, format_size(downloaded)))
  2297. if md5_hash:
  2298. download_hash.update(chunk)
  2299. fp.write(chunk)
  2300. fp.close()
  2301. finally:
  2302. if show_progress:
  2303. logger.end_progress('%s downloaded' % format_size(downloaded))
  2304. if md5_hash:
  2305. download_hash = download_hash.hexdigest()
  2306. if download_hash != md5_hash:
  2307. logger.fatal("MD5 hash of the package %s (%s) doesn't match the expected hash %s!"
  2308. % (link, download_hash, md5_hash))
  2309. raise InstallationError('Bad MD5 hash for package %s' % link)
  2310. if only_download:
  2311. self.copy_file(temp_location, location, content_type, link)
  2312. else:
  2313. self.unpack_file(temp_location, location, content_type, link)
  2314. if target_file and target_file != temp_location:
  2315. logger.notify('Storing download in cache at %s' % display_path(target_file))
  2316. shutil.copyfile(temp_location, target_file)
  2317. fp = open(target_file+'.content-type', 'w')
  2318. fp.write(content_type)
  2319. fp.close()
  2320. os.unlink(temp_location)
  2321. if target_file is None:
  2322. os.unlink(temp_location)
  2323. def copy_file(self, filename, location, content_type, link):
  2324. copy = True
  2325. download_location = os.path.join(location, link.filename)
  2326. if os.path.exists(download_location):
  2327. response = ask('The file %s exists. (i)gnore, (w)ipe, (b)ackup '
  2328. % display_path(download_location), ('i', 'w', 'b'))
  2329. if response == 'i':
  2330. copy = False
  2331. elif response == 'w':
  2332. logger.warn('Deleting %s' % display_path(download_location))
  2333. os.remove(download_location)
  2334. elif response == 'b':
  2335. dest_file = backup_dir(download_location)
  2336. logger.warn('Backing up %s to %s'
  2337. % (display_path(download_location), display_path(dest_file)))
  2338. shutil.move(download_location, dest_file)
  2339. if copy:
  2340. shutil.copy(filename, download_location)
  2341. logger.indent -= 2
  2342. logger.notify('Saved %s' % display_path(download_location))
  2343. def unpack_file(self, filename, location, content_type, link):
  2344. if (content_type == 'application/zip'
  2345. or filename.endswith('.zip')
  2346. or filename.endswith('.pybundle')
  2347. or zipfile.is_zipfile(filename)):
  2348. self.unzip_file(filename, location, flatten=not filename.endswith('.pybundle'))
  2349. elif (content_type == 'application/x-gzip'
  2350. or tarfile.is_tarfile(filename)
  2351. or splitext(filename)[1].lower() in ('.tar', '.tar.gz', '.tar.bz2', '.tgz')):
  2352. self.untar_file(filename, location)
  2353. elif (content_type.startswith('text/html')
  2354. and is_svn_page(file_contents(filename))):
  2355. # We don't really care about this
  2356. Subversion('svn+' + link.url).unpack(location)
  2357. else:
  2358. ## FIXME: handle?
  2359. ## FIXME: magic signatures?
  2360. logger.fatal('Cannot unpack file %s (downloaded from %s, content-type: %s); cannot detect archive format'
  2361. % (filename, location, content_type))
  2362. raise InstallationError('Cannot determine archive format of %s' % location)
  2363. def unzip_file(self, filename, location, flatten=True):
  2364. """Unzip the file (zip file located at filename) to the destination
  2365. location"""
  2366. if not os.path.exists(location):
  2367. os.makedirs(location)
  2368. zipfp = open(filename, 'rb')
  2369. try:
  2370. zip = zipfile.ZipFile(zipfp)
  2371. leading = has_leading_dir(zip.namelist()) and flatten
  2372. for name in zip.namelist():
  2373. data = zip.read(name)
  2374. fn = name
  2375. if leading:
  2376. fn = split_leading_dir(name)[1]
  2377. fn = os.path.join(location, fn)
  2378. dir = os.path.dirname(fn)
  2379. if not os.path.exists(dir):
  2380. os.makedirs(dir)
  2381. if fn.endswith('/') or fn.endswith('\\'):
  2382. # A directory
  2383. if not os.path.exists(fn):
  2384. os.makedirs(fn)
  2385. else:
  2386. fp = open(fn, 'wb')
  2387. try:
  2388. fp.write(data)
  2389. finally:
  2390. fp.close()
  2391. finally:
  2392. zipfp.close()
  2393. def untar_file(self, filename, location):
  2394. """Untar the file (tar file located at filename) to the destination location"""
  2395. if not os.path.exists(location):
  2396. os.makedirs(location)
  2397. if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
  2398. mode = 'r:gz'
  2399. elif filename.lower().endswith('.bz2'):
  2400. mode = 'r:bz2'
  2401. elif filename.lower().endswith('.tar'):
  2402. mode = 'r'
  2403. else:
  2404. logger.warn('Cannot determine compression type for file %s' % filename)
  2405. mode = 'r:*'
  2406. tar = tarfile.open(filename, mode)
  2407. try:
  2408. leading = has_leading_dir([member.name for member in tar.getmembers()])
  2409. for member in tar.getmembers():
  2410. fn = member.name
  2411. if leading:
  2412. fn = split_leading_dir(fn)[1]
  2413. path = os.path.join(location, fn)
  2414. if member.isdir():
  2415. if not os.path.exists(path):
  2416. os.makedirs(path)
  2417. else:
  2418. try:
  2419. fp = tar.extractfile(member)
  2420. except (KeyError, AttributeError), e:
  2421. # Some corrupt tar files seem to produce this
  2422. # (specifically bad symlinks)
  2423. logger.warn(
  2424. 'In the tar file %s the member %s is invalid: %s'
  2425. % (filename, member.name, e))
  2426. continue
  2427. if not os.path.exists(os.path.dirname(path)):
  2428. os.makedirs(os.path.dirname(path))
  2429. destfp = open(path, 'wb')
  2430. try:
  2431. shutil.copyfileobj(fp, destfp)
  2432. finally:
  2433. destfp.close()
  2434. fp.close()
  2435. finally:
  2436. tar.close()
  2437. def install(self, install_options):
  2438. """Install everything in this set (after having downloaded and unpacked the packages)"""
  2439. to_install = sorted([r for r in self.requirements.values()
  2440. if self.upgrade or not r.satisfied_by],
  2441. key=lambda p: p.name.lower())
  2442. if to_install:
  2443. logger.notify('Installing collected packages: %s' % (', '.join([req.name for req in to_install])))
  2444. logger.indent += 2
  2445. try:
  2446. for requirement in to_install:
  2447. if requirement.conflicts_with:
  2448. logger.notify('Found existing installation: %s'
  2449. % requirement.conflicts_with)
  2450. logger.indent += 2
  2451. try:
  2452. requirement.uninstall(auto_confirm=True)
  2453. finally:
  2454. logger.indent -= 2
  2455. try:
  2456. requirement.install(install_options)
  2457. except:
  2458. # if install did not succeed, rollback previous uninstall
  2459. if requirement.conflicts_with and not requirement.install_succeeded:
  2460. requirement.rollback_uninstall()
  2461. raise
  2462. requirement.remove_temporary_source()
  2463. finally:
  2464. logger.indent -= 2
  2465. self.successfully_installed = to_install
  2466. def create_bundle(self, bundle_filename):
  2467. ## FIXME: can't decide which is better; zip is easier to read
  2468. ## random files from, but tar.bz2 is smaller and not as lame a
  2469. ## format.
  2470. ## FIXME: this file should really include a manifest of the
  2471. ## packages, maybe some other metadata files. It would make
  2472. ## it easier to detect as well.
  2473. zip = zipfile.ZipFile(bundle_filename, 'w', zipfile.ZIP_DEFLATED)
  2474. vcs_dirs = []
  2475. for dir, basename in (self.build_dir, 'build'), (self.src_dir, 'src'):
  2476. dir = os.path.normcase(os.path.abspath(dir))
  2477. for dirpath, dirnames, filenames in os.walk(dir):
  2478. for backend in vcs.backends:
  2479. vcs_backend = backend()
  2480. vcs_url = vcs_rev = None
  2481. if vcs_backend.dirname in dirnames:
  2482. for vcs_dir in vcs_dirs:
  2483. if dirpath.startswith(vcs_dir):
  2484. # vcs bundle file already in parent directory
  2485. break
  2486. else:
  2487. vcs_url, vcs_rev = vcs_backend.get_info(
  2488. os.path.join(dir, dirpath))
  2489. vcs_dirs.append(dirpath)
  2490. vcs_bundle_file = vcs_backend.bundle_file
  2491. vcs_guide = vcs_backend.guide % {'url': vcs_url,
  2492. 'rev': vcs_rev}
  2493. dirnames.remove(vcs_backend.dirname)
  2494. break
  2495. if 'pip-egg-info' in dirnames:
  2496. dirnames.remove('pip-egg-info')
  2497. for dirname in dirnames:
  2498. dirname = os.path.join(dirpath, dirname)
  2499. name = self._clean_zip_name(dirname, dir)
  2500. zip.writestr(basename + '/' + name + '/', '')
  2501. for filename in filenames:
  2502. if filename == 'pip-delete-this-directory.txt':
  2503. continue
  2504. filename = os.path.join(dirpath, filename)
  2505. name = self._clean_zip_name(filename, dir)
  2506. zip.write(filename, basename + '/' + name)
  2507. if vcs_url:
  2508. name = os.path.join(dirpath, vcs_bundle_file)
  2509. name = self._clean_zip_name(name, dir)
  2510. zip.writestr(basename + '/' + name, vcs_guide)
  2511. zip.writestr('pip-manifest.txt', self.bundle_requirements())
  2512. zip.close()
  2513. # Unlike installation, this will always delete the build directories
  2514. logger.info('Removing temporary build dir %s and source dir %s'
  2515. % (self.build_dir, self.src_dir))
  2516. for dir in self.build_dir, self.src_dir:
  2517. if os.path.exists(dir):
  2518. shutil.rmtree(dir)
  2519. BUNDLE_HEADER = '''\
  2520. # This is a pip bundle file, that contains many source packages
  2521. # that can be installed as a group. You can install this like:
  2522. # pip this_file.zip
  2523. # The rest of the file contains a list of all the packages included:
  2524. '''
  2525. def bundle_requirements(self):
  2526. parts = [self.BUNDLE_HEADER]
  2527. for req in sorted(
  2528. [req for req in self.requirements.values()
  2529. if not req.comes_from],
  2530. key=lambda x: x.name):
  2531. parts.append('%s==%s\n' % (req.name, req.installed_version))
  2532. parts.append('# These packages were installed to satisfy the above requirements:\n')
  2533. for req in sorted(
  2534. [req for req in self.requirements.values()
  2535. if req.comes_from],
  2536. key=lambda x: x.name):
  2537. parts.append('%s==%s\n' % (req.name, req.installed_version))
  2538. ## FIXME: should we do something with self.unnamed_requirements?
  2539. return ''.join(parts)
  2540. def _clean_zip_name(self, name, prefix):
  2541. assert name.startswith(prefix+'/'), (
  2542. "name %r doesn't start with prefix %r" % (name, prefix))
  2543. name = name[len(prefix)+1:]
  2544. name = name.replace(os.path.sep, '/')
  2545. return name
  2546. class HTMLPage(object):
  2547. """Represents one page, along with its URL"""
  2548. ## FIXME: these regexes are horrible hacks:
  2549. _homepage_re = re.compile(r'<th>\s*home\s*page', re.I)
  2550. _download_re = re.compile(r'<th>\s*download\s+url', re.I)
  2551. ## These aren't so aweful:
  2552. _rel_re = re.compile("""<[^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*>""", re.I)
  2553. _href_re = re.compile('href=(?:"([^"]*)"|\'([^\']*)\'|([^>\\s\\n]*))', re.I|re.S)
  2554. _base_re = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.I)
  2555. def __init__(self, content, url, headers=None):
  2556. self.content = content
  2557. self.url = url
  2558. self.headers = headers
  2559. def __str__(self):
  2560. return self.url
  2561. @classmethod
  2562. def get_page(cls, link, req, cache=None, skip_archives=True):
  2563. url = link.url
  2564. url = url.split('#', 1)[0]
  2565. if cache.too_many_failures(url):
  2566. return None
  2567. if url.lower().startswith('svn'):
  2568. logger.debug('Cannot look at svn URL %s' % link)
  2569. return None
  2570. if cache is not None:
  2571. inst = cache.get_page(url)
  2572. if inst is not None:
  2573. return inst
  2574. try:
  2575. if skip_archives:
  2576. if cache is not None:
  2577. if cache.is_archive(url):
  2578. return None
  2579. filename = link.filename
  2580. for bad_ext in ['.tar', '.tar.gz', '.tar.bz2', '.tgz', '.zip']:
  2581. if filename.endswith(bad_ext):
  2582. content_type = cls._get_content_type(url)
  2583. if content_type.lower().startswith('text/html'):
  2584. break
  2585. else:
  2586. logger.debug('Skipping page %s because of Content-Type: %s' % (link, content_type))
  2587. if cache is not None:
  2588. cache.set_is_archive(url)
  2589. return None
  2590. logger.debug('Getting page %s' % url)
  2591. resp = urllib2.urlopen(url)
  2592. real_url = resp.geturl()
  2593. headers = resp.info()
  2594. inst = cls(resp.read(), real_url, headers)
  2595. except (urllib2.HTTPError, urllib2.URLError, socket.timeout, socket.error), e:
  2596. desc = str(e)
  2597. if isinstance(e, socket.timeout):
  2598. log_meth = logger.info
  2599. level =1
  2600. desc = 'timed out'
  2601. elif isinstance(e, urllib2.URLError):
  2602. log_meth = logger.info
  2603. if hasattr(e, 'reason') and isinstance(e.reason, socket.timeout):
  2604. desc = 'timed out'
  2605. level = 1
  2606. else:
  2607. level = 2
  2608. elif isinstance(e, urllib2.HTTPError) and e.code == 404:
  2609. ## FIXME: notify?
  2610. log_meth = logger.info
  2611. level = 2
  2612. else:
  2613. log_meth = logger.info
  2614. level = 1
  2615. log_meth('Could not fetch URL %s: %s' % (link, desc))
  2616. log_meth('Will skip URL %s when looking for download links for %s' % (link.url, req))
  2617. if cache is not None:
  2618. cache.add_page_failure(url, level)
  2619. return None
  2620. if cache is not None:
  2621. cache.add_page([url, real_url], inst)
  2622. return inst
  2623. @staticmethod
  2624. def _get_content_type(url):
  2625. """Get the Content-Type of the given url, using a HEAD request"""
  2626. scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
  2627. if scheme == 'http':
  2628. ConnClass = httplib.HTTPConnection
  2629. elif scheme == 'https':
  2630. ConnClass = httplib.HTTPSConnection
  2631. else:
  2632. ## FIXME: some warning or something?
  2633. ## assertion error?
  2634. return ''
  2635. if query:
  2636. path += '?' + query
  2637. conn = ConnClass(netloc)
  2638. try:
  2639. conn.request('HEAD', path, headers={'Host': netloc})
  2640. resp = conn.getresponse()
  2641. if resp.status != 200:
  2642. ## FIXME: doesn't handle redirects
  2643. return ''
  2644. return resp.getheader('Content-Type') or ''
  2645. finally:
  2646. conn.close()
  2647. @property
  2648. def base_url(self):
  2649. if not hasattr(self, "_base_url"):
  2650. match = self._base_re.search(self.content)
  2651. if match:
  2652. self._base_url = match.group(1)
  2653. else:
  2654. self._base_url = self.url
  2655. return self._base_url
  2656. @property
  2657. def links(self):
  2658. """Yields all links in the page"""
  2659. for match in self._href_re.finditer(self.content):
  2660. url = match.group(1) or match.group(2) or match.group(3)
  2661. url = self.clean_link(urlparse.urljoin(self.base_url, url))
  2662. yield Link(url, self)
  2663. def rel_links(self):
  2664. for url in self.explicit_rel_links():
  2665. yield url
  2666. for url in self.scraped_rel_links():
  2667. yield url
  2668. def explicit_rel_links(self, rels=('homepage', 'download')):
  2669. """Yields all links with the given relations"""
  2670. for match in self._rel_re.finditer(self.content):
  2671. found_rels = match.group(1).lower().split()
  2672. for rel in rels:
  2673. if rel in found_rels:
  2674. break
  2675. else:
  2676. continue
  2677. match = self._href_re.search(match.group(0))
  2678. if not match:
  2679. continue
  2680. url = match.group(1) or match.group(2) or match.group(3)
  2681. url = self.clean_link(urlparse.urljoin(self.base_url, url))
  2682. yield Link(url, self)
  2683. def scraped_rel_links(self):
  2684. for regex in (self._homepage_re, self._download_re):
  2685. match = regex.search(self.content)
  2686. if not match:
  2687. continue
  2688. href_match = self._href_re.search(self.content, pos=match.end())
  2689. if not href_match:
  2690. continue
  2691. url = match.group(1) or match.group(2) or match.group(3)
  2692. if not url:
  2693. continue
  2694. url = self.clean_link(urlparse.urljoin(self.base_url, url))
  2695. yield Link(url, self)
  2696. _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
  2697. def clean_link(self, url):
  2698. """Makes sure a link is fully encoded. That is, if a ' ' shows up in
  2699. the link, it will be rewritten to %20 (while not over-quoting
  2700. % or other characters)."""
  2701. return self._clean_re.sub(
  2702. lambda match: '%%%2x' % ord(match.group(0)), url)
  2703. class PageCache(object):
  2704. """Cache of HTML pages"""
  2705. failure_limit = 3
  2706. def __init__(self):
  2707. self._failures = {}
  2708. self._pages = {}
  2709. self._archives = {}
  2710. def too_many_failures(self, url):
  2711. return self._failures.get(url, 0) >= self.failure_limit
  2712. def get_page(self, url):
  2713. return self._pages.get(url)
  2714. def is_archive(self, url):
  2715. return self._archives.get(url, False)
  2716. def set_is_archive(self, url, value=True):
  2717. self._archives[url] = value
  2718. def add_page_failure(self, url, level):
  2719. self._failures[url] = self._failures.get(url, 0)+level
  2720. def add_page(self, urls, page):
  2721. for url in urls:
  2722. self._pages[url] = page
  2723. class Link(object):
  2724. def __init__(self, url, comes_from=None):
  2725. self.url = url
  2726. self.comes_from = comes_from
  2727. def __str__(self):
  2728. if self.comes_from:
  2729. return '%s (from %s)' % (self.url, self.comes_from)
  2730. else:
  2731. return self.url
  2732. def __repr__(self):
  2733. return '<Link %s>' % self
  2734. @property
  2735. def filename(self):
  2736. url = self.url
  2737. url = url.split('#', 1)[0]
  2738. url = url.split('?', 1)[0]
  2739. url = url.rstrip('/')
  2740. name = posixpath.basename(url)
  2741. assert name, (
  2742. 'URL %r produced no filename' % url)
  2743. return name
  2744. @property
  2745. def scheme(self):
  2746. return urlparse.urlsplit(self.url)[0]
  2747. @property
  2748. def path(self):
  2749. return urlparse.urlsplit(self.url)[2]
  2750. def splitext(self):
  2751. return splitext(posixpath.basename(self.path.rstrip('/')))
  2752. _egg_fragment_re = re.compile(r'#egg=([^&]*)')
  2753. @property
  2754. def egg_fragment(self):
  2755. match = self._egg_fragment_re.search(self.url)
  2756. if not match:
  2757. return None
  2758. return match.group(1)
  2759. _md5_re = re.compile(r'md5=([a-f0-9]+)')
  2760. @property
  2761. def md5_hash(self):
  2762. match = self._md5_re.search(self.url)
  2763. if match:
  2764. return match.group(1)
  2765. return None
  2766. @property
  2767. def show_url(self):
  2768. return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0])
  2769. ############################################################
  2770. ## Writing freeze files
  2771. class FrozenRequirement(object):
  2772. def __init__(self, name, req, editable, comments=()):
  2773. self.name = name
  2774. self.req = req
  2775. self.editable = editable
  2776. self.comments = comments
  2777. _rev_re = re.compile(r'-r(\d+)$')
  2778. _date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
  2779. @classmethod
  2780. def from_dist(cls, dist, dependency_links, find_tags=False):
  2781. location = os.path.normcase(os.path.abspath(dist.location))
  2782. comments = []
  2783. if vcs.get_backend_name(location):
  2784. editable = True
  2785. req = get_src_requirement(dist, location, find_tags)
  2786. if req is None:
  2787. logger.warn('Could not determine repository location of %s' % location)
  2788. comments.append('## !! Could not determine repository location')
  2789. req = dist.as_requirement()
  2790. editable = False
  2791. else:
  2792. editable = False
  2793. req = dist.as_requirement()
  2794. specs = req.specs
  2795. assert len(specs) == 1 and specs[0][0] == '=='
  2796. version = specs[0][1]
  2797. ver_match = cls._rev_re.search(version)
  2798. date_match = cls._date_re.search(version)
  2799. if ver_match or date_match:
  2800. svn_backend = vcs.get_backend('svn')
  2801. if svn_backend:
  2802. svn_location = svn_backend(
  2803. ).get_location(dist, dependency_links)
  2804. if not svn_location:
  2805. logger.warn(
  2806. 'Warning: cannot find svn location for %s' % req)
  2807. comments.append('## FIXME: could not find svn URL in dependency_links for this package:')
  2808. else:
  2809. comments.append('# Installing as editable to satisfy requirement %s:' % req)
  2810. if ver_match:
  2811. rev = ver_match.group(1)
  2812. else:
  2813. rev = '{%s}' % date_match.group(1)
  2814. editable = True
  2815. req = 'svn+%s@%s#egg=%s' % (svn_location, rev, cls.egg_name(dist))
  2816. return cls(dist.project_name, req, editable, comments)
  2817. @staticmethod
  2818. def egg_name(dist):
  2819. name = dist.egg_name()
  2820. match = re.search(r'-py\d\.\d$', name)
  2821. if match:
  2822. name = name[:match.start()]
  2823. return name
  2824. def __str__(self):
  2825. req = self.req
  2826. if self.editable:
  2827. req = '-e %s' % req
  2828. return '\n'.join(list(self.comments)+[str(req)])+'\n'
  2829. class VersionControl(object):
  2830. name = ''
  2831. dirname = ''
  2832. def __init__(self, url=None, *args, **kwargs):
  2833. self.url = url
  2834. self._cmd = None
  2835. super(VersionControl, self).__init__(*args, **kwargs)
  2836. def _filter(self, line):
  2837. return (Logger.INFO, line)
  2838. @property
  2839. def cmd(self):
  2840. if self._cmd is not None:
  2841. return self._cmd
  2842. command = find_command(self.name)
  2843. if command is None:
  2844. raise BadCommand('Cannot find command %s' % self.name)
  2845. logger.info('Found command %s at %s' % (self.name, command))
  2846. self._cmd = command
  2847. return command
  2848. def get_url_rev(self):
  2849. """
  2850. Returns the correct repository URL and revision by parsing the given
  2851. repository URL
  2852. """
  2853. url = self.url.split('+', 1)[1]
  2854. scheme, netloc, path, query, frag = urlparse.urlsplit(url)
  2855. rev = None
  2856. if '@' in path:
  2857. path, rev = path.rsplit('@', 1)
  2858. url = urlparse.urlunsplit((scheme, netloc, path, query, ''))
  2859. return url, rev
  2860. def get_info(self, location):
  2861. """
  2862. Returns (url, revision), where both are strings
  2863. """
  2864. assert not location.rstrip('/').endswith(self.dirname), 'Bad directory: %s' % location
  2865. return self.get_url(location), self.get_revision(location)
  2866. def normalize_url(self, url):
  2867. """
  2868. Normalize a URL for comparison by unquoting it and removing any trailing slash.
  2869. """
  2870. return urllib.unquote(url).rstrip('/')
  2871. def compare_urls(self, url1, url2):
  2872. """
  2873. Compare two repo URLs for identity, ignoring incidental differences.
  2874. """
  2875. return (self.normalize_url(url1) == self.normalize_url(url2))
  2876. def parse_vcs_bundle_file(self, content):
  2877. """
  2878. Takes the contents of the bundled text file that explains how to revert
  2879. the stripped off version control data of the given package and returns
  2880. the URL and revision of it.
  2881. """
  2882. raise NotImplementedError
  2883. def obtain(self, dest):
  2884. """
  2885. Called when installing or updating an editable package, takes the
  2886. source path of the checkout.
  2887. """
  2888. raise NotImplementedError
  2889. def switch(self, dest, url, rev_options):
  2890. """
  2891. Switch the repo at ``dest`` to point to ``URL``.
  2892. """
  2893. raise NotImplemented
  2894. def update(self, dest, rev_options):
  2895. """
  2896. Update an already-existing repo to the given ``rev_options``.
  2897. """
  2898. raise NotImplementedError
  2899. def check_destination(self, dest, url, rev_options, rev_display):
  2900. """
  2901. Prepare a location to receive a checkout/clone.
  2902. Return True if the location is ready for (and requires) a
  2903. checkout/clone, False otherwise.
  2904. """
  2905. checkout = True
  2906. prompt = False
  2907. if os.path.exists(dest):
  2908. checkout = False
  2909. if os.path.exists(os.path.join(dest, self.dirname)):
  2910. existing_url = self.get_url(dest)
  2911. if self.compare_urls(existing_url, url):
  2912. logger.info('%s in %s exists, and has correct URL (%s)'
  2913. % (self.repo_name.title(), display_path(dest), url))
  2914. logger.notify('Updating %s %s%s'
  2915. % (display_path(dest), self.repo_name, rev_display))
  2916. self.update(dest, rev_options)
  2917. else:
  2918. logger.warn('%s %s in %s exists with URL %s'
  2919. % (self.name, self.repo_name, display_path(dest), existing_url))
  2920. prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ', ('s', 'i', 'w', 'b'))
  2921. else:
  2922. logger.warn('Directory %s already exists, and is not a %s %s.'
  2923. % (dest, self.name, self.repo_name))
  2924. prompt = ('(i)gnore, (w)ipe, (b)ackup ', ('i', 'w', 'b'))
  2925. if prompt:
  2926. logger.warn('The plan is to install the %s repository %s'
  2927. % (self.name, url))
  2928. response = ask('What to do? %s' % prompt[0], prompt[1])
  2929. if response == 's':
  2930. logger.notify('Switching %s %s to %s%s'
  2931. % (self.repo_name, display_path(dest), url, rev_display))
  2932. self.switch(dest, url, rev_options)
  2933. elif response == 'i':
  2934. # do nothing
  2935. pass
  2936. elif response == 'w':
  2937. logger.warn('Deleting %s' % display_path(dest))
  2938. shutil.rmtree(dest)
  2939. checkout = True
  2940. elif response == 'b':
  2941. dest_dir = backup_dir(dest)
  2942. logger.warn('Backing up %s to %s'
  2943. % (display_path(dest), dest_dir))
  2944. shutil.move(dest, dest_dir)
  2945. checkout = True
  2946. return checkout
  2947. def unpack(self, location):
  2948. raise NotImplementedError
  2949. def get_src_requirement(self, dist, location, find_tags=False):
  2950. raise NotImplementedError
  2951. _svn_xml_url_re = re.compile('url="([^"]+)"')
  2952. _svn_rev_re = re.compile('committed-rev="(\d+)"')
  2953. _svn_url_re = re.compile(r'URL: (.+)')
  2954. _svn_revision_re = re.compile(r'Revision: (.+)')
  2955. class Subversion(VersionControl):
  2956. name = 'svn'
  2957. dirname = '.svn'
  2958. repo_name = 'checkout'
  2959. schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https')
  2960. bundle_file = 'svn-checkout.txt'
  2961. guide = ('# This was an svn checkout; to make it a checkout again run:\n'
  2962. 'svn checkout --force -r %(rev)s %(url)s .\n')
  2963. def get_info(self, location):
  2964. """Returns (url, revision), where both are strings"""
  2965. assert not location.rstrip('/').endswith(self.dirname), 'Bad directory: %s' % location
  2966. output = call_subprocess(
  2967. ['svn', 'info', location], show_stdout=False, extra_environ={'LANG': 'C'})
  2968. match = _svn_url_re.search(output)
  2969. if not match:
  2970. logger.warn('Cannot determine URL of svn checkout %s' % display_path(location))
  2971. logger.info('Output that cannot be parsed: \n%s' % output)
  2972. return None, None
  2973. url = match.group(1).strip()
  2974. match = _svn_revision_re.search(output)
  2975. if not match:
  2976. logger.warn('Cannot determine revision of svn checkout %s' % display_path(location))
  2977. logger.info('Output that cannot be parsed: \n%s' % output)
  2978. return url, None
  2979. return url, match.group(1)
  2980. def get_url(self, location):
  2981. return self.get_info(location)[0]
  2982. def get_revision(self, location):
  2983. return self.get_info(location)[1]
  2984. def parse_vcs_bundle_file(self, content):
  2985. for line in content.splitlines():
  2986. if not line.strip() or line.strip().startswith('#'):
  2987. continue
  2988. match = re.search(r'^-r\s*([^ ])?', line)
  2989. if not match:
  2990. return None, None
  2991. rev = match.group(1)
  2992. rest = line[match.end():].strip().split(None, 1)[0]
  2993. return rest, rev
  2994. return None, None
  2995. def unpack(self, location):
  2996. """Check out the svn repository at the url to the destination location"""
  2997. url, rev = self.get_url_rev()
  2998. logger.notify('Checking out svn repository %s to %s' % (url, location))
  2999. logger.indent += 2
  3000. try:
  3001. if os.path.exists(location):
  3002. # Subversion doesn't like to check out over an existing directory
  3003. # --force fixes this, but was only added in svn 1.5
  3004. shutil.rmtree(location, onerror=rmtree_errorhandler)
  3005. call_subprocess(
  3006. ['svn', 'checkout', url, location],
  3007. filter_stdout=self._filter, show_stdout=False)
  3008. finally:
  3009. logger.indent -= 2
  3010. def export(self, location):
  3011. """Export the svn repository at the url to the destination location"""
  3012. url, rev = self.get_url_rev()
  3013. logger.notify('Checking out svn repository %s to %s' % (url, location))
  3014. logger.indent += 2
  3015. try:
  3016. if os.path.exists(location):
  3017. # Subversion doesn't like to check out over an existing directory
  3018. # --force fixes this, but was only added in svn 1.5
  3019. shutil.rmtree(location, onerror=rmtree_errorhandler)
  3020. call_subprocess(
  3021. ['svn', 'export', url, location],
  3022. filter_stdout=self._filter, show_stdout=False)
  3023. finally:
  3024. logger.indent -= 2
  3025. def switch(self, dest, url, rev_options):
  3026. call_subprocess(
  3027. ['svn', 'switch'] + rev_options + [url, dest])
  3028. def update(self, dest, rev_options):
  3029. call_subprocess(
  3030. ['svn', 'update'] + rev_options + [dest])
  3031. def obtain(self, dest):
  3032. url, rev = self.get_url_rev()
  3033. if rev:
  3034. rev_options = ['-r', rev]
  3035. rev_display = ' (to revision %s)' % rev
  3036. else:
  3037. rev_options = []
  3038. rev_display = ''
  3039. if self.check_destination(dest, url, rev_options, rev_display):
  3040. logger.notify('Checking out %s%s to %s'
  3041. % (url, rev_display, display_path(dest)))
  3042. call_subprocess(
  3043. ['svn', 'checkout', '-q'] + rev_options + [url, dest])
  3044. def get_location(self, dist, dependency_links):
  3045. egg_fragment_re = re.compile(r'#egg=(.*)$')
  3046. for url in dependency_links:
  3047. egg_fragment = Link(url).egg_fragment
  3048. if not egg_fragment:
  3049. continue
  3050. if '-' in egg_fragment:
  3051. ## FIXME: will this work when a package has - in the name?
  3052. key = '-'.join(egg_fragment.split('-')[:-1]).lower()
  3053. else:
  3054. key = egg_fragment
  3055. if key == dist.key:
  3056. return url.split('#', 1)[0]
  3057. return None
  3058. def get_revision(self, location):
  3059. """
  3060. Return the maximum revision for all files under a given location
  3061. """
  3062. # Note: taken from setuptools.command.egg_info
  3063. revision = 0
  3064. for base, dirs, files in os.walk(location):
  3065. if self.dirname not in dirs:
  3066. dirs[:] = []
  3067. continue # no sense walking uncontrolled subdirs
  3068. dirs.remove(self.dirname)
  3069. entries_fn = os.path.join(base, self.dirname, 'entries')
  3070. if not os.path.exists(entries_fn):
  3071. ## FIXME: should we warn?
  3072. continue
  3073. f = open(entries_fn)
  3074. data = f.read()
  3075. f.close()
  3076. if data.startswith('8') or data.startswith('9') or data.startswith('10'):
  3077. data = map(str.splitlines,data.split('\n\x0c\n'))
  3078. del data[0][0] # get rid of the '8'
  3079. dirurl = data[0][3]
  3080. revs = [int(d[9]) for d in data if len(d)>9 and d[9]]+[0]
  3081. if revs:
  3082. localrev = max(revs)
  3083. else:
  3084. localrev = 0
  3085. elif data.startswith('<?xml'):
  3086. dirurl = _svn_xml_url_re.search(data).group(1) # get repository URL
  3087. revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)]+[0]
  3088. if revs:
  3089. localrev = max(revs)
  3090. else:
  3091. localrev = 0
  3092. else:
  3093. logger.warn("Unrecognized .svn/entries format; skipping %s", base)
  3094. dirs[:] = []
  3095. continue
  3096. if base == location:
  3097. base_url = dirurl+'/' # save the root url
  3098. elif not dirurl.startswith(base_url):
  3099. dirs[:] = []
  3100. continue # not part of the same svn tree, skip it
  3101. revision = max(revision, localrev)
  3102. return revision
  3103. def get_url(self, location):
  3104. # In cases where the source is in a subdirectory, not alongside setup.py
  3105. # we have to look up in the location until we find a real setup.py
  3106. orig_location = location
  3107. while not os.path.exists(os.path.join(location, 'setup.py')):
  3108. last_location = location
  3109. location = os.path.dirname(location)
  3110. if location == last_location:
  3111. # We've traversed up to the root of the filesystem without finding setup.py
  3112. logger.warn("Could not find setup.py for directory %s (tried all parent directories)"
  3113. % orig_location)
  3114. return None
  3115. f = open(os.path.join(location, self.dirname, 'entries'))
  3116. data = f.read()
  3117. f.close()
  3118. if data.startswith('8') or data.startswith('9') or data.startswith('10'):
  3119. data = map(str.splitlines,data.split('\n\x0c\n'))
  3120. del data[0][0] # get rid of the '8'
  3121. return data[0][3]
  3122. elif data.startswith('<?xml'):
  3123. match = _svn_xml_url_re.search(data)
  3124. if not match:
  3125. raise ValueError('Badly formatted data: %r' % data)
  3126. return match.group(1) # get repository URL
  3127. else:
  3128. logger.warn("Unrecognized .svn/entries format in %s" % location)
  3129. # Or raise exception?
  3130. return None
  3131. def get_tag_revs(self, svn_tag_url):
  3132. stdout = call_subprocess(
  3133. ['svn', 'ls', '-v', svn_tag_url], show_stdout=False)
  3134. results = []
  3135. for line in stdout.splitlines():
  3136. parts = line.split()
  3137. rev = int(parts[0])
  3138. tag = parts[-1].strip('/')
  3139. results.append((tag, rev))
  3140. return results
  3141. def find_tag_match(self, rev, tag_revs):
  3142. best_match_rev = None
  3143. best_tag = None
  3144. for tag, tag_rev in tag_revs:
  3145. if (tag_rev > rev and
  3146. (best_match_rev is None or best_match_rev > tag_rev)):
  3147. # FIXME: Is best_match > tag_rev really possible?
  3148. # or is it a sign something is wacky?
  3149. best_match_rev = tag_rev
  3150. best_tag = tag
  3151. return best_tag
  3152. def get_src_requirement(self, dist, location, find_tags=False):
  3153. repo = self.get_url(location)
  3154. if repo is None:
  3155. return None
  3156. parts = repo.split('/')
  3157. ## FIXME: why not project name?
  3158. egg_project_name = dist.egg_name().split('-', 1)[0]
  3159. rev = self.get_revision(location)
  3160. if parts[-2] in ('tags', 'tag'):
  3161. # It's a tag, perfect!
  3162. full_egg_name = '%s-%s' % (egg_project_name, parts[-1])
  3163. elif parts[-2] in ('branches', 'branch'):
  3164. # It's a branch :(
  3165. full_egg_name = '%s-%s-r%s' % (dist.egg_name(), parts[-1], rev)
  3166. elif parts[-1] == 'trunk':
  3167. # Trunk :-/
  3168. full_egg_name = '%s-dev_r%s' % (dist.egg_name(), rev)
  3169. if find_tags:
  3170. tag_url = '/'.join(parts[:-1]) + '/tags'
  3171. tag_revs = self.get_tag_revs(tag_url)
  3172. match = self.find_tag_match(rev, tag_revs)
  3173. if match:
  3174. logger.notify('trunk checkout %s seems to be equivalent to tag %s' % match)
  3175. repo = '%s/%s' % (tag_url, match)
  3176. full_egg_name = '%s-%s' % (egg_project_name, match)
  3177. else:
  3178. # Don't know what it is
  3179. logger.warn('svn URL does not fit normal structure (tags/branches/trunk): %s' % repo)
  3180. full_egg_name = '%s-dev_r%s' % (egg_project_name, rev)
  3181. return 'svn+%s@%s#egg=%s' % (repo, rev, full_egg_name)
  3182. vcs.register(Subversion)
  3183. class Git(VersionControl):
  3184. name = 'git'
  3185. dirname = '.git'
  3186. repo_name = 'clone'
  3187. schemes = ('git', 'git+http', 'git+ssh', 'git+git')
  3188. bundle_file = 'git-clone.txt'
  3189. guide = ('# This was a Git repo; to make it a repo again run:\n'
  3190. 'git init\ngit remote add origin %(url)s -f\ngit checkout %(rev)s\n')
  3191. def parse_vcs_bundle_file(self, content):
  3192. url = rev = None
  3193. for line in content.splitlines():
  3194. if not line.strip() or line.strip().startswith('#'):
  3195. continue
  3196. url_match = re.search(r'git\s*remote\s*add\s*origin(.*)\s*-f', line)
  3197. if url_match:
  3198. url = url_match.group(1).strip()
  3199. rev_match = re.search(r'^git\s*checkout\s*-q\s*(.*)\s*', line)
  3200. if rev_match:
  3201. rev = rev_match.group(1).strip()
  3202. if url and rev:
  3203. return url, rev
  3204. return None, None
  3205. def unpack(self, location):
  3206. """Clone the Git repository at the url to the destination location"""
  3207. url, rev = self.get_url_rev()
  3208. logger.notify('Cloning Git repository %s to %s' % (url, location))
  3209. logger.indent += 2
  3210. try:
  3211. if os.path.exists(location):
  3212. os.rmdir(location)
  3213. call_subprocess(
  3214. [self.cmd, 'clone', url, location],
  3215. filter_stdout=self._filter, show_stdout=False)
  3216. finally:
  3217. logger.indent -= 2
  3218. def export(self, location):
  3219. """Export the Git repository at the url to the destination location"""
  3220. temp_dir = tempfile.mkdtemp('-export', 'pip-')
  3221. self.unpack(temp_dir)
  3222. try:
  3223. if not location.endswith('/'):
  3224. location = location + '/'
  3225. call_subprocess(
  3226. [self.cmd, 'checkout-index', '-a', '-f', '--prefix', location],
  3227. filter_stdout=self._filter, show_stdout=False, cwd=temp_dir)
  3228. finally:
  3229. shutil.rmtree(temp_dir)
  3230. def check_rev_options(self, rev, dest, rev_options):
  3231. """Check the revision options before checkout to compensate that tags
  3232. and branches may need origin/ as a prefix"""
  3233. if rev is None:
  3234. # bail and use preset
  3235. return rev_options
  3236. revisions = self.get_tag_revs(dest)
  3237. revisions.update(self.get_branch_revs(dest))
  3238. if rev in revisions:
  3239. # if rev is a sha
  3240. return [rev]
  3241. inverse_revisions = dict((v,k) for k, v in revisions.iteritems())
  3242. if rev not in inverse_revisions: # is rev a name or tag?
  3243. origin_rev = 'origin/%s' % rev
  3244. if origin_rev in inverse_revisions:
  3245. rev = inverse_revisions[origin_rev]
  3246. else:
  3247. raise InstallationError("Could not find a tag or branch '%s' in repository %s"
  3248. % (rev, display_path(dest)))
  3249. return [rev]
  3250. def switch(self, dest, url, rev_options):
  3251. call_subprocess(
  3252. [self.cmd, 'config', 'remote.origin.url', url], cwd=dest)
  3253. call_subprocess(
  3254. [self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)
  3255. def update(self, dest, rev_options):
  3256. call_subprocess([self.cmd, 'fetch', '-q'], cwd=dest)
  3257. call_subprocess(
  3258. [self.cmd, 'checkout', '-q', '-f'] + rev_options, cwd=dest)
  3259. def obtain(self, dest):
  3260. url, rev = self.get_url_rev()
  3261. if rev:
  3262. rev_options = [rev]
  3263. rev_display = ' (to revision %s)' % rev
  3264. else:
  3265. rev_options = ['origin/master']
  3266. rev_display = ''
  3267. if self.check_destination(dest, url, rev_options, rev_display):
  3268. logger.notify('Cloning %s%s to %s' % (url, rev_display, display_path(dest)))
  3269. call_subprocess(
  3270. [self.cmd, 'clone', '-q', url, dest])
  3271. rev_options = self.check_rev_options(rev, dest, rev_options)
  3272. call_subprocess(
  3273. [self.cmd, 'checkout', '-q'] + rev_options, cwd=dest)
  3274. def get_url(self, location):
  3275. url = call_subprocess(
  3276. [self.cmd, 'config', 'remote.origin.url'],
  3277. show_stdout=False, cwd=location)
  3278. return url.strip()
  3279. def get_revision(self, location):
  3280. current_rev = call_subprocess(
  3281. [self.cmd, 'rev-parse', 'HEAD'], show_stdout=False, cwd=location)
  3282. return current_rev.strip()
  3283. def get_tag_revs(self, location):
  3284. tags = call_subprocess(
  3285. [self.cmd, 'tag'], show_stdout=False, cwd=location)
  3286. tag_revs = []
  3287. for line in tags.splitlines():
  3288. tag = line.strip()
  3289. rev = call_subprocess(
  3290. [self.cmd, 'rev-parse', tag], show_stdout=False, cwd=location)
  3291. tag_revs.append((rev.strip(), tag))
  3292. tag_revs = dict(tag_revs)
  3293. return tag_revs
  3294. def get_branch_revs(self, location):
  3295. branches = call_subprocess(
  3296. [self.cmd, 'branch', '-r'], show_stdout=False, cwd=location)
  3297. branch_revs = []
  3298. for line in branches.splitlines():
  3299. line = line.split('->')[0].strip()
  3300. branch = "".join([b for b in line.split() if b != '*'])
  3301. rev = call_subprocess(
  3302. [self.cmd, 'rev-parse', branch], show_stdout=False, cwd=location)
  3303. branch_revs.append((rev.strip(), branch))
  3304. branch_revs = dict(branch_revs)
  3305. return branch_revs
  3306. def get_src_requirement(self, dist, location, find_tags):
  3307. repo = self.get_url(location)
  3308. if not repo.lower().startswith('git:'):
  3309. repo = 'git+' + repo
  3310. egg_project_name = dist.egg_name().split('-', 1)[0]
  3311. if not repo:
  3312. return None
  3313. current_rev = self.get_revision(location)
  3314. tag_revs = self.get_tag_revs(location)
  3315. branch_revs = self.get_branch_revs(location)
  3316. if current_rev in tag_revs:
  3317. # It's a tag
  3318. full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
  3319. elif (current_rev in branch_revs and
  3320. branch_revs[current_rev] != 'origin/master'):
  3321. # It's the head of a branch
  3322. full_egg_name = '%s-%s' % (dist.egg_name(),
  3323. branch_revs[current_rev].replace('origin/', ''))
  3324. else:
  3325. full_egg_name = '%s-dev' % dist.egg_name()
  3326. return '%s@%s#egg=%s' % (repo, current_rev, full_egg_name)
  3327. def get_url_rev(self):
  3328. """
  3329. Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
  3330. That's required because although they use SSH they sometimes doesn't
  3331. work with a ssh:// scheme (e.g. Github). But we need a scheme for
  3332. parsing. Hence we remove it again afterwards and return it as a stub.
  3333. """
  3334. if not '://' in self.url:
  3335. self.url = self.url.replace('git+', 'git+ssh://')
  3336. url, rev = super(Git, self).get_url_rev()
  3337. url = url.replace('ssh://', '')
  3338. return url, rev
  3339. return super(Git, self).get_url_rev()
  3340. vcs.register(Git)
  3341. class Mercurial(VersionControl):
  3342. name = 'hg'
  3343. dirname = '.hg'
  3344. repo_name = 'clone'
  3345. schemes = ('hg', 'hg+http', 'hg+ssh')
  3346. bundle_file = 'hg-clone.txt'
  3347. guide = ('# This was a Mercurial repo; to make it a repo again run:\n'
  3348. 'hg init\nhg pull %(url)s\nhg update -r %(rev)s\n')
  3349. def parse_vcs_bundle_file(self, content):
  3350. url = rev = None
  3351. for line in content.splitlines():
  3352. if not line.strip() or line.strip().startswith('#'):
  3353. continue
  3354. url_match = re.search(r'hg\s*pull\s*(.*)\s*', line)
  3355. if url_match:
  3356. url = url_match.group(1).strip()
  3357. rev_match = re.search(r'^hg\s*update\s*-r\s*(.*)\s*', line)
  3358. if rev_match:
  3359. rev = rev_match.group(1).strip()
  3360. if url and rev:
  3361. return url, rev
  3362. return None, None
  3363. def unpack(self, location):
  3364. """Clone the Hg repository at the url to the destination location"""
  3365. url, rev = self.get_url_rev()
  3366. logger.notify('Cloning Mercurial repository %s to %s' % (url, location))
  3367. logger.indent += 2
  3368. try:
  3369. if os.path.exists(location):
  3370. os.rmdir(location)
  3371. call_subprocess(
  3372. ['hg', 'clone', url, location],
  3373. filter_stdout=self._filter, show_stdout=False)
  3374. finally:
  3375. logger.indent -= 2
  3376. def export(self, location):
  3377. """Export the Hg repository at the url to the destination location"""
  3378. temp_dir = tempfile.mkdtemp('-export', 'pip-')
  3379. self.unpack(temp_dir)
  3380. try:
  3381. call_subprocess(
  3382. ['hg', 'archive', location],
  3383. filter_stdout=self._filter, show_stdout=False, cwd=temp_dir)
  3384. finally:
  3385. shutil.rmtree(temp_dir)
  3386. def switch(self, dest, url, rev_options):
  3387. repo_config = os.path.join(dest, self.dirname, 'hgrc')
  3388. config = ConfigParser.SafeConfigParser()
  3389. try:
  3390. config.read(repo_config)
  3391. config.set('paths', 'default', url)
  3392. config_file = open(repo_config, 'w')
  3393. config.write(config_file)
  3394. config_file.close()
  3395. except (OSError, ConfigParser.NoSectionError), e:
  3396. logger.warn(
  3397. 'Could not switch Mercurial repository to %s: %s'
  3398. % (url, e))
  3399. else:
  3400. call_subprocess(['hg', 'update', '-q'] + rev_options, cwd=dest)
  3401. def update(self, dest, rev_options):
  3402. call_subprocess(['hg', 'pull', '-q'], cwd=dest)
  3403. call_subprocess(
  3404. ['hg', 'update', '-q'] + rev_options, cwd=dest)
  3405. def obtain(self, dest):
  3406. url, rev = self.get_url_rev()
  3407. if rev:
  3408. rev_options = [rev]
  3409. rev_display = ' (to revision %s)' % rev
  3410. else:
  3411. rev_options = []
  3412. rev_display = ''
  3413. if self.check_destination(dest, url, rev_options, rev_display):
  3414. logger.notify('Cloning hg %s%s to %s'
  3415. % (url, rev_display, display_path(dest)))
  3416. call_subprocess(['hg', 'clone', '-q', url, dest])
  3417. call_subprocess(['hg', 'update', '-q'] + rev_options, cwd=dest)
  3418. def get_url(self, location):
  3419. url = call_subprocess(
  3420. ['hg', 'showconfig', 'paths.default'],
  3421. show_stdout=False, cwd=location).strip()
  3422. if url.startswith('/') or url.startswith('\\'):
  3423. url = filename_to_url(url)
  3424. return url.strip()
  3425. def get_tag_revs(self, location):
  3426. tags = call_subprocess(
  3427. ['hg', 'tags'], show_stdout=False, cwd=location)
  3428. tag_revs = []
  3429. for line in tags.splitlines():
  3430. tags_match = re.search(r'([\w\d\.-]+)\s*([\d]+):.*$', line)
  3431. if tags_match:
  3432. tag = tags_match.group(1)
  3433. rev = tags_match.group(2)
  3434. tag_revs.append((rev.strip(), tag.strip()))
  3435. return dict(tag_revs)
  3436. def get_branch_revs(self, location):
  3437. branches = call_subprocess(
  3438. ['hg', 'branches'], show_stdout=False, cwd=location)
  3439. branch_revs = []
  3440. for line in branches.splitlines():
  3441. branches_match = re.search(r'([\w\d\.-]+)\s*([\d]+):.*$', line)
  3442. if branches_match:
  3443. branch = branches_match.group(1)
  3444. rev = branches_match.group(2)
  3445. branch_revs.append((rev.strip(), branch.strip()))
  3446. return dict(branch_revs)
  3447. def get_revision(self, location):
  3448. current_revision = call_subprocess(
  3449. ['hg', 'parents', '--template={rev}'],
  3450. show_stdout=False, cwd=location).strip()
  3451. return current_revision
  3452. def get_revision_hash(self, location):
  3453. current_rev_hash = call_subprocess(
  3454. ['hg', 'parents', '--template={node}'],
  3455. show_stdout=False, cwd=location).strip()
  3456. return current_rev_hash
  3457. def get_src_requirement(self, dist, location, find_tags):
  3458. repo = self.get_url(location)
  3459. if not repo.lower().startswith('hg:'):
  3460. repo = 'hg+' + repo
  3461. egg_project_name = dist.egg_name().split('-', 1)[0]
  3462. if not repo:
  3463. return None
  3464. current_rev = self.get_revision(location)
  3465. current_rev_hash = self.get_revision_hash(location)
  3466. tag_revs = self.get_tag_revs(location)
  3467. branch_revs = self.get_branch_revs(location)
  3468. if current_rev in tag_revs:
  3469. # It's a tag
  3470. full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
  3471. elif current_rev in branch_revs:
  3472. # It's the tip of a branch
  3473. full_egg_name = '%s-%s' % (dist.egg_name(), branch_revs[current_rev])
  3474. else:
  3475. full_egg_name = '%s-dev' % dist.egg_name()
  3476. return '%s@%s#egg=%s' % (repo, current_rev_hash, full_egg_name)
  3477. vcs.register(Mercurial)
  3478. class Bazaar(VersionControl):
  3479. name = 'bzr'
  3480. dirname = '.bzr'
  3481. repo_name = 'branch'
  3482. bundle_file = 'bzr-branch.txt'
  3483. schemes = ('bzr', 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp')
  3484. guide = ('# This was a Bazaar branch; to make it a branch again run:\n'
  3485. 'bzr branch -r %(rev)s %(url)s .\n')
  3486. def parse_vcs_bundle_file(self, content):
  3487. url = rev = None
  3488. for line in content.splitlines():
  3489. if not line.strip() or line.strip().startswith('#'):
  3490. continue
  3491. match = re.search(r'^bzr\s*branch\s*-r\s*(\d*)', line)
  3492. if match:
  3493. rev = match.group(1).strip()
  3494. url = line[match.end():].strip().split(None, 1)[0]
  3495. if url and rev:
  3496. return url, rev
  3497. return None, None
  3498. def unpack(self, location):
  3499. """Get the bzr branch at the url to the destination location"""
  3500. url, rev = self.get_url_rev()
  3501. logger.notify('Checking out bzr repository %s to %s' % (url, location))
  3502. logger.indent += 2
  3503. try:
  3504. if os.path.exists(location):
  3505. os.rmdir(location)
  3506. call_subprocess(
  3507. [self.cmd, 'branch', url, location],
  3508. filter_stdout=self._filter, show_stdout=False)
  3509. finally:
  3510. logger.indent -= 2
  3511. def export(self, location):
  3512. """Export the Bazaar repository at the url to the destination location"""
  3513. temp_dir = tempfile.mkdtemp('-export', 'pip-')
  3514. self.unpack(temp_dir)
  3515. if os.path.exists(location):
  3516. # Remove the location to make sure Bazaar can export it correctly
  3517. shutil.rmtree(location, onerror=rmtree_errorhandler)
  3518. try:
  3519. call_subprocess([self.cmd, 'export', location], cwd=temp_dir,
  3520. filter_stdout=self._filter, show_stdout=False)
  3521. finally:
  3522. shutil.rmtree(temp_dir)
  3523. def switch(self, dest, url, rev_options):
  3524. call_subprocess([self.cmd, 'switch', url], cwd=dest)
  3525. def update(self, dest, rev_options):
  3526. call_subprocess(
  3527. [self.cmd, 'pull', '-q'] + rev_options, cwd=dest)
  3528. def obtain(self, dest):
  3529. url, rev = self.get_url_rev()
  3530. if rev:
  3531. rev_options = ['-r', rev]
  3532. rev_display = ' (to revision %s)' % rev
  3533. else:
  3534. rev_options = []
  3535. rev_display = ''
  3536. if self.check_destination(dest, url, rev_options, rev_display):
  3537. logger.notify('Checking out %s%s to %s'
  3538. % (url, rev_display, display_path(dest)))
  3539. call_subprocess(
  3540. [self.cmd, 'branch', '-q'] + rev_options + [url, dest])
  3541. def get_url_rev(self):
  3542. # hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it
  3543. url, rev = super(Bazaar, self).get_url_rev()
  3544. if url.startswith('ssh://'):
  3545. url = 'bzr+' + url
  3546. return url, rev
  3547. def get_url(self, location):
  3548. urls = call_subprocess(
  3549. [self.cmd, 'info'], show_stdout=False, cwd=location)
  3550. for line in urls.splitlines():
  3551. line = line.strip()
  3552. for x in ('checkout of branch: ',
  3553. 'parent branch: '):
  3554. if line.startswith(x):
  3555. return line.split(x)[1]
  3556. return None
  3557. def get_revision(self, location):
  3558. revision = call_subprocess(
  3559. [self.cmd, 'revno'], show_stdout=False, cwd=location)
  3560. return revision.splitlines()[-1]
  3561. def get_tag_revs(self, location):
  3562. tags = call_subprocess(
  3563. [self.cmd, 'tags'], show_stdout=False, cwd=location)
  3564. tag_revs = []
  3565. for line in tags.splitlines():
  3566. tags_match = re.search(r'([.\w-]+)\s*(.*)$', line)
  3567. if tags_match:
  3568. tag = tags_match.group(1)
  3569. rev = tags_match.group(2)
  3570. tag_revs.append((rev.strip(), tag.strip()))
  3571. return dict(tag_revs)
  3572. def get_src_requirement(self, dist, location, find_tags):
  3573. repo = self.get_url(location)
  3574. if not repo.lower().startswith('bzr:'):
  3575. repo = 'bzr+' + repo
  3576. egg_project_name = dist.egg_name().split('-', 1)[0]
  3577. if not repo:
  3578. return None
  3579. current_rev = self.get_revision(location)
  3580. tag_revs = self.get_tag_revs(location)
  3581. if current_rev in tag_revs:
  3582. # It's a tag
  3583. tag = tag_revs.get(current_rev, current_rev)
  3584. full_egg_name = '%s-%s' % (egg_project_name, tag_revs[current_rev])
  3585. else:
  3586. full_egg_name = '%s-dev_r%s' % (dist.egg_name(), current_rev)
  3587. return '%s@%s#egg=%s' % (repo, current_rev, full_egg_name)
  3588. vcs.register(Bazaar)
  3589. def get_src_requirement(dist, location, find_tags):
  3590. version_control = vcs.get_backend_from_location(location)
  3591. if version_control:
  3592. return version_control().get_src_requirement(dist, location, find_tags)
  3593. logger.warn('cannot determine version of editable source in %s (is not SVN checkout, Git clone, Mercurial clone or Bazaar branch)' % location)
  3594. return dist.as_requirement()
  3595. ############################################################
  3596. ## Requirement files
  3597. _scheme_re = re.compile(r'^(http|https|file):', re.I)
  3598. _url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I)
  3599. def get_file_content(url, comes_from=None):
  3600. """Gets the content of a file; it may be a filename, file: URL, or
  3601. http: URL. Returns (location, content)"""
  3602. match = _scheme_re.search(url)
  3603. if match:
  3604. scheme = match.group(1).lower()
  3605. if (scheme == 'file' and comes_from
  3606. and comes_from.startswith('http')):
  3607. raise InstallationError(
  3608. 'Requirements file %s references URL %s, which is local'
  3609. % (comes_from, url))
  3610. if scheme == 'file':
  3611. path = url.split(':', 1)[1]
  3612. path = path.replace('\\', '/')
  3613. match = _url_slash_drive_re.match(path)
  3614. if match:
  3615. path = match.group(1) + ':' + path.split('|', 1)[1]
  3616. path = urllib.unquote(path)
  3617. if path.startswith('/'):
  3618. path = '/' + path.lstrip('/')
  3619. url = path
  3620. else:
  3621. ## FIXME: catch some errors
  3622. resp = urllib2.urlopen(url)
  3623. return resp.geturl(), resp.read()
  3624. f = open(url)
  3625. content = f.read()
  3626. f.close()
  3627. return url, content
  3628. def parse_requirements(filename, finder=None, comes_from=None, options=None):
  3629. skip_match = None
  3630. skip_regex = options.skip_requirements_regex
  3631. if skip_regex:
  3632. skip_match = re.compile(skip_regex)
  3633. filename, content = get_file_content(filename, comes_from=comes_from)
  3634. for line_number, line in enumerate(content.splitlines()):
  3635. line_number += 1
  3636. line = line.strip()
  3637. if not line or line.startswith('#'):
  3638. continue
  3639. if skip_match and skip_match.search(line):
  3640. continue
  3641. if line.startswith('-r') or line.startswith('--requirement'):
  3642. if line.startswith('-r'):
  3643. req_url = line[2:].strip()
  3644. else:
  3645. req_url = line[len('--requirement'):].strip().strip('=')
  3646. if _scheme_re.search(filename):
  3647. # Relative to a URL
  3648. req_url = urlparse.urljoin(filename, url)
  3649. elif not _scheme_re.search(req_url):
  3650. req_url = os.path.join(os.path.dirname(filename), req_url)
  3651. for item in parse_requirements(req_url, finder, comes_from=filename, options=options):
  3652. yield item
  3653. elif line.startswith('-Z') or line.startswith('--always-unzip'):
  3654. # No longer used, but previously these were used in
  3655. # requirement files, so we'll ignore.
  3656. pass
  3657. elif finder and line.startswith('-f') or line.startswith('--find-links'):
  3658. if line.startswith('-f'):
  3659. line = line[2:].strip()
  3660. else:
  3661. line = line[len('--find-links'):].strip().lstrip('=')
  3662. ## FIXME: it would be nice to keep track of the source of
  3663. ## the find_links:
  3664. finder.find_links.append(line)
  3665. elif line.startswith('-i') or line.startswith('--index-url'):
  3666. if line.startswith('-i'):
  3667. line = line[2:].strip()
  3668. else:
  3669. line = line[len('--index-url'):].strip().lstrip('=')
  3670. finder.index_urls = [line]
  3671. elif line.startswith('--extra-index-url'):
  3672. line = line[len('--extra-index-url'):].strip().lstrip('=')
  3673. finder.index_urls.append(line)
  3674. else:
  3675. comes_from = '-r %s (line %s)' % (filename, line_number)
  3676. if line.startswith('-e') or line.startswith('--editable'):
  3677. if line.startswith('-e'):
  3678. line = line[2:].strip()
  3679. else:
  3680. line = line[len('--editable'):].strip()
  3681. req = InstallRequirement.from_editable(
  3682. line, comes_from=comes_from, default_vcs=options.default_vcs)
  3683. else:
  3684. req = InstallRequirement.from_line(line, comes_from)
  3685. yield req
  3686. ############################################################
  3687. ## Logging
  3688. class Logger(object):
  3689. """
  3690. Logging object for use in command-line script. Allows ranges of
  3691. levels, to avoid some redundancy of displayed information.
  3692. """
  3693. VERBOSE_DEBUG = logging.DEBUG-1
  3694. DEBUG = logging.DEBUG
  3695. INFO = logging.INFO
  3696. NOTIFY = (logging.INFO+logging.WARN)/2
  3697. WARN = WARNING = logging.WARN
  3698. ERROR = logging.ERROR
  3699. FATAL = logging.FATAL
  3700. LEVELS = [VERBOSE_DEBUG, DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL]
  3701. def __init__(self, consumers):
  3702. self.consumers = consumers
  3703. self.indent = 0
  3704. self.explicit_levels = False
  3705. self.in_progress = None
  3706. self.in_progress_hanging = False
  3707. def debug(self, msg, *args, **kw):
  3708. self.log(self.DEBUG, msg, *args, **kw)
  3709. def info(self, msg, *args, **kw):
  3710. self.log(self.INFO, msg, *args, **kw)
  3711. def notify(self, msg, *args, **kw):
  3712. self.log(self.NOTIFY, msg, *args, **kw)
  3713. def warn(self, msg, *args, **kw):
  3714. self.log(self.WARN, msg, *args, **kw)
  3715. def error(self, msg, *args, **kw):
  3716. self.log(self.WARN, msg, *args, **kw)
  3717. def fatal(self, msg, *args, **kw):
  3718. self.log(self.FATAL, msg, *args, **kw)
  3719. def log(self, level, msg, *args, **kw):
  3720. if args:
  3721. if kw:
  3722. raise TypeError(
  3723. "You may give positional or keyword arguments, not both")
  3724. args = args or kw
  3725. rendered = None
  3726. for consumer_level, consumer in self.consumers:
  3727. if self.level_matches(level, consumer_level):
  3728. if (self.in_progress_hanging
  3729. and consumer in (sys.stdout, sys.stderr)):
  3730. self.in_progress_hanging = False
  3731. sys.stdout.write('\n')
  3732. sys.stdout.flush()
  3733. if rendered is None:
  3734. if args:
  3735. rendered = msg % args
  3736. else:
  3737. rendered = msg
  3738. rendered = ' '*self.indent + rendered
  3739. if self.explicit_levels:
  3740. ## FIXME: should this be a name, not a level number?
  3741. rendered = '%02i %s' % (level, rendered)
  3742. if hasattr(consumer, 'write'):
  3743. consumer.write(rendered+'\n')
  3744. else:
  3745. consumer(rendered)
  3746. def start_progress(self, msg):
  3747. assert not self.in_progress, (
  3748. "Tried to start_progress(%r) while in_progress %r"
  3749. % (msg, self.in_progress))
  3750. if self.level_matches(self.NOTIFY, self._stdout_level()):
  3751. sys.stdout.write(' '*self.indent + msg)
  3752. sys.stdout.flush()
  3753. self.in_progress_hanging = True
  3754. else:
  3755. self.in_progress_hanging = False
  3756. self.in_progress = msg
  3757. self.last_message = None
  3758. def end_progress(self, msg='done.'):
  3759. assert self.in_progress, (
  3760. "Tried to end_progress without start_progress")
  3761. if self.stdout_level_matches(self.NOTIFY):
  3762. if not self.in_progress_hanging:
  3763. # Some message has been printed out since start_progress
  3764. sys.stdout.write('...' + self.in_progress + msg + '\n')
  3765. sys.stdout.flush()
  3766. else:
  3767. # These erase any messages shown with show_progress (besides .'s)
  3768. logger.show_progress('')
  3769. logger.show_progress('')
  3770. sys.stdout.write(msg + '\n')
  3771. sys.stdout.flush()
  3772. self.in_progress = None
  3773. self.in_progress_hanging = False
  3774. def show_progress(self, message=None):
  3775. """If we are in a progress scope, and no log messages have been
  3776. shown, write out another '.'"""
  3777. if self.in_progress_hanging:
  3778. if message is None:
  3779. sys.stdout.write('.')
  3780. sys.stdout.flush()
  3781. else:
  3782. if self.last_message:
  3783. padding = ' ' * max(0, len(self.last_message)-len(message))
  3784. else:
  3785. padding = ''
  3786. sys.stdout.write('\r%s%s%s%s' % (' '*self.indent, self.in_progress, message, padding))
  3787. sys.stdout.flush()
  3788. self.last_message = message
  3789. def stdout_level_matches(self, level):
  3790. """Returns true if a message at this level will go to stdout"""
  3791. return self.level_matches(level, self._stdout_level())
  3792. def _stdout_level(self):
  3793. """Returns the level that stdout runs at"""
  3794. for level, consumer in self.consumers:
  3795. if consumer is sys.stdout:
  3796. return level
  3797. return self.FATAL
  3798. def level_matches(self, level, consumer_level):
  3799. """
  3800. >>> l = Logger()
  3801. >>> l.level_matches(3, 4)
  3802. False
  3803. >>> l.level_matches(3, 2)
  3804. True
  3805. >>> l.level_matches(slice(None, 3), 3)
  3806. False
  3807. >>> l.level_matches(slice(None, 3), 2)
  3808. True
  3809. >>> l.level_matches(slice(1, 3), 1)
  3810. True
  3811. >>> l.level_matches(slice(2, 3), 1)
  3812. False
  3813. """
  3814. if isinstance(level, slice):
  3815. start, stop = level.start, level.stop
  3816. if start is not None and start > consumer_level:
  3817. return False
  3818. if stop is not None or stop <= consumer_level:
  3819. return False
  3820. return True
  3821. else:
  3822. return level >= consumer_level
  3823. @classmethod
  3824. def level_for_integer(cls, level):
  3825. levels = cls.LEVELS
  3826. if level < 0:
  3827. return levels[0]
  3828. if level >= len(levels):
  3829. return levels[-1]
  3830. return levels[level]
  3831. def move_stdout_to_stderr(self):
  3832. to_remove = []
  3833. to_add = []
  3834. for consumer_level, consumer in self.consumers:
  3835. if consumer == sys.stdout:
  3836. to_remove.append((consumer_level, consumer))
  3837. to_add.append((consumer_level, sys.stderr))
  3838. for item in to_remove:
  3839. self.consumers.remove(item)
  3840. self.consumers.extend(to_add)
  3841. def call_subprocess(cmd, show_stdout=True,
  3842. filter_stdout=None, cwd=None,
  3843. raise_on_returncode=True,
  3844. command_level=Logger.DEBUG, command_desc=None,
  3845. extra_environ=None):
  3846. if command_desc is None:
  3847. cmd_parts = []
  3848. for part in cmd:
  3849. if ' ' in part or '\n' in part or '"' in part or "'" in part:
  3850. part = '"%s"' % part.replace('"', '\\"')
  3851. cmd_parts.append(part)
  3852. command_desc = ' '.join(cmd_parts)
  3853. if show_stdout:
  3854. stdout = None
  3855. else:
  3856. stdout = subprocess.PIPE
  3857. logger.log(command_level, "Running command %s" % command_desc)
  3858. env = os.environ.copy()
  3859. if extra_environ:
  3860. env.update(extra_environ)
  3861. try:
  3862. proc = subprocess.Popen(
  3863. cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
  3864. cwd=cwd, env=env)
  3865. except Exception, e:
  3866. logger.fatal(
  3867. "Error %s while executing command %s" % (e, command_desc))
  3868. raise
  3869. all_output = []
  3870. if stdout is not None:
  3871. stdout = proc.stdout
  3872. while 1:
  3873. line = stdout.readline()
  3874. if not line:
  3875. break
  3876. line = line.rstrip()
  3877. all_output.append(line + '\n')
  3878. if filter_stdout:
  3879. level = filter_stdout(line)
  3880. if isinstance(level, tuple):
  3881. level, line = level
  3882. logger.log(level, line)
  3883. if not logger.stdout_level_matches(level):
  3884. logger.show_progress()
  3885. else:
  3886. logger.info(line)
  3887. else:
  3888. returned_stdout, returned_stderr = proc.communicate()
  3889. all_output = [returned_stdout or '']
  3890. proc.wait()
  3891. if proc.returncode:
  3892. if raise_on_returncode:
  3893. if all_output:
  3894. logger.notify('Complete output from command %s:' % command_desc)
  3895. logger.notify('\n'.join(all_output) + '\n----------------------------------------')
  3896. raise InstallationError(
  3897. "Command %s failed with error code %s"
  3898. % (command_desc, proc.returncode))
  3899. else:
  3900. logger.warn(
  3901. "Command %s had error code %s"
  3902. % (command_desc, proc.returncode))
  3903. if stdout is not None:
  3904. return ''.join(all_output)
  3905. ############################################################
  3906. ## Utility functions
  3907. def is_svn_page(html):
  3908. """Returns true if the page appears to be the index page of an svn repository"""
  3909. return (re.search(r'<title>[^<]*Revision \d+:', html)
  3910. and re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I))
  3911. def file_contents(filename):
  3912. fp = open(filename, 'rb')
  3913. try:
  3914. return fp.read()
  3915. finally:
  3916. fp.close()
  3917. def split_leading_dir(path):
  3918. path = str(path)
  3919. path = path.lstrip('/').lstrip('\\')
  3920. if '/' in path and (('\\' in path and path.find('/') < path.find('\\'))
  3921. or '\\' not in path):
  3922. return path.split('/', 1)
  3923. elif '\\' in path:
  3924. return path.split('\\', 1)
  3925. else:
  3926. return path, ''
  3927. def has_leading_dir(paths):
  3928. """Returns true if all the paths have the same leading path name
  3929. (i.e., everything is in one subdirectory in an archive)"""
  3930. common_prefix = None
  3931. for path in paths:
  3932. prefix, rest = split_leading_dir(path)
  3933. if not prefix:
  3934. return False
  3935. elif common_prefix is None:
  3936. common_prefix = prefix
  3937. elif prefix != common_prefix:
  3938. return False
  3939. return True
  3940. def format_size(bytes):
  3941. if bytes > 1000*1000:
  3942. return '%.1fMb' % (bytes/1000.0/1000)
  3943. elif bytes > 10*1000:
  3944. return '%iKb' % (bytes/1000)
  3945. elif bytes > 1000:
  3946. return '%.1fKb' % (bytes/1000.0)
  3947. else:
  3948. return '%ibytes' % bytes
  3949. _normalize_re = re.compile(r'[^a-z]', re.I)
  3950. def normalize_name(name):
  3951. return _normalize_re.sub('-', name.lower())
  3952. def make_path_relative(path, rel_to):
  3953. """
  3954. Make a filename relative, where the filename path, and it is
  3955. relative to rel_to
  3956. >>> make_relative_path('/usr/share/something/a-file.pth',
  3957. ... '/usr/share/another-place/src/Directory')
  3958. '../../../something/a-file.pth'
  3959. >>> make_relative_path('/usr/share/something/a-file.pth',
  3960. ... '/home/user/src/Directory')
  3961. '../../../usr/share/something/a-file.pth'
  3962. >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/')
  3963. 'a-file.pth'
  3964. """
  3965. path_filename = os.path.basename(path)
  3966. path = os.path.dirname(path)
  3967. path = os.path.normpath(os.path.abspath(path))
  3968. rel_to = os.path.normpath(os.path.abspath(rel_to))
  3969. path_parts = path.strip(os.path.sep).split(os.path.sep)
  3970. rel_to_parts = rel_to.strip(os.path.sep).split(os.path.sep)
  3971. while path_parts and rel_to_parts and path_parts[0] == rel_to_parts[0]:
  3972. path_parts.pop(0)
  3973. rel_to_parts.pop(0)
  3974. full_parts = ['..']*len(rel_to_parts) + path_parts + [path_filename]
  3975. if full_parts == ['']:
  3976. return '.' + os.path.sep
  3977. return os.path.sep.join(full_parts)
  3978. def display_path(path):
  3979. """Gives the display value for a given path, making it relative to cwd
  3980. if possible."""
  3981. path = os.path.normcase(os.path.abspath(path))
  3982. if path.startswith(os.getcwd() + os.path.sep):
  3983. path = '.' + path[len(os.getcwd()):]
  3984. return path
  3985. def parse_editable(editable_req, default_vcs=None):
  3986. """Parses svn+http://blahblah@rev#egg=Foobar into a requirement
  3987. (Foobar) and a URL"""
  3988. url = editable_req
  3989. if os.path.isdir(url) and os.path.exists(os.path.join(url, 'setup.py')):
  3990. # Treating it as code that has already been checked out
  3991. url = filename_to_url(url)
  3992. if url.lower().startswith('file:'):
  3993. return None, url
  3994. for version_control in vcs:
  3995. if url.lower().startswith('%s:' % version_control):
  3996. url = '%s+%s' % (version_control, url)
  3997. if '+' not in url:
  3998. if default_vcs:
  3999. url = default_vcs + '+' + url
  4000. else:
  4001. raise InstallationError(
  4002. '--editable=%s should be formatted with svn+URL, git+URL, hg+URL or bzr+URL' % editable_req)
  4003. vc_type = url.split('+', 1)[0].lower()
  4004. if not vcs.get_backend(vc_type):
  4005. raise InstallationError(
  4006. 'For --editable=%s only svn (svn+URL), Git (git+URL), Mercurial (hg+URL) and Bazaar (bzr+URL) is currently supported' % editable_req)
  4007. match = re.search(r'(?:#|#.*?&)egg=([^&]*)', editable_req)
  4008. if (not match or not match.group(1)) and vcs.get_backend(vc_type):
  4009. parts = [p for p in editable_req.split('#', 1)[0].split('/') if p]
  4010. if parts[-2] in ('tags', 'branches', 'tag', 'branch'):
  4011. req = parts[-3]
  4012. elif parts[-1] == 'trunk':
  4013. req = parts[-2]
  4014. else:
  4015. raise InstallationError(
  4016. '--editable=%s is not the right format; it must have #egg=Package'
  4017. % editable_req)
  4018. else:
  4019. req = match.group(1)
  4020. ## FIXME: use package_to_requirement?
  4021. match = re.search(r'^(.*?)(?:-dev|-\d.*)', req)
  4022. if match:
  4023. # Strip off -dev, -0.2, etc.
  4024. req = match.group(1)
  4025. return req, url
  4026. def backup_dir(dir, ext='.bak'):
  4027. """Figure out the name of a directory to back up the given dir to
  4028. (adding .bak, .bak2, etc)"""
  4029. n = 1
  4030. extension = ext
  4031. while os.path.exists(dir + extension):
  4032. n += 1
  4033. extension = ext + str(n)
  4034. return dir + extension
  4035. def ask(message, options):
  4036. """Ask the message interactively, with the given possible responses"""
  4037. while 1:
  4038. if os.environ.get('PIP_NO_INPUT'):
  4039. raise Exception('No input was expected ($PIP_NO_INPUT set); question: %s' % message)
  4040. response = raw_input(message)
  4041. response = response.strip().lower()
  4042. if response not in options:
  4043. print 'Your response (%r) was not one of the expected responses: %s' % (
  4044. response, ', '.join(options))
  4045. else:
  4046. return response
  4047. def open_logfile_append(filename):
  4048. """Open the named log file in append mode.
  4049. If the file already exists, a separator will also be printed to
  4050. the file to separate past activity from current activity.
  4051. """
  4052. exists = os.path.exists(filename)
  4053. log_fp = open(filename, 'a')
  4054. if exists:
  4055. print >> log_fp, '-'*60
  4056. print >> log_fp, '%s run on %s' % (sys.argv[0], time.strftime('%c'))
  4057. return log_fp
  4058. def is_url(name):
  4059. """Returns true if the name looks like a URL"""
  4060. if ':' not in name:
  4061. return False
  4062. scheme = name.split(':', 1)[0].lower()
  4063. return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes
  4064. def is_filename(name):
  4065. if (splitext(name)[1].lower() in ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar', '.pybundle')
  4066. and os.path.exists(name)):
  4067. return True
  4068. if os.path.sep not in name and '/' not in name:
  4069. # Doesn't have any path components, probably a requirement like 'Foo'
  4070. return False
  4071. return True
  4072. _drive_re = re.compile('^([a-z]):', re.I)
  4073. _url_drive_re = re.compile('^([a-z])[:|]', re.I)
  4074. def filename_to_url(filename):
  4075. """
  4076. Convert a path to a file: URL. The path will be made absolute.
  4077. """
  4078. filename = os.path.normcase(os.path.abspath(filename))
  4079. if _drive_re.match(filename):
  4080. filename = filename[0] + '|' + filename[2:]
  4081. url = urllib.quote(filename)
  4082. url = url.replace(os.path.sep, '/')
  4083. url = url.lstrip('/')
  4084. return 'file:///' + url
  4085. def filename_to_url2(filename):
  4086. """
  4087. Convert a path to a file: URL. The path will be made absolute and have
  4088. quoted path parts.
  4089. """
  4090. filename = os.path.normcase(os.path.abspath(filename))
  4091. drive, filename = os.path.splitdrive(filename)
  4092. filepath = filename.split(os.path.sep)
  4093. url = '/'.join([urllib.quote(part) for part in filepath])
  4094. if not drive:
  4095. url = url.lstrip('/')
  4096. return 'file:///' + drive + url
  4097. def url_to_filename(url):
  4098. """
  4099. Convert a file: URL to a path.
  4100. """
  4101. assert url.startswith('file:'), (
  4102. "You can only turn file: urls into filenames (not %r)" % url)
  4103. filename = url[len('file:'):].lstrip('/')
  4104. filename = urllib.unquote(filename)
  4105. if _url_drive_re.match(filename):
  4106. filename = filename[0] + ':' + filename[2:]
  4107. else:
  4108. filename = '/' + filename
  4109. return filename
  4110. def get_requirement_from_url(url):
  4111. """Get a requirement from the URL, if possible. This looks for #egg
  4112. in the URL"""
  4113. link = Link(url)
  4114. egg_info = link.egg_fragment
  4115. if not egg_info:
  4116. egg_info = splitext(link.filename)[0]
  4117. return package_to_requirement(egg_info)
  4118. def package_to_requirement(package_name):
  4119. """Translate a name like Foo-1.2 to Foo==1.3"""
  4120. match = re.search(r'^(.*?)(-dev|-\d.*)', package_name)
  4121. if match:
  4122. name = match.group(1)
  4123. version = match.group(2)
  4124. else:
  4125. name = package_name
  4126. version = ''
  4127. if version:
  4128. return '%s==%s' % (name, version)
  4129. else:
  4130. return name
  4131. def is_framework_layout(path):
  4132. """Return True if the current platform is the default Python of Mac OS X
  4133. which installs scripts in /usr/local/bin"""
  4134. return (sys.platform[:6] == 'darwin' and
  4135. (path[:9] == '/Library/' or path[:16] == '/System/Library/'))
  4136. def strip_prefix(path, prefix):
  4137. """ If ``path`` begins with ``prefix``, return ``path`` with
  4138. ``prefix`` stripped off. Otherwise return None."""
  4139. prefixes = [prefix]
  4140. # Yep, we are special casing the framework layout of MacPython here
  4141. if is_framework_layout(sys.prefix):
  4142. for location in ('/Library', '/usr/local'):
  4143. if path.startswith(location):
  4144. prefixes.append(location)
  4145. for prefix in prefixes:
  4146. if path.startswith(prefix):
  4147. return prefix, path.replace(prefix + os.path.sep, '')
  4148. return None, None
  4149. class UninstallPathSet(object):
  4150. """A set of file paths to be removed in the uninstallation of a
  4151. requirement."""
  4152. def __init__(self, dist, restrict_to_prefix):
  4153. self.paths = set()
  4154. self._refuse = set()
  4155. self.pth = {}
  4156. self.prefix = os.path.normcase(os.path.realpath(restrict_to_prefix))
  4157. self.dist = dist
  4158. self.location = dist.location
  4159. self.save_dir = None
  4160. self._moved_paths = []
  4161. def _can_uninstall(self):
  4162. prefix, stripped = strip_prefix(self.location, self.prefix)
  4163. if not stripped:
  4164. logger.notify("Not uninstalling %s at %s, outside environment %s"
  4165. % (self.dist.project_name, self.dist.location,
  4166. self.prefix))
  4167. return False
  4168. return True
  4169. def add(self, path):
  4170. path = os.path.abspath(path)
  4171. if not os.path.exists(path):
  4172. return
  4173. prefix, stripped = strip_prefix(os.path.normcase(path), self.prefix)
  4174. if stripped:
  4175. self.paths.add((prefix, stripped))
  4176. else:
  4177. self._refuse.add((prefix, path))
  4178. def add_pth(self, pth_file, entry):
  4179. prefix, stripped = strip_prefix(os.path.normcase(pth_file), self.prefix)
  4180. if stripped:
  4181. entry = os.path.normcase(entry)
  4182. if stripped not in self.pth:
  4183. self.pth[stripped] = UninstallPthEntries(os.path.join(prefix, stripped))
  4184. self.pth[stripped].add(os.path.normcase(entry))
  4185. else:
  4186. self._refuse.add((prefix, pth_file))
  4187. def compact(self, paths):
  4188. """Compact a path set to contain the minimal number of paths
  4189. necessary to contain all paths in the set. If /a/path/ and
  4190. /a/path/to/a/file.txt are both in the set, leave only the
  4191. shorter path."""
  4192. short_paths = set()
  4193. def sort_set(x, y):
  4194. prefix_x, path_x = x
  4195. prefix_y, path_y = y
  4196. return cmp(len(path_x), len(path_y))
  4197. for prefix, path in sorted(paths, sort_set):
  4198. if not any([(path.startswith(shortpath) and
  4199. path[len(shortpath.rstrip(os.path.sep))] == os.path.sep)
  4200. for shortprefix, shortpath in short_paths]):
  4201. short_paths.add((prefix, path))
  4202. return short_paths
  4203. def remove(self, auto_confirm=False):
  4204. """Remove paths in ``self.paths`` with confirmation (unless
  4205. ``auto_confirm`` is True)."""
  4206. if not self._can_uninstall():
  4207. return
  4208. logger.notify('Uninstalling %s:' % self.dist.project_name)
  4209. logger.indent += 2
  4210. paths = sorted(self.compact(self.paths))
  4211. try:
  4212. if auto_confirm:
  4213. response = 'y'
  4214. else:
  4215. for prefix, path in paths:
  4216. logger.notify(os.path.join(prefix, path))
  4217. response = ask('Proceed (y/n)? ', ('y', 'n'))
  4218. if self._refuse:
  4219. logger.notify('Not removing or modifying (outside of prefix):')
  4220. for prefix, path in self.compact(self._refuse):
  4221. logger.notify(os.path.join(prefix, path))
  4222. if response == 'y':
  4223. self.save_dir = tempfile.mkdtemp('-uninstall', 'pip-')
  4224. for prefix, path in paths:
  4225. full_path = os.path.join(prefix, path)
  4226. new_path = os.path.join(self.save_dir, path)
  4227. new_dir = os.path.dirname(new_path)
  4228. logger.info('Removing file or directory %s' % full_path)
  4229. self._moved_paths.append((prefix, path))
  4230. os.renames(full_path, new_path)
  4231. for pth in self.pth.values():
  4232. pth.remove()
  4233. logger.notify('Successfully uninstalled %s' % self.dist.project_name)
  4234. finally:
  4235. logger.indent -= 2
  4236. def rollback(self):
  4237. """Rollback the changes previously made by remove()."""
  4238. if self.save_dir is None:
  4239. logger.error("Can't roll back %s; was not uninstalled" % self.dist.project_name)
  4240. return False
  4241. logger.notify('Rolling back uninstall of %s' % self.dist.project_name)
  4242. for prefix, path in self._moved_paths:
  4243. tmp_path = os.path.join(self.save_dir, path)
  4244. real_path = os.path.join(prefix, path)
  4245. logger.info('Replacing %s' % real_path)
  4246. os.renames(tmp_path, real_path)
  4247. for pth in self.pth:
  4248. pth.rollback()
  4249. def commit(self):
  4250. """Remove temporary save dir: rollback will no longer be possible."""
  4251. if self.save_dir is not None:
  4252. shutil.rmtree(self.save_dir)
  4253. self.save_dir = None
  4254. self._moved_paths = []
  4255. class UninstallPthEntries(object):
  4256. def __init__(self, pth_file):
  4257. if not os.path.isfile(pth_file):
  4258. raise UninstallationError("Cannot remove entries from nonexistent file %s" % pth_file)
  4259. self.file = pth_file
  4260. self.entries = set()
  4261. self._saved_lines = None
  4262. def add(self, entry):
  4263. self.entries.add(entry)
  4264. def remove(self):
  4265. logger.info('Removing pth entries from %s:' % self.file)
  4266. fh = open(self.file, 'r')
  4267. lines = fh.readlines()
  4268. self._saved_lines = lines
  4269. fh.close()
  4270. try:
  4271. for entry in self.entries:
  4272. logger.info('Removing entry: %s' % entry)
  4273. try:
  4274. lines.remove(entry + '\n')
  4275. except ValueError:
  4276. pass
  4277. finally:
  4278. pass
  4279. fh = open(self.file, 'w')
  4280. fh.writelines(lines)
  4281. fh.close()
  4282. def rollback(self):
  4283. if self._saved_lines is None:
  4284. logger.error('Cannot roll back changes to %s, none were made' % self.file)
  4285. return False
  4286. logger.info('Rolling %s back to previous state' % self.file)
  4287. fh = open(self.file, 'w')
  4288. fh.writelines(self._saved_lines)
  4289. fh.close()
  4290. return True
  4291. class FakeFile(object):
  4292. """Wrap a list of lines in an object with readline() to make
  4293. ConfigParser happy."""
  4294. def __init__(self, lines):
  4295. self._gen = (l for l in lines)
  4296. def readline(self):
  4297. try:
  4298. return self._gen.next()
  4299. except StopIteration:
  4300. return ''
  4301. def splitext(path):
  4302. """Like os.path.splitext, but take off .tar too"""
  4303. base, ext = posixpath.splitext(path)
  4304. if base.lower().endswith('.tar'):
  4305. ext = base[-4:] + ext
  4306. base = base[:-4]
  4307. return base, ext
  4308. def find_command(cmd, paths=None, pathext=None):
  4309. """Searches the PATH for the given command and returns its path"""
  4310. if paths is None:
  4311. paths = os.environ.get('PATH', []).split(os.pathsep)
  4312. if isinstance(paths, basestring):
  4313. paths = [paths]
  4314. # check if there are funny path extensions for executables, e.g. Windows
  4315. if pathext is None:
  4316. pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
  4317. pathext = [ext for ext in pathext.lower().split(os.pathsep)]
  4318. # don't use extensions if the command ends with one of them
  4319. if os.path.splitext(cmd)[1].lower() in pathext:
  4320. pathext = ['']
  4321. # check if we find the command on PATH
  4322. for path in paths:
  4323. # try without extension first
  4324. cmd_path = os.path.join(path, cmd)
  4325. for ext in pathext:
  4326. # then including the extension
  4327. cmd_path_ext = cmd_path + ext
  4328. if os.path.exists(cmd_path_ext):
  4329. return cmd_path_ext
  4330. if os.path.exists(cmd_path):
  4331. return cmd_path
  4332. return None
  4333. class _Inf(object):
  4334. """I am bigger than everything!"""
  4335. def __cmp__(self, a):
  4336. if self is a:
  4337. return 0
  4338. return 1
  4339. def __repr__(self):
  4340. return 'Inf'
  4341. Inf = _Inf()
  4342. del _Inf
  4343. if __name__ == '__main__':
  4344. exit = main()
  4345. if exit:
  4346. sys.exit(exit)