Repository: pyinstaller/pyinstaller

Issue:
GTK in Windows: Typelib for HarfBuzz version '0.0' is not found
## Description of the issue

Packaging Gtk apps in Windows with pyinstaller now gives an error that the Typelib for HarfBuzz version '0.0' is not found. Recent changes to Gtk/Pango are now including the typelib for HarfBuzz. I think we need to add a hook to include this typelib.

### Steps to Reproduce the Problem

1. Update msys2 and install pygobject:
```
$ pacman -Suy
$ pacman -S git mingw-w64-x86_64-gcc mingw-w64-x86_64-gtk3 \
mingw-w64-x86_64-pkg-config mingw-w64-x86_64-cairo \
mingw-w64-x86_64-gobject-introspection mingw-w64-x86_64-python \
mingw-w64-x86_64-python-gobject mingw-w64-x86_64-python-cairo \
mingw-w64-x86_64-python-pip
```
2. `python -m venv .venv`
3. `source .venv/bin/activate`
4. `pip install pygobject pyinstaller==3.6`
5. `pyinstaller --exclude-module tk --exclude-module tcl --exclude-module _tkinter helloworld.py`
3. `./dist/helloworld/helloworld.exe`
```
$ ./dist/helloworld/helloworld.exe
gi.RepositoryError: Typelib file for namespace 'HarfBuzz', version '0.0' not found
```
(see full stacktrace below)

Expected behavior:
Hello world PyGObject example runs fine when packaged with pyinstaller:
```
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

window = Gtk.Window(title="Hello World")
window.show()
window.connect("destroy", Gtk.main_quit)
Gtk.main()
```

## Context information (for bug reports)

* Output of `pyinstaller --version`: 3.6 (I can't use latest because of https://github.com/pyinstaller/pyinstaller/issues/5058)
* Version of Python: 3.8.5
* Platform: Windows MSYS2 x86_64
* Did you also try this on another platform? Does it work there? N/A

### Make sure [everything is packaged correctly](https://github.com/pyinstaller/pyinstaller/wiki/How-to-Report-Bugs#make-sure-everything-is-packaged-correctly)

  * [X] start with clean installation
  * [ ] use the latest development version
  * [X] Run your frozen program **from a command window (shell)** — instead of double-clicking on it
  * [X] Package your program in **--onedir mode**
  * [X] Package **without UPX**, say: use the option `--noupx` or set `upx=False` in your .spec-file
  * [X] Repackage you application in **verbose/debug mode**. For this, pass the option `--debug` to `pyi-makespec` or `pyinstaller` or use `EXE(..., debug=1, ...)` in your .spec file.


### A minimal example program which shows the error

```
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

window = Gtk.Window(title="Hello World")
window.show()
window.connect("destroy", Gtk.main_quit)
Gtk.main()
```

### Stacktrace / full error message

```
$ ./dist/helloworld/helloworld.exe
Traceback (most recent call last):
  File "gi/importer.py", line 138, in load_module
  File "gi/module.py", line 265, in get_introspection_module
  File "gi/module.py", line 117, in __init__
gi.RepositoryError: Typelib file for namespace 'HarfBuzz', version '0.0' not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "helloworld.py", line 3, in <module>
    from gi.repository import Gtk
  File "gi/importer.py", line 140, in load_module
ImportError: Typelib file for namespace 'HarfBuzz', version '0.0' not found
[10892] Failed to execute script helloworld
```

Gtk now includes the typelib for HarfBuzz:
```
$ g-ir-inspect.exe Gtk --print-shlibs --print-typelibs
shlib: libgtk-3-0.dll
typelib: GLib-2.0
typelib: Gdk-3.0
typelib: HarfBuzz-0.0
typelib: GdkPixbuf-2.0
typelib: cairo-1.0
typelib: GObject-2.0
typelib: Pango-1.0
typelib: Gio-2.0
typelib: GModule-2.0
typelib: Atk-1.0
```
The shared library is libharfbuzz-gobject-0.dll:
```
$ g-ir-inspect.exe HarfBuzz --print-shlibs --print-typelibs
shlib: libharfbuzz-gobject-0.dll
typelib: GObject-2.0
typelib: GLib-2.0
```

## Things I Have Tried So Far
I created a new hook called hook-gi.repository.HarfBuzz.py with:
```
from PyInstaller.utils.hooks import get_gi_typelibs

binaries, datas, hiddenimports = get_gi_typelibs('HarfBuzz', '0.0')
```
`18021 WARNING: Hidden import "gi.repository.HarfBuzz" not found!`


Discussion:
I was able to resolve this by creating a pre_safe and a normal hook. I will submit a PR to close this.

Resolving patch:
diff --git a/PyInstaller/hooks/hook-gi.repository.HarfBuzz.py b/PyInstaller/hooks/hook-gi.repository.HarfBuzz.py
new file mode 100644
--- /dev/null
+++ b/PyInstaller/hooks/hook-gi.repository.HarfBuzz.py
@@ -0,0 +1,18 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2005-2020, PyInstaller Development Team.
+#
+# Distributed under the terms of the GNU General Public License (version 2
+# or later) with exception for distributing the bootloader.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#
+# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
+#-----------------------------------------------------------------------------
+"""
+Import hook for PyGObject https://wiki.gnome.org/PyGObject
+"""
+
+from PyInstaller.utils.hooks import get_gi_typelibs
+
+
+binaries, datas, hiddenimports = get_gi_typelibs('HarfBuzz', '0.0')
diff --git a/PyInstaller/hooks/pre_safe_import_module/hook-gi.repository.HarfBuzz.py b/PyInstaller/hooks/pre_safe_import_module/hook-gi.repository.HarfBuzz.py
new file mode 100644
--- /dev/null
+++ b/PyInstaller/hooks/pre_safe_import_module/hook-gi.repository.HarfBuzz.py
@@ -0,0 +1,16 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2005-2020, PyInstaller Development Team.
+#
+# Distributed under the terms of the GNU General Public License (version 2
+# or later) with exception for distributing the bootloader.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#
+# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
+#-----------------------------------------------------------------------------
+
+def pre_safe_import_module(api):
+    # PyGObject modules loaded through the gi repository are marked as
+    # MissingModules by modulegraph so we convert them to
+    # RuntimeModules so their hooks are loaded and run.
+    api.add_runtime_module(api.module_name)


Test patch: