浏览代码

freezetool

David Rose 16 年之前
父节点
当前提交
30c07c2714
共有 2 个文件被更改,包括 803 次插入0 次删除
  1. 722 0
      direct/src/showutil/FreezeTool.py
  2. 81 0
      direct/src/showutil/pfreeze.py

+ 722 - 0
direct/src/showutil/FreezeTool.py

@@ -0,0 +1,722 @@
+""" This module contains code to freeze a number of Python modules
+into a single (mostly) standalone DLL or EXE. """
+
+import modulefinder
+import sys
+import os
+import marshal
+import imp
+
+import direct
+from pandac.PandaModules import *
+
+# These are modules that Python always tries to import up-front.  They
+# must be frozen in any main.exe.
+startupModules = [
+    'site', 'sitecustomize', 'os', 'encodings.cp1252',
+    'org',
+    ]
+
+# Our own Python source trees to watch out for.
+sourceTrees = ['direct']
+
+# The command to compile a c to an object file.  Replace %(basename)s
+# with the basename of the source file, and an implicit .c extension.
+compileObj = 'error'
+
+# The command to link a single object file into an executable.  As
+# above, replace $(basename)s with the basename of the original source
+# file, and of the target executable.
+linkExe = 'error'
+
+# The command to link a single object file into a shared library.
+linkDll = 'error'
+
+# The root directory of the Python installation
+Python = None
+
+# The directory that includes Python.h.
+PythonIPath = '/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/Python.framework/Versions/2.5/include/python2.5'
+
+if sys.platform == 'win32':
+    compileObj = "cl /wd4996 /Fo%(basename)s.obj /nologo /c /MD /Zi /O2 /Ob2 /EHsc /Zm300 /W3 %(filename)s"
+    linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /out:%(basename)s.exe; mt -manifest %(basename)s.manifest -outputresource:%(basename)s.exe;2'
+    linkDll = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /out:%(basename)s.dll; mt -manifest %(basename)s.manifest -outputresource:%(basename)s.dll;1'
+
+elif sys.platform == 'darwin':
+    # OSX
+    compileObj = "gcc -fPIC -c -o %(basename)s.o -O2 -arch i386 -arch ppc -I %(pythonIPath)s %(filename)s"
+    linkExe = "gcc -o %(basename)s %(basename)s.o -framework Python"
+    linkDll = "gcc -shared -o %(basename)s.so %(basename)s.o -framework Python"
+
+else:
+    # Linux
+    compileObj = "gcc -fPIC -c -o %(basename)s.o -O2 %(filename)s"
+    linkExe = "gcc -o %(basename)s %(basename)s.o"
+    linkDll = "gcc -shared -o %(basename)s.so %(basename)s.o"
+
+# The code from frozenmain.c in the Python source repository.
+frozenMainCode = """
+/* Python interpreter main program for frozen scripts */
+
+#include "Python.h"
+
+#ifdef MS_WINDOWS
+extern void PyWinFreeze_ExeInit(void);
+extern void PyWinFreeze_ExeTerm(void);
+extern int PyInitFrozenExtensions(void);
+#endif
+
+/* Main program */
+
+int
+Py_FrozenMain(int argc, char **argv)
+{
+    char *p;
+    int n, sts;
+    int inspect = 0;
+    int unbuffered = 0;
+
+    Py_FrozenFlag = 1; /* Suppress errors from getpath.c */
+
+    if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\\0')
+        inspect = 1;
+    if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\\0')
+        unbuffered = 1;
+
+    if (unbuffered) {
+        setbuf(stdin, (char *)NULL);
+        setbuf(stdout, (char *)NULL);
+        setbuf(stderr, (char *)NULL);
+    }
+
+#ifdef MS_WINDOWS
+    PyInitFrozenExtensions();
+#endif /* MS_WINDOWS */
+    Py_SetProgramName(argv[0]);
+    Py_Initialize();
+#ifdef MS_WINDOWS
+    PyWinFreeze_ExeInit();
+#endif
+
+    if (Py_VerboseFlag)
+        fprintf(stderr, "Python %s\\n%s\\n",
+            Py_GetVersion(), Py_GetCopyright());
+
+    PySys_SetArgv(argc, argv);
+
+    n = PyImport_ImportFrozenModule("__main__");
+    if (n == 0)
+        Py_FatalError("__main__ not frozen");
+    if (n < 0) {
+        PyErr_Print();
+        sts = 1;
+    }
+    else
+        sts = 0;
+
+    if (inspect && isatty((int)fileno(stdin)))
+        sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
+
+#ifdef MS_WINDOWS
+    PyWinFreeze_ExeTerm();
+#endif
+    Py_Finalize();
+    return sts;
+}
+"""
+
+# The code from frozen_dllmain.c in the Python source repository.
+# Windows only.
+frozenDllMainCode = """
+#include "windows.h"
+
+static char *possibleModules[] = {
+    "pywintypes",
+    "pythoncom",
+    "win32ui",
+    NULL,
+};
+
+BOOL CallModuleDllMain(char *modName, DWORD dwReason);
+
+
+/*
+  Called by a frozen .EXE only, so that built-in extension
+  modules are initialized correctly
+*/
+void PyWinFreeze_ExeInit(void)
+{
+    char **modName;
+    for (modName = possibleModules;*modName;*modName++) {
+/*      printf("Initialising '%s'\\n", *modName); */
+        CallModuleDllMain(*modName, DLL_PROCESS_ATTACH);
+    }
+}
+
+/*
+  Called by a frozen .EXE only, so that built-in extension
+  modules are cleaned up 
+*/
+void PyWinFreeze_ExeTerm(void)
+{
+    // Must go backwards
+    char **modName;
+    for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2;
+         modName >= possibleModules;
+         *modName--) {
+/*      printf("Terminating '%s'\\n", *modName);*/
+        CallModuleDllMain(*modName, DLL_PROCESS_DETACH);
+    }
+}
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
+{
+    BOOL ret = TRUE;
+    switch (dwReason) {
+        case DLL_PROCESS_ATTACH: 
+        {
+            char **modName;
+            for (modName = possibleModules;*modName;*modName++) {
+                BOOL ok = CallModuleDllMain(*modName, dwReason);
+                if (!ok)
+                    ret = FALSE;
+            }
+            break;
+        }
+        case DLL_PROCESS_DETACH: 
+        {
+            // Must go backwards
+            char **modName;
+            for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2;
+                 modName >= possibleModules;
+                 *modName--)
+                CallModuleDllMain(*modName, DLL_PROCESS_DETACH);
+            break;
+        }
+    }
+    return ret;
+}
+
+BOOL CallModuleDllMain(char *modName, DWORD dwReason)
+{
+    BOOL (WINAPI * pfndllmain)(HINSTANCE, DWORD, LPVOID);
+
+    char funcName[255];
+    HMODULE hmod = GetModuleHandle(NULL);
+    strcpy(funcName, "_DllMain");
+    strcat(funcName, modName);
+    strcat(funcName, "@12"); // stdcall convention.
+    pfndllmain = (BOOL (WINAPI *)(HINSTANCE, DWORD, LPVOID))GetProcAddress(hmod, funcName);
+    if (pfndllmain==NULL) {
+        /* No function by that name exported - then that module does
+           not appear in our frozen program - return OK
+                */
+        return TRUE;
+    }
+    return (*pfndllmain)(hmod, dwReason, NULL);
+}
+"""
+
+# Our own glue code to start up a Python executable.
+mainInitCode = """
+%(frozenMainCode)s
+
+int
+main(int argc, char *argv[]) {
+  PyImport_FrozenModules = _PyImport_FrozenModules;
+  return Py_FrozenMain(argc, argv);
+}
+"""
+
+# Our own glue code to start up a Python shared library.
+dllInitCode = """
+static PyMethodDef nullMethods[] = {
+  {NULL, NULL}
+};
+
+%(dllexport)svoid init%(moduleName)s() {
+  int count;
+  struct _frozen *new_FrozenModules;
+
+  count = 0;
+  while (PyImport_FrozenModules[count].name != NULL) {
+    ++count;
+  }
+  new_FrozenModules = (struct _frozen *)malloc((count + %(newcount)s + 1) * sizeof(struct _frozen));
+  memcpy(new_FrozenModules, _PyImport_FrozenModules, %(newcount)s * sizeof(struct _frozen));
+  memcpy(new_FrozenModules + %(newcount)s, PyImport_FrozenModules, count * sizeof(struct _frozen));
+  memset(new_FrozenModules + count + %(newcount)s, 0, sizeof(struct _frozen));
+
+  PyImport_FrozenModules = new_FrozenModules;
+
+  Py_InitModule("%(moduleName)s", nullMethods);
+}
+"""
+
+programFile = """
+#include "Python.h"
+
+%(moduleDefs)s
+
+static struct _frozen _PyImport_FrozenModules[] = {
+%(moduleList)s
+  {NULL, NULL, 0}
+};
+
+%(initCode)s
+"""
+
+# Windows needs this bit.
+frozenExtensions = """
+
+static struct _inittab extensions[] = {
+        /* Sentinel */
+        {0, 0}
+};
+extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab);
+
+int PyInitFrozenExtensions()
+{
+        return PyImport_ExtendInittab(extensions);
+}
+"""
+
+okMissing = [
+    'Carbon.Folder', 'Carbon.Folders', 'HouseGlobals', 'Carbon.File',
+    'MacOS', '_emx_link', 'ce', 'mac', 'org.python.core', 'os.path',
+    'os2', 'posix', 'pwd', 'readline', 'riscos', 'riscosenviron',
+    'riscospath', 'dbm', 'fcntl', 'win32api',
+    '_winreg', 'ctypes', 'ctypes.wintypes', 'nt','msvcrt',
+    'EasyDialogs', 'SOCKS', 'ic', 'rourl2path', 'termios',
+    'OverrideFrom23._Res', 'email', 'email.Utils', 'email.Generator',
+    'email.Iterators', '_subprocess', 'gestalt',
+    'direct.extensions_native.extensions_darwin',
+    ]
+
+class Freezer:
+    # Module tokens:
+    MTAuto = 0
+    MTInclude = 1
+    MTExclude = 2
+    MTForbid = 3
+
+    def __init__(self, previous = None, debugLevel = 0):
+        # Normally, we are freezing for our own platform.  Change this
+        # if untrue.
+        self.platform = sys.platform
+
+        # You will also need to change these for a cross-compiler
+        # situation.
+        self.compileObj = compileObj
+        self.linkExe = linkExe
+        self.linkDll = linkDll
+
+        # The filename extension to append to the source file before
+        # compiling.
+        self.sourceExtension = '.c'
+
+        # The filename extension to append to the object file.
+        self.objectExtension = '.o'
+        if self.platform == 'win32':
+            self.objectExtension = '.obj'
+        
+        # True to compile to an executable, false to compile to a dll.  If
+        # setMain() is called, this is automatically set to True.
+        self.compileToExe = False
+
+        # Change any of these to change the generated startup and glue
+        # code.
+        self.frozenMainCode = frozenMainCode
+        self.frozenDllMainCode = frozenDllMainCode
+        self.mainInitCode = mainInitCode
+        self.frozenExtensions = frozenExtensions
+
+
+        # End of public interface.  These remaining members should not
+        # be directly manipulated by callers.
+        self.previousModules = {}
+        self.modules = {}
+
+        if previous:
+            self.previousModules = dict(previous.modules)
+            self.modules = dict(previous.modules)
+
+        self.mainModule = None
+        self.mf = None
+
+        # Make sure we know how to find "direct".
+        if direct.__path__:
+            modulefinder.AddPackagePath('direct', direct.__path__[0])
+
+    def excludeModule(self, moduleName, forbid = False):
+        """ Adds a module to the list of modules not to be exported by
+        this tool.  If forbid is true, the module is furthermore
+        forbidden to be imported, even if it exists on disk. """
+        
+        if forbid:
+            self.modules[moduleName] = self.MTForbid
+        else:
+            self.modules[moduleName] = self.MTExclude
+
+    def getModulePath(self, moduleName):
+        """ Looks for the indicated directory module and returns its
+        __path__ member: the list of directories in which its python
+        files can be found.  If the module is a .py file and not a
+        directory, returns None. """
+
+        # First, try to import the module directly.  That's the most
+        # reliable answer, if it works.
+        try:
+            module = __import__(moduleName)
+        except:
+            module = None
+
+        if module != None:
+            for symbol in moduleName.split('.')[1:]:
+                module = getattr(module, symbol)
+            return module.__path__
+        
+        # If it didn't work--maybe the module is unimportable because
+        # it makes certain assumptions about the builtins, or
+        # whatever--then just look for file on disk.  That's usually
+        # good enough.
+        path = None
+        baseName = moduleName
+        if '.' in baseName:
+            parentName, baseName = moduleName.rsplit('.', 1)
+            path = self.getModulePath(parentName)
+            if path == None:
+                return None
+            
+        file, pathname, description = imp.find_module(baseName, path)
+
+        if os.path.isdir(pathname):
+            return [pathname]
+        else:
+            return None
+            
+    def addModule(self, moduleName, implicit = False):
+        """ Adds a module to the list of modules to be exported by
+        this tool.  If implicit is true, it is OK if the module does
+        not actually exist.
+
+        The module name may end in ".*", which means to add all of the
+        .py files (other than __init__.py) in a particular directory.
+        It may also end in ".*.*", which means to cycle through all
+        directories within a particular directory.
+        """
+
+        if implicit:
+            token = self.MTAuto
+        else:
+            token = self.MTInclude
+
+        if moduleName.endswith('.*'):
+            # Find the parent module, so we can get its directory.
+            parentName = moduleName[:-2]
+            parentNames = [parentName]
+
+            if parentName.endswith('.*'):
+                # Another special case.  The parent name "*" means to
+                # return all possible directories within a particular
+                # directory.
+
+                topName = parentName[:-2]
+                parentNames = []
+                for dirname in self.getModulePath(topName):
+                    for filename in os.listdir(dirname):
+                        if os.path.exists(os.path.join(dirname, filename, '__init__.py')):
+                            parentName = '%s.%s' % (topName, filename)
+                            if self.getModulePath(parentName):
+                                parentNames.append(parentName)
+
+            for parentName in parentNames:
+                path = self.getModulePath(parentName)
+
+                if path == None:
+                    # It's actually a regular module.
+                    self.modules[parentName] = token
+
+                else:
+                    # Now get all the py files in the parent directory.
+                    for dirname in path:
+                        for filename in os.listdir(dirname):
+                            if '-' in filename:
+                                continue
+                            if filename.endswith('.py') and filename != '__init__.py':
+                                moduleName = '%s.%s' % (parentName, filename[:-3])
+                                self.modules[moduleName] = token
+        else:
+            # A normal, explicit module name.
+            self.modules[moduleName] = token
+
+    def setMain(self, moduleName):
+        self.addModule(moduleName)
+        self.mainModule = moduleName
+        self.compileToExe = True
+
+    def done(self):
+        assert self.mf == None
+
+        if self.compileToExe:
+            # Ensure that each of our required startup modules is
+            # on the list.
+            for moduleName in startupModules:
+                if moduleName not in self.modules:
+                    self.modules[moduleName] = self.MTAuto
+
+        # Excluding a parent module also excludes all its children.
+        # Walk through the list in sorted order, so we reach children
+        # before parents.
+        names = self.modules.items()
+        names.sort()
+
+        excludes = []
+        excludeDict = {}
+        includes = []
+        autoIncludes = []
+        for moduleName, token in names:
+            if '.' in moduleName:
+                parentName, baseName = moduleName.rsplit('.', 1)
+                if parentName in excludeDict:
+                    token = excludeDict[parentName]
+            
+            if token == self.MTInclude:
+                includes.append(moduleName)
+            elif token == self.MTAuto:
+                autoIncludes.append(moduleName)
+            elif token == self.MTExclude or token == self.MTForbid:
+                excludes.append(moduleName)
+                excludeDict[moduleName] = token
+
+        self.mf = modulefinder.ModuleFinder(excludes = excludes)
+
+        # Attempt to import the explicit modules into the modulefinder.
+        for moduleName in includes:
+            self.mf.import_hook(moduleName)
+
+        # Also attempt to import any implicit modules.  If any of
+        # these fail to import, we don't care.
+        for moduleName in autoIncludes:
+            try:
+                self.mf.import_hook(moduleName)
+            except ImportError:
+                pass
+
+        # Now, any new modules we found get added to the export list.
+        for moduleName in self.mf.modules.keys():
+            if moduleName not in self.modules:
+                self.modules[moduleName] = self.MTAuto
+
+        missing = []
+        for moduleName in self.mf.any_missing():
+            if moduleName in startupModules:
+                continue
+            if moduleName in self.previousModules:
+                continue
+
+            # This module is missing.  Let it be missing in the
+            # runtime also.
+            self.modules[moduleName] = self.MTExclude
+
+            if moduleName in okMissing:
+                # If it's listed in okMissing, don't even report it.
+                continue
+
+            prefix = moduleName.split('.')[0]
+            if prefix not in sourceTrees:
+                # If it's in not one of our standard source trees, assume
+                # it's some wacky system file we don't need.
+                continue
+                
+            missing.append(moduleName)
+                
+        if missing:
+            error = "There are some missing modules: %r" % missing
+            print error
+            raise StandardError, error
+
+    def mangleName(self, moduleName):
+        return 'M_' + moduleName.replace('.', '__')
+
+    def generateCode(self, basename):
+
+        # Collect a list of all of the modules we will be explicitly
+        # referencing.
+        moduleNames = []
+
+        for moduleName, token in self.modules.items():
+            prevToken = self.previousModules.get(moduleName, None)
+            if token == self.MTInclude or token == self.MTAuto:
+                # Include this module (even if a previous pass
+                # excluded it).  But don't bother if we exported it
+                # previously.
+                if prevToken != self.MTInclude and prevToken != self.MTAuto:
+                    if moduleName in self.mf.modules or \
+                       moduleName in startupModules:
+                        moduleNames.append(moduleName)
+            elif token == self.MTForbid:
+                if prevToken != self.MTForbid:
+                    moduleNames.append(moduleName)
+
+        # Build up the replacement pathname table, so we can eliminate
+        # the personal information in the frozen pathnames.  The
+        # actual filename we put in there is meaningful only for stack
+        # traces, so we'll just use the module name.
+        replace_paths = []
+        for moduleName, module in self.mf.modules.items():
+            if module.__code__:
+                origPathname = module.__code__.co_filename
+                replace_paths.append((origPathname, moduleName))
+        self.mf.replace_paths = replace_paths
+
+        # Now that we have built up the replacement mapping, go back
+        # through and actually replace the paths.
+        for moduleName, module in self.mf.modules.items():
+            if module.__code__:
+                co = self.mf.replace_paths_in_code(module.__code__)
+                module.__code__ = co;
+
+        # Now generate the actual export table.
+        moduleNames.sort()
+
+        moduleDefs = []
+        moduleList = []
+        
+        for moduleName in moduleNames:
+            token = self.modules[moduleName]
+            if token == self.MTForbid:
+                # Explicitly disallow importing this module.
+                moduleList.append(self.makeForbiddenModuleListEntry(moduleName))
+            else:
+                assert token != self.MTExclude
+                # Allow importing this module.
+                module = self.mf.modules.get(moduleName, None)
+                code = getattr(module, "__code__", None)
+                if not code and moduleName in startupModules:
+                    # Forbid the loading of this startup module.
+                    moduleList.append(self.makeForbiddenModuleListEntry(moduleName))
+                else:
+                    if moduleName in sourceTrees:
+                        # This is one of our Python source trees.
+                        # These are a special case: we don't compile
+                        # the __init__.py files within them, since
+                        # their only purpose is to munge the __path__
+                        # variable anyway.  Instead, we pretend the
+                        # __init__.py files are empty.
+                        code = compile('', moduleName, 'exec')
+
+                    if code:
+                        code = marshal.dumps(code)
+
+                        mangledName = self.mangleName(moduleName)
+                        moduleDefs.append(self.makeModuleDef(mangledName, code))
+                        moduleList.append(self.makeModuleListEntry(mangledName, code, moduleName, module))
+                        if moduleName == self.mainModule:
+                            # Add a special entry for __main__.
+                            moduleList.append(self.makeModuleListEntry(mangledName, code, '__main__', module))
+
+        if self.compileToExe:
+            code = self.frozenMainCode
+            if self.platform == 'win32':
+                code += self.frozenDllMainCode
+            initCode = self.mainInitCode % {
+                'frozenMainCode' : code,
+                'programName' : basename,
+                }
+            if self.platform == 'win32':
+                initCode += self.frozenExtensions
+                target = basename + '.exe'
+            else:
+                target = basename
+
+            doCompile = self.compileExe
+            
+        else:
+            dllexport = ''
+            if self.platform == 'win32':
+                dllexport = '__declspec(dllexport) '
+                target = basename + '.pyd'
+            else:
+                target = basename + '.so'
+                
+            initCode = dllInitCode % {
+                'dllexport' : dllexport,
+                'moduleName' : basename,
+                'newcount' : len(moduleList),
+                }
+            doCompile = self.compileDll
+
+        text = programFile % {
+            'moduleDefs' : '\n'.join(moduleDefs),
+            'moduleList' : '\n'.join(moduleList),
+            'initCode' : initCode,
+            }
+
+        filename = basename + self.sourceExtension
+        file = open(filename, 'w')
+        file.write(text)
+        file.close()
+
+        doCompile(filename, basename)
+        os.unlink(filename)
+        os.unlink(basename + self.objectExtension)
+
+        return target
+
+    def compileExe(self, filename, basename):
+        compile = self.compileObj % {
+            'pythonIPath' : PythonIPath,
+            'filename' : filename,
+            'basename' : basename,
+            }
+        print >> sys.stderr, compile
+        if os.system(compile) != 0:
+            raise StandardError
+
+        link = self.linkExe % {
+            'filename' : filename,
+            'basename' : basename,
+            }
+        print >> sys.stderr, link
+        if os.system(link) != 0:
+            raise StandardError
+
+    def compileDll(self, filename, basename):
+        compile = self.compileObj % {
+            'pythonIPath' : PythonIPath,
+            'filename' : filename,
+            'basename' : basename,
+            }
+        print >> sys.stderr, compile
+        if os.system(compile) != 0:
+            raise StandardError
+
+        link = self.linkDll % {
+            'filename' : filename,
+            'basename' : basename,
+            }
+        print >> sys.stderr, link
+        if os.system(link) != 0:
+            raise StandardError
+
+    def makeModuleDef(self, mangledName, code):
+        result = ''
+        result += 'static unsigned char %s[] = {' % (mangledName)
+        for i in range(0, len(code), 16):
+            result += '\n  '
+            for c in code[i:i+16]:
+                result += ('%d,' % ord(c))
+        result += '\n};\n'
+        return result
+
+    def makeModuleListEntry(self, mangledName, code, moduleName, module):
+        size = len(code)
+        if getattr(module, "__path__", None):
+            # Indicate package by negative size
+            size = -size
+        return '  {"%s", %s, %s},' % (moduleName, mangledName, size)
+
+    def makeForbiddenModuleListEntry(self, moduleName):
+        return '  {"%s", NULL, 0},' % (moduleName)

