0001 """distutils.command.build_py 0002 0003 Implements the Distutils 'build_py' command.""" 0004 0005 # This module should be kept compatible with Python 1.5.2. 0006 0007 __revision__ = "$Id: build_py.py,v 1.45 2004/06/18 20:39:11 sjoerd Exp $" 0008 0009 import sys, string, os 0010 from types import * 0011 from glob import glob 0012 0013 from distutils.core import Command 0014 from distutils.errors import * 0015 from distutils.util import convert_path 0016 from distutils import log 0017 0018 class build_py (Command): 0019 0020 description = "\"build\" pure Python modules (copy to build directory)" 0021 0022 user_options = [ 0023 ('build-lib=', 'd', "directory to \"build\" (copy) to"), 0024 ('compile', 'c', "compile .py to .pyc"), 0025 ('no-compile', None, "don't compile .py files [default]"), 0026 ('optimize=', 'O', 0027 "also compile with optimization: -O1 for \"python -O\", " 0028 "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), 0029 ('force', 'f', "forcibly build everything (ignore file timestamps)"), 0030 ] 0031 0032 boolean_options = ['compile', 'force'] 0033 negative_opt = {'no-compile' : 'compile'} 0034 0035 0036 def initialize_options (self): 0037 self.build_lib = None 0038 self.py_modules = None 0039 self.package = None 0040 self.package_data = None 0041 self.package_dir = None 0042 self.compile = 0 0043 self.optimize = 0 0044 self.force = None 0045 0046 def finalize_options (self): 0047 self.set_undefined_options('build', 0048 ('build_lib', 'build_lib'), 0049 ('force', 'force')) 0050 0051 # Get the distribution options that are aliases for build_py 0052 # options -- list of packages and list of modules. 0053 self.packages = self.distribution.packages 0054 self.py_modules = self.distribution.py_modules 0055 self.package_data = self.distribution.package_data 0056 self.package_dir = {} 0057 if self.distribution.package_dir: 0058 for name, path in self.distribution.package_dir.items(): 0059 self.package_dir[name] = convert_path(path) 0060 self.data_files = self.get_data_files() 0061 0062 # Ick, copied straight from install_lib.py (fancy_getopt needs a 0063 # type system! Hell, *everything* needs a type system!!!) 0064 if type(self.optimize) is not IntType: 0065 try: 0066 self.optimize = int(self.optimize) 0067 assert 0 <= self.optimize <= 2 0068 except (ValueError, AssertionError): 0069 raise DistutilsOptionError, "optimize must be 0, 1, or 2" 0070 0071 def run (self): 0072 0073 # XXX copy_file by default preserves atime and mtime. IMHO this is 0074 # the right thing to do, but perhaps it should be an option -- in 0075 # particular, a site administrator might want installed files to 0076 # reflect the time of installation rather than the last 0077 # modification time before the installed release. 0078 0079 # XXX copy_file by default preserves mode, which appears to be the 0080 # wrong thing to do: if a file is read-only in the working 0081 # directory, we want it to be installed read/write so that the next 0082 # installation of the same module distribution can overwrite it 0083 # without problems. (This might be a Unix-specific issue.) Thus 0084 # we turn off 'preserve_mode' when copying to the build directory, 0085 # since the build directory is supposed to be exactly what the 0086 # installation will look like (ie. we preserve mode when 0087 # installing). 0088 0089 # Two options control which modules will be installed: 'packages' 0090 # and 'py_modules'. The former lets us work with whole packages, not 0091 # specifying individual modules at all; the latter is for 0092 # specifying modules one-at-a-time. 0093 0094 if self.py_modules: 0095 self.build_modules() 0096 if self.packages: 0097 self.build_packages() 0098 self.build_package_data() 0099 0100 self.byte_compile(self.get_outputs(include_bytecode=0)) 0101 0102 # run () 0103 0104 def get_data_files (self): 0105 """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" 0106 data = [] 0107 if not self.packages: 0108 return data 0109 for package in self.packages: 0110 # Locate package source directory 0111 src_dir = self.get_package_dir(package) 0112 0113 # Compute package build directory 0114 build_dir = os.path.join(*([self.build_lib] + package.split('.'))) 0115 0116 # Length of path to strip from found files 0117 plen = len(src_dir)+1 0118 0119 # Strip directory from globbed filenames 0120 filenames = [ 0121 file[plen:] for file in self.find_data_files(package, src_dir) 0122 ] 0123 data.append((package, src_dir, build_dir, filenames)) 0124 return data 0125 0126 def find_data_files (self, package, src_dir): 0127 """Return filenames for package's data files in 'src_dir'""" 0128 globs = (self.package_data.get('', []) 0129 + self.package_data.get(package, [])) 0130 files = [] 0131 for pattern in globs: 0132 # Each pattern has to be converted to a platform-specific path 0133 filelist = glob(os.path.join(src_dir, convert_path(pattern))) 0134 # Files that match more than one pattern are only added once 0135 files.extend([fn for fn in filelist if fn not in files]) 0136 return files 0137 0138 def build_package_data (self): 0139 """Copy data files into build directory""" 0140 lastdir = None 0141 for package, src_dir, build_dir, filenames in self.data_files: 0142 for filename in filenames: 0143 target = os.path.join(build_dir, filename) 0144 self.mkpath(os.path.dirname(target)) 0145 self.copy_file(os.path.join(src_dir, filename), target, 0146 preserve_mode=False) 0147 0148 def get_package_dir (self, package): 0149 """Return the directory, relative to the top of the source 0150 distribution, where package 'package' should be found 0151 (at least according to the 'package_dir' option, if any).""" 0152 0153 path = string.split(package, '.') 0154 0155 if not self.package_dir: 0156 if path: 0157 return apply(os.path.join, path) 0158 else: 0159 return '' 0160 else: 0161 tail = [] 0162 while path: 0163 try: 0164 pdir = self.package_dir[string.join(path, '.')] 0165 except KeyError: 0166 tail.insert(0, path[-1]) 0167 del path[-1] 0168 else: 0169 tail.insert(0, pdir) 0170 return apply(os.path.join, tail) 0171 else: 0172 # Oops, got all the way through 'path' without finding a 0173 # match in package_dir. If package_dir defines a directory 0174 # for the root (nameless) package, then fallback on it; 0175 # otherwise, we might as well have not consulted 0176 # package_dir at all, as we just use the directory implied 0177 # by 'tail' (which should be the same as the original value 0178 # of 'path' at this point). 0179 pdir = self.package_dir.get('') 0180 if pdir is not None: 0181 tail.insert(0, pdir) 0182 0183 if tail: 0184 return apply(os.path.join, tail) 0185 else: 0186 return '' 0187 0188 # get_package_dir () 0189 0190 0191 def check_package (self, package, package_dir): 0192 0193 # Empty dir name means current directory, which we can probably 0194 # assume exists. Also, os.path.exists and isdir don't know about 0195 # my "empty string means current dir" convention, so we have to 0196 # circumvent them. 0197 if package_dir != "": 0198 if not os.path.exists(package_dir): 0199 raise DistutilsFileError, \ 0200 "package directory '%s' does not exist" % package_dir 0201 if not os.path.isdir(package_dir): 0202 raise DistutilsFileError, \ 0203 ("supposed package directory '%s' exists, " + 0204 "but is not a directory") % package_dir 0205 0206 # Require __init__.py for all but the "root package" 0207 if package: 0208 init_py = os.path.join(package_dir, "__init__.py") 0209 if os.path.isfile(init_py): 0210 return init_py 0211 else: 0212 log.warn(("package init file '%s' not found " + 0213 "(or not a regular file)"), init_py) 0214 0215 # Either not in a package at all (__init__.py not expected), or 0216 # __init__.py doesn't exist -- so don't return the filename. 0217 return None 0218 0219 # check_package () 0220 0221 0222 def check_module (self, module, module_file): 0223 if not os.path.isfile(module_file): 0224 log.warn("file %s (for module %s) not found", module_file, module) 0225 return 0 0226 else: 0227 return 1 0228 0229 # check_module () 0230 0231 0232 def find_package_modules (self, package, package_dir): 0233 self.check_package(package, package_dir) 0234 module_files = glob(os.path.join(package_dir, "*.py")) 0235 modules = [] 0236 setup_script = os.path.abspath(self.distribution.script_name) 0237 0238 for f in module_files: 0239 abs_f = os.path.abspath(f) 0240 if abs_f != setup_script: 0241 module = os.path.splitext(os.path.basename(f))[0] 0242 modules.append((package, module, f)) 0243 else: 0244 self.debug_print("excluding %s" % setup_script) 0245 return modules 0246 0247 0248 def find_modules (self): 0249 """Finds individually-specified Python modules, ie. those listed by 0250 module name in 'self.py_modules'. Returns a list of tuples (package, 0251 module_base, filename): 'package' is a tuple of the path through 0252 package-space to the module; 'module_base' is the bare (no 0253 packages, no dots) module name, and 'filename' is the path to the 0254 ".py" file (relative to the distribution root) that implements the 0255 module. 0256 """ 0257 0258 # Map package names to tuples of useful info about the package: 0259 # (package_dir, checked) 0260 # package_dir - the directory where we'll find source files for 0261 # this package 0262 # checked - true if we have checked that the package directory 0263 # is valid (exists, contains __init__.py, ... ?) 0264 packages = {} 0265 0266 # List of (package, module, filename) tuples to return 0267 modules = [] 0268 0269 # We treat modules-in-packages almost the same as toplevel modules, 0270 # just the "package" for a toplevel is empty (either an empty 0271 # string or empty list, depending on context). Differences: 0272 # - don't check for __init__.py in directory for empty package 0273 0274 for module in self.py_modules: 0275 path = string.split(module, '.') 0276 package = string.join(path[0:-1], '.') 0277 module_base = path[-1] 0278 0279 try: 0280 (package_dir, checked) = packages[package] 0281 except KeyError: 0282 package_dir = self.get_package_dir(package) 0283 checked = 0 0284 0285 if not checked: 0286 init_py = self.check_package(package, package_dir) 0287 packages[package] = (package_dir, 1) 0288 if init_py: 0289 modules.append((package, "__init__", init_py)) 0290 0291 # XXX perhaps we should also check for just .pyc files 0292 # (so greedy closed-source bastards can distribute Python 0293 # modules too) 0294 module_file = os.path.join(package_dir, module_base + ".py") 0295 if not self.check_module(module, module_file): 0296 continue 0297 0298 modules.append((package, module_base, module_file)) 0299 0300 return modules 0301 0302 # find_modules () 0303 0304 0305 def find_all_modules (self): 0306 """Compute the list of all modules that will be built, whether 0307 they are specified one-module-at-a-time ('self.py_modules') or 0308 by whole packages ('self.packages'). Return a list of tuples 0309 (package, module, module_file), just like 'find_modules()' and 0310 'find_package_modules()' do.""" 0311 0312 modules = [] 0313 if self.py_modules: 0314 modules.extend(self.find_modules()) 0315 if self.packages: 0316 for package in self.packages: 0317 package_dir = self.get_package_dir(package) 0318 m = self.find_package_modules(package, package_dir) 0319 modules.extend(m) 0320 0321 return modules 0322 0323 # find_all_modules () 0324 0325 0326 def get_source_files (self): 0327 0328 modules = self.find_all_modules() 0329 filenames = [] 0330 for module in modules: 0331 filenames.append(module[-1]) 0332 0333 return filenames 0334 0335 0336 def get_module_outfile (self, build_dir, package, module): 0337 outfile_path = [build_dir] + list(package) + [module + ".py"] 0338 return apply(os.path.join, outfile_path) 0339 0340 0341 def get_outputs (self, include_bytecode=1): 0342 modules = self.find_all_modules() 0343 outputs = [] 0344 for (package, module, module_file) in modules: 0345 package = string.split(package, '.') 0346 filename = self.get_module_outfile(self.build_lib, package, module) 0347 outputs.append(filename) 0348 if include_bytecode: 0349 if self.compile: 0350 outputs.append(filename + "c") 0351 if self.optimize > 0: 0352 outputs.append(filename + "o") 0353 0354 outputs += [ 0355 os.path.join(build_dir, filename) 0356 for package, src_dir, build_dir, filenames in self.data_files 0357 for filename in filenames 0358 ] 0359 0360 return outputs 0361 0362 0363 def build_module (self, module, module_file, package): 0364 if type(package) is StringType: 0365 package = string.split(package, '.') 0366 elif type(package) not in (ListType, TupleType): 0367 raise TypeError, \ 0368 "'package' must be a string (dot-separated), list, or tuple" 0369 0370 # Now put the module source file into the "build" area -- this is 0371 # easy, we just copy it somewhere under self.build_lib (the build 0372 # directory for Python source). 0373 outfile = self.get_module_outfile(self.build_lib, package, module) 0374 dir = os.path.dirname(outfile) 0375 self.mkpath(dir) 0376 return self.copy_file(module_file, outfile, preserve_mode=0) 0377 0378 0379 def build_modules (self): 0380 0381 modules = self.find_modules() 0382 for (package, module, module_file) in modules: 0383 0384 # Now "build" the module -- ie. copy the source file to 0385 # self.build_lib (the build directory for Python source). 0386 # (Actually, it gets copied to the directory for this package 0387 # under self.build_lib.) 0388 self.build_module(module, module_file, package) 0389 0390 # build_modules () 0391 0392 0393 def build_packages (self): 0394 0395 for package in self.packages: 0396 0397 # Get list of (package, module, module_file) tuples based on 0398 # scanning the package directory. 'package' is only included 0399 # in the tuple so that 'find_modules()' and 0400 # 'find_package_tuples()' have a consistent interface; it's 0401 # ignored here (apart from a sanity check). Also, 'module' is 0402 # the *unqualified* module name (ie. no dots, no package -- we 0403 # already know its package!), and 'module_file' is the path to 0404 # the .py file, relative to the current directory 0405 # (ie. including 'package_dir'). 0406 package_dir = self.get_package_dir(package) 0407 modules = self.find_package_modules(package, package_dir) 0408 0409 # Now loop over the modules we found, "building" each one (just 0410 # copy it to self.build_lib). 0411 for (package_, module, module_file) in modules: 0412 assert package == package_ 0413 self.build_module(module, module_file, package) 0414 0415 # build_packages () 0416 0417 0418 def byte_compile (self, files): 0419 from distutils.util import byte_compile 0420 prefix = self.build_lib 0421 if prefix[-1] != os.sep: 0422 prefix = prefix + os.sep 0423 0424 # XXX this code is essentially the same as the 'byte_compile() 0425 # method of the "install_lib" command, except for the determination 0426 # of the 'prefix' string. Hmmm. 0427 0428 if self.compile: 0429 byte_compile(files, optimize=0, 0430 force=self.force, prefix=prefix, dry_run=self.dry_run) 0431 if self.optimize > 0: 0432 byte_compile(files, optimize=self.optimize, 0433 force=self.force, prefix=prefix, dry_run=self.dry_run) 0434 0435 # class build_py 0436
Generated by PyXR 0.9.4