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