]> rtime.felk.cvut.cz Git - l4.git/blob - l4/pkg/python/contrib/Lib/distutils/command/build_py.py
Inital import
[l4.git] / l4 / pkg / python / contrib / Lib / 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 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