113ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward"""distutils.command.build_py
213ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
313ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg WardImplements the Distutils 'build_py' command."""
413ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
53ce77fd05ed00168f618b63401d770ccc4f04b09Greg Ward__revision__ = "$Id$"
613ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
7fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadéimport os
8b9c1cfc42881370322ff6894568b45dcfec5ba2eTarek Ziadéimport sys
917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Wardfrom glob import glob
1017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
1113ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Wardfrom distutils.core import Command
12fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadéfrom distutils.errors import DistutilsOptionError, DistutilsFileError
130c350bfad0ec9350aec57e37962b1aadb8492173Thomas Hellerfrom distutils.util import convert_path
14cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hyltonfrom distutils import log
1513ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
16fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadéclass build_py(Command):
1713ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
1837bc81505379facad85a7c6ff273de0201f28656Greg Ward    description = "\"build\" pure Python modules (copy to build directory)"
1937bc81505379facad85a7c6ff273de0201f28656Greg Ward
20bbeceeaf9a5edf878154b17a6a94403a26822d51Greg Ward    user_options = [
21e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        ('build-lib=', 'd', "directory to \"build\" (copy) to"),
2273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        ('compile', 'c', "compile .py to .pyc"),
2373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        ('no-compile', None, "don't compile .py files [default]"),
2473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        ('optimize=', 'O',
2573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward         "also compile with optimization: -O1 for \"python -O\", "
2673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward         "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
276a2035d76ba9572ccd2ed61d59df46e2bdcb74edGreg Ward        ('force', 'f', "forcibly build everything (ignore file timestamps)"),
28bbeceeaf9a5edf878154b17a6a94403a26822d51Greg Ward        ]
2913ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
3073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward    boolean_options = ['compile', 'force']
3173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward    negative_opt = {'no-compile' : 'compile'}
3299b032eaf24e1887f6451eceea7f1e24ced05a0fGreg Ward
33fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def initialize_options(self):
34e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        self.build_lib = None
356f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        self.py_modules = None
3671eb8644d7e27fd379a2cf78c509155bdb179332Greg Ward        self.package = None
370eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        self.package_data = None
3817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        self.package_dir = None
3973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        self.compile = 0
4073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        self.optimize = 0
41c41d6b35a9de3dd9e3057a0db9a83b182e792f79Greg Ward        self.force = None
4213ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
43fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def finalize_options(self):
44cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        self.set_undefined_options('build',
45cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                                   ('build_lib', 'build_lib'),
46cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                                   ('force', 'force'))
4717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
4817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Get the distribution options that are aliases for build_py
4917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # options -- list of packages and list of modules.
5017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        self.packages = self.distribution.packages
516f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        self.py_modules = self.distribution.py_modules
520eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        self.package_data = self.distribution.package_data
530c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller        self.package_dir = {}
540c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller        if self.distribution.package_dir:
550c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller            for name, path in self.distribution.package_dir.items():
560c350bfad0ec9350aec57e37962b1aadb8492173Thomas Heller                self.package_dir[name] = convert_path(path)
57b849eddde6722549f9df1be1cd6ea7cf79d28807Fred Drake        self.data_files = self.get_data_files()
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!!!)
61fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé        if not isinstance(self.optimize, int):
6273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            try:
6373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                self.optimize = int(self.optimize)
6473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                assert 0 <= self.optimize <= 2
6573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            except (ValueError, AssertionError):
66fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                raise DistutilsOptionError("optimize must be 0, 1, or 2")
6713ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
68fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def run(self):
699b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # XXX copy_file by default preserves atime and mtime.  IMHO this is
709b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # the right thing to do, but perhaps it should be an option -- in
719b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # particular, a site administrator might want installed files to
729b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # reflect the time of installation rather than the last
739b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # modification time before the installed release.
749b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward
759b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # XXX copy_file by default preserves mode, which appears to be the
769b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # wrong thing to do: if a file is read-only in the working
779b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # directory, we want it to be installed read/write so that the next
789b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installation of the same module distribution can overwrite it
799b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # without problems.  (This might be a Unix-specific issue.)  Thus
809b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # we turn off 'preserve_mode' when copying to the build directory,
819b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # since the build directory is supposed to be exactly what the
829b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installation will look like (ie. we preserve mode when
839b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward        # installing).
8413ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward
8517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Two options control which modules will be installed: 'packages'
866f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        # and 'py_modules'.  The former lets us work with whole packages, not
8717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # specifying individual modules at all; the latter is for
88e557f3556f1f87825e00a018eabf837c4c55f7d5Andrew M. Kuchling        # specifying modules one-at-a-time.
89e557f3556f1f87825e00a018eabf837c4c55f7d5Andrew M. Kuchling
906f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules:
91cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            self.build_modules()
92e557f3556f1f87825e00a018eabf837c4c55f7d5Andrew M. Kuchling        if self.packages:
93cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            self.build_packages()
940eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            self.build_package_data()
9517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
9673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        self.byte_compile(self.get_outputs(include_bytecode=0))
9773a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
98fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def get_data_files(self):
990eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
1000eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        data = []
1014dbda47aea1759faa68e491237f078cf0c7050beSjoerd Mullender        if not self.packages:
1024dbda47aea1759faa68e491237f078cf0c7050beSjoerd Mullender            return data
1030eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        for package in self.packages:
1040eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            # Locate package source directory
1050eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            src_dir = self.get_package_dir(package)
1060eb32a65b06613d25aeb18f0104554171bea4840Fred Drake
1070eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            # Compute package build directory
1080eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            build_dir = os.path.join(*([self.build_lib] + package.split('.')))
1090eb32a65b06613d25aeb18f0104554171bea4840Fred Drake
1100eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            # Length of path to strip from found files
1114a700bb4698c1ec7f94b26f78f866929b013647fNeal Norwitz            plen = 0
1124a700bb4698c1ec7f94b26f78f866929b013647fNeal Norwitz            if src_dir:
1134a700bb4698c1ec7f94b26f78f866929b013647fNeal Norwitz                plen = len(src_dir)+1
1140eb32a65b06613d25aeb18f0104554171bea4840Fred Drake
1150eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            # Strip directory from globbed filenames
1160eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            filenames = [
1170eb32a65b06613d25aeb18f0104554171bea4840Fred Drake                file[plen:] for file in self.find_data_files(package, src_dir)
1180eb32a65b06613d25aeb18f0104554171bea4840Fred Drake                ]
1190eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            data.append((package, src_dir, build_dir, filenames))
1200eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        return data
1210eb32a65b06613d25aeb18f0104554171bea4840Fred Drake
122fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def find_data_files(self, package, src_dir):
1230eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        """Return filenames for package's data files in 'src_dir'"""
1240eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        globs = (self.package_data.get('', [])
1250eb32a65b06613d25aeb18f0104554171bea4840Fred Drake                 + self.package_data.get(package, []))
1260eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        files = []
1270eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        for pattern in globs:
1280eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            # Each pattern has to be converted to a platform-specific path
1290eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            filelist = glob(os.path.join(src_dir, convert_path(pattern)))
1300eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            # Files that match more than one pattern are only added once
1313c0713550ec72fbba502466e84bd5271fd4e4cf3Jason R. Coombs            files.extend([fn for fn in filelist if fn not in files
1323c0713550ec72fbba502466e84bd5271fd4e4cf3Jason R. Coombs                and os.path.isfile(fn)])
1330eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        return files
1340eb32a65b06613d25aeb18f0104554171bea4840Fred Drake
135fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def build_package_data(self):
1360eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        """Copy data files into build directory"""
1370eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        for package, src_dir, build_dir, filenames in self.data_files:
1380eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            for filename in filenames:
1390eb32a65b06613d25aeb18f0104554171bea4840Fred Drake                target = os.path.join(build_dir, filename)
1400eb32a65b06613d25aeb18f0104554171bea4840Fred Drake                self.mkpath(os.path.dirname(target))
1410eb32a65b06613d25aeb18f0104554171bea4840Fred Drake                self.copy_file(os.path.join(src_dir, filename), target,
1420eb32a65b06613d25aeb18f0104554171bea4840Fred Drake                               preserve_mode=False)
14317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
144fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def get_package_dir(self, package):
14517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        """Return the directory, relative to the top of the source
14617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward           distribution, where package 'package' should be found
14717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward           (at least according to the 'package_dir' option, if any)."""
14817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
149fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé        path = package.split('.')
15017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
15117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if not self.package_dir:
152631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            if path:
153fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                return os.path.join(*path)
154631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward            else:
155631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                return ''
15617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
15717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            tail = []
15817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            while path:
15917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                try:
160fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                    pdir = self.package_dir['.'.join(path)]
16117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                except KeyError:
162cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                    tail.insert(0, path[-1])
16317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                    del path[-1]
16417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                else:
165cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                    tail.insert(0, pdir)
166047e4a915d899b31dcb49fd820914df6a9991870Brett Cannon                    return os.path.join(*tail)
16713ae1c8ff81befcfd0b0ece98ef471cd504642d8Greg Ward            else:
1688bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # Oops, got all the way through 'path' without finding a
1698bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # match in package_dir.  If package_dir defines a directory
1708bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # for the root (nameless) package, then fallback on it;
1718bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # otherwise, we might as well have not consulted
1728bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # package_dir at all, as we just use the directory implied
1738bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # by 'tail' (which should be the same as the original value
1748bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                # of 'path' at this point).
1758bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                pdir = self.package_dir.get('')
1768bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                if pdir is not None:
1778bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                    tail.insert(0, pdir)
1788bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
179631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                if tail:
180fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                    return os.path.join(*tail)
181631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                else:
182631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward                    return ''
18317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
184fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def check_package(self, package, package_dir):
18517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Empty dir name means current directory, which we can probably
18617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # assume exists.  Also, os.path.exists and isdir don't know about
18717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # my "empty string means current dir" convention, so we have to
18817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # circumvent them.
18917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        if package_dir != "":
190cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if not os.path.exists(package_dir):
191fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                raise DistutilsFileError(
192fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                      "package directory '%s' does not exist" % package_dir)
193cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if not os.path.isdir(package_dir):
194fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                raise DistutilsFileError(
195fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                       "supposed package directory '%s' exists, "
196fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                       "but is not a directory" % package_dir)
19717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
19817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Require __init__.py for all but the "root package"
199631e6a0c070810b064c48ff6cf777ebb0276f038Greg Ward        if package:
200cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            init_py = os.path.join(package_dir, "__init__.py")
201cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if os.path.isfile(init_py):
2028bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                return init_py
2038bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward            else:
204cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                log.warn(("package init file '%s' not found " +
205cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                          "(or not a regular file)"), init_py)
2068bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward
2078bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # Either not in a package at all (__init__.py not expected), or
2088bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # __init__.py doesn't exist -- so don't return the filename.
20911a52708f7f2223b335734e584e2ba30eae74a5fJeremy Hylton        return None
21021d45356b8a22bb3091a97f50b919b57798675f5Fred Drake
211fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def check_module(self, module, module_file):
212cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        if not os.path.isfile(module_file):
213cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton            log.warn("file %s (for module %s) not found", module_file, module)
214fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé            return False
21517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        else:
216fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé            return True
21717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
218fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def find_package_modules(self, package, package_dir):
219cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        self.check_package(package, package_dir)
220cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        module_files = glob(os.path.join(package_dir, "*.py"))
2218b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        modules = []
2229821bf4e62bcb7d503aed782a8f6398e5de720afGreg Ward        setup_script = os.path.abspath(self.distribution.script_name)
2239b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward
22417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        for f in module_files:
225cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            abs_f = os.path.abspath(f)
2269b45443c1bdf99b0f27b12baf06fea475b60e145Greg Ward            if abs_f != setup_script:
227cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                module = os.path.splitext(os.path.basename(f))[0]
228cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                modules.append((package, module, f))
2299821bf4e62bcb7d503aed782a8f6398e5de720afGreg Ward            else:
2309821bf4e62bcb7d503aed782a8f6398e5de720afGreg Ward                self.debug_print("excluding %s" % setup_script)
2318b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return modules
23217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
233fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def find_modules(self):
2348bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        """Finds individually-specified Python modules, ie. those listed by
2356f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        module name in 'self.py_modules'.  Returns a list of tuples (package,
2368bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        module_base, filename): 'package' is a tuple of the path through
2378bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        package-space to the module; 'module_base' is the bare (no
2388bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        packages, no dots) module name, and 'filename' is the path to the
2398bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        ".py" file (relative to the distribution root) that implements the
2408bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        module.
2418bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        """
24217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # Map package names to tuples of useful info about the package:
24317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #    (package_dir, checked)
24417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # package_dir - the directory where we'll find source files for
24517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   this package
24617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # checked - true if we have checked that the package directory
24717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   is valid (exists, contains __init__.py, ... ?)
24817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        packages = {}
24917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
2508bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward        # List of (package, module, filename) tuples to return
2512a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        modules = []
2522a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
25317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # We treat modules-in-packages almost the same as toplevel modules,
25417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # just the "package" for a toplevel is empty (either an empty
25517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        # string or empty list, depending on context).  Differences:
25617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        #   - don't check for __init__.py in directory for empty package
2576f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        for module in self.py_modules:
258fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé            path = module.split('.')
259fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé            package = '.'.join(path[0:-1])
2602a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            module_base = path[-1]
26117dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
26217dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            try:
26317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                (package_dir, checked) = packages[package]
26417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            except KeyError:
265cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                package_dir = self.get_package_dir(package)
26617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                checked = 0
26717dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
26817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            if not checked:
269cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                init_py = self.check_package(package, package_dir)
27017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                packages[package] = (package_dir, 1)
2718bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                if init_py:
2728bbba17d3815a44eefbd0cf33db937a56fe50db5Greg Ward                    modules.append((package, "__init__", init_py))
27317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
27417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # XXX perhaps we should also check for just .pyc files
27517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # (so greedy closed-source bastards can distribute Python
27617dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # modules too)
277cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            module_file = os.path.join(package_dir, module_base + ".py")
278cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            if not self.check_module(module, module_file):
27917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward                continue
28017dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
281cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            modules.append((package, module_base, module_file))
2822a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
2832a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        return modules
2842a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
285fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def find_all_modules(self):
2868b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        """Compute the list of all modules that will be built, whether
2876f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        they are specified one-module-at-a-time ('self.py_modules') or
2888b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        by whole packages ('self.packages').  Return a list of tuples
2898b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        (package, module, module_file), just like 'find_modules()' and
2908b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        'find_package_modules()' do."""
291e557f3556f1f87825e00a018eabf837c4c55f7d5Andrew M. Kuchling        modules = []
2926f980b59368c022fe7c1925e83f909dabf74e634Greg Ward        if self.py_modules:
293e557f3556f1f87825e00a018eabf837c4c55f7d5Andrew M. Kuchling            modules.extend(self.find_modules())
294e557f3556f1f87825e00a018eabf837c4c55f7d5Andrew M. Kuchling        if self.packages:
2952a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            for package in self.packages:
296cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                package_dir = self.get_package_dir(package)
297cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                m = self.find_package_modules(package, package_dir)
298cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                modules.extend(m)
2998b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return modules
3008b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
301fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def get_source_files(self):
302fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé        return [module[-1] for module in self.find_all_modules()]
3032a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
304fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def get_module_outfile(self, build_dir, package, module):
3058b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        outfile_path = [build_dir] + list(package) + [module + ".py"]
306047e4a915d899b31dcb49fd820914df6a9991870Brett Cannon        return os.path.join(*outfile_path)
3078b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
308fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def get_outputs(self, include_bytecode=1):
309cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        modules = self.find_all_modules()
3108b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        outputs = []
3118b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        for (package, module, module_file) in modules:
312fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé            package = package.split('.')
31373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            filename = self.get_module_outfile(self.build_lib, package, module)
31473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            outputs.append(filename)
31573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            if include_bytecode:
31673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                if self.compile:
31773a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                    outputs.append(filename + "c")
31873a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                if self.optimize > 0:
31973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward                    outputs.append(filename + "o")
32073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
3210eb32a65b06613d25aeb18f0104554171bea4840Fred Drake        outputs += [
3220eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            os.path.join(build_dir, filename)
3230eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            for package, src_dir, build_dir, filenames in self.data_files
3240eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            for filename in filenames
3250eb32a65b06613d25aeb18f0104554171bea4840Fred Drake            ]
3260eb32a65b06613d25aeb18f0104554171bea4840Fred Drake
3278b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        return outputs
3282a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
329fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def build_module(self, module, module_file, package):
330fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé        if isinstance(package, str):
331fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé            package = package.split('.')
332fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé        elif not isinstance(package, (list, tuple)):
333fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé            raise TypeError(
334fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé                  "'package' must be a string (dot-separated), list, or tuple")
3352a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
3362a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        # Now put the module source file into the "build" area -- this is
337e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward        # easy, we just copy it somewhere under self.build_lib (the build
3382a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        # directory for Python source).
339cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        outfile = self.get_module_outfile(self.build_lib, package, module)
340cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        dir = os.path.dirname(outfile)
341cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        self.mkpath(dir)
342cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward        return self.copy_file(module_file, outfile, preserve_mode=0)
3432a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
344fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def build_modules(self):
3452a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward        modules = self.find_modules()
3468b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward        for (package, module, module_file) in modules:
3472a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward
34817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # Now "build" the module -- ie. copy the source file to
349e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # self.build_lib (the build directory for Python source).
3502a612067e60a98f05d39b39f4a7a5a7a8065bfc9Greg Ward            # (Actually, it gets copied to the directory for this package
351e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # under self.build_lib.)
352cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            self.build_module(module, module_file, package)
35317dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
354fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def build_packages(self):
35517dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward        for package in self.packages:
3568b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward
3578b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # Get list of (package, module, module_file) tuples based on
3588b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # scanning the package directory.  'package' is only included
3598b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # in the tuple so that 'find_modules()' and
3608b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # 'find_package_tuples()' have a consistent interface; it's
3618b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # ignored here (apart from a sanity check).  Also, 'module' is
3628b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # the *unqualified* module name (ie. no dots, no package -- we
3638b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # already know its package!), and 'module_file' is the path to
3648b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # the .py file, relative to the current directory
3658b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            # (ie. including 'package_dir').
366cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            package_dir = self.get_package_dir(package)
367cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward            modules = self.find_package_modules(package, package_dir)
36817dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
36917dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward            # Now loop over the modules we found, "building" each one (just
370e6916516828a88ab71fab7a749f8c9cf6b52775aGreg Ward            # copy it to self.build_lib).
3718b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward            for (package_, module, module_file) in modules:
3728b2e95edd69cacafdfbcf00270065bd6444f3336Greg Ward                assert package == package_
373cb1f4c4d33711a964dcd40b32a562e2acf745fc2Greg Ward                self.build_module(module, module_file, package)
37417dc6e7ed8cd62068b5f244a9f1023917d3caf4aGreg Ward
375fe97ebbf6215d3127e44241fd7beda8465725e31Tarek Ziadé    def byte_compile(self, files):
376b9c1cfc42881370322ff6894568b45dcfec5ba2eTarek Ziadé        if sys.dont_write_bytecode:
3771733c9362b7520278120423550de28da44d15f33Tarek Ziadé            self.warn('byte-compiling is disabled, skipping.')
378b9c1cfc42881370322ff6894568b45dcfec5ba2eTarek Ziadé            return
379b9c1cfc42881370322ff6894568b45dcfec5ba2eTarek Ziadé
38073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        from distutils.util import byte_compile
38173a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        prefix = self.build_lib
38273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        if prefix[-1] != os.sep:
38373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            prefix = prefix + os.sep
38473a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
38573a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # XXX this code is essentially the same as the 'byte_compile()
38673a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # method of the "install_lib" command, except for the determination
38773a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        # of the 'prefix' string.  Hmmm.
38873a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward
38973a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        if self.compile:
39073a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            byte_compile(files, optimize=0,
391cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                         force=self.force, prefix=prefix, dry_run=self.dry_run)
39273a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward        if self.optimize > 0:
39373a6c942cda33c0b6c97a10b7ef5664e043f987fGreg Ward            byte_compile(files, optimize=self.optimize,
394cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                         force=self.force, prefix=prefix, dry_run=self.dry_run)
395