Browse Source

Various pfreeze improvements, allow linking in extension modules

rdb 9 years ago
parent
commit
6c70b41a21
2 changed files with 174 additions and 63 deletions
  1. 161 59
      direct/src/showutil/FreezeTool.py
  2. 13 4
      direct/src/showutil/pfreeze.py

+ 161 - 59
direct/src/showutil/FreezeTool.py

@@ -8,7 +8,7 @@ import marshal
 import imp
 import imp
 import platform
 import platform
 from io import StringIO
 from io import StringIO
-from distutils.sysconfig import PREFIX, get_python_inc, get_python_version, get_config_var
+import distutils.sysconfig as sysconf
 
 
 # Temporary (?) try..except to protect against unbuilt p3extend_frozen.
 # Temporary (?) try..except to protect against unbuilt p3extend_frozen.
 try:
 try:
@@ -29,9 +29,23 @@ isDebugBuild = (python.lower().endswith('_d'))
 # These are modules that Python always tries to import up-front.  They
 # These are modules that Python always tries to import up-front.  They
 # must be frozen in any main.exe.
 # must be frozen in any main.exe.
 startupModules = [
 startupModules = [
-    'os', 'encodings.cp1252',
-    'encodings.latin_1', 'encodings.utf_8', 'io',
+    'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8',
     ]
     ]
+if sys.version_info >= (3, 0):
+    startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util']
+
+# These are some special init functions for some built-in Python modules that
+# deviate from the standard naming convention.  A value of None means that a
+# dummy entry should be written to the inittab.
+builtinInitFuncs = {
+    'builtins': None,
+    '__builtin__': None,
+    'sys': None,
+    'exceptions': None,
+    '_imp': 'PyInit_imp',
+    '_warnings': '_PyWarnings_Init',
+    'marshal': 'PyMarshal_Init',
+}
 
 
 # These are missing modules that we've reported already this session.
 # These are missing modules that we've reported already this session.
 reportedMissing = {}
 reportedMissing = {}
@@ -60,8 +74,8 @@ class CompilationEnvironment:
 
 
         # Paths to Python stuff.
         # Paths to Python stuff.
         self.Python = None
         self.Python = None
-        self.PythonIPath = get_python_inc()
-        self.PythonVersion = get_config_var("LDVERSION") or get_python_version()
+        self.PythonIPath = sysconf.get_python_inc()
+        self.PythonVersion = sysconf.get_config_var("LDVERSION") or sysconf.get_python_version()
 
 
         # The VC directory of Microsoft Visual Studio (if relevant)
         # The VC directory of Microsoft Visual Studio (if relevant)
         self.MSVC = None
         self.MSVC = None
@@ -85,7 +99,7 @@ class CompilationEnvironment:
 
 
     def determineStandardSetup(self):
     def determineStandardSetup(self):
         if self.platform.startswith('win'):
         if self.platform.startswith('win'):
-            self.Python = PREFIX
+            self.Python = sysconf.PREFIX
 
 
             if ('VCINSTALLDIR' in os.environ):
             if ('VCINSTALLDIR' in os.environ):
                 self.MSVC = os.environ['VCINSTALLDIR']
                 self.MSVC = os.environ['VCINSTALLDIR']
@@ -122,13 +136,15 @@ class CompilationEnvironment:
 
 
             # If it is run by makepanda, it handles the MSVC and PlatformSDK paths itself.
             # If it is run by makepanda, it handles the MSVC and PlatformSDK paths itself.
             if ('MAKEPANDA' in os.environ):
             if ('MAKEPANDA' in os.environ):
-                self.compileObj = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" %(filename)s'
+                self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" %(filename)s'
+                self.compileObjDll = self.compileObjExe
                 self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\libs"  /out:%(basename)s.exe %(basename)s.obj'
                 self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(python)s\libs"  /out:%(basename)s.exe %(basename)s.obj'
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(python)s\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
             else:
             else:
                 os.environ['PATH'] += ';' + self.MSVC + '\\bin' + self.suffix64 + ';' + self.MSVC + '\\Common7\\IDE;' + self.PSDK + '\\bin'
                 os.environ['PATH'] += ';' + self.MSVC + '\\bin' + self.suffix64 + ';' + self.MSVC + '\\Common7\\IDE;' + self.PSDK + '\\bin'
 
 
