Repository: pyinstaller/pyinstaller

Issue:
Freezing a PyGI app - Part 4
PyInstaller 3.3 runs successfully to completion for my simple PyGI app, but the .exe complaints of missing typelibs, when they are added it just crashes. Using an older version of PyInstaller works fine with the same app.  
  
My initial set up was Python 3.4, 64 bits on Windows 10, PyGI version 3.24.1 rev 1 from the all-in-one installer available at sourceforge, and PyInstaller 3.3.1 installed via pip.  
In the PyGI installer I only selected GTK+ 3.18.9 from the available libraries (besides the default GI and dependencies) and Glade from the aditional software options.  
  
In my specfile, I only added `css` and `glade` directories in the datas section.  
  
PyInstaller run successfully but when I tried to execute the .exe it wouldn't do anything.  
  
Googling I found this issue: #1966 , so I tried using pyinstaller with a even simpler app, in this case the Button example from the PyGobject Tutorial. The outcome was the same.  
Making a console installer and running it from powershell printed out the following traceback:  
```
Traceback (most recent call last):
  File "site-packages\gi\importer.py", line 138, in load_module
  File "site-packages\gi\module.py", line 275, in get_introspection_module
  File "site-packages\gi\module.py", line 122, in __init__
gi.RepositoryError: Typelib file for namespace 'GdkPixbuf', version '2.0' not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "gui.py", line 3, in <module>
  File "site-packages\gi\importer.py", line 140, in load_module
ImportError: Typelib file for namespace 'GdkPixbuf', version '2.0' not found
[5336] Failed to execute script gui
```
  
This was looking a lot like the original issue, so following the comments I manually added the GdkPixbuf typelib (`GdkPixbuf-2.0.typelib`) into the `gi_typelibs` directory of my installer.  
Now there was another traceback:  
```
Traceback (most recent call last):
  File "site-packages\gi\importer.py", line 138, in load_module
  File "site-packages\gi\module.py", line 275, in get_introspection_module
  File "site-packages\gi\module.py", line 122, in __init__
gi.RepositoryError: Typelib file for namespace 'GModule', version '2.0' not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "gui.py", line 3, in <module>
  File "site-packages\gi\importer.py", line 140, in load_module
ImportError: Typelib file for namespace 'GModule', version '2.0' not found
[10380] Failed to execute script gui
```  
  
So I manually copied the GModule typelib (`GModule-2.0.typelib`).  
  
After doing this the executable simply crashed with no traceback. And windows showed the typical "gui.exe has stopped working" message.  
  
Here I'd like to mention that per the patch developed in the mentioned issue, the `gi_typelibs` directory was created automatically in my installer's `dist/gui` directory. Also, the GdkPixbuff and GModule typelibs (that were not copied) were in my `/gnome/lib/girepository-1.0` directory among all the other typelibs that were indeed copied.  
  
I added some `print`s to the script before and after the call to the main class, like this.  
```
print("before main class call")
win = ButtonWindow()
print("after main class call")
```
The first print worked fine, but it crashed after that, just like before.  
  
Next thing, I tried using the latest PyInstaller version from github (v 3.4.dev0+ab8fd9753). The result was the same.  
  
After that I tried using an older version of PyGI (v 3.18.2 rev 12). The result was the same.    
  
I tried making the installer several times, using fresh installs of Python 3.4 and PyGI. Also added the hook to copy all typelib files. But it was all the same.  
  
Finally I tried using an older version of PyInstaller. I used version 3.2 (the same as in the mentioned issue) and I added the hook to copy all the typelib files from the PyGI package. This worked perfectly.  
  
