1import os 2import sys 3from itertools import product, starmap 4import distutils.command.install_lib as orig 5 6 7class install_lib(orig.install_lib): 8 """Don't add compiled flags to filenames of non-Python files""" 9 10 def run(self): 11 self.build() 12 outfiles = self.install() 13 if outfiles is not None: 14 # always compile, in case we have any extension stubs to deal with 15 self.byte_compile(outfiles) 16 17 def get_exclusions(self): 18 """ 19 Return a collections.Sized collections.Container of paths to be 20 excluded for single_version_externally_managed installations. 21 """ 22 all_packages = ( 23 pkg 24 for ns_pkg in self._get_SVEM_NSPs() 25 for pkg in self._all_packages(ns_pkg) 26 ) 27 28 excl_specs = product(all_packages, self._gen_exclusion_paths()) 29 return set(starmap(self._exclude_pkg_path, excl_specs)) 30 31 def _exclude_pkg_path(self, pkg, exclusion_path): 32 """ 33 Given a package name and exclusion path within that package, 34 compute the full exclusion path. 35 """ 36 parts = pkg.split('.') + [exclusion_path] 37 return os.path.join(self.install_dir, *parts) 38 39 @staticmethod 40 def _all_packages(pkg_name): 41 """ 42 >>> list(install_lib._all_packages('foo.bar.baz')) 43 ['foo.bar.baz', 'foo.bar', 'foo'] 44 """ 45 while pkg_name: 46 yield pkg_name 47 pkg_name, sep, child = pkg_name.rpartition('.') 48 49 def _get_SVEM_NSPs(self): 50 """ 51 Get namespace packages (list) but only for 52 single_version_externally_managed installations and empty otherwise. 53 """ 54 # TODO: is it necessary to short-circuit here? i.e. what's the cost 55 # if get_finalized_command is called even when namespace_packages is 56 # False? 57 if not self.distribution.namespace_packages: 58 return [] 59 60 install_cmd = self.get_finalized_command('install') 61 svem = install_cmd.single_version_externally_managed 62 63 return self.distribution.namespace_packages if svem else [] 64 65 @staticmethod 66 def _gen_exclusion_paths(): 67 """ 68 Generate file paths to be excluded for namespace packages (bytecode 69 cache files). 70 """ 71 # always exclude the package module itself 72 yield '__init__.py' 73 74 yield '__init__.pyc' 75 yield '__init__.pyo' 76 77 if not hasattr(sys, 'implementation'): 78 return 79 80 base = os.path.join( 81 '__pycache__', '__init__.' + sys.implementation.cache_tag) 82 yield base + '.pyc' 83 yield base + '.pyo' 84 yield base + '.opt-1.pyc' 85 yield base + '.opt-2.pyc' 86 87 def copy_tree( 88 self, infile, outfile, 89 preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1 90 ): 91 assert preserve_mode and preserve_times and not preserve_symlinks 92 exclude = self.get_exclusions() 93 94 if not exclude: 95 return orig.install_lib.copy_tree(self, infile, outfile) 96 97 # Exclude namespace package __init__.py* files from the output 98 99 from setuptools.archive_util import unpack_directory 100 from distutils import log 101 102 outfiles = [] 103 104 def pf(src, dst): 105 if dst in exclude: 106 log.warn("Skipping installation of %s (namespace package)", 107 dst) 108 return False 109 110 log.info("copying %s -> %s", src, os.path.dirname(dst)) 111 outfiles.append(dst) 112 return dst 113 114 unpack_directory(infile, outfile, pf) 115 return outfiles 116 117 def get_outputs(self): 118 outputs = orig.install_lib.get_outputs(self) 119 exclude = self.get_exclusions() 120 if exclude: 121 return [f for f in outputs if f not in exclude] 122 return outputs 123