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