__init__.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """Extensions to the 'distutils' for large or complex distributions"""
  2. import functools
  3. import os
  4. import re
  5. import warnings
  6. import _distutils_hack.override # noqa: F401
  7. import distutils.core
  8. from distutils.errors import DistutilsOptionError
  9. from distutils.util import convert_path as _convert_path
  10. from ._deprecation_warning import SetuptoolsDeprecationWarning
  11. import setuptools.version
  12. from setuptools.extension import Extension
  13. from setuptools.dist import Distribution
  14. from setuptools.depends import Require
  15. from setuptools.discovery import PackageFinder, PEP420PackageFinder
  16. from . import monkey
  17. from . import logging
  18. __all__ = [
  19. 'setup',
  20. 'Distribution',
  21. 'Command',
  22. 'Extension',
  23. 'Require',
  24. 'SetuptoolsDeprecationWarning',
  25. 'find_packages',
  26. 'find_namespace_packages',
  27. ]
  28. __version__ = setuptools.version.__version__
  29. bootstrap_install_from = None
  30. find_packages = PackageFinder.find
  31. find_namespace_packages = PEP420PackageFinder.find
  32. def _install_setup_requires(attrs):
  33. # Note: do not use `setuptools.Distribution` directly, as
  34. # our PEP 517 backend patch `distutils.core.Distribution`.
  35. class MinimalDistribution(distutils.core.Distribution):
  36. """
  37. A minimal version of a distribution for supporting the
  38. fetch_build_eggs interface.
  39. """
  40. def __init__(self, attrs):
  41. _incl = 'dependency_links', 'setup_requires'
  42. filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
  43. super().__init__(filtered)
  44. # Prevent accidentally triggering discovery with incomplete set of attrs
  45. self.set_defaults._disable()
  46. def _get_project_config_files(self, filenames=None):
  47. """Ignore ``pyproject.toml``, they are not related to setup_requires"""
  48. try:
  49. cfg, toml = super()._split_standard_project_metadata(filenames)
  50. return cfg, ()
  51. except Exception:
  52. return filenames, ()
  53. def finalize_options(self):
  54. """
  55. Disable finalize_options to avoid building the working set.
  56. Ref #2158.
  57. """
  58. dist = MinimalDistribution(attrs)
  59. # Honor setup.cfg's options.
  60. dist.parse_config_files(ignore_option_errors=True)
  61. if dist.setup_requires:
  62. dist.fetch_build_eggs(dist.setup_requires)
  63. def setup(**attrs):
  64. # Make sure we have any requirements needed to interpret 'attrs'.
  65. logging.configure()
  66. _install_setup_requires(attrs)
  67. return distutils.core.setup(**attrs)
  68. setup.__doc__ = distutils.core.setup.__doc__
  69. _Command = monkey.get_unpatched(distutils.core.Command)
  70. class Command(_Command):
  71. __doc__ = _Command.__doc__
  72. command_consumes_arguments = False
  73. def __init__(self, dist, **kw):
  74. """
  75. Construct the command for dist, updating
  76. vars(self) with any keyword parameters.
  77. """
  78. super().__init__(dist)
  79. vars(self).update(kw)
  80. def _ensure_stringlike(self, option, what, default=None):
  81. val = getattr(self, option)
  82. if val is None:
  83. setattr(self, option, default)
  84. return default
  85. elif not isinstance(val, str):
  86. raise DistutilsOptionError(
  87. "'%s' must be a %s (got `%s`)" % (option, what, val)
  88. )
  89. return val
  90. def ensure_string_list(self, option):
  91. r"""Ensure that 'option' is a list of strings. If 'option' is
  92. currently a string, we split it either on /,\s*/ or /\s+/, so
  93. "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
  94. ["foo", "bar", "baz"].
  95. """
  96. val = getattr(self, option)
  97. if val is None:
  98. return
  99. elif isinstance(val, str):
  100. setattr(self, option, re.split(r',\s*|\s+', val))
  101. else:
  102. if isinstance(val, list):
  103. ok = all(isinstance(v, str) for v in val)
  104. else:
  105. ok = False
  106. if not ok:
  107. raise DistutilsOptionError(
  108. "'%s' must be a list of strings (got %r)" % (option, val)
  109. )
  110. def reinitialize_command(self, command, reinit_subcommands=0, **kw):
  111. cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
  112. vars(cmd).update(kw)
  113. return cmd
  114. def _find_all_simple(path):
  115. """
  116. Find all files under 'path'
  117. """
  118. results = (
  119. os.path.join(base, file)
  120. for base, dirs, files in os.walk(path, followlinks=True)
  121. for file in files
  122. )
  123. return filter(os.path.isfile, results)
  124. def findall(dir=os.curdir):
  125. """
  126. Find all files under 'dir' and return the list of full filenames.
  127. Unless dir is '.', return full filenames with dir prepended.
  128. """
  129. files = _find_all_simple(dir)
  130. if dir == os.curdir:
  131. make_rel = functools.partial(os.path.relpath, start=dir)
  132. files = map(make_rel, files)
  133. return list(files)
  134. @functools.wraps(_convert_path)
  135. def convert_path(pathname):
  136. from inspect import cleandoc
  137. msg = """
  138. The function `convert_path` is considered internal and not part of the public API.
  139. Its direct usage by 3rd-party packages is considered deprecated and the function
  140. may be removed in the future.
  141. """
  142. warnings.warn(cleandoc(msg), SetuptoolsDeprecationWarning)
  143. return _convert_path(pathname)
  144. class sic(str):
  145. """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
  146. # Apply monkey patches
  147. monkey.patch_all()