]>
Commit | Line | Data |
---|---|---|
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. | |
90 | ||
91 | if self.py_modules: | |
92 | self.build_modules() | |
93 | if self.packages: | |
94 | self.build_packages() | |
95 | ||
96 | self.byte_compile(self.get_outputs(include_bytecode=0)) | |
97 | ||
98 | # run () | |
99 | ||
100 | ||
101 | def get_package_dir (self, package): | |
102 | """Return the directory, relative to the top of the source | |
103 | distribution, where package 'package' should be found | |
104 | (at least according to the 'package_dir' option, if any).""" | |
105 | ||
106 | path = string.split(package, '.') | |
107 | ||
108 | if not self.package_dir: | |
109 | if path: | |
110 | return apply(os.path.join, path) | |
111 | else: | |
112 | return '' | |
113 | else: | |
114 | tail = [] | |
115 | while path: | |
116 | try: | |
117 | pdir = self.package_dir[string.join(path, '.')] | |
118 | except KeyError: | |
119 | tail.insert(0, path[-1]) | |
120 | del path[-1] | |
121 | else: | |
122 | tail.insert(0, pdir) | |
123 | return apply(os.path.join, tail) | |
124 | else: | |
125 | # Oops, got all the way through 'path' without finding a | |
126 | # match in package_dir. If package_dir defines a directory | |
127 | # for the root (nameless) package, then fallback on it; | |
128 | # otherwise, we might as well have not consulted | |
129 | # package_dir at all, as we just use the directory implied | |
130 | # by 'tail' (which should be the same as the original value | |
131 | # of 'path' at this point). | |
132 | pdir = self.package_dir.get('') | |
133 | if pdir is not None: | |
134 | tail.insert(0, pdir) | |
135 | ||
136 | if tail: | |
137 | return apply(os.path.join, tail) | |
138 | else: | |
139 | return '' | |
140 | ||
141 | # get_package_dir () | |
142 | ||
143 | ||
144 | def check_package (self, package, package_dir): | |
145 | ||
146 | # Empty dir name means current directory, which we can probably | |
147 | # assume exists. Also, os.path.exists and isdir don't know about | |
148 | # my "empty string means current dir" convention, so we have to | |
149 | # circumvent them. | |
150 | if package_dir != "": | |
151 | if not os.path.exists(package_dir): | |
152 | raise DistutilsFileError, \ | |
153 | "package directory '%s' does not exist" % package_dir | |
154 | if not os.path.isdir(package_dir): | |
155 | raise DistutilsFileError, \ | |
156 | ("supposed package directory '%s' exists, " + | |
157 | "but is not a directory") % package_dir | |
158 | ||
159 | # Require __init__.py for all but the "root package" | |
160 | if package: | |
161 | init_py = os.path.join(package_dir, "__init__.py") | |
162 | if os.path.isfile(init_py): | |
163 | return init_py | |
164 | else: | |
165 | log.warn(("package init file '%s' not found " + | |
166 | "(or not a regular file)"), init_py) | |
167 | ||
168 | # Either not in a package at all (__init__.py not expected), or | |
169 | # __init__.py doesn't exist -- so don't return the filename. | |
170 | return None | |
171 | ||
172 | # check_package () | |
173 | ||
174 | ||
175 | def check_module (self, module, module_file): | |
176 | if not os.path.isfile(module_file): | |
177 | log.warn("file %s (for module %s) not found", module_file, module) | |
178 | return 0 | |
179 | else: | |
180 | return 1 | |
181 | ||
182 | # check_module () | |
183 | ||
184 | ||
185 | def find_package_modules (self, package, package_dir): | |
186 | self.check_package(package, package_dir) | |
187 | module_files = glob(os.path.join(package_dir, "*.py")) | |
188 | modules = [] | |
189 | setup_script = os.path.abspath(self.distribution.script_name) | |
190 | ||
191 | for f in module_files: | |
192 | abs_f = os.path.abspath(f) | |
193 | if abs_f != setup_script: | |
194 | module = os.path.splitext(os.path.basename(f))[0] | |
195 | modules.append((package, module, f)) | |
196 | else: | |
197 | self.debug_print("excluding %s" % setup_script) | |
198 | return modules | |
199 | ||
200 | ||
201 | def find_modules (self): | |
202 | """Finds individually-specified Python modules, ie. those listed by | |
203 | module name in 'self.py_modules'. Returns a list of tuples (package, | |
204 | module_base, filename): 'package' is a tuple of the path through | |
205 | package-space to the module; 'module_base' is the bare (no | |
206 | packages, no dots) module name, and 'filename' is the path to the | |
207 | ".py" file (relative to the distribution root) that implements the | |
208 | module. | |
209 | """ | |
210 | ||
211 | # Map package names to tuples of useful info about the package: | |
212 | # (package_dir, checked) | |
213 | # package_dir - the directory where we'll find source files for | |
214 | # this package | |
215 | # checked - true if we have checked that the package directory | |
216 | # is valid (exists, contains __init__.py, ... ?) | |
217 | packages = {} | |
218 | ||
219 | # List of (package, module, filename) tuples to return | |
220 | modules = [] | |
221 | ||
222 | # We treat modules-in-packages almost the same as toplevel modules, | |
223 | # just the "package" for a toplevel is empty (either an empty | |
224 | # string or empty list, depending on context). Differences: | |
225 | # - don't check for __init__.py in directory for empty package | |
226 | ||
227 | for module in self.py_modules: | |
228 | path = string.split(module, '.') | |
229 | package = string.join(path[0:-1], '.') | |
230 | module_base = path[-1] | |
231 | ||
232 | try: | |
233 | (package_dir, checked) = packages[package] | |
234 | except KeyError: | |
235 | package_dir = self.get_package_dir(package) | |
236 | checked = 0 | |
237 | ||
238 | if not checked: | |
239 | init_py = self.check_package(package, package_dir) | |
240 | packages[package] = (package_dir, 1) | |
241 | if init_py: | |
242 | modules.append((package, "__init__", init_py)) | |
243 | ||
244 | # XXX perhaps we should also check for just .pyc files | |
245 | # (so greedy closed-source bastards can distribute Python | |
246 | # modules too) | |
247 | module_file = os.path.join(package_dir, module_base + ".py") | |
248 | if not self.check_module(module, module_file): | |
249 | continue | |
250 | ||
251 | modules.append((package, module_base, module_file)) | |
252 | ||
253 | return modules | |
254 | ||
255 | # find_modules () | |
256 | ||
257 | ||
258 | def find_all_modules (self): | |
259 | """Compute the list of all modules that will be built, whether | |
260 | they are specified one-module-at-a-time ('self.py_modules') or | |
261 | by whole packages ('self.packages'). Return a list of tuples | |
262 | (package, module, module_file), just like 'find_modules()' and | |
263 | 'find_package_modules()' do.""" | |
264 | ||
265 | modules = [] | |
266 | if self.py_modules: | |
267 | modules.extend(self.find_modules()) | |
268 | if self.packages: | |
269 | for package in self.packages: | |
270 | package_dir = self.get_package_dir(package) | |
271 | m = self.find_package_modules(package, package_dir) | |
272 | modules.extend(m) | |
273 | ||
274 | return modules | |
275 | ||
276 | # find_all_modules () | |
277 | ||
278 | ||
279 | def get_source_files (self): | |
280 | ||
281 | modules = self.find_all_modules() | |
282 | filenames = [] | |
283 | for module in modules: | |
284 | filenames.append(module[-1]) | |
285 | ||
286 | return filenames | |
287 | ||
288 | ||
289 | def get_module_outfile (self, build_dir, package, module): | |
290 | outfile_path = [build_dir] + list(package) + [module + ".py"] | |
291 | return apply(os.path.join, outfile_path) | |
292 | ||
293 | ||
294 | def get_outputs (self, include_bytecode=1): | |
295 | modules = self.find_all_modules() | |
296 | outputs = [] | |
297 | for (package, module, module_file) in modules: | |
298 | package = string.split(package, '.') | |
299 | filename = self.get_module_outfile(self.build_lib, package, module) | |
300 | outputs.append(filename) | |
301 | if include_bytecode: | |
302 | if self.compile: | |
303 | outputs.append(filename + "c") | |
304 | if self.optimize > 0: | |
305 | outputs.append(filename + "o") | |
306 | ||
307 | return outputs | |
308 | ||
309 | ||
310 | def build_module (self, module, module_file, package): | |
311 | if type(package) is StringType: | |
312 | package = string.split(package, '.') | |
313 | elif type(package) not in (ListType, TupleType): | |
314 | raise TypeError, \ | |
315 | "'package' must be a string (dot-separated), list, or tuple" | |
316 | ||
317 | # Now put the module source file into the "build" area -- this is | |
318 | # easy, we just copy it somewhere under self.build_lib (the build | |
319 | # directory for Python source). | |
320 | outfile = self.get_module_outfile(self.build_lib, package, module) | |
321 | dir = os.path.dirname(outfile) | |
322 | self.mkpath(dir) | |
323 | return self.copy_file(module_file, outfile, preserve_mode=0) | |
324 | ||
325 | ||
326 | def build_modules (self): | |
327 | ||
328 | modules = self.find_modules() | |
329 | for (package, module, module_file) in modules: | |
330 | ||
331 | # Now "build" the module -- ie. copy the source file to | |
332 | # self.build_lib (the build directory for Python source). | |
333 | # (Actually, it gets copied to the directory for this package | |
334 | # under self.build_lib.) | |
335 | self.build_module(module, module_file, package) | |
336 | ||
337 | # build_modules () | |
338 | ||
339 | ||
340 | def build_packages (self): | |
341 | ||
342 | for package in self.packages: | |
343 | ||
344 | # Get list of (package, module, module_file) tuples based on | |
345 | # scanning the package directory. 'package' is only included | |
346 | # in the tuple so that 'find_modules()' and | |
347 | # 'find_package_tuples()' have a consistent interface; it's | |
348 | # ignored here (apart from a sanity check). Also, 'module' is | |
349 | # the *unqualified* module name (ie. no dots, no package -- we | |
350 | # already know its package!), and 'module_file' is the path to | |
351 | # the .py file, relative to the current directory | |
352 | # (ie. including 'package_dir'). | |
353 | package_dir = self.get_package_dir(package) | |
354 | modules = self.find_package_modules(package, package_dir) | |
355 | ||
356 | # Now loop over the modules we found, "building" each one (just | |
357 | # copy it to self.build_lib). | |
358 | for (package_, module, module_file) in modules: | |
359 | assert package == package_ | |
360 | self.build_module(module, module_file, package) | |
361 | ||
362 | # build_packages () | |
363 | ||
364 | ||
365 | def byte_compile (self, files): | |
366 | from distutils.util import byte_compile | |
367 | prefix = self.build_lib | |
368 | if prefix[-1] != os.sep: | |
369 | prefix = prefix + os.sep | |
370 | ||
371 | # XXX this code is essentially the same as the 'byte_compile() | |
372 | # method of the "install_lib" command, except for the determination | |
373 | # of the 'prefix' string. Hmmm. | |
374 | ||
375 | if self.compile: | |
376 | byte_compile(files, optimize=0, | |
377 | force=self.force, prefix=prefix, dry_run=self.dry_run) | |
378 | if self.optimize > 0: | |
379 | byte_compile(files, optimize=self.optimize, | |
380 | force=self.force, prefix=prefix, dry_run=self.dry_run) | |
381 | ||
382 | # class build_py |