Path: blob/master/venv/Lib/site-packages/setuptools/command/build_py.py
811 views
from glob import glob1from distutils.util import convert_path2import distutils.command.build_py as orig3import os4import fnmatch5import textwrap6import io7import distutils.errors8import itertools9import stat1011from setuptools.extern import six12from setuptools.extern.six.moves import map, filter, filterfalse1314try:15from setuptools.lib2to3_ex import Mixin2to316except ImportError:1718class Mixin2to3:19def run_2to3(self, files, doctests=True):20"do nothing"212223def make_writable(target):24os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE)252627class build_py(orig.build_py, Mixin2to3):28"""Enhanced 'build_py' command that includes data files with packages2930The data files are specified via a 'package_data' argument to 'setup()'.31See 'setuptools.dist.Distribution' for more details.3233Also, this version of the 'build_py' command allows you to specify both34'py_modules' and 'packages' in the same setup operation.35"""3637def finalize_options(self):38orig.build_py.finalize_options(self)39self.package_data = self.distribution.package_data40self.exclude_package_data = (self.distribution.exclude_package_data or41{})42if 'data_files' in self.__dict__:43del self.__dict__['data_files']44self.__updated_files = []45self.__doctests_2to3 = []4647def run(self):48"""Build modules, packages, and copy data files to build directory"""49if not self.py_modules and not self.packages:50return5152if self.py_modules:53self.build_modules()5455if self.packages:56self.build_packages()57self.build_package_data()5859self.run_2to3(self.__updated_files, False)60self.run_2to3(self.__updated_files, True)61self.run_2to3(self.__doctests_2to3, True)6263# Only compile actual .py files, using our base class' idea of what our64# output files are.65self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0))6667def __getattr__(self, attr):68"lazily compute data files"69if attr == 'data_files':70self.data_files = self._get_data_files()71return self.data_files72return orig.build_py.__getattr__(self, attr)7374def build_module(self, module, module_file, package):75if six.PY2 and isinstance(package, six.string_types):76# avoid errors on Python 2 when unicode is passed (#190)77package = package.split('.')78outfile, copied = orig.build_py.build_module(self, module, module_file,79package)80if copied:81self.__updated_files.append(outfile)82return outfile, copied8384def _get_data_files(self):85"""Generate list of '(package,src_dir,build_dir,filenames)' tuples"""86self.analyze_manifest()87return list(map(self._get_pkg_data_files, self.packages or ()))8889def _get_pkg_data_files(self, package):90# Locate package source directory91src_dir = self.get_package_dir(package)9293# Compute package build directory94build_dir = os.path.join(*([self.build_lib] + package.split('.')))9596# Strip directory from globbed filenames97filenames = [98os.path.relpath(file, src_dir)99for file in self.find_data_files(package, src_dir)100]101return package, src_dir, build_dir, filenames102103def find_data_files(self, package, src_dir):104"""Return filenames for package's data files in 'src_dir'"""105patterns = self._get_platform_patterns(106self.package_data,107package,108src_dir,109)110globs_expanded = map(glob, patterns)111# flatten the expanded globs into an iterable of matches112globs_matches = itertools.chain.from_iterable(globs_expanded)113glob_files = filter(os.path.isfile, globs_matches)114files = itertools.chain(115self.manifest_files.get(package, []),116glob_files,117)118return self.exclude_data_files(package, src_dir, files)119120def build_package_data(self):121"""Copy data files into build directory"""122for package, src_dir, build_dir, filenames in self.data_files:123for filename in filenames:124target = os.path.join(build_dir, filename)125self.mkpath(os.path.dirname(target))126srcfile = os.path.join(src_dir, filename)127outf, copied = self.copy_file(srcfile, target)128make_writable(target)129srcfile = os.path.abspath(srcfile)130if (copied and131srcfile in self.distribution.convert_2to3_doctests):132self.__doctests_2to3.append(outf)133134def analyze_manifest(self):135self.manifest_files = mf = {}136if not self.distribution.include_package_data:137return138src_dirs = {}139for package in self.packages or ():140# Locate package source directory141src_dirs[assert_relative(self.get_package_dir(package))] = package142143self.run_command('egg_info')144ei_cmd = self.get_finalized_command('egg_info')145for path in ei_cmd.filelist.files:146d, f = os.path.split(assert_relative(path))147prev = None148oldf = f149while d and d != prev and d not in src_dirs:150prev = d151d, df = os.path.split(d)152f = os.path.join(df, f)153if d in src_dirs:154if path.endswith('.py') and f == oldf:155continue # it's a module, not data156mf.setdefault(src_dirs[d], []).append(path)157158def get_data_files(self):159pass # Lazily compute data files in _get_data_files() function.160161def check_package(self, package, package_dir):162"""Check namespace packages' __init__ for declare_namespace"""163try:164return self.packages_checked[package]165except KeyError:166pass167168init_py = orig.build_py.check_package(self, package, package_dir)169self.packages_checked[package] = init_py170171if not init_py or not self.distribution.namespace_packages:172return init_py173174for pkg in self.distribution.namespace_packages:175if pkg == package or pkg.startswith(package + '.'):176break177else:178return init_py179180with io.open(init_py, 'rb') as f:181contents = f.read()182if b'declare_namespace' not in contents:183raise distutils.errors.DistutilsError(184"Namespace package problem: %s is a namespace package, but "185"its\n__init__.py does not call declare_namespace()! Please "186'fix it.\n(See the setuptools manual under '187'"Namespace Packages" for details.)\n"' % (package,)188)189return init_py190191def initialize_options(self):192self.packages_checked = {}193orig.build_py.initialize_options(self)194195def get_package_dir(self, package):196res = orig.build_py.get_package_dir(self, package)197if self.distribution.src_root is not None:198return os.path.join(self.distribution.src_root, res)199return res200201def exclude_data_files(self, package, src_dir, files):202"""Filter filenames for package's data files in 'src_dir'"""203files = list(files)204patterns = self._get_platform_patterns(205self.exclude_package_data,206package,207src_dir,208)209match_groups = (210fnmatch.filter(files, pattern)211for pattern in patterns212)213# flatten the groups of matches into an iterable of matches214matches = itertools.chain.from_iterable(match_groups)215bad = set(matches)216keepers = (217fn218for fn in files219if fn not in bad220)221# ditch dupes222return list(_unique_everseen(keepers))223224@staticmethod225def _get_platform_patterns(spec, package, src_dir):226"""227yield platform-specific path patterns (suitable for glob228or fn_match) from a glob-based spec (such as229self.package_data or self.exclude_package_data)230matching package in src_dir.231"""232raw_patterns = itertools.chain(233spec.get('', []),234spec.get(package, []),235)236return (237# Each pattern has to be converted to a platform-specific path238os.path.join(src_dir, convert_path(pattern))239for pattern in raw_patterns240)241242243# from Python docs244def _unique_everseen(iterable, key=None):245"List unique elements, preserving order. Remember all elements ever seen."246# unique_everseen('AAAABBBCCDAABBB') --> A B C D247# unique_everseen('ABBCcAD', str.lower) --> A B C D248seen = set()249seen_add = seen.add250if key is None:251for element in filterfalse(seen.__contains__, iterable):252seen_add(element)253yield element254else:255for element in iterable:256k = key(element)257if k not in seen:258seen_add(k)259yield element260261262def assert_relative(path):263if not os.path.isabs(path):264return path265from distutils.errors import DistutilsSetupError266267msg = textwrap.dedent("""268Error: setup script specifies an absolute path:269270%s271272setup() arguments must *always* be /-separated paths relative to the273setup.py directory, *never* absolute paths.274""").lstrip() % path275raise DistutilsSetupError(msg)276277278