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']
23 if sys.platform == "win32":
24 from setuptools.command.scriptsetup import do_scriptsetup
28 """Build modules, packages, and copy data files to build directory"""
29 if not self.py_modules and not self.packages:
37 self.build_package_data()
39 # Only compile actual .py files, using our base class' idea of what our
41 self.byte_compile(_build_py.get_outputs(self, include_bytecode=0))
43 def __getattr__(self,attr):
44 if attr=='data_files': # lazily compute data files
45 self.data_files = files = self._get_data_files(); return files
46 return _build_py.__getattr__(self,attr)
48 def _get_data_files(self):
49 """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
50 self.analyze_manifest()
52 for package in self.packages or ():
53 # Locate package source directory
54 src_dir = self.get_package_dir(package)
56 # Compute package build directory
57 build_dir = os.path.join(*([self.build_lib] + package.split('.')))
59 # Length of path to strip from found files
62 # Strip directory from globbed filenames
64 file[plen:] for file in self.find_data_files(package, src_dir)
66 data.append( (package, src_dir, build_dir, filenames) )
69 def find_data_files(self, package, src_dir):
70 """Return filenames for package's data files in 'src_dir'"""
71 globs = (self.package_data.get('', [])
72 + self.package_data.get(package, []))
73 files = self.manifest_files.get(package, [])[:]
75 # Each pattern has to be converted to a platform-specific path
76 files.extend(glob(os.path.join(src_dir, convert_path(pattern))))
77 return self.exclude_data_files(package, src_dir, files)
79 def build_package_data(self):
80 """Copy data files into build directory"""
82 for package, src_dir, build_dir, filenames in self.data_files:
83 for filename in filenames:
84 target = os.path.join(build_dir, filename)
85 self.mkpath(os.path.dirname(target))
86 self.copy_file(os.path.join(src_dir, filename), target)
89 def analyze_manifest(self):
90 self.manifest_files = mf = {}
91 if not self.distribution.include_package_data:
94 for package in self.packages or ():
95 # Locate package source directory
96 src_dirs[assert_relative(self.get_package_dir(package))] = package
98 self.run_command('egg_info')
99 ei_cmd = self.get_finalized_command('egg_info')
100 for path in ei_cmd.filelist.files:
101 d,f = os.path.split(assert_relative(path))
104 while d and d!=prev and d not in src_dirs:
106 d, df = os.path.split(d)
107 f = os.path.join(df, f)
109 if path.endswith('.py') and f==oldf:
110 continue # it's a module, not data
111 mf.setdefault(src_dirs[d],[]).append(path)
113 def get_data_files(self): pass # kludge 2.4 for lazy computation
115 if sys.version<"2.4": # Python 2.4 already has this code
116 def get_outputs(self, include_bytecode=1):
117 """Return complete list of files copied to the build directory
119 This includes both '.py' files and data files, as well as '.pyc'
120 and '.pyo' files if 'include_bytecode' is true. (This method is
121 needed for the 'install_lib' command to do its job properly, and to
122 generate a correct installation manifest.)
124 return _build_py.get_outputs(self, include_bytecode) + [
125 os.path.join(build_dir, filename)
126 for package, src_dir, build_dir,filenames in self.data_files
127 for filename in filenames
130 def check_package(self, package, package_dir):
131 """Check namespace packages' __init__ for declare_namespace"""
133 return self.packages_checked[package]
137 init_py = _build_py.check_package(self, package, package_dir)
138 self.packages_checked[package] = init_py
140 if not init_py or not self.distribution.namespace_packages:
143 for pkg in self.distribution.namespace_packages:
144 if pkg==package or pkg.startswith(package+'.'):
149 f = open(init_py,'rU')
150 if 'declare_namespace' not in f.read():
151 from distutils.errors import DistutilsError
152 raise DistutilsError(
153 "Namespace package problem: %s is a namespace package, but its\n"
154 "__init__.py does not call declare_namespace()! Please fix it.\n"
155 '(See the setuptools manual under "Namespace Packages" for '
156 "details.)\n" % (package,)
161 def initialize_options(self):
162 self.packages_checked={}
163 _build_py.initialize_options(self)
171 def exclude_data_files(self, package, src_dir, files):
172 """Filter filenames for package's data files in 'src_dir'"""
173 globs = (self.exclude_package_data.get('', [])
174 + self.exclude_package_data.get(package, []))
176 for pattern in globs:
179 files, os.path.join(src_dir, convert_path(pattern))
182 bad = dict.fromkeys(bad)
185 f for f in files if f not in bad
186 and f not in seen and seen.setdefault(f,1) # ditch dupes
190 def assert_relative(path):
191 if not os.path.isabs(path):
193 from distutils.errors import DistutilsSetupError
194 raise DistutilsSetupError(
195 """Error: setup script specifies an absolute path:
199 setup() arguments must *always* be /-separated paths relative to the
200 setup.py directory, *never* absolute paths.