build.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """Build a project using PEP 517 hooks.
  2. """
  3. import argparse
  4. import logging
  5. import os
  6. from pip._vendor import toml
  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
  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 open(pyproject) 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. # determine which dists to build
  94. dists = list(filter(None, (
  95. 'sdist' if args.source or not args.binary else None,
  96. 'wheel' if args.binary or not args.source else None,
  97. )))
  98. for dist in dists:
  99. build(args.source_dir, dist, args.out_dir)
  100. if __name__ == '__main__':
  101. main(parser.parse_args())