+ 81 - 0
direct/src/showutil/pfreeze.py

@@ -0,0 +1,81 @@
+#! /usr/bin/env python
+
+"""
+
+This script can be used to produce a standalone executable from
+arbitrary Python code.  You supply the name of the starting Python
+file to import, and this script attempts to generate an executable
+that will produce the same results as "python startfile.py".
+
+This script is actually a wrapper around Panda's FreezeTool.py, which
+is itself a tool to use Python's built-in "freeze" utility to compile
+Python code into a standalone executable.  It also uses Python's
+built-in modulefinder module, which it uses to find all of the modules
+imported directly or indirectly by the original startfile.py.
+
+Usage:
+
+  pfreeze.py [opts] startfile
+
+Options:
+
+  -o output
+     Specifies the name of the resulting executable file to produce.
+
+  -x module[,module...]
+     Specifies a comma-separated list of Python modules to exclude from
+     the resulting file, even if they appear to be referenced.  You
+     may also repeat the -x command for each module.
+
+  -i module[,module...]
+     Specifies a comma-separated list of Python modules to include in
+     the resulting file, even if they do not appear to be referenced.
+     You may also repeat the -i command for each module.
+
+"""
+
+import getopt
+import sys
+import os
+from direct.showutil import FreezeTool
+
+def usage(code, msg = ''):
+    print >> sys.stderr, __doc__
+    print >> sys.stderr, msg
+    sys.exit(code)
+
+if __name__ == '__main__':
+    freezer = FreezeTool.Freezer()
+
+    basename = None
+    
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], 'o:i:x:h')
+    except getopt.error, msg:
+        usage(1, msg)
+
+    for opt, arg in opts:
+        if opt == '-o':
+            basename = arg
+        elif opt == '-i':
+            for module in arg.split(','):
+                freezer.addModule(module)
+        elif opt == '-x':
+            for module in arg.split(','):
+                freezer.excludeModule(module)
+        elif opt == '-h':
+            usage(0)
+
+    if not args:
+        usage(0)
+
+    if not basename:
+        usage(1, 'You did not specify an output file.')
+
+    if len(args) != 1:
+        usage(1, 'Only one main file may be specified.')
+
+    freezer.setMain(args[0])
+    freezer.done()
+
+    freezer.generateCode(basename)