build_py.py revision 73a6c942cda33c0b6c97a10b7ef5664e043f987f
1"""distutils.command.build_py 2 3Implements the Distutils 'build_py' command.""" 4 5# created 1999/03/08, Greg Ward 6 7__revision__ = "$Id$" 8 9import sys, string, os 10from types import * 11from glob import glob 12 13from distutils.core import Command 14from distutils.errors import * 15 16 17class build_py (Command): 18 19 description = "\"build\" pure Python modules (copy to build directory)" 20 21 user_options = [ 22 ('build-lib=', 'd', "directory to \"build\" (copy) to"), 23 ('compile', 'c', "compile .py to .pyc"), 24 ('no-compile', None, "don't compile .py files [default]"), 25 ('optimize=', 'O', 26 "also compile with optimization: -O1 for \"python -O\", " 27 "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), 28 ('force', 'f', "forcibly build everything (ignore file timestamps)"), 29 ] 30 31 boolean_options = ['compile', 'force'] 32 negative_opt = {'no-compile' : 'compile'} 33 34 35 def initialize_options (self): 36 self.build_lib = None 37 self.py_modules = None 38 self.package = None 39 self.package_dir = None 40 self.compile = 0 41 self.optimize = 0 42 self.force = None 43 44 def finalize_options (self): 45 self.set_undefined_options('build', 46 ('build_lib', 'build_lib'), 47 ('force', 'force')) 48 49 # Get the distribution options that are aliases for build_py 50 # options -- list of packages and list of modules. 51 self.packages = self.distribution.packages 52 self.py_modules = self.distribution.py_modules 53 self.package_dir = self.distribution.package_dir 54 55 # Ick, copied straight from install_lib.py (fancy_getopt needs a 56 # type system! Hell, *everything* needs a type system!!!) 57 if type(self.optimize) is not IntType: 58 try: 59 self.optimize = int(self.optimize) 60 assert 0 <= self.optimize <= 2 61 except (ValueError, AssertionError): 62 raise DistutilsOptionError, "optimize must be 0, 1, or 2" 63 64 def run (self): 65 66 # XXX copy_file by default preserves atime and mtime. IMHO this is 67 # the right thing to do, but perhaps it should be an option -- in 68 # particular, a site administrator might want installed files to 69 # reflect the time of installation rather than the last 70 # modification time before the installed release. 71 72 # XXX copy_file by default preserves mode, which appears to be the 73 # wrong thing to do: if a file is read-only in the working 74 # directory, we want it to be installed read/write so that the next 75 # installation of the same module distribution can overwrite it 76 # without problems. (This might be a Unix-specific issue.) Thus 77 # we turn off 'preserve_mode' when copying to the build directory, 78 # since the build directory is supposed to be exactly what the 79 # installation will look like (ie. we preserve mode when 80 # installing). 81 82 # Two options control which modules will be installed: 'packages' 83 # and 'py_modules'. The former lets us work with whole packages, not 84 # specifying individual modules at all; the latter is for 85 # specifying modules one-at-a-time. Currently they are mutually 86 # exclusive: you can define one or the other (or neither), but not 87 # both. It remains to be seen how limiting this is. 88 89 # Dispose of the two "unusual" cases first: no pure Python modules 90 # at all (no problem, just return silently), and over-specified 91 # 'packages' and 'py_modules' options. 92 93 if not self.py_modules and not self.packages: 94 return 95 if self.py_modules and self.packages: 96 raise DistutilsOptionError, \ 97 "build_py: supplying both 'packages' and 'py_modules' " + \ 98 "options is not allowed" 99 100 # Now we're down to two cases: 'py_modules' only and 'packages' only. 101 if self.py_modules: 102 self.build_modules() 103 else: 104 self.build_packages() 105 106 self.byte_compile(self.get_outputs(include_bytecode=0)) 107 108 # run () 109 110 111 def get_package_dir (self, package): 112 """Return the directory, relative to the top of the source 113 distribution, where package 'package' should be found 114 (at least according to the 'package_dir' option, if any).""" 115 116 path = string.split(package, '.') 117 118 if not self.package_dir: 119 if path: 120 return apply(os.path.join, path) 121 else: 122 return '' 123 else: 124 tail = [] 125 while path: 126 try: 127 pdir = self.package_dir[string.join(path, '.')] 128 except KeyError: 129 tail.insert(0, path[-1]) 130 del path[-1] 131 else: 132 tail.insert(0, pdir) 133 return apply(os.path.join, tail) 134 else: 135 # Oops, got all the way through 'path' without finding a 136 # match in package_dir. If package_dir defines a directory 137 # for the root (nameless) package, then fallback on it; 138 # otherwise, we might as well have not consulted 139 # package_dir at all, as we just use the directory implied 140 # by 'tail' (which should be the same as the original value 141 # of 'path' at this point). 142 pdir = self.package_dir.get('') 143 if pdir is not None: 144 tail.insert(0, pdir) 145 146 if tail: 147 return apply(os.path.join, tail) 148 else: 149 return '' 150 151 # get_package_dir () 152 153 154 def check_package (self, package, package_dir): 155 156 # Empty dir name means current directory, which we can probably 157 # assume exists. Also, os.path.exists and isdir don't know about 158 # my "empty string means current dir" convention, so we have to 159 # circumvent them. 160 if package_dir != "": 161 if not os.path.exists(package_dir): 162 raise DistutilsFileError, \ 163 "package directory '%s' does not exist" % package_dir 164 if not os.path.isdir(package_dir): 165 raise DistutilsFileError, \ 166 ("supposed package directory '%s' exists, " + 167 "but is not a directory") % package_dir 168 169 # Require __init__.py for all but the "root package" 170 if package: 171 init_py = os.path.join(package_dir, "__init__.py") 172 if os.path.isfile(init_py): 173 return init_py 174 else: 175 self.warn(("package init file '%s' not found " + 176 "(or not a regular file)") % init_py) 177 178 # Either not in a package at all (__init__.py not expected), or 179 # __init__.py doesn't exist -- so don't return the filename. 180 return 181 182 # check_package () 183 184 185 def check_module (self, module, module_file): 186 if not os.path.isfile(module_file): 187 self.warn("file %s (for module %s) not found" % 188 (module_file, module)) 189 return 0 190 else: 191 return 1 192 193 # check_module () 194 195 196 def find_package_modules (self, package, package_dir): 197 self.check_package(package, package_dir) 198 module_files = glob(os.path.join(package_dir, "*.py")) 199 modules = [] 200 setup_script = os.path.abspath(self.distribution.script_name) 201 202 for f in module_files: 203 abs_f = os.path.abspath(f) 204 if abs_f != setup_script: 205 module = os.path.splitext(os.path.basename(f))[0] 206 modules.append((package, module, f)) 207 else: 208 self.debug_print("excluding %s" % setup_script) 209 return modules 210 211 212 def find_modules (self): 213 """Finds individually-specified Python modules, ie. those listed by 214 module name in 'self.py_modules'. Returns a list of tuples (package, 215 module_base, filename): 'package' is a tuple of the path through 216 package-space to the module; 'module_base' is the bare (no 217 packages, no dots) module name, and 'filename' is the path to the 218 ".py" file (relative to the distribution root) that implements the 219 module. 220 """ 221 222 # Map package names to tuples of useful info about the package: 223 # (package_dir, checked) 224 # package_dir - the directory where we'll find source files for 225 # this package 226 # checked - true if we have checked that the package directory 227 # is valid (exists, contains __init__.py, ... ?) 228 packages = {} 229 230 # List of (package, module, filename) tuples to return 231 modules = [] 232 233 # We treat modules-in-packages almost the same as toplevel modules, 234 # just the "package" for a toplevel is empty (either an empty 235 # string or empty list, depending on context). Differences: 236 # - don't check for __init__.py in directory for empty package 237 238 for module in self.py_modules: 239 path = string.split(module, '.') 240 package = string.join(path[0:-1], '.') 241 module_base = path[-1] 242 243 try: 244 (package_dir, checked) = packages[package] 245 except KeyError: 246 package_dir = self.get_package_dir(package) 247 checked = 0 248 249 if not checked: 250 init_py = self.check_package(package, package_dir) 251 packages[package] = (package_dir, 1) 252 if init_py: 253 modules.append((package, "__init__", init_py)) 254 255 # XXX perhaps we should also check for just .pyc files 256 # (so greedy closed-source bastards can distribute Python 257 # modules too) 258 module_file = os.path.join(package_dir, module_base + ".py") 259 if not self.check_module(module, module_file): 260 continue 261 262 modules.append((package, module_base, module_file)) 263 264 return modules 265 266 # find_modules () 267 268 269 def find_all_modules (self): 270 """Compute the list of all modules that will be built, whether 271 they are specified one-module-at-a-time ('self.py_modules') or 272 by whole packages ('self.packages'). Return a list of tuples 273 (package, module, module_file), just like 'find_modules()' and 274 'find_package_modules()' do.""" 275 276 if self.py_modules: 277 modules = self.find_modules() 278 else: 279 modules = [] 280 for package in self.packages: 281 package_dir = self.get_package_dir(package) 282 m = self.find_package_modules(package, package_dir) 283 modules.extend(m) 284 285 return modules 286 287 # find_all_modules () 288 289 290 def get_source_files (self): 291 292 modules = self.find_all_modules() 293 filenames = [] 294 for module in modules: 295 filenames.append(module[-1]) 296 297 return filenames 298 299 300 def get_module_outfile (self, build_dir, package, module): 301 outfile_path = [build_dir] + list(package) + [module + ".py"] 302 return apply(os.path.join, outfile_path) 303 304 305 def get_outputs (self, include_bytecode=1): 306 modules = self.find_all_modules() 307 outputs = [] 308 for (package, module, module_file) in modules: 309 package = string.split(package, '.') 310 filename = self.get_module_outfile(self.build_lib, package, module) 311 outputs.append(filename) 312 if include_bytecode: 313 if self.compile: 314 outputs.append(filename + "c") 315 if self.optimize > 0: 316 outputs.append(filename + "o") 317 318 return outputs 319 320 321 def build_module (self, module, module_file, package): 322 if type(package) is StringType: 323 package = string.split(package, '.') 324 elif type(package) not in (ListType, TupleType): 325 raise TypeError, \ 326 "'package' must be a string (dot-separated), list, or tuple" 327 328 # Now put the module source file into the "build" area -- this is 329 # easy, we just copy it somewhere under self.build_lib (the build 330 # directory for Python source). 331 outfile = self.get_module_outfile(self.build_lib, package, module) 332 dir = os.path.dirname(outfile) 333 self.mkpath(dir) 334 return self.copy_file(module_file, outfile, preserve_mode=0) 335 336 337 def build_modules (self): 338 339 modules = self.find_modules() 340 for (package, module, module_file) in modules: 341 342 # Now "build" the module -- ie. copy the source file to 343 # self.build_lib (the build directory for Python source). 344 # (Actually, it gets copied to the directory for this package 345 # under self.build_lib.) 346 self.build_module(module, module_file, package) 347 348 # build_modules () 349 350 351 def build_packages (self): 352 353 for package in self.packages: 354 355 # Get list of (package, module, module_file) tuples based on 356 # scanning the package directory. 'package' is only included 357 # in the tuple so that 'find_modules()' and 358 # 'find_package_tuples()' have a consistent interface; it's 359 # ignored here (apart from a sanity check). Also, 'module' is 360 # the *unqualified* module name (ie. no dots, no package -- we 361 # already know its package!), and 'module_file' is the path to 362 # the .py file, relative to the current directory 363 # (ie. including 'package_dir'). 364 package_dir = self.get_package_dir(package) 365 modules = self.find_package_modules(package, package_dir) 366 367 # Now loop over the modules we found, "building" each one (just 368 # copy it to self.build_lib). 369 for (package_, module, module_file) in modules: 370 assert package == package_ 371 self.build_module(module, module_file, package) 372 373 # build_packages () 374 375 376 def byte_compile (self, files): 377 from distutils.util import byte_compile 378 prefix = self.build_lib 379 if prefix[-1] != os.sep: 380 prefix = prefix + os.sep 381 382 # XXX this code is essentially the same as the 'byte_compile() 383 # method of the "install_lib" command, except for the determination 384 # of the 'prefix' string. Hmmm. 385 386 if self.compile: 387 byte_compile(files, optimize=0, 388 force=self.force, 389 prefix=prefix, 390 verbose=self.verbose, dry_run=self.dry_run) 391 if self.optimize > 0: 392 byte_compile(files, optimize=self.optimize, 393 force=self.force, 394 prefix=prefix, 395 verbose=self.verbose, dry_run=self.dry_run) 396 397# class build_py 398