-                self.compileObj = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\include" /I"%(MSVC)s\include" %(filename)s'
+                self.compileObjExe = 'cl /wd4996 /Fo%(basename)s.obj /nologo /c %(MD)s /Zi /O2 /Ob2 /EHsc /Zm300 /W3 /I"%(pythonIPath)s" /I"%(PSDK)s\include" /I"%(MSVC)s\include" %(filename)s'
+                self.compileObjDll = self.compileObjExe
                 self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs"  /out:%(basename)s.exe %(basename)s.obj'
                 self.linkExe = 'link /nologo /MAP:NUL /FIXED:NO /OPT:REF /STACK:4194304 /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs"  /out:%(basename)s.exe %(basename)s.obj'
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
                 self.linkDll = 'link /nologo /DLL /MAP:NUL /FIXED:NO /OPT:REF /INCREMENTAL:NO /LIBPATH:"%(PSDK)s\lib" /LIBPATH:"%(MSVC)s\\lib%(suffix64)s" /LIBPATH:"%(python)s\libs"  /out:%(basename)s%(dllext)s.pyd %(basename)s.obj'
 
 
@@ -141,22 +157,26 @@ class CompilationEnvironment:
                 self.arch = '-arch ppc'
                 self.arch = '-arch ppc'
             elif proc == 'amd64':
             elif proc == 'amd64':
                 self.arch = '-arch x86_64'
                 self.arch = '-arch x86_64'
-            self.compileObj = "gcc -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
+            self.compileObjExe = "gcc -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
+            self.compileObjDll = "gcc -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
             self.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python"
             self.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python"
             self.linkDll = "gcc %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o"
             self.linkDll = "gcc %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o"
 
 
         else:
         else:
             # Unix
             # Unix
-            self.compileObj = "gcc -fPIC -c -o %(basename)s.o -O2 %(filename)s -I%(pythonIPath)s"
-            self.linkExe = "gcc -o %(basename)s %(basename)s.o -L/usr/local/lib -lpython%(pythonVersion)s"
-            self.linkDll = "gcc -shared -o %(basename)s.so %(basename)s.o -L/usr/local/lib -lpython%(pythonVersion)s"
+            lib_dir = sysconf.get_python_lib(plat_specific=1, standard_lib=1)
+            #python_a = os.path.join(lib_dir, "config", "libpython%(pythonVersion)s.a")
+            self.compileObjExe = "%(CC)s %(CFLAGS)s -c -o %(basename)s.o -pthread -O2 %(filename)s -I%(pythonIPath)s"
+            self.compileObjDll = "%(CC)s %(CFLAGS)s %(CCSHARED)s -c -o %(basename)s.o -O2 %(filename)s -I%(pythonIPath)s"
+            self.linkExe = "%(CC)s -o %(basename)s %(basename)s.o -L/usr/local/lib -lpython%(pythonVersion)s"
+            self.linkDll = "%(LDSHARED)s -o %(basename)s.so %(basename)s.o -L/usr/local/lib -lpython%(pythonVersion)s"
 
 
             if (os.path.isdir("/usr/PCBSD/local/lib")):
             if (os.path.isdir("/usr/PCBSD/local/lib")):
                 self.linkExe += " -L/usr/PCBSD/local/lib"
                 self.linkExe += " -L/usr/PCBSD/local/lib"
                 self.linkDll += " -L/usr/PCBSD/local/lib"
                 self.linkDll += " -L/usr/PCBSD/local/lib"
 
 
-    def compileExe(self, filename, basename):
-        compile = self.compileObj % {
+    def compileExe(self, filename, basename, extraLink=[]):
+        compile = self.compileObjExe % dict({
             'python' : self.Python,
             'python' : self.Python,
             'MSVC' : self.MSVC,
             'MSVC' : self.MSVC,
             'PSDK' : self.PSDK,
             'PSDK' : self.PSDK,
@@ -167,12 +187,12 @@ class CompilationEnvironment:
             'arch' : self.arch,
             'arch' : self.arch,
             'filename' : filename,
             'filename' : filename,
             'basename' : basename,
             'basename' : basename,
-            }
+            }, **sysconf.get_config_vars())
         sys.stderr.write(compile + '\n')
         sys.stderr.write(compile + '\n')
         if os.system(compile) != 0:
         if os.system(compile) != 0:
             raise Exception('failed to compile %s.' % basename)
             raise Exception('failed to compile %s.' % basename)
 
 
