| | """Extensions to the 'distutils' for large or complex distributions""" |
| |
|
| | import functools |
| | import os |
| | import re |
| | import warnings |
| |
|
| | import _distutils_hack.override |
| |
|
| | import distutils.core |
| | from distutils.errors import DistutilsOptionError |
| | from distutils.util import convert_path as _convert_path |
| |
|
| | from ._deprecation_warning import SetuptoolsDeprecationWarning |
| |
|
| | import setuptools.version |
| | from setuptools.extension import Extension |
| | from setuptools.dist import Distribution |
| | from setuptools.depends import Require |
| | from setuptools.discovery import PackageFinder, PEP420PackageFinder |
| | from . import monkey |
| | from . import logging |
| |
|
| |
|
| | __all__ = [ |
| | 'setup', |
| | 'Distribution', |
| | 'Command', |
| | 'Extension', |
| | 'Require', |
| | 'SetuptoolsDeprecationWarning', |
| | 'find_packages', |
| | 'find_namespace_packages', |
| | ] |
| |
|
| | __version__ = setuptools.version.__version__ |
| |
|
| | bootstrap_install_from = None |
| |
|
| |
|
| | find_packages = PackageFinder.find |
| | find_namespace_packages = PEP420PackageFinder.find |
| |
|
| |
|
| | def _install_setup_requires(attrs): |
| | |
| | |
| | class MinimalDistribution(distutils.core.Distribution): |
| | """ |
| | A minimal version of a distribution for supporting the |
| | fetch_build_eggs interface. |
| | """ |
| |
|
| | def __init__(self, attrs): |
| | _incl = 'dependency_links', 'setup_requires' |
| | filtered = {k: attrs[k] for k in set(_incl) & set(attrs)} |
| | super().__init__(filtered) |
| | |
| | self.set_defaults._disable() |
| |
|
| | def _get_project_config_files(self, filenames=None): |
| | """Ignore ``pyproject.toml``, they are not related to setup_requires""" |
| | try: |
| | cfg, toml = super()._split_standard_project_metadata(filenames) |
| | return cfg, () |
| | except Exception: |
| | return filenames, () |
| |
|
| | def finalize_options(self): |
| | """ |
| | Disable finalize_options to avoid building the working set. |
| | Ref #2158. |
| | """ |
| |
|
| | dist = MinimalDistribution(attrs) |
| |
|
| | |
| | dist.parse_config_files(ignore_option_errors=True) |
| | if dist.setup_requires: |
| | dist.fetch_build_eggs(dist.setup_requires) |
| |
|
| |
|
| | def setup(**attrs): |
| | |
| | logging.configure() |
| | _install_setup_requires(attrs) |
| | return distutils.core.setup(**attrs) |
| |
|
| |
|
| | setup.__doc__ = distutils.core.setup.__doc__ |
| |
|
| |
|
| | _Command = monkey.get_unpatched(distutils.core.Command) |
| |
|
| |
|
| | class Command(_Command): |
| | """ |
| | Setuptools internal actions are organized using a *command design pattern*. |
| | This means that each action (or group of closely related actions) executed during |
| | the build should be implemented as a ``Command`` subclass. |
| | |
| | These commands are abstractions and do not necessarily correspond to a command that |
| | can (or should) be executed via a terminal, in a CLI fashion (although historically |
| | they would). |
| | |
| | When creating a new command from scratch, custom defined classes **SHOULD** inherit |
| | from ``setuptools.Command`` and implement a few mandatory methods. |
| | Between these mandatory methods, are listed: |
| | |
| | .. method:: initialize_options(self) |
| | |
| | Set or (reset) all options/attributes/caches used by the command |
| | to their default values. Note that these values may be overwritten during |
| | the build. |
| | |
| | .. method:: finalize_options(self) |
| | |
| | Set final values for all options/attributes used by the command. |
| | Most of the time, each option/attribute/cache should only be set if it does not |
| | have any value yet (e.g. ``if self.attr is None: self.attr = val``). |
| | |
| | .. method:: run(self) |
| | |
| | Execute the actions intended by the command. |
| | (Side effects **SHOULD** only take place when ``run`` is executed, |
| | for example, creating new files or writing to the terminal output). |
| | |
| | A useful analogy for command classes is to think of them as subroutines with local |
| | variables called "options". The options are "declared" in ``initialize_options()`` |
| | and "defined" (given their final values, aka "finalized") in ``finalize_options()``, |
| | both of which must be defined by every command class. The "body" of the subroutine, |
| | (where it does all the work) is the ``run()`` method. |
| | Between ``initialize_options()`` and ``finalize_options()``, ``setuptools`` may set |
| | the values for options/attributes based on user's input (or circumstance), |
| | which means that the implementation should be careful to not overwrite values in |
| | ``finalize_options`` unless necessary. |
| | |
| | Please note that other commands (or other parts of setuptools) may also overwrite |
| | the values of the command's options/attributes multiple times during the build |
| | process. |
| | Therefore it is important to consistently implement ``initialize_options()`` and |
| | ``finalize_options()``. For example, all derived attributes (or attributes that |
| | depend on the value of other attributes) **SHOULD** be recomputed in |
| | ``finalize_options``. |
| | |
| | When overwriting existing commands, custom defined classes **MUST** abide by the |
| | same APIs implemented by the original class. They also **SHOULD** inherit from the |
| | original class. |
| | """ |
| |
|
| | command_consumes_arguments = False |
| |
|
| | def __init__(self, dist, **kw): |
| | """ |
| | Construct the command for dist, updating |
| | vars(self) with any keyword parameters. |
| | """ |
| | super().__init__(dist) |
| | vars(self).update(kw) |
| |
|
| | def _ensure_stringlike(self, option, what, default=None): |
| | val = getattr(self, option) |
| | if val is None: |
| | setattr(self, option, default) |
| | return default |
| | elif not isinstance(val, str): |
| | raise DistutilsOptionError( |
| | "'%s' must be a %s (got `%s`)" % (option, what, val) |
| | ) |
| | return val |
| |
|
| | def ensure_string_list(self, option): |
| | r"""Ensure that 'option' is a list of strings. If 'option' is |
| | currently a string, we split it either on /,\s*/ or /\s+/, so |
| | "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become |
| | ["foo", "bar", "baz"]. |
| | |
| | .. |
| | TODO: This method seems to be similar to the one in ``distutils.cmd`` |
| | Probably it is just here for backward compatibility with old Python versions? |
| | |
| | :meta private: |
| | """ |
| | val = getattr(self, option) |
| | if val is None: |
| | return |
| | elif isinstance(val, str): |
| | setattr(self, option, re.split(r',\s*|\s+', val)) |
| | else: |
| | if isinstance(val, list): |
| | ok = all(isinstance(v, str) for v in val) |
| | else: |
| | ok = False |
| | if not ok: |
| | raise DistutilsOptionError( |
| | "'%s' must be a list of strings (got %r)" % (option, val) |
| | ) |
| |
|
| | def reinitialize_command(self, command, reinit_subcommands=0, **kw): |
| | cmd = _Command.reinitialize_command(self, command, reinit_subcommands) |
| | vars(cmd).update(kw) |
| | return cmd |
| |
|
| |
|
| | def _find_all_simple(path): |
| | """ |
| | Find all files under 'path' |
| | """ |
| | results = ( |
| | os.path.join(base, file) |
| | for base, dirs, files in os.walk(path, followlinks=True) |
| | for file in files |
| | ) |
| | return filter(os.path.isfile, results) |
| |
|
| |
|
| | def findall(dir=os.curdir): |
| | """ |
| | Find all files under 'dir' and return the list of full filenames. |
| | Unless dir is '.', return full filenames with dir prepended. |
| | """ |
| | files = _find_all_simple(dir) |
| | if dir == os.curdir: |
| | make_rel = functools.partial(os.path.relpath, start=dir) |
| | files = map(make_rel, files) |
| | return list(files) |
| |
|
| |
|
| | @functools.wraps(_convert_path) |
| | def convert_path(pathname): |
| | from inspect import cleandoc |
| |
|
| | msg = """ |
| | The function `convert_path` is considered internal and not part of the public API. |
| | Its direct usage by 3rd-party packages is considered deprecated and the function |
| | may be removed in the future. |
| | """ |
| | warnings.warn(cleandoc(msg), SetuptoolsDeprecationWarning) |
| | return _convert_path(pathname) |
| |
|
| |
|
| | class sic(str): |
| | """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)""" |
| |
|
| |
|
| | |
| | monkey.patch_all() |
| |
|