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