generate_metadata.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """Metadata generation logic for source distributions.
  2. """
  3. import logging
  4. import os
  5. from pip._internal.exceptions import InstallationError
  6. from pip._internal.utils.misc import ensure_dir
  7. from pip._internal.utils.setuptools_build import make_setuptools_shim_args
  8. from pip._internal.utils.subprocess import call_subprocess
  9. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  10. from pip._internal.vcs import vcs
  11. if MYPY_CHECK_RUNNING:
  12. from typing import Callable, List
  13. from pip._internal.req.req_install import InstallRequirement
  14. logger = logging.getLogger(__name__)
  15. def get_metadata_generator(install_req):
  16. # type: (InstallRequirement) -> Callable[[InstallRequirement], str]
  17. """Return a callable metadata generator for this InstallRequirement.
  18. A metadata generator takes an InstallRequirement (install_req) as an input,
  19. generates metadata via the appropriate process for that install_req and
  20. returns the generated metadata directory.
  21. """
  22. if not install_req.use_pep517:
  23. return _generate_metadata_legacy
  24. return _generate_metadata
  25. def _find_egg_info(source_directory, is_editable):
  26. # type: (str, bool) -> str
  27. """Find an .egg-info in `source_directory`, based on `is_editable`.
  28. """
  29. def looks_like_virtual_env(path):
  30. # type: (str) -> bool
  31. return (
  32. os.path.lexists(os.path.join(path, 'bin', 'python')) or
  33. os.path.exists(os.path.join(path, 'Scripts', 'Python.exe'))
  34. )
  35. def locate_editable_egg_info(base):
  36. # type: (str) -> List[str]
  37. candidates = [] # type: List[str]
  38. for root, dirs, files in os.walk(base):
  39. for dir_ in vcs.dirnames:
  40. if dir_ in dirs:
  41. dirs.remove(dir_)
  42. # Iterate over a copy of ``dirs``, since mutating
  43. # a list while iterating over it can cause trouble.
  44. # (See https://github.com/pypa/pip/pull/462.)
  45. for dir_ in list(dirs):
  46. if looks_like_virtual_env(os.path.join(root, dir_)):
  47. dirs.remove(dir_)
  48. # Also don't search through tests
  49. elif dir_ == 'test' or dir_ == 'tests':
  50. dirs.remove(dir_)
  51. candidates.extend(os.path.join(root, dir_) for dir_ in dirs)
  52. return [f for f in candidates if f.endswith('.egg-info')]
  53. def depth_of_directory(dir_):
  54. # type: (str) -> int
  55. return (
  56. dir_.count(os.path.sep) +
  57. (os.path.altsep and dir_.count(os.path.altsep) or 0)
  58. )
  59. base = source_directory
  60. if is_editable:
  61. filenames = locate_editable_egg_info(base)
  62. else:
  63. base = os.path.join(base, 'pip-egg-info')
  64. filenames = os.listdir(base)
  65. if not filenames:
  66. raise InstallationError(
  67. "Files/directories not found in %s" % base
  68. )
  69. # If we have more than one match, we pick the toplevel one. This
  70. # can easily be the case if there is a dist folder which contains
  71. # an extracted tarball for testing purposes.
  72. if len(filenames) > 1:
  73. filenames.sort(key=depth_of_directory)
  74. return os.path.join(base, filenames[0])
  75. def _generate_metadata_legacy(install_req):
  76. # type: (InstallRequirement) -> str
  77. req_details_str = install_req.name or "from {}".format(install_req.link)
  78. logger.debug(
  79. 'Running setup.py (path:%s) egg_info for package %s',
  80. install_req.setup_py_path, req_details_str,
  81. )
  82. # Compose arguments for subprocess call
  83. base_cmd = make_setuptools_shim_args(install_req.setup_py_path)
  84. if install_req.isolated:
  85. base_cmd += ["--no-user-cfg"]
  86. # For non-editable installs, don't put the .egg-info files at the root,
  87. # to avoid confusion due to the source code being considered an installed
  88. # egg.
  89. egg_base_option = [] # type: List[str]
  90. if not install_req.editable:
  91. egg_info_dir = os.path.join(
  92. install_req.unpacked_source_directory, 'pip-egg-info',
  93. )
  94. egg_base_option = ['--egg-base', egg_info_dir]
  95. # setuptools complains if the target directory does not exist.
  96. ensure_dir(egg_info_dir)
  97. with install_req.build_env:
  98. call_subprocess(
  99. base_cmd + ["egg_info"] + egg_base_option,
  100. cwd=install_req.unpacked_source_directory,
  101. command_desc='python setup.py egg_info',
  102. )
  103. # Return the .egg-info directory.
  104. return _find_egg_info(
  105. install_req.unpacked_source_directory,
  106. install_req.editable,
  107. )
  108. def _generate_metadata(install_req):
  109. # type: (InstallRequirement) -> str
  110. return install_req.prepare_pep517_metadata()