Hello,

PyInstaller doesn't currently have analysis support to detect libraries 
used through ctypes.

The attached patch implements a minimal support for most common uses of 
ctypes, namely:

  * CDLL("library.so")
  * ctypes.DLL("library.so")
  * cdll.library
  * cdll.LoadLibrary("library.so")

(and their windows counterparts of course).

The patch adds some bytecode magic in scan_code to detect the above uses 
of ctypes, and gives PyModule instances the ability to store binary file 
names for the referenced libraries (see mf.py).  Analysis class (see 
Build.py) is finally given awareness of this new ability for PyModule 
instances, so that it can update the binaries' list accordingly.

-- 
Lorenzo Mancini


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"PyInstaller" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/PyInstaller?hl=en
-~----------~----~----~----~------~----~------~--~---

Index: ../pyinstaller/Build.py
===================================================================
--- ../pyinstaller/Build.py	(revision 598)
+++ ../pyinstaller/Build.py	(working copy)
@@ -317,8 +317,11 @@
                         zipfiles.append((os.path.basename(str(mod.owner)),
                                          str(mod.owner), 'ZIPFILE'))
                     elif modnm == '__main__':
-                        pass
+                        # This and the next case are of type mf.PyModule.
+                        # Check if they require shared libraries via ctypes.
+                        binaries.extend([(bin, bin, "BINARY") for bin in mod.binaries])
                     else:
+                        binaries.extend([(bin, bin, "BINARY") for bin in mod.binaries])
                         pure.append((modnm, fnm, 'PYMODULE'))
         binaries.extend(bindepend.Dependencies(binaries,
                                                platform=target_platform))
Index: ../pyinstaller/mf.py
===================================================================
--- ../pyinstaller/mf.py	(revision 598)
+++ ../pyinstaller/mf.py	(working copy)
@@ -78,7 +78,7 @@
 
     def _getsuffixes(self):
         return suffixes.get_suffixes(self.target_platform)
-        
+
     def getmod(self, nm, getsuffixes=None, loadco=marshal.loads):
         if getsuffixes is None:
             getsuffixes = self._getsuffixes
@@ -602,6 +602,7 @@
         self._all = []
         self.imports = []
         self.warnings = []
+        self.binaries = []
         self._xref = {}
 
     def ispackage(self):
@@ -614,7 +615,7 @@
         self._xref[nm] = 1
 
     def __str__(self):
-        return "<Module %s %s %s>" % (self.__name__, self.__file__, self.imports)
+        return "<Module %s %s %s %s>" % (self.__name__, self.__file__, self.imports, self.binaries)
 
 class BuiltinModule(Module):
     typ = 'BUILTIN'
@@ -641,7 +642,7 @@
         self.scancode()
 
     def scancode(self):
-        self.imports, self.warnings, allnms = scan_code(self.co)
+        self.imports, self.warnings, self.binaries, allnms = scan_code(self.co)
         if allnms:
             self._all = allnms
 
@@ -767,12 +768,14 @@
             instrs.append((op, oparg, incondition, curline))
     return instrs
 
-def scan_code(co, m=None, w=None, nested=0):
+def scan_code(co, m=None, w=None, b=None, nested=0):
     instrs = pass1(co.co_code)
     if m is None:
         m = []
     if w is None:
         w = []
+    if b is None:
+        b = []
     all = None
     lastname = None
     level = -1 # import-level, same behaviour as up to Python 2.4
@@ -833,7 +836,85 @@
             w.append("W: %s %s exec statement detected at line %s"  % (lvl, cndtl, curline))
         else:
             lastname = None
+
+        # ctypes scanning requires a scope wider than one bytecode instruction,
+        # so the code resides in a separate function for clarity.
+        ctypesb = scan_code_for_ctypes(co, instrs, i)
+        b.extend(ctypesb)
+
     for c in co.co_consts:
         if isinstance(c, type(co)):
-            scan_code(c, m, w, 1)
-    return m, w, all
+            # FIXME: "all" was not updated here nor returned. Was it the desired
+            # behaviour?
+            _, _, _, all_nested = scan_code(c, m, w, b, 1)
+            if all_nested:
+                all.extend(all_nested)
+    return m, w, b, all
+
+def scan_code_for_ctypes(co, instrs, i):
+    """Detects ctypes dependencies, using reasonable heuristics that should
+    cover most common ctypes usages; returns a list of binaries detected as
+    dependencies.
+    """
+
+    def _libFromConst(i):
+        """Extracts library name from an expected LOAD_CONST instruction and
+        appends it to local binaries list.
+        """
+        op, oparg, conditional, curline = instrs[i]
+        if op == LOAD_CONST:
+            soname = co.co_consts[oparg]
+            b.append(soname)
+
+    b = []
+
+    op, oparg, conditional, curline = instrs[i]
+
+    if op == LOAD_GLOBAL:
+        name = co.co_names[oparg]
+
+        if name in ("CDLL", "WinDLL"):
+            # Guesses ctypes imports of this type: CDLL("library.so")
+
+            # LOAD_GLOBAL 0 (CDLL) <--- we "are" here right now
+            # LOAD_CONST 1 ('library.so')
+
+            _libFromConst(i+1)
+
+        elif name == "ctypes":
+            # Guesses ctypes imports of this type: ctypes.DLL("library.so")
+
+            # LOAD_GLOBAL 0 (ctypes) <--- we "are" here right now
+            # LOAD_ATTR 1 (CDLL)
+            # LOAD_CONST 1 ('library.so')
+
+            op2, oparg2, conditional2, curline2 = instrs[i+1]
+            if op2 == LOAD_ATTR:
+                if co.co_names[oparg2] in ("CDLL", "WinDLL"):
+                    # Fetch next, and finally get the library name
+                    _libFromConst(i+2)
+
+        elif name == ("cdll", "windll"):
+            # Guesses ctypes imports of these types:
+
+            #  * cdll.library (only valid on Windows)
+
+            #     LOAD_GLOBAL 0 (cdll) <--- we "are" here right now
+            #     LOAD_ATTR 1 (library)
+
+            #  * cdll.LoadLibrary("library.so")
+
+            #     LOAD_GLOBAL              0 (cdll) <--- we "are" here right now
+            #     LOAD_ATTR                1 (LoadLibrary)
+            #     LOAD_CONST               1 ('library.so')
+
+            op2, oparg2, conditional2, curline2 = instrs[i+1]
+            if op2 == LOAD_ATTR:
+                if co.co_names[oparg2] != "LoadLibrary":
+                    # First type
+                    soname = co.co_names[oparg2] + ".dll"
+                    b.append(soname)
+                else:
+                    # Second type, needs to fetch one more instruction
+                    _libFromConst(i+2)
+    return b

Reply via email to