| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 | from glob import globfrom distutils.util import convert_pathimport distutils.command.build_py as origimport osimport fnmatchimport textwrapimport ioimport distutils.errorsimport itertoolsimport statfrom setuptools.extern.more_itertools import unique_everseendef make_writable(target):    os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE)class build_py(orig.build_py):    """Enhanced 'build_py' command that includes data files with packages    The data files are specified via a 'package_data' argument to 'setup()'.    See 'setuptools.dist.Distribution' for more details.    Also, this version of the 'build_py' command allows you to specify both    'py_modules' and 'packages' in the same setup operation.    """    def finalize_options(self):        orig.build_py.finalize_options(self)        self.package_data = self.distribution.package_data        self.exclude_package_data = self.distribution.exclude_package_data or {}        if 'data_files' in self.__dict__:            del self.__dict__['data_files']        self.__updated_files = []    def run(self):        """Build modules, packages, and copy data files to build directory"""        if not self.py_modules and not self.packages:            return        if self.py_modules:            self.build_modules()        if self.packages:            self.build_packages()            self.build_package_data()        # Only compile actual .py files, using our base class' idea of what our        # output files are.        self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0))    def __getattr__(self, attr):        "lazily compute data files"        if attr == 'data_files':            self.data_files = self._get_data_files()            return self.data_files        return orig.build_py.__getattr__(self, attr)    def build_module(self, module, module_file, package):        outfile, copied = orig.build_py.build_module(self, module, module_file, package)        if copied:            self.__updated_files.append(outfile)        return outfile, copied    def _get_data_files(self):        """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""        self.analyze_manifest()        return list(map(self._get_pkg_data_files, self.packages or ()))    def _get_pkg_data_files(self, package):        # Locate package source directory        src_dir = self.get_package_dir(package)        # Compute package build directory        build_dir = os.path.join(*([self.build_lib] + package.split('.')))        # Strip directory from globbed filenames        filenames = [            os.path.relpath(file, src_dir)            for file in self.find_data_files(package, src_dir)        ]        return package, src_dir, build_dir, filenames    def find_data_files(self, package, src_dir):        """Return filenames for package's data files in 'src_dir'"""        patterns = self._get_platform_patterns(            self.package_data,            package,            src_dir,        )        globs_expanded = map(glob, patterns)        # flatten the expanded globs into an iterable of matches        globs_matches = itertools.chain.from_iterable(globs_expanded)        glob_files = filter(os.path.isfile, globs_matches)        files = itertools.chain(            self.manifest_files.get(package, []),            glob_files,        )        return self.exclude_data_files(package, src_dir, files)    def build_package_data(self):        """Copy data files into build directory"""        for package, src_dir, build_dir, filenames in self.data_files:            for filename in filenames:                target = os.path.join(build_dir, filename)                self.mkpath(os.path.dirname(target))                srcfile = os.path.join(src_dir, filename)                outf, copied = self.copy_file(srcfile, target)                make_writable(target)                srcfile = os.path.abspath(srcfile)    def analyze_manifest(self):        self.manifest_files = mf = {}        if not self.distribution.include_package_data:            return        src_dirs = {}        for package in self.packages or ():            # Locate package source directory            src_dirs[assert_relative(self.get_package_dir(package))] = package        self.run_command('egg_info')        ei_cmd = self.get_finalized_command('egg_info')        for path in ei_cmd.filelist.files:            d, f = os.path.split(assert_relative(path))            prev = None            oldf = f            while d and d != prev and d not in src_dirs:                prev = d                d, df = os.path.split(d)                f = os.path.join(df, f)            if d in src_dirs:                if path.endswith('.py') and f == oldf:                    continue  # it's a module, not data                mf.setdefault(src_dirs[d], []).append(path)    def get_data_files(self):        pass  # Lazily compute data files in _get_data_files() function.    def check_package(self, package, package_dir):        """Check namespace packages' __init__ for declare_namespace"""        try:            return self.packages_checked[package]        except KeyError:            pass        init_py = orig.build_py.check_package(self, package, package_dir)        self.packages_checked[package] = init_py        if not init_py or not self.distribution.namespace_packages:            return init_py        for pkg in self.distribution.namespace_packages:            if pkg == package or pkg.startswith(package + '.'):                break        else:            return init_py        with io.open(init_py, 'rb') as f:            contents = f.read()        if b'declare_namespace' not in contents:            raise distutils.errors.DistutilsError(                "Namespace package problem: %s is a namespace package, but "                "its\n__init__.py does not call declare_namespace()! Please "                'fix it.\n(See the setuptools manual under '                '"Namespace Packages" for details.)\n"' % (package,)            )        return init_py    def initialize_options(self):        self.packages_checked = {}        orig.build_py.initialize_options(self)    def get_package_dir(self, package):        res = orig.build_py.get_package_dir(self, package)        if self.distribution.src_root is not None:            return os.path.join(self.distribution.src_root, res)        return res    def exclude_data_files(self, package, src_dir, files):        """Filter filenames for package's data files in 'src_dir'"""        files = list(files)        patterns = self._get_platform_patterns(            self.exclude_package_data,            package,            src_dir,        )        match_groups = (fnmatch.filter(files, pattern) for pattern in patterns)        # flatten the groups of matches into an iterable of matches        matches = itertools.chain.from_iterable(match_groups)        bad = set(matches)        keepers = (fn for fn in files if fn not in bad)        # ditch dupes        return list(unique_everseen(keepers))    @staticmethod    def _get_platform_patterns(spec, package, src_dir):        """        yield platform-specific path patterns (suitable for glob        or fn_match) from a glob-based spec (such as        self.package_data or self.exclude_package_data)        matching package in src_dir.        """        raw_patterns = itertools.chain(            spec.get('', []),            spec.get(package, []),        )        return (            # Each pattern has to be converted to a platform-specific path            os.path.join(src_dir, convert_path(pattern))            for pattern in raw_patterns        )def assert_relative(path):    if not os.path.isabs(path):        return path    from distutils.errors import DistutilsSetupError    msg = (        textwrap.dedent(            """        Error: setup script specifies an absolute path:            %s        setup() arguments must *always* be /-separated paths relative to the        setup.py directory, *never* absolute paths.        """        ).lstrip()        % path    )    raise DistutilsSetupError(msg)
 |