build.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. """Build a project using PEP 517 hooks.
  2. """
  3. import argparse
  4. import io
  5. import logging
  6. import os
  7. import shutil
  8. from .envbuild import BuildEnvironment
  9. from .wrappers import Pep517HookCaller
  10. from .dirtools import tempdir, mkdir_p
  11. from .compat import FileNotFoundError, toml_load
  12. log = logging.getLogger(__name__)
  13. def validate_system(system):
  14. """
  15. Ensure build system has the requisite fields.
  16. """
  17. required = {'requires', 'build-backend'}
  18. if not (required <= set(system)):
  19. message = "Missing required fields: {missing}".format(
  20. missing=required-set(system),
  21. )
  22. raise ValueError(message)
  23. def load_system(source_dir):
  24. """
  25. Load the build system from a source dir (pyproject.toml).
  26. """
  27. pyproject = os.path.join(source_dir, 'pyproject.toml')
  28. with io.open(pyproject, encoding="utf-8") as f:
  29. pyproject_data = toml_load(f)
  30. return pyproject_data['build-system']
  31. def compat_system(source_dir):
  32. """
  33. Given a source dir, attempt to get a build system backend
  34. and requirements from pyproject.toml. Fallback to
  35. setuptools but only if the file was not found or a build
  36. system was not indicated.
  37. """
  38. try:
  39. system = load_system(source_dir)
  40. except (FileNotFoundError, KeyError):
  41. system = {}
  42. system.setdefault(
  43. 'build-backend',
  44. 'setuptools.build_meta:__legacy__',
  45. )
  46. system.setdefault('requires', ['setuptools', 'wheel'])
  47. return system
  48. def _do_build(hooks, env, dist, dest):
  49. get_requires_name = 'get_requires_for_build_{dist}'.format(**locals())
  50. get_requires = getattr(hooks, get_requires_name)
  51. reqs = get_requires({})
  52. log.info('Got build requires: %s', reqs)
  53. env.pip_install(reqs)
  54. log.info('Installed dynamic build dependencies')
  55. with tempdir() as td:
  56. log.info('Trying to build %s in %s', dist, td)
  57. build_name = 'build_{dist}'.format(**locals())
  58. build = getattr(hooks, build_name)
  59. filename = build(td, {})
  60. source = os.path.join(td, filename)
  61. shutil.move(source, os.path.join(dest, os.path.basename(filename)))
  62. def build(source_dir, dist, dest=None, system=None):
  63. system = system or load_system(source_dir)
  64. dest = os.path.join(source_dir, dest or 'dist')
  65. mkdir_p(dest)
  66. validate_system(system)
  67. hooks = Pep517HookCaller(
  68. source_dir, system['build-backend'], system.get('backend-path')
  69. )
  70. with BuildEnvironment() as env:
  71. env.pip_install(system['requires'])
  72. _do_build(hooks, env, dist, dest)
  73. parser = argparse.ArgumentParser()
  74. parser.add_argument(
  75. 'source_dir',
  76. help="A directory containing pyproject.toml",
  77. )
  78. parser.add_argument(
  79. '--binary', '-b',
  80. action='store_true',
  81. default=False,
  82. )
  83. parser.add_argument(
  84. '--source', '-s',
  85. action='store_true',
  86. default=False,
  87. )
  88. parser.add_argument(
  89. '--out-dir', '-o',
  90. help="Destination in which to save the builds relative to source dir",
  91. )
  92. def main(args):
  93. log.warning('pep517.build is deprecated. '
  94. 'Consider switching to https://pypi.org/project/build/')
  95. # determine which dists to build
  96. dists = list(filter(None, (
  97. 'sdist' if args.source or not args.binary else None,
  98. 'wheel' if args.binary or not args.source else None,
  99. )))
  100. for dist in dists:
  101. build(args.source_dir, dist, args.out_dir)
  102. if __name__ == '__main__':
  103. main(parser.parse_args())