From 1babccfe503de346dfd9722348e2acead742b70a Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Thu, 14 Oct 2021 17:01:01 -0300 Subject: [PATCH] python: Fix using overrides when not building PyGObject Since 547570cd790f2b2e390edc1dfb5df4c7a33de45c we do not always build PyGObject and our development environment is broken when trying to use GStreamer python when built against system PyGObject with the following error importing Gst in there: ``` 12345678** (gst-plugin-scanner:710617): CRITICAL **: 11:45:02.343: can't find gi.repository.Gst Traceback (most recent call last): File "/usr/lib/python3.9/site-packages/gi/repository/__init__.py", line 23, in from ..importer import DynamicImporter File "/usr/lib64/python3.9/site-packages/gi/importer.py", line 33, in from .overrides import load_overrides ImportError: cannot import name 'load_overrides' from 'gi.overrides' (/var/home/thiblahute/devel/gstreamer/gstreamer/subprojects/gst-editing-services/bindings/python/gi/overrides/__init__.py) Factory Details: ``` The approach to fixing it is to implement override `gi` in `gst-python/gi/` which we add to `PYTHONPATH`) and in there reset the `gi` module to the right place and we get overrides from paths from `_GI_OVERRIDES_PATH` we set in `gst-env.py` which points to all the overrides that will be installed. Part-of: --- gst-env.py | 13 ++++++- meson.build | 4 +-- subprojects/gst-python/gi/__init__.py | 45 +++++++++++++++++++++++++ tests/meson.build | 1 + tests/python/meson.build | 19 +++++++++++ tests/python/python-devenv-overrides.py | 20 +++++++++++ 6 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 subprojects/gst-python/gi/__init__.py create mode 100644 tests/python/meson.build create mode 100755 tests/python/python-devenv-overrides.py diff --git a/gst-env.py b/gst-env.py index 7b7d3ae7f7..c64042d92b 100755 --- a/gst-env.py +++ b/gst-env.py @@ -386,6 +386,7 @@ def get_subprocess_env(options, gst_version): presets = set() encoding_targets = set() python_dirs = setup_gdb(options) + overrides_dirs = set() if '--installed' in subprocess.check_output(meson + ['introspect', '-h']).decode(): installed_s = subprocess.check_output(meson + ['introspect', options.builddir, '--installed']) for path, installpath in json.loads(installed_s.decode()).items(): @@ -406,7 +407,10 @@ def get_subprocess_env(options, gst_version): if 'site-packages' in installpath_parts: install_subpath = os.path.join(*installpath_parts[installpath_parts.index('site-packages') + 1:]) if path.endswith(install_subpath): - python_dirs.add(path[:len (install_subpath) * -1]) + if os.path.commonprefix(["gi/overrides", install_subpath]): + overrides_dirs.add(os.path.dirname(path)) + else: + python_dirs.add(path[:len (install_subpath) * -1]) if path.endswith('.prs'): presets.add(os.path.dirname(path)) @@ -432,11 +436,18 @@ def get_subprocess_env(options, gst_version): for python_dir in sorted(python_dirs): prepend_env_var(env, 'PYTHONPATH', python_dir, options.sysroot) + for python_dir in sorted(overrides_dirs): + prepend_env_var(env, '_GI_OVERRIDES_PATH', python_dir, options.sysroot) + mesonpath = os.path.join(SCRIPTDIR, "meson") if os.path.join(mesonpath): # Add meson/ into PYTHONPATH if we are using a local meson prepend_env_var(env, 'PYTHONPATH', mesonpath, options.sysroot) + # Ensure that gst-python/gi is used first + prepend_env_var(env, "PYTHONPATH", os.path.join(SCRIPTDIR, 'subprojects', 'gst-python'), + options.sysroot) + # For devhelp books if 'XDG_DATA_DIRS' not in env or not env['XDG_DATA_DIRS']: # Preserve default paths when empty diff --git a/meson.build b/meson.build index 1458acc2fb..c23724c5e5 100644 --- a/meson.build +++ b/meson.build @@ -368,14 +368,12 @@ endforeach message('Building subprojects: ' + ', '.join(subprojects_names)) -subdir('tests') - setenv = find_program('gst-env.py') - devenv_cmd = [setenv, '--builddir=@0@'.format(meson.build_root()), '--gstbuilddir=@0@'.format(meson.current_build_dir()), '--srcdir=@0@'.format(meson.source_root())] +subdir('tests') if meson.has_exe_wrapper() and build_machine.system() == 'linux' and host_machine.system() == 'windows' # FIXME: Ideally we could get the wrapper directly from meson devenv_cmd += ['--wine', host_machine.cpu_family() == 'x86_64' ? 'wine64' : 'wine32'] diff --git a/subprojects/gst-python/gi/__init__.py b/subprojects/gst-python/gi/__init__.py new file mode 100644 index 0000000000..4f356a6ede --- /dev/null +++ b/subprojects/gst-python/gi/__init__.py @@ -0,0 +1,45 @@ +import gi +import os +import sys +import imp +from pathlib import Path + +# Remove this dummy module the python path and +# try to import the actual gi module +sys.path.remove(str(Path(__file__).parents[1])) +del sys.modules["gi"] +import gi + + +class GstOverrideImport: + def find_module(self, fullname, path=None, target=None): + if fullname.startswith('gi.overrides'): + fp = None + try: + fp, _, _ = imp.find_module(fullname.split( + '.')[-1], os.environ.get('_GI_OVERRIDES_PATH', '').split(os.pathsep),) + except ImportError: + return None + finally: + if fp: + fp.close() + return self + return None + + def load_module(self, name): + if name in sys.modules: + return sys.modules[name] + + fp, pathname, description = imp.find_module(name.split( + '.')[-1], os.environ.get('_GI_OVERRIDES_PATH', '').split(os.pathsep),) + + try: + module = imp.load_module(name, fp, pathname, description) + finally: + if fp: + fp.close() + sys.modules[name] = module + return module + + +sys.meta_path.insert(0, GstOverrideImport()) diff --git a/tests/meson.build b/tests/meson.build index 6884250f76..3159b9cdb1 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -2,3 +2,4 @@ if get_option('tests').disabled() subdir_done() endif subdir('static-plugins') +subdir('python') \ No newline at end of file diff --git a/tests/python/meson.build b/tests/python/meson.build new file mode 100644 index 0000000000..b391044b7e --- /dev/null +++ b/tests/python/meson.build @@ -0,0 +1,19 @@ +gst_python = subproject('gst-python', required: false) +gir = find_program('g-ir-scanner', required : get_option('introspection')) +if not gst_python.found() or not gir.found() + message('Not running python devenv tests: gst_python: @0@ gir: @1@'.format(gst_python.found(), gir.found())) + subdir_done() +endif + +root_rel = '../..' +python = import('python').find_installation() + +if run_command(python, '-c', 'import gi').returncode() != 0 + message('PyGObject not found, not running PyGObject tests') + subdir_done() +endif + +test('python-overrides-devenv', setenv, args: ['--builddir=@0@'.format(meson.build_root()), + '--gstbuilddir=@0@'.format(meson.current_build_dir() / '..' / '..'), + '--srcdir=@0@'.format(meson.source_root()), + meson.current_source_dir() / 'python-devenv-overrides.py']) \ No newline at end of file diff --git a/tests/python/python-devenv-overrides.py b/tests/python/python-devenv-overrides.py new file mode 100755 index 0000000000..0c099118ea --- /dev/null +++ b/tests/python/python-devenv-overrides.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 +import unittest +from pathlib import Path +from unittest import TestCase +from gi.repository import Gst + + +class TestBin(TestCase): + def test_overrides(self): + from gi.overrides import Gst + self.assertEqual(Path(Gst.__file__), Path(__file__).parents[2] / "subprojects/gst-python/gi/overrides/Gst.py") + + def simple_functional_test(self): + Gst.init(None) + self.assertEqual(Gst.ElementFactory.make("bin", None).sinkpads, []) + self.assertEqual(float(Gst.Fraction(1, 2)), 0.5) + + +if __name__ == "__main__": + unittest.main()