| 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 |