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