codekingpro/portable-devtools
114k
1"""Extensions to the 'distutils' for large or complex distributions"""2 3import functools4import os5import re6from typing import TYPE_CHECKING7 8import _distutils_hack.override # noqa: F4019import distutils.core10from distutils.errors import DistutilsOptionError11from distutils.util import convert_path as _convert_path12 13from . import logging, monkey14from . import version as _version_module15from .depends import Require16from .discovery import PackageFinder, PEP420PackageFinder17from .dist import Distribution18from .extension import Extension19from .warnings import SetuptoolsDeprecationWarning20 21__all__ = [22 'setup',23 'Distribution',24 'Command',25 'Extension',26 'Require',27 'SetuptoolsDeprecationWarning',28 'find_packages',29 'find_namespace_packages',30]31 32__version__ = _version_module.__version__33 34bootstrap_install_from = None35 36 37find_packages = PackageFinder.find38find_namespace_packages = PEP420PackageFinder.find39 40 41def _install_setup_requires(attrs):42 # Note: do not use `setuptools.Distribution` directly, as43 # our PEP 517 backend patch `distutils.core.Distribution`.44 class MinimalDistribution(distutils.core.Distribution):45 """46 A minimal version of a distribution for supporting the47 fetch_build_eggs interface.48 """49 50 def __init__(self, attrs):51 _incl = 'dependency_links', 'setup_requires'52 filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}53 super().__init__(filtered)54 # Prevent accidentally triggering discovery with incomplete set of attrs55 self.set_defaults._disable()56 57 def _get_project_config_files(self, filenames=None):58 """Ignore ``pyproject.toml``, they are not related to setup_requires"""59 try:60 cfg, toml = super()._split_standard_project_metadata(filenames)61 return cfg, ()62 except Exception:63 return filenames, ()64 65 def finalize_options(self):66 """67 Disable finalize_options to avoid building the working set.68 Ref #2158.69 """70 71 dist = MinimalDistribution(attrs)72 73 # Honor setup.cfg's options.74 dist.parse_config_files(ignore_option_errors=True)75 if dist.setup_requires:76 _fetch_build_eggs(dist)77 78 79def _fetch_build_eggs(dist):80 try:81 dist.fetch_build_eggs(dist.setup_requires)82 except Exception as ex:83 msg = """84 It is possible a package already installed in your system85 contains an version that is invalid according to PEP 440.86 You can try `pip install --use-pep517` as a workaround for this problem,87 or rely on a new virtual environment.88 89 If the problem refers to a package that is not installed yet,90 please contact that package's maintainers or distributors.91 """92 if "InvalidVersion" in ex.__class__.__name__:93 if hasattr(ex, "add_note"):94 ex.add_note(msg) # PEP 67895 else:96 dist.announce(f"\n{msg}\n")97 raise98 99 100def setup(**attrs):101 # Make sure we have any requirements needed to interpret 'attrs'.102 logging.configure()103 _install_setup_requires(attrs)104 return distutils.core.setup(**attrs)105 106 107setup.__doc__ = distutils.core.setup.__doc__108 109if TYPE_CHECKING:110 # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962111 _Command = distutils.core.Command112else:113 _Command = monkey.get_unpatched(distutils.core.Command)114 115 116class Command(_Command):117 """118 Setuptools internal actions are organized using a *command design pattern*.119 This means that each action (or group of closely related actions) executed during120 the build should be implemented as a ``Command`` subclass.121 122 These commands are abstractions and do not necessarily correspond to a command that123 can (or should) be executed via a terminal, in a CLI fashion (although historically124 they would).125 126 When creating a new command from scratch, custom defined classes **SHOULD** inherit127 from ``setuptools.Command`` and implement a few mandatory methods.128 Between these mandatory methods, are listed:129 130 .. method:: initialize_options(self)131 132 Set or (reset) all options/attributes/caches used by the command133 to their default values. Note that these values may be overwritten during134 the build.135 136 .. method:: finalize_options(self)137 138 Set final values for all options/attributes used by the command.139 Most of the time, each option/attribute/cache should only be set if it does not140 have any value yet (e.g. ``if self.attr is None: self.attr = val``).141 142 .. method:: run(self)143 144 Execute the actions intended by the command.145 (Side effects **SHOULD** only take place when ``run`` is executed,146 for example, creating new files or writing to the terminal output).147 148 A useful analogy for command classes is to think of them as subroutines with local149 variables called "options". The options are "declared" in ``initialize_options()``150 and "defined" (given their final values, aka "finalized") in ``finalize_options()``,151 both of which must be defined by every command class. The "body" of the subroutine,152 (where it does all the work) is the ``run()`` method.153 Between ``initialize_options()`` and ``finalize_options()``, ``setuptools`` may set154 the values for options/attributes based on user's input (or circumstance),155 which means that the implementation should be careful to not overwrite values in156 ``finalize_options`` unless necessary.157 158 Please note that other commands (or other parts of setuptools) may also overwrite159 the values of the command's options/attributes multiple times during the build160 process.161 Therefore it is important to consistently implement ``initialize_options()`` and162 ``finalize_options()``. For example, all derived attributes (or attributes that163 depend on the value of other attributes) **SHOULD** be recomputed in164 ``finalize_options``.165 166 When overwriting existing commands, custom defined classes **MUST** abide by the167 same APIs implemented by the original class. They also **SHOULD** inherit from the168 original class.169 """170 171 command_consumes_arguments = False172 distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution173 174 def __init__(self, dist: Distribution, **kw):175 """176 Construct the command for dist, updating177 vars(self) with any keyword parameters.178 """179 super().__init__(dist)180 vars(self).update(kw)181 182 def _ensure_stringlike(self, option, what, default=None):183 val = getattr(self, option)184 if val is None:185 setattr(self, option, default)186 return default187 elif not isinstance(val, str):188 raise DistutilsOptionError(189 "'%s' must be a %s (got `%s`)" % (option, what, val)190 )191 return val192 193 def ensure_string_list(self, option):194 r"""Ensure that 'option' is a list of strings. If 'option' is195 currently a string, we split it either on /,\s*/ or /\s+/, so196 "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become197 ["foo", "bar", "baz"].198 199 ..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?202 203 :meta private:204 """205 val = getattr(self, option)206 if val is None:207 return208 elif isinstance(val, str):209 setattr(self, option, re.split(r',\s*|\s+', val))210 else:211 if isinstance(val, list):212 ok = all(isinstance(v, str) for v in val)213 else:214 ok = False215 if not ok:216 raise DistutilsOptionError(217 "'%s' must be a list of strings (got %r)" % (option, val)218 )219 220 def reinitialize_command(self, command, reinit_subcommands=0, **kw):221 cmd = _Command.reinitialize_command(self, command, reinit_subcommands)222 vars(cmd).update(kw)223 return cmd224 225 226def _find_all_simple(path):227 """228 Find all files under 'path'229 """230 results = (231 os.path.join(base, file)232 for base, dirs, files in os.walk(path, followlinks=True)233 for file in files234 )235 return filter(os.path.isfile, results)236 237 238def findall(dir=os.curdir):239 """240 Find all files under 'dir' and return the list of full filenames.241 Unless dir is '.', return full filenames with dir prepended.242 """243 files = _find_all_simple(dir)244 if dir == os.curdir:245 make_rel = functools.partial(os.path.relpath, start=dir)246 files = map(make_rel, files)247 return list(files)248 249 250@functools.wraps(_convert_path)251def convert_path(pathname):252 SetuptoolsDeprecationWarning.emit(253 "Access to implementation detail",254 """255 The function `convert_path` is not provided by setuptools itself,256 and therefore not part of the public API.257 258 Its direct usage by 3rd-party packages is considered improper and the function259 may be removed in the future.260 """,261 due_date=(2023, 12, 13), # initial deprecation 2022-03-25, see #3201262 )263 return _convert_path(pathname)264 265 266class sic(str):267 """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""268 269 270# Apply monkey patches271monkey.patch_all()272 