You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
4.7 KiB

  1. """Build Environment used for isolation during sdist building
  2. """
  3. import logging
  4. import os
  5. import sys
  6. from distutils.sysconfig import get_python_lib
  7. from sysconfig import get_paths
  8. from pip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
  9. from pip._internal.utils.misc import call_subprocess
  10. from pip._internal.utils.temp_dir import TempDirectory
  11. from pip._internal.utils.ui import open_spinner
  12. logger = logging.getLogger(__name__)
  13. class BuildEnvironment(object):
  14. """Creates and manages an isolated environment to install build deps
  15. """
  16. def __init__(self):
  17. self._temp_dir = TempDirectory(kind="build-env")
  18. self._temp_dir.create()
  19. @property
  20. def path(self):
  21. return self._temp_dir.path
  22. def __enter__(self):
  23. self.save_path = os.environ.get('PATH', None)
  24. self.save_pythonpath = os.environ.get('PYTHONPATH', None)
  25. self.save_nousersite = os.environ.get('PYTHONNOUSERSITE', None)
  26. install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
  27. install_dirs = get_paths(install_scheme, vars={
  28. 'base': self.path,
  29. 'platbase': self.path,
  30. })
  31. scripts = install_dirs['scripts']
  32. if self.save_path:
  33. os.environ['PATH'] = scripts + os.pathsep + self.save_path
  34. else:
  35. os.environ['PATH'] = scripts + os.pathsep + os.defpath
  36. # Note: prefer distutils' sysconfig to get the
  37. # library paths so PyPy is correctly supported.
  38. purelib = get_python_lib(plat_specific=0, prefix=self.path)
  39. platlib = get_python_lib(plat_specific=1, prefix=self.path)
  40. if purelib == platlib:
  41. lib_dirs = purelib
  42. else:
  43. lib_dirs = purelib + os.pathsep + platlib
  44. if self.save_pythonpath:
  45. os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
  46. self.save_pythonpath
  47. else:
  48. os.environ['PYTHONPATH'] = lib_dirs
  49. os.environ['PYTHONNOUSERSITE'] = '1'
  50. return self.path
  51. def __exit__(self, exc_type, exc_val, exc_tb):
  52. def restore_var(varname, old_value):
  53. if old_value is None:
  54. os.environ.pop(varname, None)
  55. else:
  56. os.environ[varname] = old_value
  57. restore_var('PATH', self.save_path)
  58. restore_var('PYTHONPATH', self.save_pythonpath)
  59. restore_var('PYTHONNOUSERSITE', self.save_nousersite)
  60. def cleanup(self):
  61. self._temp_dir.cleanup()
  62. def missing_requirements(self, reqs):
  63. """Return a list of the requirements from reqs that are not present
  64. """
  65. missing = []
  66. with self:
  67. ws = WorkingSet(os.environ["PYTHONPATH"].split(os.pathsep))
  68. for req in reqs:
  69. try:
  70. if ws.find(Requirement.parse(req)) is None:
  71. missing.append(req)
  72. except VersionConflict:
  73. missing.append(req)
  74. return missing
  75. def install_requirements(self, finder, requirements, message):
  76. args = [
  77. sys.executable, '-m', 'pip', 'install', '--ignore-installed',
  78. '--no-user', '--prefix', self.path, '--no-warn-script-location',
  79. ]
  80. if logger.getEffectiveLevel() <= logging.DEBUG:
  81. args.append('-v')
  82. for format_control in ('no_binary', 'only_binary'):
  83. formats = getattr(finder.format_control, format_control)
  84. args.extend(('--' + format_control.replace('_', '-'),
  85. ','.join(sorted(formats or {':none:'}))))
  86. if finder.index_urls:
  87. args.extend(['-i', finder.index_urls[0]])
  88. for extra_index in finder.index_urls[1:]:
  89. args.extend(['--extra-index-url', extra_index])
  90. else:
  91. args.append('--no-index')
  92. for link in finder.find_links:
  93. args.extend(['--find-links', link])
  94. for _, host, _ in finder.secure_origins:
  95. args.extend(['--trusted-host', host])
  96. if finder.allow_all_prereleases:
  97. args.append('--pre')
  98. if finder.process_dependency_links:
  99. args.append('--process-dependency-links')
  100. args.append('--')
  101. args.extend(requirements)
  102. with open_spinner(message) as spinner:
  103. call_subprocess(args, show_stdout=False, spinner=spinner)
  104. class NoOpBuildEnvironment(BuildEnvironment):
  105. """A no-op drop-in replacement for BuildEnvironment
  106. """
  107. def __init__(self):
  108. pass
  109. def __enter__(self):
  110. pass
  111. def __exit__(self, exc_type, exc_val, exc_tb):
  112. pass
  113. def cleanup(self):
  114. pass
  115. def install_requirements(self, finder, requirements, message):
  116. raise NotImplementedError()