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.

_in_process.py 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. """This is invoked in a subprocess to call the build backend hooks.
  2. It expects:
  3. - Command line args: hook_name, control_dir
  4. - Environment variable: PEP517_BUILD_BACKEND=entry.point:spec
  5. - control_dir/input.json:
  6. - {"kwargs": {...}}
  7. Results:
  8. - control_dir/output.json
  9. - {"return_val": ...}
  10. """
  11. from glob import glob
  12. from importlib import import_module
  13. import os
  14. from os.path import join as pjoin
  15. import re
  16. import shutil
  17. import sys
  18. # This is run as a script, not a module, so it can't do a relative import
  19. import compat
  20. class BackendUnavailable(Exception):
  21. """Raised if we cannot import the backend"""
  22. def _build_backend():
  23. """Find and load the build backend"""
  24. ep = os.environ['PEP517_BUILD_BACKEND']
  25. mod_path, _, obj_path = ep.partition(':')
  26. try:
  27. obj = import_module(mod_path)
  28. except ImportError:
  29. raise BackendUnavailable
  30. if obj_path:
  31. for path_part in obj_path.split('.'):
  32. obj = getattr(obj, path_part)
  33. return obj
  34. def get_requires_for_build_wheel(config_settings):
  35. """Invoke the optional get_requires_for_build_wheel hook
  36. Returns [] if the hook is not defined.
  37. """
  38. backend = _build_backend()
  39. try:
  40. hook = backend.get_requires_for_build_wheel
  41. except AttributeError:
  42. return []
  43. else:
  44. return hook(config_settings)
  45. def prepare_metadata_for_build_wheel(metadata_directory, config_settings):
  46. """Invoke optional prepare_metadata_for_build_wheel
  47. Implements a fallback by building a wheel if the hook isn't defined.
  48. """
  49. backend = _build_backend()
  50. try:
  51. hook = backend.prepare_metadata_for_build_wheel
  52. except AttributeError:
  53. return _get_wheel_metadata_from_wheel(backend, metadata_directory,
  54. config_settings)
  55. else:
  56. return hook(metadata_directory, config_settings)
  57. WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL'
  58. def _dist_info_files(whl_zip):
  59. """Identify the .dist-info folder inside a wheel ZipFile."""
  60. res = []
  61. for path in whl_zip.namelist():
  62. m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path)
  63. if m:
  64. res.append(path)
  65. if res:
  66. return res
  67. raise Exception("No .dist-info folder found in wheel")
  68. def _get_wheel_metadata_from_wheel(
  69. backend, metadata_directory, config_settings):
  70. """Build a wheel and extract the metadata from it.
  71. Fallback for when the build backend does not
  72. define the 'get_wheel_metadata' hook.
  73. """
  74. from zipfile import ZipFile
  75. whl_basename = backend.build_wheel(metadata_directory, config_settings)
  76. with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'):
  77. pass # Touch marker file
  78. whl_file = os.path.join(metadata_directory, whl_basename)
  79. with ZipFile(whl_file) as zipf:
  80. dist_info = _dist_info_files(zipf)
  81. zipf.extractall(path=metadata_directory, members=dist_info)
  82. return dist_info[0].split('/')[0]
  83. def _find_already_built_wheel(metadata_directory):
  84. """Check for a wheel already built during the get_wheel_metadata hook.
  85. """
  86. if not metadata_directory:
  87. return None
  88. metadata_parent = os.path.dirname(metadata_directory)
  89. if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
  90. return None
  91. whl_files = glob(os.path.join(metadata_parent, '*.whl'))
  92. if not whl_files:
  93. print('Found wheel built marker, but no .whl files')
  94. return None
  95. if len(whl_files) > 1:
  96. print('Found multiple .whl files; unspecified behaviour. '
  97. 'Will call build_wheel.')
  98. return None
  99. # Exactly one .whl file
  100. return whl_files[0]
  101. def build_wheel(wheel_directory, config_settings, metadata_directory=None):
  102. """Invoke the mandatory build_wheel hook.
  103. If a wheel was already built in the
  104. prepare_metadata_for_build_wheel fallback, this
  105. will copy it rather than rebuilding the wheel.
  106. """
  107. prebuilt_whl = _find_already_built_wheel(metadata_directory)
  108. if prebuilt_whl:
  109. shutil.copy2(prebuilt_whl, wheel_directory)
  110. return os.path.basename(prebuilt_whl)
  111. return _build_backend().build_wheel(wheel_directory, config_settings,
  112. metadata_directory)
  113. def get_requires_for_build_sdist(config_settings):
  114. """Invoke the optional get_requires_for_build_wheel hook
  115. Returns [] if the hook is not defined.
  116. """
  117. backend = _build_backend()
  118. try:
  119. hook = backend.get_requires_for_build_sdist
  120. except AttributeError:
  121. return []
  122. else:
  123. return hook(config_settings)
  124. class _DummyException(Exception):
  125. """Nothing should ever raise this exception"""
  126. class GotUnsupportedOperation(Exception):
  127. """For internal use when backend raises UnsupportedOperation"""
  128. def build_sdist(sdist_directory, config_settings):
  129. """Invoke the mandatory build_sdist hook."""
  130. backend = _build_backend()
  131. try:
  132. return backend.build_sdist(sdist_directory, config_settings)
  133. except getattr(backend, 'UnsupportedOperation', _DummyException):
  134. raise GotUnsupportedOperation
  135. HOOK_NAMES = {
  136. 'get_requires_for_build_wheel',
  137. 'prepare_metadata_for_build_wheel',
  138. 'build_wheel',
  139. 'get_requires_for_build_sdist',
  140. 'build_sdist',
  141. }
  142. def main():
  143. if len(sys.argv) < 3:
  144. sys.exit("Needs args: hook_name, control_dir")
  145. hook_name = sys.argv[1]
  146. control_dir = sys.argv[2]
  147. if hook_name not in HOOK_NAMES:
  148. sys.exit("Unknown hook: %s" % hook_name)
  149. hook = globals()[hook_name]
  150. hook_input = compat.read_json(pjoin(control_dir, 'input.json'))
  151. json_out = {'unsupported': False, 'return_val': None}
  152. try:
  153. json_out['return_val'] = hook(**hook_input['kwargs'])
  154. except BackendUnavailable:
  155. json_out['no_backend'] = True
  156. except GotUnsupportedOperation:
  157. json_out['unsupported'] = True
  158. compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2)
  159. if __name__ == '__main__':
  160. main()