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.

194 lines
5.8 KiB

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