__init__.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. """Extensions to the 'distutils' for large or complex distributions"""
  2. import functools
  3. import os
  4. import re
  5. import _distutils_hack.override # noqa: F401
  6. import distutils.core
  7. from distutils.errors import DistutilsOptionError
  8. from distutils.util import convert_path as _convert_path
  9. from . import logging, monkey
  10. from . import version as _version_module
  11. from .depends import Require
  12. from .discovery import PackageFinder, PEP420PackageFinder
  13. from .dist import Distribution
  14. from .extension import Extension
  15. from .warnings import SetuptoolsDeprecationWarning
  16. __all__ = [
  17. 'setup',
  18. 'Distribution',
  19. 'Command',
  20. 'Extension',
  21. 'Require',
  22. 'SetuptoolsDeprecationWarning',
  23. 'find_packages',
  24. 'find_namespace_packages',
  25. ]
  26. __version__ = _version_module.__version__
  27. bootstrap_install_from = None
  28. find_packages = PackageFinder.find
  29. find_namespace_packages = PEP420PackageFinder.find
  30. def _install_setup_requires(attrs):
  31. # Note: do not use `setuptools.Distribution` directly, as
  32. # our PEP 517 backend patch `distutils.core.Distribution`.
  33. class MinimalDistribution(distutils.core.Distribution):
  34. """
  35. A minimal version of a distribution for supporting the
  36. fetch_build_eggs interface.
  37. """
  38. def __init__(self, attrs):
  39. _incl = 'dependency_links', 'setup_requires'
  40. filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
  41. super().__init__(filtered)
  42. # Prevent accidentally triggering discovery with incomplete set of attrs
  43. self.set_defaults._disable()
  44. def _get_project_config_files(self, filenames=None):
  45. """Ignore ``pyproject.toml``, they are not related to setup_requires"""
  46. try:
  47. cfg, toml = super()._split_standard_project_metadata(filenames)
  48. return cfg, ()
  49. except Exception:
  50. return filenames, ()
  51. def finalize_options(self):
  52. """
  53. Disable finalize_options to avoid building the working set.
  54. Ref #2158.
  55. """
  56. dist = MinimalDistribution(attrs)
  57. # Honor setup.cfg's options.
  58. dist.parse_config_files(ignore_option_errors=True)
  59. if dist.setup_requires:
  60. _fetch_build_eggs(dist)
  61. def _fetch_build_eggs(dist):
  62. try:
  63. dist.fetch_build_eggs(dist.setup_requires)
  64. except Exception as ex:
  65. msg = """
  66. It is possible a package already installed in your system
  67. contains an version that is invalid according to PEP 440.
  68. You can try `pip install --use-pep517` as a workaround for this problem,
  69. or rely on a new virtual environment.
  70. If the problem refers to a package that is not installed yet,
  71. please contact that package's maintainers or distributors.
  72. """
  73. if "InvalidVersion" in ex.__class__.__name__:
  74. if hasattr(ex, "add_note"):
  75. ex.add_note(msg) # PEP 678
  76. else:
  77. dist.announce(f"\n{msg}\n")
  78. raise
  79. def setup(**attrs):
  80. # Make sure we have any requirements needed to interpret 'attrs'.
  81. logging.configure()
  82. _install_setup_requires(attrs)
  83. return distutils.core.setup(**attrs)
  84. setup.__doc__ = distutils.core.setup.__doc__
  85. _Command = monkey.get_unpatched(distutils.core.Command)
  86. class Command(_Command):
  87. """
  88. Setuptools internal actions are organized using a *command design pattern*.
  89. This means that each action (or group of closely related actions) executed during
  90. the build should be implemented as a ``Command`` subclass.
  91. These commands are abstractions and do not necessarily correspond to a command that
  92. can (or should) be executed via a terminal, in a CLI fashion (although historically
  93. they would).
  94. When creating a new command from scratch, custom defined classes **SHOULD** inherit
  95. from ``setuptools.Command`` and implement a few mandatory methods.
  96. Between these mandatory methods, are listed:
  97. .. method:: initialize_options(self)
  98. Set or (reset) all options/attributes/caches used by the command
  99. to their default values. Note that these values may be overwritten during
  100. the build.
  101. .. method:: finalize_options(self)
  102. Set final values for all options/attributes used by the command.
  103. Most of the time, each option/attribute/cache should only be set if it does not
  104. have any value yet (e.g. ``if self.attr is None: self.attr = val``).
  105. .. method:: run(self)
  106. Execute the actions intended by the command.
  107. (Side effects **SHOULD** only take place when ``run`` is executed,
  108. for example, creating new files or writing to the terminal output).
  109. A useful analogy for command classes is to think of them as subroutines with local
  110. variables called "options". The options are "declared" in ``initialize_options()``
  111. and "defined" (given their final values, aka "finalized") in ``finalize_options()``,
  112. both of which must be defined by every command class. The "body" of the subroutine,
  113. (where it does all the work) is the ``run()`` method.
  114. Between ``initialize_options()`` and ``finalize_options()``, ``setuptools`` may set
  115. the values for options/attributes based on user's input (or circumstance),
  116. which means that the implementation should be careful to not overwrite values in
  117. ``finalize_options`` unless necessary.
  118. Please note that other commands (or other parts of setuptools) may also overwrite
  119. the values of the command's options/attributes multiple times during the build
  120. process.
  121. Therefore it is important to consistently implement ``initialize_options()`` and
  122. ``finalize_options()``. For example, all derived attributes (or attributes that
  123. depend on the value of other attributes) **SHOULD** be recomputed in
  124. ``finalize_options``.
  125. When overwriting existing commands, custom defined classes **MUST** abide by the
  126. same APIs implemented by the original class. They also **SHOULD** inherit from the
  127. original class.
  128. """
  129. command_consumes_arguments = False
  130. def __init__(self, dist, **kw):
  131. """
  132. Construct the command for dist, updating
  133. vars(self) with any keyword parameters.
  134. """
  135. super().__init__(dist)
  136. vars(self).update(kw)
  137. def _ensure_stringlike(self, option, what, default=None):
  138. val = getattr(self, option)
  139. if val is None:
  140. setattr(self, option, default)
  141. return default
  142. elif not isinstance(val, str):
  143. raise DistutilsOptionError(
  144. "'%s' must be a %s (got `%s`)" % (option, what, val)
  145. )
  146. return val
  147. def ensure_string_list(self, option):
  148. r"""Ensure that 'option' is a list of strings. If 'option' is
  149. currently a string, we split it either on /,\s*/ or /\s+/, so
  150. "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
  151. ["foo", "bar", "baz"].
  152. ..
  153. TODO: This method seems to be similar to the one in ``distutils.cmd``
  154. Probably it is just here for backward compatibility with old Python versions?
  155. :meta private:
  156. """
  157. val = getattr(self, option)
  158. if val is None:
  159. return
  160. elif isinstance(val, str):
  161. setattr(self, option, re.split(r',\s*|\s+', val))
  162. else:
  163. if isinstance(val, list):
  164. ok = all(isinstance(v, str) for v in val)
  165. else:
  166. ok = False
  167. if not ok:
  168. raise DistutilsOptionError(
  169. "'%s' must be a list of strings (got %r)" % (option, val)
  170. )
  171. def reinitialize_command(self, command, reinit_subcommands=0, **kw):
  172. cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
  173. vars(cmd).update(kw)
  174. return cmd
  175. def _find_all_simple(path):
  176. """
  177. Find all files under 'path'
  178. """
  179. results = (
  180. os.path.join(base, file)
  181. for base, dirs, files in os.walk(path, followlinks=True)
  182. for file in files
  183. )
  184. return filter(os.path.isfile, results)
  185. def findall(dir=os.curdir):
  186. """
  187. Find all files under 'dir' and return the list of full filenames.
  188. Unless dir is '.', return full filenames with dir prepended.
  189. """
  190. files = _find_all_simple(dir)
  191. if dir == os.curdir:
  192. make_rel = functools.partial(os.path.relpath, start=dir)
  193. files = map(make_rel, files)
  194. return list(files)
  195. @functools.wraps(_convert_path)
  196. def convert_path(pathname):
  197. SetuptoolsDeprecationWarning.emit(
  198. "Access to implementation detail",
  199. """
  200. The function `convert_path` is not provided by setuptools itself,
  201. and therefore not part of the public API.
  202. Its direct usage by 3rd-party packages is considered improper and the function
  203. may be removed in the future.
  204. """,
  205. due_date=(2023, 12, 13), # initial deprecation 2022-03-25, see #3201
  206. )
  207. return _convert_path(pathname)
  208. class sic(str):
  209. """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
  210. # Apply monkey patches
  211. monkey.patch_all()