1
"""distutils.command.build_py
3
Implements the Distutils 'build_py' command."""
5
# This module should be kept compatible with Python 2.1.
7
__revision__ = "$Id: build_py.py,v 1.46 2004/11/10 22:23:15 loewis Exp $"
13
from distutils.core import Command
14
from distutils.errors import *
15
from distutils.util import convert_path
16
from distutils import log
18
class build_py (Command):
20
description = "\"build\" pure Python modules (copy to build directory)"
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]"),
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)"),
32
boolean_options = ['compile', 'force']
33
negative_opt = {'no-compile' : 'compile'}
36
def initialize_options (self):
38
self.py_modules = None
40
self.package_data = None
41
self.package_dir = None
46
def finalize_options (self):
47
self.set_undefined_options('build',
48
('build_lib', 'build_lib'),
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
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()
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:
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"
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.
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
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.
98
self.build_package_data()
100
self.byte_compile(self.get_outputs(include_bytecode=0))
104
def get_data_files (self):
105
"""Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
107
if not self.packages:
109
for package in self.packages:
110
# Locate package source directory
111
src_dir = self.get_package_dir(package)
113
# Compute package build directory
114
build_dir = os.path.join(*([self.build_lib] + package.split('.')))
116
# Length of path to strip from found files
117
plen = len(src_dir)+1
119
# Strip directory from globbed filenames
121
file[plen:] for file in self.find_data_files(package, src_dir)
123
data.append((package, src_dir, build_dir, filenames))
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, []))
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])
138
def build_package_data (self):
139
"""Copy data files into build directory"""
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,
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)."""
153
path = string.split(package, '.')
155
if not self.package_dir:
157
return apply(os.path.join, path)
164
pdir = self.package_dir[string.join(path, '.')]
166
tail.insert(0, path[-1])
170
return apply(os.path.join, tail)
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('')
184
return apply(os.path.join, tail)
191
def check_package (self, package, package_dir):
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
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
206
# Require __init__.py for all but the "root package"
208
init_py = os.path.join(package_dir, "__init__.py")
209
if os.path.isfile(init_py):
212
log.warn(("package init file '%s' not found " +
213
"(or not a regular file)"), init_py)
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.
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)
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"))
236
setup_script = os.path.abspath(self.distribution.script_name)
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))
244
self.debug_print("excluding %s" % setup_script)
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
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
262
# checked - true if we have checked that the package directory
263
# is valid (exists, contains __init__.py, ... ?)
266
# List of (package, module, filename) tuples to return
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
274
for module in self.py_modules:
275
path = string.split(module, '.')
276
package = string.join(path[0:-1], '.')
277
module_base = path[-1]
280
(package_dir, checked) = packages[package]
282
package_dir = self.get_package_dir(package)
286
init_py = self.check_package(package, package_dir)
287
packages[package] = (package_dir, 1)
289
modules.append((package, "__init__", init_py))
291
# XXX perhaps we should also check for just .pyc files
292
# (so greedy closed-source bastards can distribute Python
294
module_file = os.path.join(package_dir, module_base + ".py")
295
if not self.check_module(module, module_file):
298
modules.append((package, module_base, module_file))
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."""
314
modules.extend(self.find_modules())
316
for package in self.packages:
317
package_dir = self.get_package_dir(package)
318
m = self.find_package_modules(package, package_dir)
323
# find_all_modules ()
326
def get_source_files (self):
328
modules = self.find_all_modules()
330
for module in modules:
331
filenames.append(module[-1])
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)
341
def get_outputs (self, include_bytecode=1):
342
modules = self.find_all_modules()
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)
350
outputs.append(filename + "c")
351
if self.optimize > 0:
352
outputs.append(filename + "o")
355
os.path.join(build_dir, filename)
356
for package, src_dir, build_dir, filenames in self.data_files
357
for filename in filenames
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):
368
"'package' must be a string (dot-separated), list, or tuple"
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)
376
return self.copy_file(module_file, outfile, preserve_mode=0)
379
def build_modules (self):
381
modules = self.find_modules()
382
for (package, module, module_file) in modules:
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)
393
def build_packages (self):
395
for package in self.packages:
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)
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)
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
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.
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)