|
1 """distutils.command.build_py |
|
2 |
|
3 Implements the Distutils 'build_py' command.""" |
|
4 |
|
5 # This module should be kept compatible with Python 2.1. |
|
6 |
|
7 __revision__ = "$Id: build_py.py 65742 2008-08-17 04:16:04Z brett.cannon $" |
|
8 |
|
9 import string, os |
|
10 from types import * |
|
11 from glob import glob |
|
12 |
|
13 from distutils.core import Command |
|
14 from distutils.errors import * |
|
15 from distutils.util import convert_path |
|
16 from distutils import log |
|
17 |
|
18 class build_py (Command): |
|
19 |
|
20 description = "\"build\" pure Python modules (copy to build directory)" |
|
21 |
|
22 user_options = [ |
|
23 ('build-lib=', 'd', "directory to \"build\" (copy) to"), |
|
24 ('compile', 'c', "compile .py to .pyc"), |
|
25 ('no-compile', None, "don't compile .py files [default]"), |
|
26 ('optimize=', 'O', |
|
27 "also compile with optimization: -O1 for \"python -O\", " |
|
28 "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), |
|
29 ('force', 'f', "forcibly build everything (ignore file timestamps)"), |
|
30 ] |
|
31 |
|
32 boolean_options = ['compile', 'force'] |
|
33 negative_opt = {'no-compile' : 'compile'} |
|
34 |
|
35 |
|
36 def initialize_options (self): |
|
37 self.build_lib = None |
|
38 self.py_modules = None |
|
39 self.package = None |
|
40 self.package_data = None |
|
41 self.package_dir = None |
|
42 self.compile = 0 |
|
43 self.optimize = 0 |
|
44 self.force = None |
|
45 |
|
46 def finalize_options (self): |
|
47 self.set_undefined_options('build', |
|
48 ('build_lib', 'build_lib'), |
|
49 ('force', 'force')) |
|
50 |
|
51 # Get the distribution options that are aliases for build_py |
|
52 # options -- list of packages and list of modules. |
|
53 self.packages = self.distribution.packages |
|
54 self.py_modules = self.distribution.py_modules |
|
55 self.package_data = self.distribution.package_data |
|
56 self.package_dir = {} |
|
57 if self.distribution.package_dir: |
|
58 for name, path in self.distribution.package_dir.items(): |
|
59 self.package_dir[name] = convert_path(path) |
|
60 self.data_files = self.get_data_files() |
|
61 |
|
62 # Ick, copied straight from install_lib.py (fancy_getopt needs a |
|
63 # type system! Hell, *everything* needs a type system!!!) |
|
64 if type(self.optimize) is not IntType: |
|
65 try: |
|
66 self.optimize = int(self.optimize) |
|
67 assert 0 <= self.optimize <= 2 |
|
68 except (ValueError, AssertionError): |
|
69 raise DistutilsOptionError, "optimize must be 0, 1, or 2" |
|
70 |
|
71 def run (self): |
|
72 |
|
73 # XXX copy_file by default preserves atime and mtime. IMHO this is |
|
74 # the right thing to do, but perhaps it should be an option -- in |
|
75 # particular, a site administrator might want installed files to |
|
76 # reflect the time of installation rather than the last |
|
77 # modification time before the installed release. |
|
78 |
|
79 # XXX copy_file by default preserves mode, which appears to be the |
|
80 # wrong thing to do: if a file is read-only in the working |
|
81 # directory, we want it to be installed read/write so that the next |
|
82 # installation of the same module distribution can overwrite it |
|
83 # without problems. (This might be a Unix-specific issue.) Thus |
|
84 # we turn off 'preserve_mode' when copying to the build directory, |
|
85 # since the build directory is supposed to be exactly what the |
|
86 # installation will look like (ie. we preserve mode when |
|
87 # installing). |
|
88 |
|
89 # Two options control which modules will be installed: 'packages' |
|
90 # and 'py_modules'. The former lets us work with whole packages, not |
|
91 # specifying individual modules at all; the latter is for |
|
92 # specifying modules one-at-a-time. |
|
93 |
|
94 if self.py_modules: |
|
95 self.build_modules() |
|
96 if self.packages: |
|
97 self.build_packages() |
|
98 self.build_package_data() |
|
99 |
|
100 self.byte_compile(self.get_outputs(include_bytecode=0)) |
|
101 |
|
102 # run () |
|
103 |
|
104 def get_data_files (self): |
|
105 """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" |
|
106 data = [] |
|
107 if not self.packages: |
|
108 return data |
|
109 for package in self.packages: |
|
110 # Locate package source directory |
|
111 src_dir = self.get_package_dir(package) |
|
112 |
|
113 # Compute package build directory |
|
114 build_dir = os.path.join(*([self.build_lib] + package.split('.'))) |
|
115 |
|
116 # Length of path to strip from found files |
|
117 plen = 0 |
|
118 if src_dir: |
|
119 plen = len(src_dir)+1 |
|
120 |
|
121 # Strip directory from globbed filenames |
|
122 filenames = [ |
|
123 file[plen:] for file in self.find_data_files(package, src_dir) |
|
124 ] |
|
125 data.append((package, src_dir, build_dir, filenames)) |
|
126 return data |
|
127 |
|
128 def find_data_files (self, package, src_dir): |
|
129 """Return filenames for package's data files in 'src_dir'""" |
|
130 globs = (self.package_data.get('', []) |
|
131 + self.package_data.get(package, [])) |
|
132 files = [] |
|
133 for pattern in globs: |
|
134 # Each pattern has to be converted to a platform-specific path |
|
135 filelist = glob(os.path.join(src_dir, convert_path(pattern))) |
|
136 # Files that match more than one pattern are only added once |
|
137 files.extend([fn for fn in filelist if fn not in files]) |
|
138 return files |
|
139 |
|
140 def build_package_data (self): |
|
141 """Copy data files into build directory""" |
|
142 lastdir = None |
|
143 for package, src_dir, build_dir, filenames in self.data_files: |
|
144 for filename in filenames: |
|
145 target = os.path.join(build_dir, filename) |
|
146 self.mkpath(os.path.dirname(target)) |
|
147 self.copy_file(os.path.join(src_dir, filename), target, |
|
148 preserve_mode=False) |
|
149 |
|
150 def get_package_dir (self, package): |
|
151 """Return the directory, relative to the top of the source |
|
152 distribution, where package 'package' should be found |
|
153 (at least according to the 'package_dir' option, if any).""" |
|
154 |
|
155 path = string.split(package, '.') |
|
156 |
|
157 if not self.package_dir: |
|
158 if path: |
|
159 return apply(os.path.join, path) |
|
160 else: |
|
161 return '' |
|
162 else: |
|
163 tail = [] |
|
164 while path: |
|
165 try: |
|
166 pdir = self.package_dir[string.join(path, '.')] |
|
167 except KeyError: |
|
168 tail.insert(0, path[-1]) |
|
169 del path[-1] |
|
170 else: |
|
171 tail.insert(0, pdir) |
|
172 return os.path.join(*tail) |
|
173 else: |
|
174 # Oops, got all the way through 'path' without finding a |
|
175 # match in package_dir. If package_dir defines a directory |
|
176 # for the root (nameless) package, then fallback on it; |
|
177 # otherwise, we might as well have not consulted |
|
178 # package_dir at all, as we just use the directory implied |
|
179 # by 'tail' (which should be the same as the original value |
|
180 # of 'path' at this point). |
|
181 pdir = self.package_dir.get('') |
|
182 if pdir is not None: |
|
183 tail.insert(0, pdir) |
|
184 |
|
185 if tail: |
|
186 return apply(os.path.join, tail) |
|
187 else: |
|
188 return '' |
|
189 |
|
190 # get_package_dir () |
|
191 |
|
192 |
|
193 def check_package (self, package, package_dir): |
|
194 |
|
195 # Empty dir name means current directory, which we can probably |
|
196 # assume exists. Also, os.path.exists and isdir don't know about |
|
197 # my "empty string means current dir" convention, so we have to |
|
198 # circumvent them. |
|
199 if package_dir != "": |
|
200 if not os.path.exists(package_dir): |
|
201 raise DistutilsFileError, \ |
|
202 "package directory '%s' does not exist" % package_dir |
|
203 if not os.path.isdir(package_dir): |
|
204 raise DistutilsFileError, \ |
|
205 ("supposed package directory '%s' exists, " + |
|
206 "but is not a directory") % package_dir |
|
207 |
|
208 # Require __init__.py for all but the "root package" |
|
209 if package: |
|
210 init_py = os.path.join(package_dir, "__init__.py") |
|
211 if os.path.isfile(init_py): |
|
212 return init_py |
|
213 else: |
|
214 log.warn(("package init file '%s' not found " + |
|
215 "(or not a regular file)"), init_py) |
|
216 |
|
217 # Either not in a package at all (__init__.py not expected), or |
|
218 # __init__.py doesn't exist -- so don't return the filename. |
|
219 return None |
|
220 |
|
221 # check_package () |
|
222 |
|
223 |
|
224 def check_module (self, module, module_file): |
|
225 if not os.path.isfile(module_file): |
|
226 log.warn("file %s (for module %s) not found", module_file, module) |
|
227 return 0 |
|
228 else: |
|
229 return 1 |
|
230 |
|
231 # check_module () |
|
232 |
|
233 |
|
234 def find_package_modules (self, package, package_dir): |
|
235 self.check_package(package, package_dir) |
|
236 module_files = glob(os.path.join(package_dir, "*.py")) |
|
237 modules = [] |
|
238 setup_script = os.path.abspath(self.distribution.script_name) |
|
239 |
|
240 for f in module_files: |
|
241 abs_f = os.path.abspath(f) |
|
242 if abs_f != setup_script: |
|
243 module = os.path.splitext(os.path.basename(f))[0] |
|
244 modules.append((package, module, f)) |
|
245 else: |
|
246 self.debug_print("excluding %s" % setup_script) |
|
247 return modules |
|
248 |
|
249 |
|
250 def find_modules (self): |
|
251 """Finds individually-specified Python modules, ie. those listed by |
|
252 module name in 'self.py_modules'. Returns a list of tuples (package, |
|
253 module_base, filename): 'package' is a tuple of the path through |
|
254 package-space to the module; 'module_base' is the bare (no |
|
255 packages, no dots) module name, and 'filename' is the path to the |
|
256 ".py" file (relative to the distribution root) that implements the |
|
257 module. |
|
258 """ |
|
259 |
|
260 # Map package names to tuples of useful info about the package: |
|
261 # (package_dir, checked) |
|
262 # package_dir - the directory where we'll find source files for |
|
263 # this package |
|
264 # checked - true if we have checked that the package directory |
|
265 # is valid (exists, contains __init__.py, ... ?) |
|
266 packages = {} |
|
267 |
|
268 # List of (package, module, filename) tuples to return |
|
269 modules = [] |
|
270 |
|
271 # We treat modules-in-packages almost the same as toplevel modules, |
|
272 # just the "package" for a toplevel is empty (either an empty |
|
273 # string or empty list, depending on context). Differences: |
|
274 # - don't check for __init__.py in directory for empty package |
|
275 |
|
276 for module in self.py_modules: |
|
277 path = string.split(module, '.') |
|
278 package = string.join(path[0:-1], '.') |
|
279 module_base = path[-1] |
|
280 |
|
281 try: |
|
282 (package_dir, checked) = packages[package] |
|
283 except KeyError: |
|
284 package_dir = self.get_package_dir(package) |
|
285 checked = 0 |
|
286 |
|
287 if not checked: |
|
288 init_py = self.check_package(package, package_dir) |
|
289 packages[package] = (package_dir, 1) |
|
290 if init_py: |
|
291 modules.append((package, "__init__", init_py)) |
|
292 |
|
293 # XXX perhaps we should also check for just .pyc files |
|
294 # (so greedy closed-source bastards can distribute Python |
|
295 # modules too) |
|
296 module_file = os.path.join(package_dir, module_base + ".py") |
|
297 if not self.check_module(module, module_file): |
|
298 continue |
|
299 |
|
300 modules.append((package, module_base, module_file)) |
|
301 |
|
302 return modules |
|
303 |
|
304 # find_modules () |
|
305 |
|
306 |
|
307 def find_all_modules (self): |
|
308 """Compute the list of all modules that will be built, whether |
|
309 they are specified one-module-at-a-time ('self.py_modules') or |
|
310 by whole packages ('self.packages'). Return a list of tuples |
|
311 (package, module, module_file), just like 'find_modules()' and |
|
312 'find_package_modules()' do.""" |
|
313 |
|
314 modules = [] |
|
315 if self.py_modules: |
|
316 modules.extend(self.find_modules()) |
|
317 if self.packages: |
|
318 for package in self.packages: |
|
319 package_dir = self.get_package_dir(package) |
|
320 m = self.find_package_modules(package, package_dir) |
|
321 modules.extend(m) |
|
322 |
|
323 return modules |
|
324 |
|
325 # find_all_modules () |
|
326 |
|
327 |
|
328 def get_source_files (self): |
|
329 |
|
330 modules = self.find_all_modules() |
|
331 filenames = [] |
|
332 for module in modules: |
|
333 filenames.append(module[-1]) |
|
334 |
|
335 return filenames |
|
336 |
|
337 |
|
338 def get_module_outfile (self, build_dir, package, module): |
|
339 outfile_path = [build_dir] + list(package) + [module + ".py"] |
|
340 return os.path.join(*outfile_path) |
|
341 |
|
342 |
|
343 def get_outputs (self, include_bytecode=1): |
|
344 modules = self.find_all_modules() |
|
345 outputs = [] |
|
346 for (package, module, module_file) in modules: |
|
347 package = string.split(package, '.') |
|
348 filename = self.get_module_outfile(self.build_lib, package, module) |
|
349 outputs.append(filename) |
|
350 if include_bytecode: |
|
351 if self.compile: |
|
352 outputs.append(filename + "c") |
|
353 if self.optimize > 0: |
|
354 outputs.append(filename + "o") |
|
355 |
|
356 outputs += [ |
|
357 os.path.join(build_dir, filename) |
|
358 for package, src_dir, build_dir, filenames in self.data_files |
|
359 for filename in filenames |
|
360 ] |
|
361 |
|
362 return outputs |
|
363 |
|
364 |
|
365 def build_module (self, module, module_file, package): |
|
366 if type(package) is StringType: |
|
367 package = string.split(package, '.') |
|
368 elif type(package) not in (ListType, TupleType): |
|
369 raise TypeError, \ |
|
370 "'package' must be a string (dot-separated), list, or tuple" |
|
371 |
|
372 # Now put the module source file into the "build" area -- this is |
|
373 # easy, we just copy it somewhere under self.build_lib (the build |
|
374 # directory for Python source). |
|
375 outfile = self.get_module_outfile(self.build_lib, package, module) |
|
376 dir = os.path.dirname(outfile) |
|
377 self.mkpath(dir) |
|
378 return self.copy_file(module_file, outfile, preserve_mode=0) |
|
379 |
|
380 |
|
381 def build_modules (self): |
|
382 |
|
383 modules = self.find_modules() |
|
384 for (package, module, module_file) in modules: |
|
385 |
|
386 # Now "build" the module -- ie. copy the source file to |
|
387 # self.build_lib (the build directory for Python source). |
|
388 # (Actually, it gets copied to the directory for this package |
|
389 # under self.build_lib.) |
|
390 self.build_module(module, module_file, package) |
|
391 |
|
392 # build_modules () |
|
393 |
|
394 |
|
395 def build_packages (self): |
|
396 |
|
397 for package in self.packages: |
|
398 |
|
399 # Get list of (package, module, module_file) tuples based on |
|
400 # scanning the package directory. 'package' is only included |
|
401 # in the tuple so that 'find_modules()' and |
|
402 # 'find_package_tuples()' have a consistent interface; it's |
|
403 # ignored here (apart from a sanity check). Also, 'module' is |
|
404 # the *unqualified* module name (ie. no dots, no package -- we |
|
405 # already know its package!), and 'module_file' is the path to |
|
406 # the .py file, relative to the current directory |
|
407 # (ie. including 'package_dir'). |
|
408 package_dir = self.get_package_dir(package) |
|
409 modules = self.find_package_modules(package, package_dir) |
|
410 |
|
411 # Now loop over the modules we found, "building" each one (just |
|
412 # copy it to self.build_lib). |
|
413 for (package_, module, module_file) in modules: |
|
414 assert package == package_ |
|
415 self.build_module(module, module_file, package) |
|
416 |
|
417 # build_packages () |
|
418 |
|
419 |
|
420 def byte_compile (self, files): |
|
421 from distutils.util import byte_compile |
|
422 prefix = self.build_lib |
|
423 if prefix[-1] != os.sep: |
|
424 prefix = prefix + os.sep |
|
425 |
|
426 # XXX this code is essentially the same as the 'byte_compile() |
|
427 # method of the "install_lib" command, except for the determination |
|
428 # of the 'prefix' string. Hmmm. |
|
429 |
|
430 if self.compile: |
|
431 byte_compile(files, optimize=0, |
|
432 force=self.force, prefix=prefix, dry_run=self.dry_run) |
|
433 if self.optimize > 0: |
|
434 byte_compile(files, optimize=self.optimize, |
|
435 force=self.force, prefix=prefix, dry_run=self.dry_run) |
|
436 |
|
437 # class build_py |