123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- """Extensions to the 'distutils' for large or complex distributions"""
- import os
- import functools
- import distutils.core
- import distutils.filelist
- import re
- from distutils.errors import DistutilsOptionError
- from distutils.util import convert_path
- from fnmatch import fnmatchcase
- from ._deprecation_warning import SetuptoolsDeprecationWarning
- from setuptools.extern.six import PY3, string_types
- from setuptools.extern.six.moves import filter, map
- import setuptools.version
- from setuptools.extension import Extension
- from setuptools.dist import Distribution
- from setuptools.depends import Require
- from . import monkey
- __metaclass__ = type
- __all__ = [
- 'setup', 'Distribution', 'Command', 'Extension', 'Require',
- 'SetuptoolsDeprecationWarning',
- 'find_packages'
- ]
- if PY3:
- __all__.append('find_namespace_packages')
- __version__ = setuptools.version.__version__
- bootstrap_install_from = None
- # If we run 2to3 on .py files, should we also convert docstrings?
- # Default: yes; assume that we can detect doctests reliably
- run_2to3_on_doctests = True
- # Standard package names for fixer packages
- lib2to3_fixer_packages = ['lib2to3.fixes']
- class PackageFinder:
- """
- Generate a list of all Python packages found within a directory
- """
- @classmethod
- def find(cls, where='.', exclude=(), include=('*',)):
- """Return a list all Python packages found within directory 'where'
- 'where' is the root directory which will be searched for packages. It
- should be supplied as a "cross-platform" (i.e. URL-style) path; it will
- be converted to the appropriate local path syntax.
- 'exclude' is a sequence of package names to exclude; '*' can be used
- as a wildcard in the names, such that 'foo.*' will exclude all
- subpackages of 'foo' (but not 'foo' itself).
- 'include' is a sequence of package names to include. If it's
- specified, only the named packages will be included. If it's not
- specified, all found packages will be included. 'include' can contain
- shell style wildcard patterns just like 'exclude'.
- """
- return list(cls._find_packages_iter(
- convert_path(where),
- cls._build_filter('ez_setup', '*__pycache__', *exclude),
- cls._build_filter(*include)))
- @classmethod
- def _find_packages_iter(cls, where, exclude, include):
- """
- All the packages found in 'where' that pass the 'include' filter, but
- not the 'exclude' filter.
- """
- for root, dirs, files in os.walk(where, followlinks=True):
- # Copy dirs to iterate over it, then empty dirs.
- all_dirs = dirs[:]
- dirs[:] = []
- for dir in all_dirs:
- full_path = os.path.join(root, dir)
- rel_path = os.path.relpath(full_path, where)
- package = rel_path.replace(os.path.sep, '.')
- # Skip directory trees that are not valid packages
- if ('.' in dir or not cls._looks_like_package(full_path)):
- continue
- # Should this package be included?
- if include(package) and not exclude(package):
- yield package
- # Keep searching subdirectories, as there may be more packages
- # down there, even if the parent was excluded.
- dirs.append(dir)
- @staticmethod
- def _looks_like_package(path):
- """Does a directory look like a package?"""
- return os.path.isfile(os.path.join(path, '__init__.py'))
- @staticmethod
- def _build_filter(*patterns):
- """
- Given a list of patterns, return a callable that will be true only if
- the input matches at least one of the patterns.
- """
- return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns)
- class PEP420PackageFinder(PackageFinder):
- @staticmethod
- def _looks_like_package(path):
- return True
- find_packages = PackageFinder.find
- if PY3:
- find_namespace_packages = PEP420PackageFinder.find
- def _install_setup_requires(attrs):
- # Note: do not use `setuptools.Distribution` directly, as
- # our PEP 517 backend patch `distutils.core.Distribution`.
- 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)
- }
- distutils.core.Distribution.__init__(self, filtered)
- def finalize_options(self):
- """
- Disable finalize_options to avoid building the working set.
- Ref #2158.
- """
- dist = MinimalDistribution(attrs)
- # Honor setup.cfg's options.
- dist.parse_config_files(ignore_option_errors=True)
- if dist.setup_requires:
- dist.fetch_build_eggs(dist.setup_requires)
- def setup(**attrs):
- # Make sure we have any requirements needed to interpret 'attrs'.
- _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):
- __doc__ = _Command.__doc__
- command_consumes_arguments = False
- def __init__(self, dist, **kw):
- """
- Construct the command for dist, updating
- vars(self) with any keyword parameters.
- """
- _Command.__init__(self, 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, string_types):
- 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"].
- """
- val = getattr(self, option)
- if val is None:
- return
- elif isinstance(val, string_types):
- setattr(self, option, re.split(r',\s*|\s+', val))
- else:
- if isinstance(val, list):
- ok = all(isinstance(v, string_types) 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)
- class sic(str):
- """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
- # Apply monkey patches
- monkey.patch_all()
|