check.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. """Check a project and backend by attempting to build using PEP 517 hooks.
  2. """
  3. import argparse
  4. import io
  5. import logging
  6. import os
  7. from os.path import isfile, join as pjoin
  8. import shutil
  9. from subprocess import CalledProcessError
  10. import sys
  11. import tarfile
  12. from tempfile import mkdtemp
  13. import zipfile
  14. from .colorlog import enable_colourful_output
  15. from .compat import TOMLDecodeError, toml_load
  16. from .envbuild import BuildEnvironment
  17. from .wrappers import Pep517HookCaller
  18. log = logging.getLogger(__name__)
  19. def check_build_sdist(hooks, build_sys_requires):
  20. with BuildEnvironment() as env:
  21. try:
  22. env.pip_install(build_sys_requires)
  23. log.info('Installed static build dependencies')
  24. except CalledProcessError:
  25. log.error('Failed to install static build dependencies')
  26. return False
  27. try:
  28. reqs = hooks.get_requires_for_build_sdist({})
  29. log.info('Got build requires: %s', reqs)
  30. except Exception:
  31. log.error('Failure in get_requires_for_build_sdist', exc_info=True)
  32. return False
  33. try:
  34. env.pip_install(reqs)
  35. log.info('Installed dynamic build dependencies')
  36. except CalledProcessError:
  37. log.error('Failed to install dynamic build dependencies')
  38. return False
  39. td = mkdtemp()
  40. log.info('Trying to build sdist in %s', td)
  41. try:
  42. try:
  43. filename = hooks.build_sdist(td, {})
  44. log.info('build_sdist returned %r', filename)
  45. except Exception:
  46. log.info('Failure in build_sdist', exc_info=True)
  47. return False
  48. if not filename.endswith('.tar.gz'):
  49. log.error(
  50. "Filename %s doesn't have .tar.gz extension", filename)
  51. return False
  52. path = pjoin(td, filename)
  53. if isfile(path):
  54. log.info("Output file %s exists", path)
  55. else:
  56. log.error("Output file %s does not exist", path)
  57. return False
  58. if tarfile.is_tarfile(path):
  59. log.info("Output file is a tar file")
  60. else:
  61. log.error("Output file is not a tar file")
  62. return False
  63. finally:
  64. shutil.rmtree(td)
  65. return True
  66. def check_build_wheel(hooks, build_sys_requires):
  67. with BuildEnvironment() as env:
  68. try:
  69. env.pip_install(build_sys_requires)
  70. log.info('Installed static build dependencies')
  71. except CalledProcessError:
  72. log.error('Failed to install static build dependencies')
  73. return False
  74. try:
  75. reqs = hooks.get_requires_for_build_wheel({})
  76. log.info('Got build requires: %s', reqs)
  77. except Exception:
  78. log.error('Failure in get_requires_for_build_sdist', exc_info=True)
  79. return False
  80. try:
  81. env.pip_install(reqs)
  82. log.info('Installed dynamic build dependencies')
  83. except CalledProcessError:
  84. log.error('Failed to install dynamic build dependencies')
  85. return False
  86. td = mkdtemp()
  87. log.info('Trying to build wheel in %s', td)
  88. try:
  89. try:
  90. filename = hooks.build_wheel(td, {})
  91. log.info('build_wheel returned %r', filename)
  92. except Exception:
  93. log.info('Failure in build_wheel', exc_info=True)
  94. return False
  95. if not filename.endswith('.whl'):
  96. log.error("Filename %s doesn't have .whl extension", filename)
  97. return False
  98. path = pjoin(td, filename)
  99. if isfile(path):
  100. log.info("Output file %s exists", path)
  101. else:
  102. log.error("Output file %s does not exist", path)
  103. return False
  104. if zipfile.is_zipfile(path):
  105. log.info("Output file is a zip file")
  106. else:
  107. log.error("Output file is not a zip file")
  108. return False
  109. finally:
  110. shutil.rmtree(td)
  111. return True
  112. def check(source_dir):
  113. pyproject = pjoin(source_dir, 'pyproject.toml')
  114. if isfile(pyproject):
  115. log.info('Found pyproject.toml')
  116. else:
  117. log.error('Missing pyproject.toml')
  118. return False
  119. try:
  120. with io.open(pyproject, encoding="utf-8") as f:
  121. pyproject_data = toml_load(f)
  122. # Ensure the mandatory data can be loaded
  123. buildsys = pyproject_data['build-system']
  124. requires = buildsys['requires']
  125. backend = buildsys['build-backend']
  126. backend_path = buildsys.get('backend-path')
  127. log.info('Loaded pyproject.toml')
  128. except (TOMLDecodeError, KeyError):
  129. log.error("Invalid pyproject.toml", exc_info=True)
  130. return False
  131. hooks = Pep517HookCaller(source_dir, backend, backend_path)
  132. sdist_ok = check_build_sdist(hooks, requires)
  133. wheel_ok = check_build_wheel(hooks, requires)
  134. if not sdist_ok:
  135. log.warning('Sdist checks failed; scroll up to see')
  136. if not wheel_ok:
  137. log.warning('Wheel checks failed')
  138. return sdist_ok
  139. def main(argv=None):
  140. log.warning('pep517.check is deprecated. '
  141. 'Consider switching to https://pypi.org/project/build/')
  142. ap = argparse.ArgumentParser()
  143. ap.add_argument(
  144. 'source_dir',
  145. help="A directory containing pyproject.toml")
  146. args = ap.parse_args(argv)
  147. enable_colourful_output()
  148. ok = check(args.source_dir)
  149. if ok:
  150. print(ansi('Checks passed', 'green'))
  151. else:
  152. print(ansi('Checks failed', 'red'))
  153. sys.exit(1)
  154. ansi_codes = {
  155. 'reset': '\x1b[0m',
  156. 'bold': '\x1b[1m',
  157. 'red': '\x1b[31m',
  158. 'green': '\x1b[32m',
  159. }
  160. def ansi(s, attr):
  161. if os.name != 'nt' and sys.stdout.isatty():
  162. return ansi_codes[attr] + str(s) + ansi_codes['reset']
  163. else:
  164. return str(s)
  165. if __name__ == '__main__':
  166. main()