build_py.py revision 6f980b59368c022fe7c1925e83f909dabf74e634
113ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward"""distutils.command.build_py
213ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
313ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg WardImplements the Distutils 'build_py' command."""
413ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward# created 1999/03/08, Greg Ward
613ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
73ce77fd05ed00168f618b63401d770ccc4f04b09Greg Ward__revision__ = "$Id$"
813ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
99b45443c1bdf99b0f27b12baf06fea475b60e145Greg Wardimport sys, string, os
1017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Wardfrom types import *
1117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Wardfrom glob import glob
1217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
1313ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Wardfrom distutils.core import Command
1413ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Wardfrom distutils.errors import *
1513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
1613ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
171993f9ad0e2cf53c8dc441cbbb44eb2e3a190538Greg Wardclass build_py (Command):
1813ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
1937bc81505379facad85a7c6ff273de0201f28656Greg Ward    description = "\"build\" pure Python modules (copy to build directory)"
2037bc81505379facad85a7c6ff273de0201f28656Greg Ward
21bbeceeaf9a5edf878154b17a6a94403a26822d51Greg Ward    user_options = [
22e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        ('build-lib=', 'd', "directory to \"build\" (copy) to"),
23c41d6b35a9de3dd9e3057a0db9a83b182e792f79Greg Ward        ('force', 'f', "forcibly build everything (ignore file timestamps"),
24bbeceeaf9a5edf878154b17a6a94403a26822d51Greg Ward        ]
2513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
2613ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
27e01149cbe83778a5cf872a6b429ff33179b7cdcbGreg Ward    def initialize_options (self):
28e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        self.build_lib = None
296f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        self.py_modules = None
3071eb8644d7e27fd379a2cf78c509155bdb179332Greg Ward        self.package = None
3117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        self.package_dir = None
32c41d6b35a9de3dd9e3057a0db9a83b182e792f79Greg Ward        self.force = None
3313ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
34e01149cbe83778a5cf872a6b429ff33179b7cdcbGreg Ward    def finalize_options (self):
3513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward        self.set_undefined_options ('build',
36c41d6b35a9de3dd9e3057a0db9a83b182e792f79Greg Ward                                    ('build_lib', 'build_lib'),
37c41d6b35a9de3dd9e3057a0db9a83b182e792f79Greg Ward                                    ('force', 'force'))
3817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
3917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Get the distribution options that are aliases for build_py
4017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # options -- list of packages and list of modules.
4117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        self.packages = self.distribution.packages
426f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        self.py_modules = self.distribution.py_modules
4317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        self.package_dir = self.distribution.package_dir
4413ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
4513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
4613ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward    def run (self):
4713ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
489b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # XXX copy_file by default preserves atime and mtime.  IMHO this is
499b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # the right thing to do, but perhaps it should be an option -- in
509b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # particular, a site administrator might want installed files to
519b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # reflect the time of installation rather than the last
529b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # modification time before the installed release.
539b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward
549b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # XXX copy_file by default preserves mode, which appears to be the
559b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # wrong thing to do: if a file is read-only in the working
569b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # directory, we want it to be installed read/write so that the next
579b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installation of the same module distribution can overwrite it
589b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # without problems.  (This might be a Unix-specific issue.)  Thus
599b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # we turn off 'preserve_mode' when copying to the build directory,
609b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # since the build directory is supposed to be exactly what the
619b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installation will look like (ie. we preserve mode when
629b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installing).
6313ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
6417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Two options control which modules will be installed: 'packages'
656f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        # and 'py_modules'.  The former lets us work with whole packages, not
6617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # specifying individual modules at all; the latter is for
6717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # specifying modules one-at-a-time.  Currently they are mutually
6817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # exclusive: you can define one or the other (or neither), but not
6917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # both.  It remains to be seen how limiting this is.
7013ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
7117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Dispose of the two "unusual" cases first: no pure Python modules
7217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # at all (no problem, just return silently), and over-specified
736f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        # 'packages' and 'py_modules' options.
7417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
756f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if not self.py_modules and not self.packages:
765d60fcf02a7050a07067a12c7a98c8b6b1e68372Greg Ward            return
776f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules and self.packages:
7817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            raise DistutilsOptionError, \
796f980b59368c022fe7c1925e83f909dabf74e634Greg Ward                  "build_py: supplying both 'packages' and 'py_modules' " + \
809b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward                  "options is not allowed"
8117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
826f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        # Now we're down to two cases: 'py_modules' only and 'packages' only.
836f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules:
8417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            self.build_modules ()
8517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
8617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            self.build_packages ()
8717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
8817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # run ()
895d60fcf02a7050a07067a12c7a98c8b6b1e68372Greg Ward
9017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
9117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def get_package_dir (self, package):
9217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        """Return the directory, relative to the top of the source
9317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward           distribution, where package 'package' should be found
9417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward           (at least according to the 'package_dir' option, if any)."""
9517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
9617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if type (package) is StringType:
9717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            path = string.split (package, '.')
9817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        elif type (package) in (TupleType, ListType):
99631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            path = list (package)
10017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
10117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            raise TypeError, "'package' must be a string, list, or tuple"
10217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
10317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if not self.package_dir:
104631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            if path:
105631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                return apply (os.path.join, path)
106631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            else:
107631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                return ''
10817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
10917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            tail = []
11017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            while path:
11117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                try:
11217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                    pdir = self.package_dir[string.join (path, '.')]
11317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                except KeyError:
11417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                    tail.insert (0, path[-1])
11517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                    del path[-1]
11617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                else:
11717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                    tail.insert (0, pdir)
11817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                    return apply (os.path.join, tail)
11913ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward            else:
1208bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # Oops, got all the way through 'path' without finding a
1218bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # match in package_dir.  If package_dir defines a directory
1228bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # for the root (nameless) package, then fallback on it;
1238bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # otherwise, we might as well have not consulted
1248bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # package_dir at all, as we just use the directory implied
1258bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # by 'tail' (which should be the same as the original value
1268bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # of 'path' at this point).
1278bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                pdir = self.package_dir.get('')
1288bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                if pdir is not None:
1298bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                    tail.insert(0, pdir)
1308bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
131631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                if tail:
132631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                    return apply (os.path.join, tail)
133631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                else:
134631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                    return ''
13517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
13617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # get_package_dir ()
13717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
13817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
13917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def check_package (self, package, package_dir):
14017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
14117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Empty dir name means current directory, which we can probably
14217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # assume exists.  Also, os.path.exists and isdir don't know about
14317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # my "empty string means current dir" convention, so we have to
14417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # circumvent them.
14517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if package_dir != "":
14617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            if not os.path.exists (package_dir):
14717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                raise DistutilsFileError, \
14817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                      "package directory '%s' does not exist" % package_dir
14917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            if not os.path.isdir (package_dir):
15056359f591b361f41f661a14e5ed129bf8f22fa87Greg Ward                raise DistutilsFileError, \
15117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                      ("supposed package directory '%s' exists, " +
15217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                       "but is not a directory") % package_dir
15317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
15417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Require __init__.py for all but the "root package"
155631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward        if package:
15617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            init_py = os.path.join (package_dir, "__init__.py")
1578bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward            if os.path.isfile (init_py):
1588bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                return init_py
1598bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward            else:
16017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                self.warn (("package init file '%s' not found " +
16117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                            "(or not a regular file)") % init_py)
1628bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
1638bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # Either not in a package at all (__init__.py not expected), or
1648bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # __init__.py doesn't exist -- so don't return the filename.
1658bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        return
1668bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
16717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # check_package ()
16817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
16917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
17017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def check_module (self, module, module_file):
17117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if not os.path.isfile (module_file):
172113e70efa2b932a3ad2662875114133a1edb600cGreg Ward            self.warn ("file %s (for module %s) not found" %
173113e70efa2b932a3ad2662875114133a1edb600cGreg Ward                       (module_file, module))
17417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            return 0
17517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
17617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            return 1
17717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
17817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # check_module ()
17917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
18017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
1812a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    def find_package_modules (self, package, package_dir):
1828b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        self.check_package (package, package_dir)
18317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        module_files = glob (os.path.join (package_dir, "*.py"))
1848b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        modules = []
1859b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        setup_script = os.path.abspath (sys.argv[0])
1869b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward
18717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        for f in module_files:
1889b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward            abs_f = os.path.abspath (f)
1899b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward            if abs_f != setup_script:
1909b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward                module = os.path.splitext (os.path.basename (f))[0]
1918b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward                modules.append ((package, module, f))
1928b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return modules
19317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
19417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
1952a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    def find_modules (self):
1968bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        """Finds individually-specified Python modules, ie. those listed by
1976f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        module name in 'self.py_modules'.  Returns a list of tuples (package,
1988bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        module_base, filename): 'package' is a tuple of the path through
1998bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        package-space to the module; 'module_base' is the bare (no
2008bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        packages, no dots) module name, and 'filename' is the path to the
2018bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        ".py" file (relative to the distribution root) that implements the
2028bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        module.
2038bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        """
2048bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
20517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Map package names to tuples of useful info about the package:
20617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #    (package_dir, checked)
20717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # package_dir - the directory where we'll find source files for
20817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   this package
20917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # checked - true if we have checked that the package directory
21017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   is valid (exists, contains __init__.py, ... ?)
21117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        packages = {}
21217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2138bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # List of (package, module, filename) tuples to return
2142a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        modules = []
2152a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
21617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # We treat modules-in-packages almost the same as toplevel modules,
21717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # just the "package" for a toplevel is empty (either an empty
21817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # string or empty list, depending on context).  Differences:
21917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   - don't check for __init__.py in directory for empty package
22017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2216f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        for module in self.py_modules:
22217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            path = string.split (module, '.')
22317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            package = tuple (path[0:-1])
2242a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            module_base = path[-1]
22517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
22617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            try:
22717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                (package_dir, checked) = packages[package]
22817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            except KeyError:
22917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                package_dir = self.get_package_dir (package)
23017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                checked = 0
23117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
23217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            if not checked:
2338bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                init_py = self.check_package (package, package_dir)
23417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                packages[package] = (package_dir, 1)
2358bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                if init_py:
2368bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                    modules.append((package, "__init__", init_py))
23717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
23817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # XXX perhaps we should also check for just .pyc files
23917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # (so greedy closed-source bastards can distribute Python
24017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # modules too)
2412a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            module_file = os.path.join (package_dir, module_base + ".py")
24217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            if not self.check_module (module, module_file):
24317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                continue
24417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2458bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward            modules.append ((package, module_base, module_file))
2462a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2472a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        return modules
2482a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2492a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    # find_modules ()
2502a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2512a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2528b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def find_all_modules (self):
2538b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        """Compute the list of all modules that will be built, whether
2546f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        they are specified one-module-at-a-time ('self.py_modules') or
2558b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        by whole packages ('self.packages').  Return a list of tuples
2568b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        (package, module, module_file), just like 'find_modules()' and
2578b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        'find_package_modules()' do."""
2582a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2596f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules:
2602a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            modules = self.find_modules ()
2612a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        else:
2622a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            modules = []
2632a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            for package in self.packages:
2642a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward                package_dir = self.get_package_dir (package)
2652a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward                m = self.find_package_modules (package, package_dir)
2662a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward                modules.extend (m)
2672a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2688b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return modules
2698b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2708b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    # find_all_modules ()
2718b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2728b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2738b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def get_source_files (self):
2748b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2758b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        modules = self.find_all_modules ()
2762a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        filenames = []
2772a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        for module in modules:
2782a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            filenames.append (module[-1])
2792a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2808b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return filenames
2812a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2822a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2838b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def get_module_outfile (self, build_dir, package, module):
2848b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        outfile_path = [build_dir] + list(package) + [module + ".py"]
2858b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return apply (os.path.join, outfile_path)
2868b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2878b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2888b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def get_outputs (self):
2898b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        modules = self.find_all_modules ()
2908b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        outputs = []
2918b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        for (package, module, module_file) in modules:
2928b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            package = string.split (package, '.')
2938b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            outputs.append (self.get_module_outfile (self.build_lib,
2948b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward                                                     package, module))
2958b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return outputs
2962a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2978b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2988b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def build_module (self, module, module_file, package):
2992a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        if type (package) is StringType:
3002a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            package = string.split (package, '.')
301631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward        elif type (package) not in (ListType, TupleType):
302631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            raise TypeError, \
303631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                  "'package' must be a string (dot-separated), list, or tuple"
3042a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3052a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        # Now put the module source file into the "build" area -- this is
306e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        # easy, we just copy it somewhere under self.build_lib (the build
3072a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        # directory for Python source).
3088b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        outfile = self.get_module_outfile (self.build_lib, package, module)
3092a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        dir = os.path.dirname (outfile)
3102a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        self.mkpath (dir)
3119b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        self.copy_file (module_file, outfile, preserve_mode=0)
3122a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3132a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3142a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    def build_modules (self):
3152a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3162a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        modules = self.find_modules()
3178b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        for (package, module, module_file) in modules:
3182a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
31917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # Now "build" the module -- ie. copy the source file to
320e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # self.build_lib (the build directory for Python source).
3212a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            # (Actually, it gets copied to the directory for this package
322e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # under self.build_lib.)
32317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            self.build_module (module, module_file, package)
32417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
32517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # build_modules ()
32617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
32717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
32817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def build_packages (self):
32917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
33017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        for package in self.packages:
3318b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
3328b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # Get list of (package, module, module_file) tuples based on
3338b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # scanning the package directory.  'package' is only included
3348b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # in the tuple so that 'find_modules()' and
3358b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # 'find_package_tuples()' have a consistent interface; it's
3368b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # ignored here (apart from a sanity check).  Also, 'module' is
3378b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # the *unqualified* module name (ie. no dots, no package -- we
3388b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # already know its package!), and 'module_file' is the path to
3398b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # the .py file, relative to the current directory
3408b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # (ie. including 'package_dir').
34117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            package_dir = self.get_package_dir (package)
3422a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            modules = self.find_package_modules (package, package_dir)
34317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
34417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # Now loop over the modules we found, "building" each one (just
345e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # copy it to self.build_lib).
3468b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            for (package_, module, module_file) in modules:
3478b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward                assert package == package_
34817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                self.build_module (module, module_file, package)
34917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
35017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # build_packages ()
35113ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
352fcd974efbb71ab7cb5a75639028508e0195939b8Greg Ward# class build_py
353