Repository: pyinstaller/pyinstaller

Issue:
PyGi gives me a gi.repository error
<!--
Welcome to the PyInstaller issue tracker! Before creating an issue, please heed the following:

1. This tracker should only be used to report bugs and request features / enhancements to PyInstaller
    - For questions and general support, use the mailing list, see
	  <http://www.pyinstaller.org/support.html#mailing-list>
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
   the original discussion.
3. When making a bug report, make sure you provide all required information. The easier it is for
   maintainers to reproduce, the faster it'll be fixed.
-->

<!-- +++ ONLY TEXT +++ DO NOT POST IMAGES +++ -->

## Description of the issue

The freeze works perfectly fine but then when I go to launch it I get this error

`Traceback (most recent call last):
  File "hello-world-gtk", line 4, in <module>
  File "PyInstaller/loader/pyimod03_importers.py", line 495, in exec_module
  File "lib/application.py", line 5, in <module>
ModuleNotFoundError: No module named 'gi.repository'
[35729] Failed to execute script 'hello-world-gtk' due to unhandled exception!
`

### Context information (for bug reports)

* Output of `pyinstaller --version`: ```(5.0.1)```
* Version of Python:  3.10
* Platform: Fedora 36
* How you installed Python: dnf
* Did you also try this on another platform? Does it work there?
I have not tried another platform

* try the latest development version, using the following command:

```shell
pip install https://github.com/pyinstaller/pyinstaller/archive/develop.zip
```

* follow *all* the instructions in our "If Things Go Wrong" Guide
  (https://github.com/pyinstaller/pyinstaller/wiki/If-Things-Go-Wrong) and

### 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
  * [ ] Package your program in **--onedir mode**
  * [ ] Package **without UPX**, say: use the option `--noupx` or set `upx=False` in your .spec-file
  * [ ] 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

```
#!/usr/bin/env python3

from sys import argv
import lib.application as application


if __name__ == "__main__":
    app = application.Application()
    app.run(argv)

```

### Stacktrace / full error message



```
(Traceback (most recent call last):
  File "hello-world-gtk", line 4, in <module>
  File "PyInstaller/loader/pyimod03_importers.py", line 495, in exec_module
  File "lib/application.py", line 5, in <module>
    from gi.repository import Gtk
ModuleNotFoundError: No module named 'gi.repository'
[36970] Failed to execute script 'hello-world-gtk' due to unhandled exception!
)
```

Please also see <https://github.com/pyinstaller/pyinstaller/wiki/How-to-Report-Bugs>
for more about what would use to solve the issue.



Discussion:
That's because Fedora's `python3-gobject-base` RPM places some files in `/usr/lib64/python3.10/site-packages/gi`, and some (e.g., the `repository` sub-package) in `/usr/lib/python3.10/site-packages/gi`. 

Modulegraph is scanning the "correct" package's location in `lib64`, and ends up missing the repository sub-package in `lib`. 

While this splitting seems to work in python, I'm not sure it's entirely legal, because `gi` is not a namespace package; while `gi.__path__` after loading does contain both locations, there's no indication of the second path in neither package's `SourceFileLoader` nor in the spec returned by `importlib.util.find_spec`...

I suppose a work-around is to symlink the `/usr/lib/python3.10/site-packages/gi/repository` into `/usr/lib64/python3.10/site-packages/gi` (not sure if the split `overrides` need similar handling, too). Or, create a `venv` and `pip install PyGobject` into it; this will build PyGobject from source, but it will be linked against the system-installed libraries (and if you need any other system-installed python packages, you can try creating venv with `--system-site-packages` option).

Ah, it works in python because `gi/__init__.py` has this at the top:

```python
# support overrides in different directories than our gi module
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
```

I don't think our static analysis in modulegraph has any hope of supporting that, so installing `PyGobject` in a venv is probably the best option.


Ok. Thank you with the answers I will try your suggestions!

symlink did not work so i ended up having to create a diffrent venv. Thank you for your help!

Resolving patch:
diff --git a/PyInstaller/hooks/pre_safe_import_module/hook-gi.py b/PyInstaller/hooks/pre_safe_import_module/hook-gi.py
new file mode 100644
--- /dev/null
+++ b/PyInstaller/hooks/pre_safe_import_module/hook-gi.py
@@ -0,0 +1,32 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2022, 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)
+#-----------------------------------------------------------------------------
+
+from PyInstaller import compat
+from PyInstaller.utils import hooks as hookutils
+
+
+def pre_safe_import_module(api):
+    if compat.is_linux:
+        # RHEL/Fedora RPM package for GObject introspection is known to split the `gi` package into two locations:
+        #  - /usr/lib64/python3.x/site-packages/gi
+        #  - /usr/lib/python3.x/site-packages/gi
+        # The `__init__.py` is located in the first directory, while `repository` and `overrides` are located in
+        # the second, and `__init__.py` dynamically extends the `__path__` during package import, using
+        #  `__path__ = pkgutil.extend_path(__path__, __name__)`.
+        # The modulegraph has no way of knowing this, so we need extend the package path in this hook. Otherwise,
+        # only the first location is scanned, and the `gi.repository` ends up missing.
+        #
+        # NOTE: the `get_package_paths`/`get_package_all_paths` helpers read the paths from package's spec without
+        # importing the (top-level) package, so they do not catch run-time path modifications. Instead, we use
+        # `get_module_attribute` to import the package in isolated process and query its `__path__` attribute.
+        paths = hookutils.get_module_attribute(api.module_name, "__path__")
+        for path in paths:
+            api.append_package_path(path)


Test patch: