1 """Extensions to the 'distutils' for large or complex distributions"""
8 import _distutils_hack
.override
# noqa: F401
11 from distutils
.errors
import DistutilsOptionError
12 from distutils
.util
import convert_path
as _convert_path
14 from ._deprecation
_warning
import SetuptoolsDeprecationWarning
16 import setuptools
.version
17 from setuptools
.extension
import Extension
18 from setuptools
.dist
import Distribution
19 from setuptools
.depends
import Require
20 from setuptools
.discovery
import PackageFinder
, PEP420PackageFinder
31 'SetuptoolsDeprecationWarning',
33 'find_namespace_packages',
36 __version__
= setuptools
.version
.__version
__
38 bootstrap_install_from
= None
41 find_packages
= PackageFinder
.find
42 find_namespace_packages
= PEP420PackageFinder
.find
45 def _install_setup_requires(attrs
):
46 # Note: do not use `setuptools.Distribution` directly, as
47 # our PEP 517 backend patch `distutils.core.Distribution`.
48 class MinimalDistribution(distutils
.core
.Distribution
):
50 A minimal version of a distribution for supporting the
51 fetch_build_eggs interface.
54 def __init__(self
, attrs
):
55 _incl
= 'dependency_links', 'setup_requires'
56 filtered
= {k
: attrs
[k
] for k
in set(_incl
) & set(attrs
)}
57 super().__init
__(filtered
)
58 # Prevent accidentally triggering discovery with incomplete set of attrs
59 self
.set_defaults
._disable
()
61 def _get_project_config_files(self
, filenames
=None):
62 """Ignore ``pyproject.toml``, they are not related to setup_requires"""
64 cfg
, toml
= super()._split
_standard
_project
_metadata
(filenames
)
69 def finalize_options(self
):
71 Disable finalize_options to avoid building the working set.
75 dist
= MinimalDistribution(attrs
)
77 # Honor setup.cfg's options.
78 dist
.parse_config_files(ignore_option_errors
=True)
79 if dist
.setup_requires
:
80 _fetch_build_eggs(dist
)
83 def _fetch_build_eggs(dist
):
85 dist
.fetch_build_eggs(dist
.setup_requires
)
86 except Exception as ex
:
88 It is possible a package already installed in your system
89 contains an version that is invalid according to PEP 440.
90 You can try `pip install --use-pep517` as a workaround for this problem,
91 or rely on a new virtual environment.
93 If the problem refers to a package that is not installed yet,
94 please contact that package's maintainers or distributors.
96 if "InvalidVersion" in ex
.__class
__.__name
__:
97 if hasattr(ex
, "add_note"):
98 ex
.add_note(msg
) # PEP 678
100 dist
.announce(f
"\n{msg}\n")
105 # Make sure we have any requirements needed to interpret 'attrs'.
107 _install_setup_requires(attrs
)
108 return distutils
.core
.setup(**attrs
)
111 setup
.__doc
__ = distutils
.core
.setup
.__doc
__
114 _Command
= monkey
.get_unpatched(distutils
.core
.Command
)
117 class Command(_Command
):
119 Setuptools internal actions are organized using a *command design pattern*.
120 This means that each action (or group of closely related actions) executed during
121 the build should be implemented as a ``Command`` subclass.
123 These commands are abstractions and do not necessarily correspond to a command that
124 can (or should) be executed via a terminal, in a CLI fashion (although historically
127 When creating a new command from scratch, custom defined classes **SHOULD** inherit
128 from ``setuptools.Command`` and implement a few mandatory methods.
129 Between these mandatory methods, are listed:
131 .. method:: initialize_options(self)
133 Set or (reset) all options/attributes/caches used by the command
134 to their default values. Note that these values may be overwritten during
137 .. method:: finalize_options(self)
139 Set final values for all options/attributes used by the command.
140 Most of the time, each option/attribute/cache should only be set if it does not
141 have any value yet (e.g. ``if self.attr is None: self.attr = val``).
143 .. method:: run(self)
145 Execute the actions intended by the command.
146 (Side effects **SHOULD** only take place when ``run`` is executed,
147 for example, creating new files or writing to the terminal output).
149 A useful analogy for command classes is to think of them as subroutines with local
150 variables called "options". The options are "declared" in ``initialize_options()``
151 and "defined" (given their final values, aka "finalized") in ``finalize_options()``,
152 both of which must be defined by every command class. The "body" of the subroutine,
153 (where it does all the work) is the ``run()`` method.
154 Between ``initialize_options()`` and ``finalize_options()``, ``setuptools`` may set
155 the values for options/attributes based on user's input (or circumstance),
156 which means that the implementation should be careful to not overwrite values in
157 ``finalize_options`` unless necessary.
159 Please note that other commands (or other parts of setuptools) may also overwrite
160 the values of the command's options/attributes multiple times during the build
162 Therefore it is important to consistently implement ``initialize_options()`` and
163 ``finalize_options()``. For example, all derived attributes (or attributes that
164 depend on the value of other attributes) **SHOULD** be recomputed in
165 ``finalize_options``.
167 When overwriting existing commands, custom defined classes **MUST** abide by the
168 same APIs implemented by the original class. They also **SHOULD** inherit from the
172 command_consumes_arguments
= False
174 def __init__(self
, dist
, **kw
):
176 Construct the command for dist, updating
177 vars(self) with any keyword parameters.
179 super().__init
__(dist
)
180 vars(self
).update(kw
)
182 def _ensure_stringlike(self
, option
, what
, default
=None):
183 val
= getattr(self
, option
)
185 setattr(self
, option
, default
)
187 elif not isinstance(val
, str):
188 raise DistutilsOptionError(
189 "'%s' must be a %s (got `%s`)" % (option
, what
, val
)
193 def ensure_string_list(self
, option
):
194 r
"""Ensure that 'option' is a list of strings. If 'option' is
195 currently a string, we split it either on /,\s*/ or /\s+/, so
196 "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
197 ["foo", "bar", "baz"].
200 TODO: This method seems to be similar to the one in ``distutils.cmd``
201 Probably it is just here for backward compatibility with old Python versions?
205 val
= getattr(self
, option
)
208 elif isinstance(val
, str):
209 setattr(self
, option
, re
.split(r
',\s*|\s+', val
))
211 if isinstance(val
, list):
212 ok
= all(isinstance(v
, str) for v
in val
)
216 raise DistutilsOptionError(
217 "'%s' must be a list of strings (got %r)" % (option
, val
)
220 def reinitialize_command(self
, command
, reinit_subcommands
=0, **kw
):
221 cmd
= _Command
.reinitialize_command(self
, command
, reinit_subcommands
)
226 def _find_all_simple(path
):
228 Find all files under 'path'
231 os
.path
.join(base
, file)
232 for base
, dirs
, files
in os
.walk(path
, followlinks
=True)
235 return filter(os
.path
.isfile
, results
)
238 def findall(dir=os
.curdir
):
240 Find all files under 'dir' and return the list of full filenames.
241 Unless dir is '.', return full filenames with dir prepended.
243 files
= _find_all_simple(dir)
245 make_rel
= functools
.partial(os
.path
.relpath
, start
=dir)
246 files
= map(make_rel
, files
)
250 @functools.wraps(_convert_path
)
251 def convert_path(pathname
):
252 from inspect
import cleandoc
255 The function `convert_path` is considered internal and not part of the public API.
256 Its direct usage by 3rd-party packages is considered deprecated and the function
257 may be removed in the future.
259 warnings
.warn(cleandoc(msg
), SetuptoolsDeprecationWarning
)
260 return _convert_path(pathname
)
264 """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
267 # Apply monkey patches