diff options
author | Greg Ward <gward@python.net> | 1999-09-21 18:22:34 (GMT) |
---|---|---|
committer | Greg Ward <gward@python.net> | 1999-09-21 18:22:34 (GMT) |
commit | 17dc6e7ed8cd62068b5f244a9f1023917d3caf4a (patch) | |
tree | a130e41a0c3df826e069a1254f1cfcae7b8d630d | |
parent | 0297719180d7d4e54dfb81e77b180081ac9cc6de (diff) | |
download | cpython-17dc6e7ed8cd62068b5f244a9f1023917d3caf4a.zip cpython-17dc6e7ed8cd62068b5f244a9f1023917d3caf4a.tar.gz cpython-17dc6e7ed8cd62068b5f244a9f1023917d3caf4a.tar.bz2 |
Basically a complete rewrite to support dealing with modules in whole
packages and searching for source files by 'package_dir'.
-rw-r--r-- | Lib/distutils/command/build_py.py | 233 |
1 files changed, 197 insertions, 36 deletions
diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py index 0bbe339..187e93b 100644 --- a/Lib/distutils/command/build_py.py +++ b/Lib/distutils/command/build_py.py @@ -7,6 +7,9 @@ Implements the Distutils 'build_py' command.""" __rcsid__ = "$Id$" import string, os +from types import * +from glob import glob + from distutils.core import Command from distutils.errors import * from distutils.util import mkpath, newer, make_file, copy_file @@ -22,15 +25,17 @@ class BuildPy (Command): self.dir = None self.modules = None self.package = None + self.package_dir = None def set_final_options (self): self.set_undefined_options ('build', ('libdir', 'dir')) - # 'package' is an alias option in Distribution (hmmm, we - # really should change to "pull" options from Distribution - # rather than "pushing" them out to commands...) - if self.package is None: - self.package = '' + + # Get the distribution options that are aliases for build_py + # options -- list of packages and list of modules. + self.packages = self.distribution.packages + self.modules = self.distribution.py_modules + self.package_dir = self.distribution.package_dir def run (self): @@ -54,40 +59,196 @@ class BuildPy (Command): outfiles = [] missing = [] - # Loop over the list of "pure Python" modules, deriving - # input and output filenames and checking for missing - # input files. + # Two options control which modules will be installed: 'packages' + # and 'modules'. The former lets us work with whole packages, not + # specifying individual modules at all; the latter is for + # specifying modules one-at-a-time. Currently they are mutually + # exclusive: you can define one or the other (or neither), but not + # both. It remains to be seen how limiting this is. - # it's ok not to have *any* py files, right? - if not self.modules: + # Dispose of the two "unusual" cases first: no pure Python modules + # at all (no problem, just return silently), and over-specified + # 'packages' and 'modules' options. + + if not self.modules and not self.packages: return + if self.modules and self.packages: + raise DistutilsOptionError, \ + "build_py: supplying both 'packages' and 'modules' " + \ + "options not allowed" + + # Now we're down to two cases: 'modules' only and 'packages' only. + if self.modules: + self.build_modules () + else: + self.build_packages () + + + # run () - # XXX we should allow for wildcards, so eg. the Distutils setup.py - # file would just have to say - # py_modules = ['distutils.*', 'distutils.command.*'] - # without having to list each one explicitly. - for m in self.modules: - fn = apply (os.path.join, tuple (string.split (m, '.'))) + '.py' - if not os.path.exists (fn): - missing.append (fn) + + def get_package_dir (self, package): + """Return the directory, relative to the top of the source + distribution, where package 'package' should be found + (at least according to the 'package_dir' option, if any).""" + + if type (package) is StringType: + path = string.split (package, '.') + elif type (package) in (TupleType, ListType): + path = list (path) + else: + raise TypeError, "'package' must be a string, list, or tuple" + + if not self.package_dir: + return apply (os.path.join, path) + else: + tail = [] + while path: + try: + pdir = self.package_dir[string.join (path, '.')] + except KeyError: + tail.insert (0, path[-1]) + del path[-1] + else: + tail.insert (0, pdir) + return apply (os.path.join, tail) else: - infiles.append (fn) - outfiles.append (os.path.join (self.dir, self.package, fn)) - - # Blow up if any input files were not found. - if missing: - raise DistutilsFileError, \ - "missing files: " + string.join (missing, ' ') - - # Loop over the list of input files, copying them to their - # temporary (build) destination. - created = {} - for i in range (len (infiles)): - outdir = os.path.split (outfiles[i])[0] - if not created.get(outdir): - self.mkpath (outdir) - created[outdir] = 1 - - self.copy_file (infiles[i], outfiles[i]) + # arg! everything failed, we might as well have not even + # looked in package_dir -- oh well + return apply (os.path.join, tail) + + # get_package_dir () + + + def check_package (self, package, package_dir): + + # Empty dir name means current directory, which we can probably + # assume exists. Also, os.path.exists and isdir don't know about + # my "empty string means current dir" convention, so we have to + # circumvent them. + if package_dir != "": + if not os.path.exists (package_dir): + raise DistutilsFileError, \ + "package directory '%s' does not exist" % package_dir + if not os.path.isdir (package_dir): + raise DistutilsFileErorr, \ + ("supposed package directory '%s' exists, " + + "but is not a directory") % package_dir + + # Require __init__.py for all but the "root package" + if package != "": + init_py = os.path.join (package_dir, "__init__.py") + if not os.path.isfile (init_py): + self.warn (("package init file '%s' not found " + + "(or not a regular file)") % init_py) + # check_package () + + + def check_module (self, module, module_file): + if not os.path.isfile (module_file): + self.warn ("file %s (for module %s) not found" % + module_file, module) + return 0 + else: + return 1 + + # check_module () + + + def find_modules (self, package, package_dir): + module_files = glob (os.path.join (package_dir, "*.py")) + module_pairs = [] + for f in module_files: + module = os.path.splitext (os.path.basename (f))[0] + module_pairs.append (module, f) + return module_pairs + + + def build_module (self, module, module_file, package): + + if type (package) is StringType: + package = string.split (package, '.') + + # Now put the module source file into the "build" area -- this + # is easy, we just copy it somewhere under self.dir (the build + # directory for Python source). + outfile_path = package + outfile_path.append (module + ".py") + outfile_path.insert (0, self.dir) + outfile = apply (os.path.join, outfile_path) + + dir = os.path.dirname (outfile) + self.mkpath (dir) + self.copy_file (module_file, outfile) + + + def build_modules (self): + + # Map package names to tuples of useful info about the package: + # (package_dir, checked) + # package_dir - the directory where we'll find source files for + # this package + # checked - true if we have checked that the package directory + # is valid (exists, contains __init__.py, ... ?) + + + packages = {} + + # We treat modules-in-packages almost the same as toplevel modules, + # just the "package" for a toplevel is empty (either an empty + # string or empty list, depending on context). Differences: + # - don't check for __init__.py in directory for empty package + + for module in self.modules: + path = string.split (module, '.') + package = tuple (path[0:-1]) + module = path[-1] + + try: + (package_dir, checked) = packages[package] + except KeyError: + package_dir = self.get_package_dir (package) + checked = 0 + + if not checked: + self.check_package (package, package_dir) + packages[package] = (package_dir, 1) + + # XXX perhaps we should also check for just .pyc files + # (so greedy closed-source bastards can distribute Python + # modules too) + module_file = os.path.join (package_dir, module + ".py") + if not self.check_module (module, module_file): + continue + + # Now "build" the module -- ie. copy the source file to + # self.dir (the build directory for Python source). (Actually, + # it gets copied to the directory for this package under + # self.dir.) + self.build_module (module, module_file, package) + + # build_modules () + + + def build_packages (self): + + for package in self.packages: + package_dir = self.get_package_dir (package) + self.check_package (package, package_dir) + + # Get list of (module, module_file) tuples based on scanning + # the package directory. Here, 'module' is the *unqualified* + # module name (ie. no dots, no package -- we already know its + # package!), and module_file is the path to the .py file, + # relative to the current directory (ie. including + # 'package_dir'). + modules = self.find_modules (package, package_dir) + + # Now loop over the modules we found, "building" each one (just + # copy it to self.dir). + for (module, module_file) in modules: + self.build_module (module, module_file, package) + + # build_packages () # end class BuildPy |