You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

216 lines
7.7 KiB

  1. from glob import glob
  2. from distutils.util import convert_path
  3. import distutils.command.build_py as orig
  4. import os
  5. import sys
  6. import fnmatch
  7. import textwrap
  8. try:
  9. from setuptools.lib2to3_ex import Mixin2to3
  10. except ImportError:
  11. class Mixin2to3:
  12. def run_2to3(self, files, doctests=True):
  13. "do nothing"
  14. class build_py(orig.build_py, Mixin2to3):
  15. """Enhanced 'build_py' command that includes data files with packages
  16. The data files are specified via a 'package_data' argument to 'setup()'.
  17. See 'setuptools.dist.Distribution' for more details.
  18. Also, this version of the 'build_py' command allows you to specify both
  19. 'py_modules' and 'packages' in the same setup operation.
  20. """
  21. def finalize_options(self):
  22. orig.build_py.finalize_options(self)
  23. self.package_data = self.distribution.package_data
  24. self.exclude_package_data = (self.distribution.exclude_package_data or
  25. {})
  26. if 'data_files' in self.__dict__:
  27. del self.__dict__['data_files']
  28. self.__updated_files = []
  29. self.__doctests_2to3 = []
  30. def run(self):
  31. """Build modules, packages, and copy data files to build directory"""
  32. if not self.py_modules and not self.packages:
  33. return
  34. if self.py_modules:
  35. self.build_modules()
  36. if self.packages:
  37. self.build_packages()
  38. self.build_package_data()
  39. self.run_2to3(self.__updated_files, False)
  40. self.run_2to3(self.__updated_files, True)
  41. self.run_2to3(self.__doctests_2to3, True)
  42. # Only compile actual .py files, using our base class' idea of what our
  43. # output files are.
  44. self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0))
  45. def __getattr__(self, attr):
  46. if attr == 'data_files': # lazily compute data files
  47. self.data_files = files = self._get_data_files()
  48. return files
  49. return orig.build_py.__getattr__(self, attr)
  50. def build_module(self, module, module_file, package):
  51. outfile, copied = orig.build_py.build_module(self, module, module_file,
  52. package)
  53. if copied:
  54. self.__updated_files.append(outfile)
  55. return outfile, copied
  56. def _get_data_files(self):
  57. """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
  58. self.analyze_manifest()
  59. data = []
  60. for package in self.packages or ():
  61. # Locate package source directory
  62. src_dir = self.get_package_dir(package)
  63. # Compute package build directory
  64. build_dir = os.path.join(*([self.build_lib] + package.split('.')))
  65. # Length of path to strip from found files
  66. plen = len(src_dir) + 1
  67. # Strip directory from globbed filenames
  68. filenames = [
  69. file[plen:] for file in self.find_data_files(package, src_dir)
  70. ]
  71. data.append((package, src_dir, build_dir, filenames))
  72. return data
  73. def find_data_files(self, package, src_dir):
  74. """Return filenames for package's data files in 'src_dir'"""
  75. globs = (self.package_data.get('', [])
  76. + self.package_data.get(package, []))
  77. files = self.manifest_files.get(package, [])[:]
  78. for pattern in globs:
  79. # Each pattern has to be converted to a platform-specific path
  80. files.extend(glob(os.path.join(src_dir, convert_path(pattern))))
  81. return self.exclude_data_files(package, src_dir, files)
  82. def build_package_data(self):
  83. """Copy data files into build directory"""
  84. for package, src_dir, build_dir, filenames in self.data_files:
  85. for filename in filenames:
  86. target = os.path.join(build_dir, filename)
  87. self.mkpath(os.path.dirname(target))
  88. srcfile = os.path.join(src_dir, filename)
  89. outf, copied = self.copy_file(srcfile, target)
  90. srcfile = os.path.abspath(srcfile)
  91. if (copied and
  92. srcfile in self.distribution.convert_2to3_doctests):
  93. self.__doctests_2to3.append(outf)
  94. def analyze_manifest(self):
  95. self.manifest_files = mf = {}
  96. if not self.distribution.include_package_data:
  97. return
  98. src_dirs = {}
  99. for package in self.packages or ():
  100. # Locate package source directory
  101. src_dirs[assert_relative(self.get_package_dir(package))] = package
  102. self.run_command('egg_info')
  103. ei_cmd = self.get_finalized_command('egg_info')
  104. for path in ei_cmd.filelist.files:
  105. d, f = os.path.split(assert_relative(path))
  106. prev = None
  107. oldf = f
  108. while d and d != prev and d not in src_dirs:
  109. prev = d
  110. d, df = os.path.split(d)
  111. f = os.path.join(df, f)
  112. if d in src_dirs:
  113. if path.endswith('.py') and f == oldf:
  114. continue # it's a module, not data
  115. mf.setdefault(src_dirs[d], []).append(path)
  116. def get_data_files(self):
  117. pass # Lazily compute data files in _get_data_files() function.
  118. def check_package(self, package, package_dir):
  119. """Check namespace packages' __init__ for declare_namespace"""
  120. try:
  121. return self.packages_checked[package]
  122. except KeyError:
  123. pass
  124. init_py = orig.build_py.check_package(self, package, package_dir)
  125. self.packages_checked[package] = init_py
  126. if not init_py or not self.distribution.namespace_packages:
  127. return init_py
  128. for pkg in self.distribution.namespace_packages:
  129. if pkg == package or pkg.startswith(package + '.'):
  130. break
  131. else:
  132. return init_py
  133. f = open(init_py, 'rbU')
  134. if 'declare_namespace'.encode() not in f.read():
  135. from distutils.errors import DistutilsError
  136. raise DistutilsError(
  137. "Namespace package problem: %s is a namespace package, but "
  138. "its\n__init__.py does not call declare_namespace()! Please "
  139. 'fix it.\n(See the setuptools manual under '
  140. '"Namespace Packages" for details.)\n"' % (package,)
  141. )
  142. f.close()
  143. return init_py
  144. def initialize_options(self):
  145. self.packages_checked = {}
  146. orig.build_py.initialize_options(self)
  147. def get_package_dir(self, package):
  148. res = orig.build_py.get_package_dir(self, package)
  149. if self.distribution.src_root is not None:
  150. return os.path.join(self.distribution.src_root, res)
  151. return res
  152. def exclude_data_files(self, package, src_dir, files):
  153. """Filter filenames for package's data files in 'src_dir'"""
  154. globs = (self.exclude_package_data.get('', [])
  155. + self.exclude_package_data.get(package, []))
  156. bad = []
  157. for pattern in globs:
  158. bad.extend(
  159. fnmatch.filter(
  160. files, os.path.join(src_dir, convert_path(pattern))
  161. )
  162. )
  163. bad = dict.fromkeys(bad)
  164. seen = {}
  165. return [
  166. f for f in files if f not in bad
  167. and f not in seen and seen.setdefault(f, 1) # ditch dupes
  168. ]
  169. def assert_relative(path):
  170. if not os.path.isabs(path):
  171. return path
  172. from distutils.errors import DistutilsSetupError
  173. msg = textwrap.dedent("""
  174. Error: setup script specifies an absolute path:
  175. %s
  176. setup() arguments must *always* be /-separated paths relative to the
  177. setup.py directory, *never* absolute paths.
  178. """).lstrip() % path
  179. raise DistutilsSetupError(msg)