I hope this is enough to reproduce the bug (if there is one and I'm not missing something) and help to solve it.  
If you need anything else, tell me and I'll do my best to help.  

[Output](https://gist.github.com/dalejfer/e61d80b05b0ae3163dac933c30041813#file-output-txt) when creating the installer.  
[Output](https://gist.github.com/dalejfer/e61d80b05b0ae3163dac933c30041813#file-pyi-archive_viewer-gui-exe-txt) of py-archive_viewer on `gui.exe`  
[Output](https://gist.github.com/dalejfer/e61d80b05b0ae3163dac933c30041813#file-pyi-archive_viwer-out00-pyz-pyz-txt) of py-archive_viewer on `out00-PYZ.pyz`  
For completion, [PyGobject Button tutorial script](https://gist.github.com/dalejfer/e61d80b05b0ae3163dac933c30041813#file-gui-py) and the simple [spec file](https://gist.github.com/dalejfer/e61d80b05b0ae3163dac933c30041813#file-gui-spec).


Discussion:
I've not used PyGI AIO recently as it had gotten out of date at some point, but I've had success using the latest versions of GTK et al that are installed via msys2 -- and with some work, pyinstaller works fine with msys2.

I've got scripts and stuff setup to do that at https://github.com/exaile/python-gtk3-gst-sdk , perhaps give those a shot and let me know?
I'll try to give it a go this weekend. I'll let you know.

Resolving patch:
diff --git a/PyInstaller/hooks/hook-gi.repository.GdkPixbuf.py b/PyInstaller/hooks/hook-gi.repository.GdkPixbuf.py
--- a/PyInstaller/hooks/hook-gi.repository.GdkPixbuf.py
+++ b/PyInstaller/hooks/hook-gi.repository.GdkPixbuf.py
@@ -20,6 +20,11 @@
 from PyInstaller.utils.hooks import (
     collect_glib_translations, get_gi_typelibs, get_gi_libdir, logger)
 
+loaders_path = os.path.join('gdk-pixbuf-2.0', '2.10.0', 'loaders')
+
+destpath = "lib/gdk-pixbuf-2.0/2.10.0/loaders"
+cachedest = "lib/gdk-pixbuf-2.0/2.10.0"
+
 # If the "gdk-pixbuf-query-loaders" command is not in the current ${PATH}, GDK
 # and thus GdkPixbuf is unavailable. Return with a non-fatal warning.
 gdk_pixbuf_query_loaders = None
@@ -50,22 +55,29 @@
     # To add support for a new platform, add a new "elif" branch below with the
     # proper is_<platform>() test and glob for finding loaders on that platform.
     if is_win:
-        pattern = os.path.join(
-            libdir, 'gdk-pixbuf-2.0', '2.10.0', 'loaders', '*.dll')
+        ext = "*.dll"
     elif is_darwin or is_linux:
-        pattern = os.path.join(
-            libdir, 'gdk-pixbuf-2.0', '2.10.0', 'loaders', '*.so')
+        ext = "*.so"
 
     # If loader detection is supported on this platform, bundle all detected
     # loaders and an updated loader cache.
-    if pattern:
+    if ext:
         loader_libs = []
 
         # Bundle all found loaders with this user application.
+        pattern = os.path.join(libdir, loaders_path, ext)
         for f in glob.glob(pattern):
-            binaries.append((f, 'lib/gdk-pixbuf/loaders'))
+            binaries.append((f, destpath))
             loader_libs.append(f)
 
+        # Sometimes the loaders are stored in a different directory from
+        # the library (msys2)
+        if not loader_libs:
+            pattern = os.path.join(libdir, '..', 'lib', loaders_path, ext)
+            for f in glob.glob(pattern):
+                binaries.append((f, destpath))
+                loader_libs.append(f)
+
         # Filename of the loader cache to be written below.
         cachefile = os.path.join(CONF['workpath'], 'loaders.cache')
 
@@ -104,7 +116,7 @@
                 if line.startswith('#'):
                     continue
                 if line.startswith(prefix):
-                    line = '"@executable_path/lib/gdk-pixbuf' + line[plen:]
+                    line = '"@executable_path/' + cachedest + line[plen:]
                 cd.append(line)
 
             # Rejoin these lines in a manner preserving this object's "unicode"
@@ -122,7 +134,7 @@
                 fp.write(subprocess.check_output(gdk_pixbuf_query_loaders))
 
         # Bundle this loader cache with this frozen application.
-        datas.append((cachefile, 'lib/gdk-pixbuf'))
+        datas.append((cachefile, cachedest))
     # Else, loader detection is unsupported on this platform.
     else:
         logger.warning('GdkPixbuf loader bundling unsupported on your platform.')
diff --git a/PyInstaller/hooks/hook-gi.repository.Gst.py b/PyInstaller/hooks/hook-gi.repository.Gst.py
--- a/PyInstaller/hooks/hook-gi.repository.Gst.py
+++ b/PyInstaller/hooks/hook-gi.repository.Gst.py
@@ -53,8 +53,6 @@
 
 # Use a pattern of libgst* since all GStreamer plugins that conform to GStreamer standards start with libgst
 # and we may have mixed plugin extensions, e.g., .so and .dylib.
-pattern = os.path.join(plugin_path, 'libgst*')
-
-binaries += [(f, os.path.join('gst_plugins')) for f in glob.glob(pattern)]
-
-
+for pattern in ['libgst*.dll', 'libgst*.dylib', 'libgst*.so']:
+    pattern = os.path.join(plugin_path, pattern)
+    binaries += [(f, os.path.join('gst_plugins')) for f in glob.glob(pattern)]
diff --git a/PyInstaller/loader/rthooks/pyi_rth_gdkpixbuf.py b/PyInstaller/loader/rthooks/pyi_rth_gdkpixbuf.py
--- a/PyInstaller/loader/rthooks/pyi_rth_gdkpixbuf.py
+++ b/PyInstaller/loader/rthooks/pyi_rth_gdkpixbuf.py
@@ -12,7 +12,8 @@
 import tempfile
 import sys
 
-pixbuf_file = os.path.join(sys._MEIPASS, 'lib', 'gdk-pixbuf', 'loaders.cache')
+pixbuf_file = os.path.join(sys._MEIPASS, 'lib', 'gdk-pixbuf-2.0', '2.10.0',
+                           'loaders.cache')
 
 # If we're not on Windows we need to rewrite the cache
 # -> we rewrite on OSX to support --onefile mode
@@ -25,8 +26,8 @@
     # we injected with the actual path
     fd, pixbuf_file = tempfile.mkstemp()
     with os.fdopen(fd, 'wb') as fp:
-        fp.write(contents.replace(b'@executable_path/lib',
-                                  os.path.join(sys._MEIPASS, 'lib').encode('utf-8')))
+        libpath = os.path.join(sys._MEIPASS, 'lib').encode('utf-8')
+        fp.write(contents.replace(b'@executable_path/lib', libpath))
 
     try:
         atexit.register(os.unlink, pixbuf_file)


Test patch: