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