]> git.saurik.com Git - wxWidgets.git/blob - wxPython/distutils/command/build_py.py
Latest distutils
[wxWidgets.git] / wxPython / distutils / command / build_py.py
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