#----------------------------------------------------------------------------- # Copyright (c) 2013, PyInstaller Development Team. # # Distributed under the terms of the GNU General Public License with exception # for distributing bootloader. # # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- """ An Owner does imports from a particular piece of turf That is, there's an Owner for each thing on sys.path There are owners for directories and .pyz files. There could be owners for zip files, or even URLs. Note that they replace the string in sys.path, but str(sys.path[n]) should yield the original string. """ import imp import marshal import os from PyInstaller import depend from PyInstaller.compat import getcwd, PYCO, caseOk from PyInstaller.loader import pyi_archive import PyInstaller.log as logging logger = logging.getLogger('PyInstaller.build.mf') class OwnerError(Exception): pass class Owner: """ Base class for loading Python bytecode from different places. """ def __init__(self, path): self.path = path def __str__(self): return self.path def getmod(self, nm): return None class BaseDirOwner(Owner): """ Base class for loading bytecode of Python modules from file system. """ def _getsuffixes(self): return imp.get_suffixes() def getmod(self, nm, getsuffixes=None, loadco=marshal.loads): if getsuffixes is None: getsuffixes = self._getsuffixes possibles = [(nm, 0, None)] if self._isdir(nm) and self._caseok(nm): possibles.insert(0, (os.path.join(nm, '__init__'), 1, nm)) py = pyc = None for pth, ispkg, pkgpth in possibles: for ext, mode, typ in getsuffixes(): attempt = pth + ext modtime = self._modtime(attempt) if modtime is not None: # Check case if not self._caseok(attempt): continue if typ == imp.C_EXTENSION: #logger.debug("%s.getmod -> ExtensionModule(%s, %s)", self.__class__.__name__, nm, attempt) return depend.modules.ExtensionModule(nm, os.path.join(self.path, attempt)) elif typ == imp.PY_SOURCE: py = (attempt, modtime) else: pyc = (attempt, modtime) if py or pyc: break if py is None and pyc is None: #logger.debug("%s.getmod -> (py == pyc == None)", self.__class__.__name__) return None co = None ## if nm == 'archive': ## import pdb ; pdb.set_trace() if pyc: stuff = self._read(pyc[0]) # If this file was not generated for this version of # Python, we need to regenerate it. if stuff[:4] != imp.get_magic(): logger.warn("wrong version .py%s found (%s), will use .py", PYCO, pyc[0]) else: try: co = loadco(stuff[8:]) pth = pyc[0] except (ValueError, EOFError): pyc = None logger.warn("bad .py%s found (%s), will use .py", PYCO, pyc[0]) if (co is None and py) or (py and pyc[1] < py[1]): # If we have no pyc or py is newer try: stuff = self._read(py[0]) + '\n' co = compile(stuff.replace("\r\n", "\n"), py[0], 'exec') pth = py[0] + PYCO logger.debug("compiled %s", pth) except SyntaxError, e: logger.exception(e) raise SystemExit(10) if co is None: #logger.debug("%s.getmod -> None", self.__class__.__name__) return None pth = os.path.join(self.path, pth) if not os.path.isabs(pth): pth = os.path.abspath(pth) if ispkg: mod = self._pkgclass()(nm, pth, co) else: mod = self._modclass()(nm, pth, co) #logger.debug("%s.getmod -> %s", self.__class__.__name__, mod) return mod class DirOwner(BaseDirOwner): def __init__(self, path): if path == '': path = getcwd() if not os.path.isdir(path): raise OwnerError("%s is not a directory" % repr(path)) Owner.__init__(self, path) def _isdir(self, fn): return os.path.isdir(os.path.join(self.path, fn)) def _modtime(self, fn): fn = os.path.join(self.path, fn) try: # the file must not only by stat()-able, but also readable if os.access(fn, os.R_OK): return os.stat(fn)[8] except OSError: # return None pass return None def _read(self, fn): return open(os.path.join(self.path, fn), 'rb').read() def _pkgclass(self): return depend.modules.PkgModule def _modclass(self): return depend.modules.PyModule def _caseok(self, fn): return caseOk(os.path.join(self.path, fn)) class ZipOwner(BaseDirOwner): """ Load bytecode of Python modules from .egg files. zipimporter cannot be used here because it has a stupid bug: >>> z.find_module("setuptools.setuptools.setuptools.setuptools.setuptools") is not None True So mf will go into infinite recursion. Instead, we'll reuse the BaseDirOwner logic, simply changing the template methods. """ def __init__(self, path): import zipfile try: self.zf = zipfile.ZipFile(path, "r") except IOError: raise OwnerError("%s is not a zipfile" % path) Owner.__init__(self, path) def getmod(self, fn): fn = fn.replace(".", "/") return BaseDirOwner.getmod(self, fn) def _modtime(self, fn): # zipfiles always use forward slashes fn = fn.replace("\\", "/") try: dt = self.zf.getinfo(fn).date_time return dt except KeyError: return None def _isdir(self, fn): # No way to find out if "fn" is a directory # so just always look into it in case it is. return True def _caseok(self, fn): # zipfile is always case-sensitive, so surely # there is no case mismatch. return True def _read(self, fn): # zipfiles always use forward slashes fn = fn.replace("\\", "/") return self.zf.read(fn) def _pkgclass(self): return lambda *args: depend.modules.PkgInZipModule(self, *args) def _modclass(self): return lambda *args: depend.modules.PyInZipModule(self, *args) class PYZOwner(Owner): """ Class for loading bytecode of Python modules from PYZ files. PYZ file is internal PyInstaller format embedded into final executable. It is possible to have a custom .spec file which packs a subset of Python files into a PYZ file, and then drop it on the disk somewhere. When the PYZ file is added to sys.path, PYZOwner will parse it and make the modules within it available at import time. NOTE: PYZ format cannot be replaced by zipimport module. The problem is that we have no control over zipimport; for instance, it doesn't work if the zip file is embedded into a PKG appended to an executable, like we create in one-file. """ def __init__(self, path): self.pyz = pyi_archive.ZlibArchive(path) Owner.__init__(self, path) def getmod(self, nm): rslt = self.pyz.extract(nm) if not rslt: return None ispkg, co = rslt if ispkg: return depend.modules.PkgInPYZModule(nm, co, self) return depend.modules.PyModule(nm, self.path, co)