-        link = self.linkExe % {
+        link = self.linkExe % dict({
             'python' : self.Python,
             'python' : self.Python,
             'MSVC' : self.MSVC,
             'MSVC' : self.MSVC,
             'PSDK' : self.PSDK,
             'PSDK' : self.PSDK,
@@ -182,13 +202,14 @@ class CompilationEnvironment:
             'arch' : self.arch,
             'arch' : self.arch,
             'filename' : filename,
             'filename' : filename,
             'basename' : basename,
             'basename' : basename,
-            }
+            }, **sysconf.get_config_vars())
+        link += ' ' + ' '.join(extraLink)
         sys.stderr.write(link + '\n')
         sys.stderr.write(link + '\n')
         if os.system(link) != 0:
         if os.system(link) != 0:
             raise Exception('failed to link %s.' % basename)
             raise Exception('failed to link %s.' % basename)
 
 
-    def compileDll(self, filename, basename):
-        compile = self.compileObj % {
+    def compileDll(self, filename, basename, extraLink=[]):
+        compile = self.compileObjDll % dict({
             'python' : self.Python,
             'python' : self.Python,
             'MSVC' : self.MSVC,
             'MSVC' : self.MSVC,
             'PSDK' : self.PSDK,
             'PSDK' : self.PSDK,
@@ -199,12 +220,12 @@ class CompilationEnvironment:
             'arch' : self.arch,
             'arch' : self.arch,
             'filename' : filename,
             'filename' : filename,
             'basename' : basename,
             'basename' : basename,
-            }
+            }, **sysconf.get_config_vars())
         sys.stderr.write(compile + '\n')
         sys.stderr.write(compile + '\n')
         if os.system(compile) != 0:
         if os.system(compile) != 0:
             raise Exception('failed to compile %s.' % basename)
             raise Exception('failed to compile %s.' % basename)
 
 
-        link = self.linkDll % {
+        link = self.linkDll % dict({
             'python' : self.Python,
             'python' : self.Python,
             'MSVC' : self.MSVC,
             'MSVC' : self.MSVC,
             'PSDK' : self.PSDK,
             'PSDK' : self.PSDK,
@@ -215,7 +236,8 @@ class CompilationEnvironment:
             'filename' : filename,
             'filename' : filename,
             'basename' : basename,
             'basename' : basename,
             'dllext' : self.dllext,
             'dllext' : self.dllext,
-            }
+            }, **sysconf.get_config_vars())
+        link += ' ' + ' '.join(extraLink)
         sys.stderr.write(link + '\n')
         sys.stderr.write(link + '\n')
         if os.system(link) != 0:
         if os.system(link) != 0:
             raise Exception('failed to link %s.' % basename)
             raise Exception('failed to link %s.' % basename)
@@ -264,6 +286,8 @@ Py_FrozenMain(int argc, char **argv)
 #endif
 #endif
 
 
     Py_FrozenFlag = 1; /* Suppress errors from getpath.c */
     Py_FrozenFlag = 1; /* Suppress errors from getpath.c */
+    Py_NoSiteFlag = 1;
+    Py_NoUserSiteDirectory = 1;
 
 
     if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\\0')
     if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\\0')
         inspect = 1;
         inspect = 1;
