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