Browse Source

pfreeze: Extension module link support on Windows. Add explicit -k option for keeping temp files.

rdb 9 years ago
parent
commit
388c8d698d
2 changed files with 84 additions and 42 deletions
  1. 77 41
      direct/src/showutil/FreezeTool.py
  2. 7 1
      direct/src/showutil/pfreeze.py

+ 77 - 41
direct/src/showutil/FreezeTool.py

@@ -255,7 +255,8 @@ frozenMainCode = """
 #ifdef MS_WINDOWS
 extern void PyWinFreeze_ExeInit(void);
 extern void PyWinFreeze_ExeTerm(void);
-extern int PyInitFrozenExtensions(void);
+
+extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab);
 #endif
 
 /* Main program */
@@ -324,7 +325,7 @@ Py_FrozenMain(int argc, char **argv)
 #endif
 
 #ifdef MS_WINDOWS
-    PyInitFrozenExtensions();
+    PyImport_ExtendInittab(extensions);
 #endif /* MS_WINDOWS */
 
     if (argc >= 1) {
@@ -368,8 +369,8 @@ Py_FrozenMain(int argc, char **argv)
 #endif
     Py_Finalize();
 
-error:
 #if PY_MAJOR_VERSION >= 3
+error:
     PyMem_RawFree(argv_copy);
     if (argv_copy2) {
         for (i = 0; i < argc; i++)
@@ -567,20 +568,6 @@ struct _frozen _PyImport_FrozenModules[] = {
 };
 """
 
-# 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 = [
     '__main__', '_dummy_threading', 'Carbon', 'Carbon.Files',
@@ -682,14 +669,13 @@ class Freezer:
         if self.platform.startswith('win'):
             self.objectExtension = '.obj'
 
-        self.keepTemporaryFiles = True
+        self.keepTemporaryFiles = 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
 
         # Set this true to encode Python files in a Multifile as their
         # original source if possible, or false to encode them as
@@ -1392,39 +1378,64 @@ class Freezer:
             }
 
         if self.linkExtensionModules and self.extras:
+            # Should we link in extension modules?  If so, we write out a new
+            # built-in module table that directly hooks up with the init
+            # functions.  On Linux, we completely override Python's own
+            # built-in module table; on Windows, we can't do this, so we
+            # instead use PyImport_ExtendInittab to add to it.
+
             # 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)
+                if sys.platform != "win32" or fn:
+                    libName = module.split('.')[-1]
+                    initFunc = builtinInitFuncs.get(module, 'PyInit_' + libName)
+                    if initFunc:
+                        text += 'extern DL_IMPORT(PyObject) *%s(void);\n' % (initFunc)
             text += '\n'
-            text += 'struct _inittab _PyImport_Inittab[] = {\n'
+
+            if sys.platform == "win32":
+                text += 'static struct _inittab extensions[] = {\n'
+            else:
+                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)
+                if sys.platform != "win32" or fn:
+                    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)
+                if sys.platform != "win32" or fn:
+                    libName = module.split('.')[-1]
+                    initFunc = builtinInitFuncs.get(module, 'init' + libName)
+                    if initFunc:
+                        text += 'extern DL_IMPORT(void) %s(void);\n' % (initFunc)
             text += '\n'
-            text += 'struct _inittab _PyImport_Inittab[] = {\n'
+
+            if sys.platform == "win32":
+                text += 'static struct _inittab extensions[] = {\n'
+            else:
+                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)
+                if sys.platform != "win32" or fn:
+                    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'
 
+        elif sys.platform == "win32":
+            text += 'static struct _inittab extensions[] = {\n'
+            text += '  {0, 0},\n'
+            text += '};\n\n'
+
         text += initCode
 
         if filename is not None:
@@ -1471,7 +1482,6 @@ class Freezer:
                 'dllimport' : dllimport,
                 }
             if self.platform.startswith('win'):
-                initCode += self.frozenExtensions
                 target = basename + '.exe'
             else:
                 target = basename
@@ -1493,20 +1503,46 @@ class Freezer:
 
         self.writeCode(filename, initCode=initCode)
 
+        # Keep track of the files we should clean up after use.
+        cleanFiles = [filename, basename + self.objectExtension]
+
         extraLink = []
         if self.linkExtensionModules:
             for mod, fn in self.extras:
-                if fn:
+                if not fn:
+                    continue
+                if sys.platform == 'win32':
+                    # We can't link with a .pyd directly on Windows.  Check
+                    # if there is a corresponding .lib file in the Python libs
+                    # directory.
+                    libsdir = os.path.join(sys.exec_prefix, 'libs')
+                    libfile = os.path.join(libsdir, mod + '.lib')
+                    if os.path.isfile(libfile):
+                        extraLink.append(mod + '.lib')
+                        continue
+
+                    # No, so we have to generate a .lib file.  This is pretty
+                    # easy given that we know the only symbol we need is a
+                    # initmodule or PyInit_module function.
+                    modname = mod.split('.')[-1]
+                    libfile = modname + '.lib'
+                    if sys.version_info >= (3, 0):
+                        symbolName = 'PyInit_' + modname
+                    else:
+                        symbolName = 'init' + modname
+                    os.system('lib /nologo /def /export:%s /name:%s.pyd /out:%s' % (symbolName, modname, libfile))
+                    extraLink.append(libfile)
+                    cleanFiles += [libfile, modname + '.exp']
+                else:
                     extraLink.append(fn)
 
         try:
             compileFunc(filename, basename, extraLink=extraLink)
         finally:
             if not self.keepTemporaryFiles:
-                if os.path.exists(filename):
-                    os.unlink(filename)
-                if os.path.exists(basename + self.objectExtension):
-                    os.unlink(basename + self.objectExtension)
+                for file in cleanFiles:
+                    if os.path.exists(file):
+                        os.unlink(file)
 
         return target
 

+ 7 - 1
direct/src/showutil/pfreeze.py

@@ -50,6 +50,10 @@ Options:
      the Python interpreter.  Implicitly set if an executable is
      generated.
 
+  -k
+     Keeps temporary files generated by pfreeze.  Useful when debugging
+     FreezeTool itself.
+
 """
 
 import getopt
@@ -72,7 +76,7 @@ basename = None
 addStartupModules = False
 
 try:
-    opts, args = getopt.getopt(sys.argv[1:], 'o:i:x:p:P:slh')
+    opts, args = getopt.getopt(sys.argv[1:], 'o:i:x:p:P:slkh')
 except getopt.error as msg:
     usage(1, msg)
 
@@ -94,6 +98,8 @@ for opt, arg in opts:
         addStartupModules = True
     elif opt == '-l':
         freezer.linkExtensionModules = True
+    elif opt == '-k':
+        freezer.keepTemporaryFiles = True
     elif opt == '-h':
         usage(0)
     else: