1"""Extensions to the 'distutils' for large or complex distributions""" 2 3import os 4import sys 5import functools 6import distutils.core 7import distutils.filelist 8import re 9from distutils.errors import DistutilsOptionError 10from distutils.util import convert_path 11from fnmatch import fnmatchcase 12 13from ._deprecation_warning import SetuptoolsDeprecationWarning 14 15from setuptools.extern.six import PY3, string_types 16from setuptools.extern.six.moves import filter, map 17 18import setuptools.version 19from setuptools.extension import Extension 20from setuptools.dist import Distribution, Feature 21from setuptools.depends import Require 22from . import monkey 23 24__metaclass__ = type 25 26 27__all__ = [ 28 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 29 'SetuptoolsDeprecationWarning', 30 'find_packages' 31] 32 33if PY3: 34 __all__.append('find_namespace_packages') 35 36__version__ = setuptools.version.__version__ 37 38bootstrap_install_from = None 39 40# If we run 2to3 on .py files, should we also convert docstrings? 41# Default: yes; assume that we can detect doctests reliably 42run_2to3_on_doctests = True 43# Standard package names for fixer packages 44lib2to3_fixer_packages = ['lib2to3.fixes'] 45 46 47class PackageFinder: 48 """ 49 Generate a list of all Python packages found within a directory 50 """ 51 52 @classmethod 53 def find(cls, where='.', exclude=(), include=('*',)): 54 """Return a list all Python packages found within directory 'where' 55 56 'where' is the root directory which will be searched for packages. It 57 should be supplied as a "cross-platform" (i.e. URL-style) path; it will 58 be converted to the appropriate local path syntax. 59 60 'exclude' is a sequence of package names to exclude; '*' can be used 61 as a wildcard in the names, such that 'foo.*' will exclude all 62 subpackages of 'foo' (but not 'foo' itself). 63 64 'include' is a sequence of package names to include. If it's 65 specified, only the named packages will be included. If it's not 66 specified, all found packages will be included. 'include' can contain 67 shell style wildcard patterns just like 'exclude'. 68 """ 69 70 return list(cls._find_packages_iter( 71 convert_path(where), 72 cls._build_filter('ez_setup', '*__pycache__', *exclude), 73 cls._build_filter(*include))) 74 75 @classmethod 76 def _find_packages_iter(cls, where, exclude, include): 77 """ 78 All the packages found in 'where' that pass the 'include' filter, but 79 not the 'exclude' filter. 80 """ 81 for root, dirs, files in os.walk(where, followlinks=True): 82 # Copy dirs to iterate over it, then empty dirs. 83 all_dirs = dirs[:] 84 dirs[:] = [] 85 86 for dir in all_dirs: 87 full_path = os.path.join(root, dir) 88 rel_path = os.path.relpath(full_path, where) 89 package = rel_path.replace(os.path.sep, '.') 90 91 # Skip directory trees that are not valid packages 92 if ('.' in dir or not cls._looks_like_package(full_path)): 93 continue 94 95 # Should this package be included? 96 if include(package) and not exclude(package): 97 yield package 98 99 # Keep searching subdirectories, as there may be more packages 100 # down there, even if the parent was excluded. 101 dirs.append(dir) 102 103 @staticmethod 104 def _looks_like_package(path): 105 """Does a directory look like a package?""" 106 return os.path.isfile(os.path.join(path, '__init__.py')) 107 108 @staticmethod 109 def _build_filter(*patterns): 110 """ 111 Given a list of patterns, return a callable that will be true only if 112 the input matches at least one of the patterns. 113 """ 114 return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns) 115 116 117class PEP420PackageFinder(PackageFinder): 118 @staticmethod 119 def _looks_like_package(path): 120 return True 121 122 123find_packages = PackageFinder.find 124 125if PY3: 126 find_namespace_packages = PEP420PackageFinder.find 127 128 129def _install_setup_requires(attrs): 130 # Note: do not use `setuptools.Distribution` directly, as 131 # our PEP 517 backend patch `distutils.core.Distribution`. 132 dist = distutils.core.Distribution(dict( 133 (k, v) for k, v in attrs.items() 134 if k in ('dependency_links', 'setup_requires') 135 )) 136 # Honor setup.cfg's options. 137 dist.parse_config_files(ignore_option_errors=True) 138 if dist.setup_requires: 139 dist.fetch_build_eggs(dist.setup_requires) 140 141 142def setup(**attrs): 143 # Make sure we have any requirements needed to interpret 'attrs'. 144 _install_setup_requires(attrs) 145 return distutils.core.setup(**attrs) 146 147setup.__doc__ = distutils.core.setup.__doc__ 148 149 150_Command = monkey.get_unpatched(distutils.core.Command) 151 152 153class Command(_Command): 154 __doc__ = _Command.__doc__ 155 156 command_consumes_arguments = False 157 158 def __init__(self, dist, **kw): 159 """ 160 Construct the command for dist, updating 161 vars(self) with any keyword parameters. 162 """ 163 _Command.__init__(self, dist) 164 vars(self).update(kw) 165 166 def _ensure_stringlike(self, option, what, default=None): 167 val = getattr(self, option) 168 if val is None: 169 setattr(self, option, default) 170 return default 171 elif not isinstance(val, string_types): 172 raise DistutilsOptionError("'%s' must be a %s (got `%s`)" 173 % (option, what, val)) 174 return val 175 176 def ensure_string_list(self, option): 177 r"""Ensure that 'option' is a list of strings. If 'option' is 178 currently a string, we split it either on /,\s*/ or /\s+/, so 179 "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become 180 ["foo", "bar", "baz"]. 181 """ 182 val = getattr(self, option) 183 if val is None: 184 return 185 elif isinstance(val, string_types): 186 setattr(self, option, re.split(r',\s*|\s+', val)) 187 else: 188 if isinstance(val, list): 189 ok = all(isinstance(v, string_types) for v in val) 190 else: 191 ok = False 192 if not ok: 193 raise DistutilsOptionError( 194 "'%s' must be a list of strings (got %r)" 195 % (option, val)) 196 197 def reinitialize_command(self, command, reinit_subcommands=0, **kw): 198 cmd = _Command.reinitialize_command(self, command, reinit_subcommands) 199 vars(cmd).update(kw) 200 return cmd 201 202 203def _find_all_simple(path): 204 """ 205 Find all files under 'path' 206 """ 207 results = ( 208 os.path.join(base, file) 209 for base, dirs, files in os.walk(path, followlinks=True) 210 for file in files 211 ) 212 return filter(os.path.isfile, results) 213 214 215def findall(dir=os.curdir): 216 """ 217 Find all files under 'dir' and return the list of full filenames. 218 Unless dir is '.', return full filenames with dir prepended. 219 """ 220 files = _find_all_simple(dir) 221 if dir == os.curdir: 222 make_rel = functools.partial(os.path.relpath, start=dir) 223 files = map(make_rel, files) 224 return list(files) 225 226 227# Apply monkey patches 228monkey.patch_all() 229