@@ -463,10 +487,6 @@ main(int argc, char *argv[]) {
 
 
 # Our own glue code to start up a Python shared library.
 # Our own glue code to start up a Python shared library.
 dllInitCode = """
 dllInitCode = """
-static PyMethodDef nullMethods[] = {
-  {NULL, NULL}
-};
-
 /*
 /*
  * Call this function to extend the frozen modules array with a new
  * Call this function to extend the frozen modules array with a new
  * array of frozen modules, provided in a C-style array, at runtime.
  * array of frozen modules, provided in a C-style array, at runtime.
@@ -508,10 +528,29 @@ extend_frozen_modules(const struct _frozen *new_modules, int new_count) {
   return orig_count + new_count;
   return orig_count + new_count;
 }
 }
 
 
-%(dllexport)svoid init%(moduleName)s() {
+#if PY_MAJOR_VERSION >= 3
+static PyModuleDef mdef = {
+  PyModuleDef_HEAD_INIT,
+  "%(moduleName)s",
+  "",
+  -1,
+  NULL, NULL, NULL, NULL, NULL
+};
+
+%(dllexport)sPyObject *PyInit_%(moduleName)s(void) {
+  extend_frozen_modules(_PyImport_FrozenModules, sizeof(_PyImport_FrozenModules) / sizeof(struct _frozen));
+  return PyModule_Create(&mdef);
+}
+#else
+static PyMethodDef nullMethods[] = {
+  {NULL, NULL}
+};
+
+%(dllexport)svoid init%(moduleName)s(void) {
   extend_frozen_modules(_PyImport_FrozenModules, sizeof(_PyImport_FrozenModules) / sizeof(struct _frozen));
   extend_frozen_modules(_PyImport_FrozenModules, sizeof(_PyImport_FrozenModules) / sizeof(struct _frozen));
   Py_InitModule("%(moduleName)s", nullMethods);
   Py_InitModule("%(moduleName)s", nullMethods);
 }
 }
+#endif
 """
 """
 
 
 programFile = """
 programFile = """
@@ -526,8 +565,6 @@ struct _frozen _PyImport_FrozenModules[] = {
 %(moduleList)s
 %(moduleList)s
   {NULL, NULL, 0}
   {NULL, NULL, 0}
 };
 };
-
-%(initCode)s
 """
 """
 
 
 # Windows needs this bit.
 # Windows needs this bit.
@@ -664,9 +701,14 @@ class Freezer:
         # addToMultifile().  It contains a list of all the extension
         # addToMultifile().  It contains a list of all the extension
         # modules that were discovered, which have not been added to
         # modules that were discovered, which have not been added to
         # the output.  The list is a list of tuples of the form
         # the output.  The list is a list of tuples of the form
-        # (moduleName, filename).
+        # (moduleName, filename).  filename will be None for built-in
+        # modules.
         self.extras = []
         self.extras = []
 
 
+        # Set this to true if extension modules should be linked in to
+        # the resulting executable.
+        self.linkExtensionModules = False
+
         # End of public interface.  These remaining members should not
         # End of public interface.  These remaining members should not
         # be directly manipulated by callers.
         # be directly manipulated by callers.
         self.previousModules = {}
         self.previousModules = {}
@@ -676,6 +718,10 @@ class Freezer:
             self.previousModules = dict(previous.modules)
             self.previousModules = dict(previous.modules)
             self.modules = dict(previous.modules)
             self.modules = dict(previous.modules)
 
 
+        # Exclude doctest by default; it is not very useful in production
+        # builds.  It can be explicitly included if desired.
+        self.modules['doctest'] = self.ModuleDef('doctest', exclude = True)
+
         self.mf = None
         self.mf = None
 
 
         # Actually, make sure we know how to find all of the
         # Actually, make sure we know how to find all of the
@@ -951,8 +997,8 @@ class Freezer:
         for mdef in includes:
         for mdef in includes:
             try:
             try:
                 self.__loadModule(mdef)
                 self.__loadModule(mdef)
-            except ImportError:
-                print("Unknown module: %s" % (mdef.moduleName))
+            except ImportError as ex:
+                print("Unknown module: %s (%s)" % (mdef.moduleName, str(ex)))
 
 
         # Also attempt to import any implicit modules.  If any of
         # Also attempt to import any implicit modules.  If any of
         # these fail to import, we don't really care.
         # these fail to import, we don't really care.
@@ -1296,41 +1342,91 @@ class Freezer:
             if mdef.forbid:
             if mdef.forbid:
                 # Explicitly disallow importing this module.
                 # Explicitly disallow importing this module.
                 moduleList.append(self.makeForbiddenModuleListEntry(moduleName))
                 moduleList.append(self.makeForbiddenModuleListEntry(moduleName))
-            else:
-                assert not mdef.exclude
-                # Allow importing this module.
-                module = self.mf.modules.get(origName, None)
-                code = getattr(module, "__code__", None)
-                if code:
-                    code = marshal.dumps(code)
+                continue
 
 
-                    mangledName = self.mangleName(moduleName)
-                    moduleDefs.append(self.makeModuleDef(mangledName, code))
-                    moduleList.append(self.makeModuleListEntry(mangledName, code, moduleName, module))
+            assert not mdef.exclude
+            # Allow importing this module.
+            module = self.mf.modules.get(origName, None)
+            code = getattr(module, "__code__", None)
+            if code:
+                code = marshal.dumps(code)
 
 
-                elif moduleName in startupModules:
-                    # Forbid the loading of this startup module.
-                    moduleList.append(self.makeForbiddenModuleListEntry(moduleName))
+                mangledName = self.mangleName(moduleName)
+                moduleDefs.append(self.makeModuleDef(mangledName, code))
+                moduleList.append(self.makeModuleListEntry(mangledName, code, moduleName, module))
+                continue
 
 
-                else:
-                    # This is a module with no associated Python
-                    # code.  It must be an extension module.  Get the
-                    # filename.
-                    extensionFilename = getattr(module, '__file__', None)
-                    if extensionFilename:
-                        self.extras.append((moduleName, extensionFilename))
-                    else:
-                        # It doesn't even have a filename; it must
-                        # be a built-in module.  No worries about
-                        # this one, then.
-                        pass
+            #if moduleName in startupModules:
+            #    # Forbid the loading of this startup module.
+            #    moduleList.append(self.makeForbiddenModuleListEntry(moduleName))
+            #    continue
+
+            # This is a module with no associated Python code.  It is either
+            # an extension module or a builtin module.  Get the filename, if
+            # it is the former.
+            extensionFilename = getattr(module, '__file__', None)
+
+            if extensionFilename or self.linkExtensionModules:
+                self.extras.append((moduleName, extensionFilename))
+
+            # If it is a submodule of a frozen module, Python will have
+            # trouble importing it as a builtin module.  Synthesize a frozen
+            # module that loads it as builtin.
+            if '.' in moduleName and self.linkExtensionModules:
+                code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec')
+                code = marshal.dumps(code)
+                mangledName = self.mangleName(moduleName)
+                moduleDefs.append(self.makeModuleDef(mangledName, code))
+                moduleList.append(self.makeModuleListEntry(mangledName, code, moduleName, None))
+            elif '.' in moduleName:
+                # Nothing we can do about this case except warn the user they
+                # are in for some trouble.
+                print('WARNING: Python cannot import extension modules under '
+                      'frozen Python packages; %s will be inaccessible.  '
+                      'passing either -l to link in extension modules or use '
+                      '-x %s to exclude the entire package.' % (moduleName, moduleName.split('.')[0]))
 
 
         text = programFile % {
         text = programFile % {
             'moduleDefs': '\n'.join(moduleDefs),
             'moduleDefs': '\n'.join(moduleDefs),
             'moduleList': '\n'.join(moduleList),
             'moduleList': '\n'.join(moduleList),
-            'initCode': initCode
             }
             }
 
 
+        if self.linkExtensionModules and self.extras:
+            # Python 3 case.
+            text += '#if PY_MAJOR_VERSION >= 3\n'
+            for module, fn in self.extras:
+                libName = module.split('.')[-1]
+                initFunc = builtinInitFuncs.get(module, 'PyInit_' + libName)
+                if initFunc:
+                    text += 'extern PyObject *%s(void);\n' % (initFunc)
+            text += '\n'
+            text += 'struct _inittab _PyImport_Inittab[] = {\n'
+            for module, fn in self.extras:
+                libName = module.split('.')[-1]
+                initFunc = builtinInitFuncs.get(module, 'PyInit_' + libName) or 'NULL'
+                text += '  {"%s", %s},\n' % (module, initFunc)
+            text += '  {0, 0},\n'
+            text += '};\n\n'
+
+            # Python 2 case.
+            text += '#else\n'
+            for module, fn in self.extras:
+                libName = module.split('.')[-1]
+                initFunc = builtinInitFuncs.get(module, 'init' + libName)
+                if initFunc:
+                    text += 'extern void %s(void);\n' % (initFunc)
+            text += '\n'
+            text += 'struct _inittab _PyImport_Inittab[] = {\n'
+            for module, fn in self.extras:
+                libName = module.split('.')[-1]
+                initFunc = builtinInitFuncs.get(module, 'init' + libName) or 'NULL'
+                text += '  {"%s", %s},\n' % (module, initFunc)
+            text += '  {0, 0},\n'
+            text += '};\n'
+            text += '#endif\n\n'
+
+        text += initCode
+
         if filename is not None:
         if filename is not None:
             file = open(filename, 'w')
             file = open(filename, 'w')
             file.write(text)
             file.write(text)
@@ -1397,8 +1493,14 @@ class Freezer:
 
 
         self.writeCode(filename, initCode=initCode)
         self.writeCode(filename, initCode=initCode)
 
 
+        extraLink = []
+        if self.linkExtensionModules:
+            for mod, fn in self.extras:
+                if fn:
+                    extraLink.append(fn)
+
         try:
         try:
-            compileFunc(filename, basename)
+            compileFunc(filename, basename, extraLink=extraLink)
         finally:
         finally:
             if not self.keepTemporaryFiles:
             if not self.keepTemporaryFiles:
                 if os.path.exists(filename):
                 if os.path.exists(filename):

+ 13 - 4
direct/src/showutil/pfreeze.py

@@ -15,7 +15,7 @@ imported directly or indirectly by the original startfile.py.
 
 
 Usage:
 Usage:
 
 
-  pfreeze.py [opts] startfile
+  pfreeze.py [opts] [startfile]
 
 
 Options:
 Options:
 
 
@@ -40,6 +40,11 @@ Options:
      of the __path__ variable, and thus must be actually imported to
      of the __path__ variable, and thus must be actually imported to
      determine the true value of __path__.
      determine the true value of __path__.
 
 
+  -P path
+     Specifies an additional directory in which we should search for
+     Python modules.  This is equivalent to setting the PYTHONPATH
+     environment variable.  May be repeated.
+
   -s
   -s
      Adds the standard set of modules that are necessary for embedding
      Adds the standard set of modules that are necessary for embedding
      the Python interpreter.  Implicitly set if an executable is
      the Python interpreter.  Implicitly set if an executable is
@@ -55,7 +60,7 @@ from direct.showutil import FreezeTool
 def usage(code, msg = ''):
 def usage(code, msg = ''):
     if __doc__:
     if __doc__:
         sys.stderr.write(__doc__ + '\n')
         sys.stderr.write(__doc__ + '\n')
-    sys.stderr.write(msg + '\n')
+    sys.stderr.write(str(msg) + '\n')
     sys.exit(code)
     sys.exit(code)
 
 
 # We're not protecting the next part under a __name__ == __main__
 # We're not protecting the next part under a __name__ == __main__
@@ -67,7 +72,7 @@ basename = None
 addStartupModules = False
 addStartupModules = False
 
 
 try:
 try:
-    opts, args = getopt.getopt(sys.argv[1:], 'o:i:x:p:sh')
+    opts, args = getopt.getopt(sys.argv[1:], 'o:i:x:p:P:slh')
 except getopt.error as msg:
 except getopt.error as msg:
     usage(1, msg)
     usage(1, msg)
 
 
@@ -83,8 +88,12 @@ for opt, arg in opts:
     elif opt == '-p':
     elif opt == '-p':
         for module in arg.split(','):
         for module in arg.split(','):
             freezer.handleCustomPath(module)
             freezer.handleCustomPath(module)
+    elif opt == '-P':
+        sys.path.append(arg)
     elif opt == '-s':
     elif opt == '-s':
         addStartupModules = True
         addStartupModules = True
+    elif opt == '-l':
+        freezer.linkExtensionModules = True
     elif opt == '-h':
     elif opt == '-h':
         usage(0)
         usage(0)
     else:
     else:
@@ -126,7 +135,7 @@ if args:
 
 
 elif outputType == 'exe':
 elif outputType == 'exe':
     # We must have a main module when making an executable.
     # We must have a main module when making an executable.
-    usage(0)
+    usage(1, 'A main file needs to be specified when creating an executable.')
 
 
 freezer.done(addStartupModules = addStartupModules)
 freezer.done(addStartupModules = addStartupModules)