build_py.py revision 0c350bfad0ec9350aec57e37962b1aadb8492173
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 *
150c350bfad0ec9350aec57e37962b1aadb8492173Thomas Hellerfrom distutils.util import convert_path
1613ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
1713ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
181993f9ad0e2cf53c8dc441cbbb44eb2e3a190538Greg Wardclass build_py (Command):
1913ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
2037bc81505379facad85a7c6ff273de0201f28656Greg Ward    description = "\"build\" pure Python modules (copy to build directory)"
2137bc81505379facad85a7c6ff273de0201f28656Greg Ward
22bbeceeaf9a5edf878154b17a6a94403a26822d51Greg Ward    user_options = [
23e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        ('build-lib=', 'd', "directory to \"build\" (copy) to"),
2473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        ('compile', 'c', "compile .py to .pyc"),
2573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        ('no-compile', None, "don't compile .py files [default]"),
2673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        ('optimize=', 'O',
2773a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward         "also compile with optimization: -O1 for \"python -O\", "
2873a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward         "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
296a2035d76ba9572ccd2ed61d59df46e2bdcb74edGreg Ward        ('force', 'f', "forcibly build everything (ignore file timestamps)"),
30bbeceeaf9a5edf878154b17a6a94403a26822d51Greg Ward        ]
3113ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
3273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward    boolean_options = ['compile', 'force']
3373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward    negative_opt = {'no-compile' : 'compile'}
3499b032eaf24e1887f6451eceea7f1e24ced05a0fGreg Ward
3513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
36e01149cbe83778a5cf872a6b429ff33179b7cdcbGreg Ward    def initialize_options (self):
37e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        self.build_lib = None
386f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        self.py_modules = None
3971eb8644d7e27fd379a2cf78c509155bdb179332Greg Ward        self.package = None
4017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        self.package_dir = None
4173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        self.compile = 0
4273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        self.optimize = 0
43c41d6b35a9de3dd9e3057a0db9a83b182e792f79Greg Ward        self.force = None
4413ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
45e01149cbe83778a5cf872a6b429ff33179b7cdcbGreg Ward    def finalize_options (self):
46cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        self.set_undefined_options('build',
47cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                                   ('build_lib', 'build_lib'),
48cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                                   ('force', 'force'))
4917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
5017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Get the distribution options that are aliases for build_py
5117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # options -- list of packages and list of modules.
5217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        self.packages = self.distribution.packages
536f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        self.py_modules = self.distribution.py_modules
540c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller        self.package_dir = {}
550c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller        if self.distribution.package_dir:
560c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller            for name, path in self.distribution.package_dir.items():
570c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller                self.package_dir[name] = convert_path(path)
5813ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
5973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # Ick, copied straight from install_lib.py (fancy_getopt needs a
6073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # type system!  Hell, *everything* needs a type system!!!)
6173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        if type(self.optimize) is not IntType:
6273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            try:
6373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                self.optimize = int(self.optimize)
6473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                assert 0 <= self.optimize <= 2
6573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            except (ValueError, AssertionError):
6673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                raise DistutilsOptionError, "optimize must be 0, 1, or 2"
6713ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
6813ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward    def run (self):
6913ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
709b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # XXX copy_file by default preserves atime and mtime.  IMHO this is
719b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # the right thing to do, but perhaps it should be an option -- in
729b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # particular, a site administrator might want installed files to
739b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # reflect the time of installation rather than the last
749b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # modification time before the installed release.
759b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward
769b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # XXX copy_file by default preserves mode, which appears to be the
779b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # wrong thing to do: if a file is read-only in the working
789b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # directory, we want it to be installed read/write so that the next
799b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installation of the same module distribution can overwrite it
809b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # without problems.  (This might be a Unix-specific issue.)  Thus
819b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # we turn off 'preserve_mode' when copying to the build directory,
829b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # since the build directory is supposed to be exactly what the
839b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installation will look like (ie. we preserve mode when
849b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installing).
8513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
8617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Two options control which modules will be installed: 'packages'
876f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        # and 'py_modules'.  The former lets us work with whole packages, not
8817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # specifying individual modules at all; the latter is for
8917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # specifying modules one-at-a-time.  Currently they are mutually
9017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # exclusive: you can define one or the other (or neither), but not
9117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # both.  It remains to be seen how limiting this is.
9213ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
9317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Dispose of the two "unusual" cases first: no pure Python modules
9417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # at all (no problem, just return silently), and over-specified
956f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        # 'packages' and 'py_modules' options.
9617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
976f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if not self.py_modules and not self.packages:
985d60fcf02a7050a07067a12c7a98c8b6b1e68372Greg Ward            return
996f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules and self.packages:
10017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            raise DistutilsOptionError, \
1016f980b59368c022fe7c1925e83f909dabf74e634Greg Ward                  "build_py: supplying both 'packages' and 'py_modules' " + \
1029b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward                  "options is not allowed"
10317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
1046f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        # Now we're down to two cases: 'py_modules' only and 'packages' only.
1056f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules:
106cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            self.build_modules()
10717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
108cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            self.build_packages()
10917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
11073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        self.byte_compile(self.get_outputs(include_bytecode=0))
11173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
11217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # run ()
11321d45356b8a22bb3091a97f50b919b57798675f5Fred Drake
11417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
11517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def get_package_dir (self, package):
11617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        """Return the directory, relative to the top of the source
11717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward           distribution, where package 'package' should be found
11817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward           (at least according to the 'package_dir' option, if any)."""
11917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
120cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        path = string.split(package, '.')
12117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
12217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if not self.package_dir:
123631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            if path:
124cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                return apply(os.path.join, path)
125631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            else:
126631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                return ''
12717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
12817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            tail = []
12917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            while path:
13017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                try:
131cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                    pdir = self.package_dir[string.join(path, '.')]
13217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                except KeyError:
133cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                    tail.insert(0, path[-1])
13417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                    del path[-1]
13517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                else:
136cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                    tail.insert(0, pdir)
137cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                    return apply(os.path.join, tail)
13813ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward            else:
1398bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # Oops, got all the way through 'path' without finding a
1408bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # match in package_dir.  If package_dir defines a directory
1418bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # for the root (nameless) package, then fallback on it;
1428bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # otherwise, we might as well have not consulted
1438bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # package_dir at all, as we just use the directory implied
1448bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # by 'tail' (which should be the same as the original value
1458bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # of 'path' at this point).
1468bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                pdir = self.package_dir.get('')
1478bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                if pdir is not None:
1488bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                    tail.insert(0, pdir)
1498bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
150631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                if tail:
151cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                    return apply(os.path.join, tail)
152631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                else:
153631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                    return ''
15417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
15517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # get_package_dir ()
15617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
15717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
15817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def check_package (self, package, package_dir):
15917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
16017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Empty dir name means current directory, which we can probably
16117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # assume exists.  Also, os.path.exists and isdir don't know about
16217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # my "empty string means current dir" convention, so we have to
16317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # circumvent them.
16417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if package_dir != "":
165cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if not os.path.exists(package_dir):
16617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                raise DistutilsFileError, \
16717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                      "package directory '%s' does not exist" % package_dir
168cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if not os.path.isdir(package_dir):
16956359f591b361f41f661a14e5ed129bf8f22fa87Greg Ward                raise DistutilsFileError, \
17017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                      ("supposed package directory '%s' exists, " +
17117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                       "but is not a directory") % package_dir
17217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
17317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Require __init__.py for all but the "root package"
174631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward        if package:
175cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            init_py = os.path.join(package_dir, "__init__.py")
176cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if os.path.isfile(init_py):
1778bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                return init_py
1788bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward            else:
179cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                self.warn(("package init file '%s' not found " +
180cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                           "(or not a regular file)") % init_py)
1818bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
1828bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # Either not in a package at all (__init__.py not expected), or
1838bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # __init__.py doesn't exist -- so don't return the filename.
1848bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        return
18521d45356b8a22bb3091a97f50b919b57798675f5Fred Drake
18617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # check_package ()
18717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
18817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
18917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def check_module (self, module, module_file):
190cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        if not os.path.isfile(module_file):
19121d45356b8a22bb3091a97f50b919b57798675f5Fred Drake            self.warn("file %s (for module %s) not found" %
192cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                      (module_file, module))
19317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            return 0
19417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
19517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            return 1
19617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
19717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # check_module ()
19817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
19917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2002a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    def find_package_modules (self, package, package_dir):
201cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        self.check_package(package, package_dir)
202cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        module_files = glob(os.path.join(package_dir, "*.py"))
2038b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        modules = []
2049821bf4e62bcb7d503aed782a8f6398e5de720afGreg Ward        setup_script = os.path.abspath(self.distribution.script_name)
2059b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward
20617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        for f in module_files:
207cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            abs_f = os.path.abspath(f)
2089b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward            if abs_f != setup_script:
209cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                module = os.path.splitext(os.path.basename(f))[0]
210cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                modules.append((package, module, f))
2119821bf4e62bcb7d503aed782a8f6398e5de720afGreg Ward            else:
2129821bf4e62bcb7d503aed782a8f6398e5de720afGreg Ward                self.debug_print("excluding %s" % setup_script)
2138b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return modules
21417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
21517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2162a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    def find_modules (self):
2178bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        """Finds individually-specified Python modules, ie. those listed by
2186f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        module name in 'self.py_modules'.  Returns a list of tuples (package,
2198bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        module_base, filename): 'package' is a tuple of the path through
2208bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        package-space to the module; 'module_base' is the bare (no
2218bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        packages, no dots) module name, and 'filename' is the path to the
2228bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        ".py" file (relative to the distribution root) that implements the
2238bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        module.
2248bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        """
2258bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
22617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Map package names to tuples of useful info about the package:
22717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #    (package_dir, checked)
22817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # package_dir - the directory where we'll find source files for
22917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   this package
23017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # checked - true if we have checked that the package directory
23117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   is valid (exists, contains __init__.py, ... ?)
23217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        packages = {}
23317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2348bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # List of (package, module, filename) tuples to return
2352a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        modules = []
2362a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
23717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # We treat modules-in-packages almost the same as toplevel modules,
23817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # just the "package" for a toplevel is empty (either an empty
23917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # string or empty list, depending on context).  Differences:
24017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   - don't check for __init__.py in directory for empty package
24117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2426f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        for module in self.py_modules:
243cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            path = string.split(module, '.')
244c0fe82ca26b1da22e35f2d0676f09795d052e4f0Greg Ward            package = string.join(path[0:-1], '.')
2452a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            module_base = path[-1]
24617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
24717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            try:
24817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                (package_dir, checked) = packages[package]
24917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            except KeyError:
250cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                package_dir = self.get_package_dir(package)
25117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                checked = 0
25217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
25317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            if not checked:
254cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                init_py = self.check_package(package, package_dir)
25517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                packages[package] = (package_dir, 1)
2568bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                if init_py:
2578bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                    modules.append((package, "__init__", init_py))
25817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
25917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # XXX perhaps we should also check for just .pyc files
26017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # (so greedy closed-source bastards can distribute Python
26117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # modules too)
262cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            module_file = os.path.join(package_dir, module_base + ".py")
263cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if not self.check_module(module, module_file):
26417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                continue
26517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
266cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            modules.append((package, module_base, module_file))
2672a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2682a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        return modules
2692a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2702a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    # find_modules ()
2712a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2722a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2738b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def find_all_modules (self):
2748b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        """Compute the list of all modules that will be built, whether
2756f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        they are specified one-module-at-a-time ('self.py_modules') or
2768b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        by whole packages ('self.packages').  Return a list of tuples
2778b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        (package, module, module_file), just like 'find_modules()' and
2788b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        'find_package_modules()' do."""
2792a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2806f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules:
281cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            modules = self.find_modules()
2822a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        else:
2832a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            modules = []
2842a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            for package in self.packages:
285cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                package_dir = self.get_package_dir(package)
286cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                m = self.find_package_modules(package, package_dir)
287cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                modules.extend(m)
2882a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2898b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return modules
2908b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2918b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    # find_all_modules ()
2928b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2938b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
2948b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def get_source_files (self):
2958b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
296cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        modules = self.find_all_modules()
2972a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        filenames = []
2982a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        for module in modules:
299cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            filenames.append(module[-1])
3002a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3018b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return filenames
3022a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3032a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3048b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def get_module_outfile (self, build_dir, package, module):
3058b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        outfile_path = [build_dir] + list(package) + [module + ".py"]
306cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        return apply(os.path.join, outfile_path)
3078b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
3088b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
30973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward    def get_outputs (self, include_bytecode=1):
310cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        modules = self.find_all_modules()
3118b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        outputs = []
3128b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        for (package, module, module_file) in modules:
313cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            package = string.split(package, '.')
31473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            filename = self.get_module_outfile(self.build_lib, package, module)
31573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            outputs.append(filename)
31673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            if include_bytecode:
31773a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                if self.compile:
31873a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                    outputs.append(filename + "c")
31973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                if self.optimize > 0:
32073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                    outputs.append(filename + "o")
32173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
3228b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return outputs
3232a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3248b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
3258b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward    def build_module (self, module, module_file, package):
326cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        if type(package) is StringType:
327cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            package = string.split(package, '.')
328cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        elif type(package) not in (ListType, TupleType):
329631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            raise TypeError, \
330631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                  "'package' must be a string (dot-separated), list, or tuple"
3312a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3322a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        # Now put the module source file into the "build" area -- this is
333e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        # easy, we just copy it somewhere under self.build_lib (the build
3342a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        # directory for Python source).
335cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        outfile = self.get_module_outfile(self.build_lib, package, module)
336cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        dir = os.path.dirname(outfile)
337cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        self.mkpath(dir)
338cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        return self.copy_file(module_file, outfile, preserve_mode=0)
3392a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3402a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3412a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward    def build_modules (self):
3422a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3432a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        modules = self.find_modules()
3448b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        for (package, module, module_file) in modules:
3452a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
34617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # Now "build" the module -- ie. copy the source file to
347e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # self.build_lib (the build directory for Python source).
3482a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            # (Actually, it gets copied to the directory for this package
349e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # under self.build_lib.)
350cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            self.build_module(module, module_file, package)
35117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
35217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # build_modules ()
35317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
35417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
35517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    def build_packages (self):
35617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
35717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        for package in self.packages:
3588b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
3598b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # Get list of (package, module, module_file) tuples based on
3608b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # scanning the package directory.  'package' is only included
3618b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # in the tuple so that 'find_modules()' and
3628b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # 'find_package_tuples()' have a consistent interface; it's
3638b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # ignored here (apart from a sanity check).  Also, 'module' is
3648b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # the *unqualified* module name (ie. no dots, no package -- we
3658b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # already know its package!), and 'module_file' is the path to
3668b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # the .py file, relative to the current directory
3678b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # (ie. including 'package_dir').
368cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            package_dir = self.get_package_dir(package)
369cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            modules = self.find_package_modules(package, package_dir)
37017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
37117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # Now loop over the modules we found, "building" each one (just
372e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # copy it to self.build_lib).
3738b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            for (package_, module, module_file) in modules:
3748b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward                assert package == package_
375cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                self.build_module(module, module_file, package)
37617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
37717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward    # build_packages ()
37873a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
37973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
38073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward    def byte_compile (self, files):
38173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        from distutils.util import byte_compile
38273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        prefix = self.build_lib
38373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        if prefix[-1] != os.sep:
38473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            prefix = prefix + os.sep
38573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
38673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # XXX this code is essentially the same as the 'byte_compile()
38773a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # method of the "install_lib" command, except for the determination
38873a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # of the 'prefix' string.  Hmmm.
38973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
39073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        if self.compile:
39173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            byte_compile(files, optimize=0,
39273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                         force=self.force,
39373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                         prefix=prefix,
39473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                         verbose=self.verbose, dry_run=self.dry_run)
39573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        if self.optimize > 0:
39673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            byte_compile(files, optimize=self.optimize,
39773a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                         force=self.force,
39873a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                         prefix=prefix,
39973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                         verbose=self.verbose, dry_run=self.dry_run)
40073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
401fcd974efbb71ab7cb5a75639028508e0195939b8Greg Ward# class build_py
402