build_py.py revision 455eb616488e73c6eb69ef4ec9121cdf94d126a0
1"""distutils.command.build_py 2 3Implements the Distutils 'build_py' command.""" 4 5# created 1999/03/08, Greg Ward 6 7__rcsid__ = "$Id$" 8 9import string, os 10from types import * 11from glob import glob 12 13from distutils.core import Command 14from distutils.errors import * 15from distutils.util import mkpath, copy_file 16 17 18class BuildPy (Command): 19 20 options = [('build-dir=', 'd', "directory for platform-shared files"), 21 ] 22 23 24 def set_default_options (self): 25 self.build_dir = None 26 self.modules = None 27 self.package = None 28 self.package_dir = None 29 30 def set_final_options (self): 31 self.set_undefined_options ('build', 32 ('build_lib', 'build_dir')) 33 34 # Get the distribution options that are aliases for build_py 35 # options -- list of packages and list of modules. 36 self.packages = self.distribution.packages 37 self.modules = self.distribution.py_modules 38 self.package_dir = self.distribution.package_dir 39 40 41 def run (self): 42 43 # XXX copy_file by default preserves all stat info -- mode, atime, 44 # and mtime. IMHO this is the right thing to do, but perhaps it 45 # should be an option -- in particular, a site administrator might 46 # want installed files to reflect the time of installation rather 47 # than the last modification time before the installed release. 48 49 # XXX copy_file does *not* preserve MacOS-specific file metadata. 50 # If this is a problem for building/installing Python modules, then 51 # we'll have to fix copy_file. (And what about installing scripts, 52 # when the time comes for that -- does MacOS use its special 53 # metadata to know that a file is meant to be interpreted by 54 # Python?) 55 56 infiles = [] 57 outfiles = [] 58 missing = [] 59 60 # Two options control which modules will be installed: 'packages' 61 # and 'modules'. The former lets us work with whole packages, not 62 # specifying individual modules at all; the latter is for 63 # specifying modules one-at-a-time. Currently they are mutually 64 # exclusive: you can define one or the other (or neither), but not 65 # both. It remains to be seen how limiting this is. 66 67 # Dispose of the two "unusual" cases first: no pure Python modules 68 # at all (no problem, just return silently), and over-specified 69 # 'packages' and 'modules' options. 70 71 if not self.modules and not self.packages: 72 return 73 if self.modules and self.packages: 74 raise DistutilsOptionError, \ 75 "build_py: supplying both 'packages' and 'modules' " + \ 76 "options not allowed" 77 78 # Now we're down to two cases: 'modules' only and 'packages' only. 79 if self.modules: 80 self.build_modules () 81 else: 82 self.build_packages () 83 84 85 # run () 86 87 88 def get_package_dir (self, package): 89 """Return the directory, relative to the top of the source 90 distribution, where package 'package' should be found 91 (at least according to the 'package_dir' option, if any).""" 92 93 if type (package) is StringType: 94 path = string.split (package, '.') 95 elif type (package) in (TupleType, ListType): 96 path = list (path) 97 else: 98 raise TypeError, "'package' must be a string, list, or tuple" 99 100 if not self.package_dir: 101 return apply (os.path.join, path) 102 else: 103 tail = [] 104 while path: 105 try: 106 pdir = self.package_dir[string.join (path, '.')] 107 except KeyError: 108 tail.insert (0, path[-1]) 109 del path[-1] 110 else: 111 tail.insert (0, pdir) 112 return apply (os.path.join, tail) 113 else: 114 # arg! everything failed, we might as well have not even 115 # looked in package_dir -- oh well 116 return apply (os.path.join, tail) 117 118 # get_package_dir () 119 120 121 def check_package (self, package, package_dir): 122 123 # Empty dir name means current directory, which we can probably 124 # assume exists. Also, os.path.exists and isdir don't know about 125 # my "empty string means current dir" convention, so we have to 126 # circumvent them. 127 if package_dir != "": 128 if not os.path.exists (package_dir): 129 raise DistutilsFileError, \ 130 "package directory '%s' does not exist" % package_dir 131 if not os.path.isdir (package_dir): 132 raise DistutilsFileErorr, \ 133 ("supposed package directory '%s' exists, " + 134 "but is not a directory") % package_dir 135 136 # Require __init__.py for all but the "root package" 137 if package != "": 138 init_py = os.path.join (package_dir, "__init__.py") 139 if not os.path.isfile (init_py): 140 self.warn (("package init file '%s' not found " + 141 "(or not a regular file)") % init_py) 142 # check_package () 143 144 145 def check_module (self, module, module_file): 146 if not os.path.isfile (module_file): 147 self.warn ("file %s (for module %s) not found" % 148 module_file, module) 149 return 0 150 else: 151 return 1 152 153 # check_module () 154 155 156 def find_package_modules (self, package, package_dir): 157 module_files = glob (os.path.join (package_dir, "*.py")) 158 module_pairs = [] 159 for f in module_files: 160 module = os.path.splitext (os.path.basename (f))[0] 161 module_pairs.append (module, f) 162 return module_pairs 163 164 165 def find_modules (self): 166 # Map package names to tuples of useful info about the package: 167 # (package_dir, checked) 168 # package_dir - the directory where we'll find source files for 169 # this package 170 # checked - true if we have checked that the package directory 171 # is valid (exists, contains __init__.py, ... ?) 172 packages = {} 173 174 # List of (module, package, filename) tuples to return 175 modules = [] 176 177 # We treat modules-in-packages almost the same as toplevel modules, 178 # just the "package" for a toplevel is empty (either an empty 179 # string or empty list, depending on context). Differences: 180 # - don't check for __init__.py in directory for empty package 181 182 for module in self.modules: 183 path = string.split (module, '.') 184 package = tuple (path[0:-1]) 185 module_base = path[-1] 186 187 try: 188 (package_dir, checked) = packages[package] 189 except KeyError: 190 package_dir = self.get_package_dir (package) 191 checked = 0 192 193 if not checked: 194 self.check_package (package, package_dir) 195 packages[package] = (package_dir, 1) 196 197 # XXX perhaps we should also check for just .pyc files 198 # (so greedy closed-source bastards can distribute Python 199 # modules too) 200 module_file = os.path.join (package_dir, module_base + ".py") 201 if not self.check_module (module, module_file): 202 continue 203 204 modules.append ((module, package, module_file)) 205 206 return modules 207 208 # find_modules () 209 210 211 def get_source_files (self): 212 213 if self.modules: 214 modules = self.find_modules () 215 else: 216 modules = [] 217 for package in self.packages: 218 package_dir = self.get_package_dir (package) 219 m = self.find_package_modules (package, package_dir) 220 modules.extend (m) 221 222 # Both find_modules() and find_package_modules() return a list of 223 # tuples where the last element of each tuple is the filename -- 224 # what a happy coincidence! 225 filenames = [] 226 for module in modules: 227 filenames.append (module[-1]) 228 229 return filenames 230 231 232 def build_module (self, module, module_file, package): 233 234 if type (package) is StringType: 235 package = string.split (package, '.') 236 237 # Now put the module source file into the "build" area -- this is 238 # easy, we just copy it somewhere under self.build_dir (the build 239 # directory for Python source). 240 outfile_path = package 241 outfile_path.append (module + ".py") 242 outfile_path.insert (0, self.build_dir) 243 outfile = apply (os.path.join, outfile_path) 244 245 dir = os.path.dirname (outfile) 246 self.mkpath (dir) 247 self.copy_file (module_file, outfile) 248 249 250 def build_modules (self): 251 252 modules = self.find_modules() 253 for (module, package, module_file) in modules: 254 255 # Now "build" the module -- ie. copy the source file to 256 # self.build_dir (the build directory for Python source). 257 # (Actually, it gets copied to the directory for this package 258 # under self.build_dir.) 259 self.build_module (module, module_file, package) 260 261 # build_modules () 262 263 264 def build_packages (self): 265 266 for package in self.packages: 267 package_dir = self.get_package_dir (package) 268 self.check_package (package, package_dir) 269 270 # Get list of (module, module_file) tuples based on scanning 271 # the package directory. Here, 'module' is the *unqualified* 272 # module name (ie. no dots, no package -- we already know its 273 # package!), and module_file is the path to the .py file, 274 # relative to the current directory (ie. including 275 # 'package_dir'). 276 modules = self.find_package_modules (package, package_dir) 277 278 # Now loop over the modules we found, "building" each one (just 279 # copy it to self.build_dir). 280 for (module, module_file) in modules: 281 self.build_module (module, module_file, package) 282 283 # build_packages () 284 285# end class BuildPy 286