1 import os.path, sys, fnmatch
2 from distutils.command.build_py import build_py as _build_py
3 from distutils.util import convert_path
6 class build_py(_build_py):
7 """Enhanced 'build_py' command that includes data files with packages
9 The data files are specified via a 'package_data' argument to 'setup()'.
10 See 'setuptools.dist.Distribution' for more details.
12 Also, this version of the 'build_py' command allows you to specify both
13 'py_modules' and 'packages' in the same setup operation.
15 def finalize_options(self):
16 _build_py.finalize_options(self)
17 self.package_data = self.distribution.package_data
18 self.exclude_package_data = self.distribution.exclude_package_data or {}
19 if 'data_files' in self.__dict__: del self.__dict__['data_files']
22 """Build modules, packages, and copy data files to build directory"""
23 if not self.py_modules and not self.packages:
31 self.build_package_data()
33 # Only compile actual .py files, using our base class' idea of what our
35 self.byte_compile(_build_py.get_outputs(self, include_bytecode=0))
37 def __getattr__(self,attr):
38 if attr=='data_files': # lazily compute data files
39 self.data_files = files = self._get_data_files(); return files
40 return _build_py.__getattr__(self,attr)
42 def _get_data_files(self):
43 """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
44 self.analyze_manifest()
46 for package in self.packages or ():
47 # Locate package source directory
48 src_dir = self.get_package_dir(package)
50 # Compute package build directory
51 build_dir = os.path.join(*([self.build_lib] + package.split('.')))
53 # Length of path to strip from found files
56 # Strip directory from globbed filenames
58 file[plen:] for file in self.find_data_files(package, src_dir)
60 data.append( (package, src_dir, build_dir, filenames) )
63 def find_data_files(self, package, src_dir):
64 """Return filenames for package's data files in 'src_dir'"""
65 globs = (self.package_data.get('', [])
66 + self.package_data.get(package, []))
67 files = self.manifest_files.get(package, [])[:]
69 # Each pattern has to be converted to a platform-specific path
70 files.extend(glob(os.path.join(src_dir, convert_path(pattern))))
71 return self.exclude_data_files(package, src_dir, files)
73 def build_package_data(self):
74 """Copy data files into build directory"""
76 for package, src_dir, build_dir, filenames in self.data_files:
77 for filename in filenames:
78 target = os.path.join(build_dir, filename)
79 self.mkpath(os.path.dirname(target))
80 self.copy_file(os.path.join(src_dir, filename), target)
83 def analyze_manifest(self):
84 self.manifest_files = mf = {}
85 if not self.distribution.include_package_data:
88 for package in self.packages or ():
89 # Locate package source directory
90 src_dirs[assert_relative(self.get_package_dir(package))] = package
92 self.run_command('egg_info')
93 ei_cmd = self.get_finalized_command('egg_info')
94 for path in ei_cmd.filelist.files:
95 d,f = os.path.split(assert_relative(path))
98 while d and d!=prev and d not in src_dirs:
100 d, df = os.path.split(d)
101 f = os.path.join(df, f)
103 if path.endswith('.py') and f==oldf:
104 continue # it's a module, not data
105 mf.setdefault(src_dirs[d],[]).append(path)
107 def get_data_files(self): pass # kludge 2.4 for lazy computation
109 if sys.version<"2.4": # Python 2.4 already has this code
110 def get_outputs(self, include_bytecode=1):
111 """Return complete list of files copied to the build directory
113 This includes both '.py' files and data files, as well as '.pyc'
114 and '.pyo' files if 'include_bytecode' is true. (This method is
115 needed for the 'install_lib' command to do its job properly, and to
116 generate a correct installation manifest.)
118 return _build_py.get_outputs(self, include_bytecode) + [
119 os.path.join(build_dir, filename)
120 for package, src_dir, build_dir,filenames in self.data_files
121 for filename in filenames
124 def check_package(self, package, package_dir):
125 """Check namespace packages' __init__ for declare_namespace"""
127 return self.packages_checked[package]
131 init_py = _build_py.check_package(self, package, package_dir)
132 self.packages_checked[package] = init_py
134 if not init_py or not self.distribution.namespace_packages:
137 for pkg in self.distribution.namespace_packages:
138 if pkg==package or pkg.startswith(package+'.'):
143 f = open(init_py,'rU')
144 if 'declare_namespace' not in f.read():
145 from distutils.errors import DistutilsError
146 raise DistutilsError(
147 "Namespace package problem: %s is a namespace package, but its\n"
148 "__init__.py does not call declare_namespace()! Please fix it.\n"
149 '(See the setuptools manual under "Namespace Packages" for '
150 "details.)\n" % (package,)
155 def initialize_options(self):
156 self.packages_checked={}
157 _build_py.initialize_options(self)
165 def exclude_data_files(self, package, src_dir, files):
166 """Filter filenames for package's data files in 'src_dir'"""
167 globs = (self.exclude_package_data.get('', [])
168 + self.exclude_package_data.get(package, []))
170 for pattern in globs:
173 files, os.path.join(src_dir, convert_path(pattern))
176 bad = dict.fromkeys(bad)
179 f for f in files if f not in bad
180 and f not in seen and seen.setdefault(f,1) # ditch dupes
184 def assert_relative(path):
185 if not os.path.isabs(path):
187 from distutils.errors import DistutilsSetupError
188 raise DistutilsSetupError(
189 """Error: setup script specifies an absolute path:
193 setup() arguments must *always* be /-separated paths relative to the
194 setup.py directory, *never* absolute paths.