~ubuntu-branches/ubuntu/karmic/pypy/karmic

« back to all changes in this revision

Viewing changes to lib-python/2.4.1/distutils/command/build_py.py

  • Committer: Bazaar Package Importer
  • Author(s): Alexandre Fayolle
  • Date: 2007-04-13 09:33:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070413093309-yoojh4jcoocu2krz
Tags: upstream-1.0.0
ImportĀ upstreamĀ versionĀ 1.0.0

Show diffs side-by-side

added added

removed removed

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