check.py 5.8 KB

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