ges, gst-python: Fix libpython dlopen on macOS

First encountered at https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1171#note_2290789

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6159>
This commit is contained in:
Nirbheek Chauhan 2024-02-21 11:26:30 +05:30 committed by GStreamer Marge Bot
parent 2cc9a181f9
commit 64fdded13a
5 changed files with 66 additions and 20 deletions

View file

@ -43,7 +43,34 @@
#ifdef HAS_PYTHON
#include <Python.h>
#include "ges-resources.h"
/*
* We need to call dlopen() directly on macOS to workaround a macOS runtime
* linker bug. When there are nested dlopen() calls and the second dlopen() is
* called from another library (such as gmodule), @loader_path is resolved as
* @executable_path and RPATHs are read from the executable (gst-plugin-scanner)
* instead of the library itself (libgstges.dylib). This doesn't happen if the
* second dlopen() call is directly in the source code of the library.
* Previously seen at:
* https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1171#note_2290789
*/
#ifdef G_OS_WIN32
#include <gmodule.h>
#define ges_module_open(fname) g_module_open(fname,0)
#define ges_module_error g_module_error
#define ges_module_symbol(module,name,symbol) g_module_symbol(module,name,symbol)
#else
#include <dlfcn.h>
#define ges_module_open(fname) dlopen(fname,RTLD_NOW | RTLD_GLOBAL)
#define ges_module_error dlerror
static inline gboolean
ges_module_symbol (gpointer handle, const char *name, gpointer * symbol)
{
*symbol = dlsym (handle, name);
return *symbol != NULL;
}
#endif
#endif /* HAS_PYTHON */
GST_DEBUG_CATEGORY_STATIC (ges_formatter_debug);
#undef GST_CAT_DEFAULT
@ -558,25 +585,22 @@ load_python_formatters (void)
G_RESOURCE_LOOKUP_FLAGS_NONE, &err);
PyObject *code = NULL, *res = NULL;
gboolean we_initialized = FALSE;
GModule *libpython;
gpointer has_python = NULL;
GST_LOG ("Checking to see if libpython is already loaded");
if (g_module_symbol (g_module_open (NULL, G_MODULE_BIND_LOCAL),
if (ges_module_symbol (ges_module_open (NULL),
"_Py_NoneStruct", &has_python) && has_python) {
GST_LOG ("libpython is already loaded");
} else {
GST_LOG ("loading libpython by name: %s", PY_LIB_FNAME);
libpython = g_module_open (PY_LIB_FNAME, 0);
if (!libpython) {
GST_ERROR ("Couldn't g_module_open libpython. Reason: %s",
g_module_error ());
if (!ges_module_open (PY_LIB_FNAME)) {
GST_ERROR ("Couldn't load libpython. Reason: %s", ges_module_error ());
return;
}
}
if (!Py_IsInitialized ()) {
GST_LOG ("python wasn't initialized");
GST_LOG ("python wasn't already initialized");
/* set the correct plugin for registering stuff */
Py_Initialize ();
we_initialized = TRUE;

View file

@ -114,6 +114,7 @@ gstvalidate_dep = dependency('gstreamer-validate-1.0', version : gst_req, requir
gio_dep = dependency('gio-2.0', version: glib_req)
gmodule_dep = dependency('gmodule-no-export-2.0')
libdl = cc.find_library('dl', required: false)
libxml_dep = dependency('libxml-2.0', required: get_option('xptv'))
cdata.set('DISABLE_XPTV', not libxml_dep.found())
@ -202,7 +203,7 @@ elif build_gir and python.found()
foreach loc: pylib_locs
foreach fname: pylib_fnames
if fsmod.exists(loc / fname)
libges_deps = libges_deps + [python_dep, gmodule_dep]
libges_deps = libges_deps + [python_dep, gmodule_dep, libdl]
has_python = true
cdata.set('HAS_PYTHON', true)
cdata.set_quoted('PY_LIB_FNAME', fname)

View file

@ -22,6 +22,7 @@ gst_dep = dependency('gstreamer-1.0', version : gst_req,
gstbase_dep = dependency('gstreamer-base-1.0', version : gst_req,
fallback : ['gstreamer', 'gst_base_dep'])
gmodule_dep = dependency('gmodule-no-export-2.0')
libdl = cc.find_library('dl', required: false)
pygobject_dep = dependency('pygobject-3.0', fallback: ['pygobject', 'pygobject_dep'], version : '>= 3.8')
pymod = import('python')

View file

@ -25,8 +25,33 @@
/* include this first, before NO_IMPORT_PYGOBJECT is defined */
#include <pygobject.h>
#include <gst/gst.h>
#include <gmodule.h>
#include <Python.h>
/*
* We need to call dlopen() directly on macOS to workaround a macOS runtime
* linker bug. When there are nested dlopen() calls and the second dlopen() is
* called from another library (such as gmodule), @loader_path is resolved as
* @executable_path and RPATHs are read from the executable (gst-plugin-scanner)
* instead of the library itself (libgstges.dylib). This doesn't happen if the
* second dlopen() call is directly in the source code of the library.
* Previously seen at:
* https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1171#note_2290789
*/
#ifdef G_OS_WIN32
#include <gmodule.h>
#define gstpy_module_open(fname) g_module_open(fname,0)
#define gstpy_module_error g_module_error
#define gstpy_module_symbol(module,name,symbol) g_module_symbol(module,name,symbol)
#else
#include <dlfcn.h>
#define gstpy_module_open(fname) dlopen(fname,RTLD_NOW | RTLD_GLOBAL)
#define gstpy_module_error dlerror
static inline gboolean
gstpy_module_symbol (gpointer handle, const char *name, gpointer * symbol)
{
*symbol = dlsym (handle, name);
return *symbol != NULL;
}
#endif
void *_PyGstElement_Type;
@ -214,7 +239,6 @@ plugin_init (GstPlugin * plugin)
PyGILState_STATE state = 0;
PyObject *gi, *require_version, *args, *gst, *dict, *pyplugin;
gboolean we_initialized = FALSE;
GModule *libpython;
gpointer has_python = NULL;
const gchar *override_path;
@ -229,23 +253,19 @@ plugin_init (GstPlugin * plugin)
GST_PLUGIN_DEPENDENCY_FLAG_NONE);
GST_LOG ("Checking to see if libpython is already loaded");
if (g_module_symbol (g_module_open (NULL, G_MODULE_BIND_LOCAL),
if (gstpy_module_symbol (gstpy_module_open (NULL),
"_Py_NoneStruct", &has_python) && has_python) {
GST_LOG ("libpython is already loaded");
} else {
const gchar *libpython_path =
PY_LIB_LOC "/libpython" PYTHON_VERSION PY_ABI_FLAGS "." PY_LIB_SUFFIX;
GST_LOG ("loading libpython from '%s'", libpython_path);
libpython = g_module_open (libpython_path, 0);
if (!libpython) {
g_critical ("Couldn't g_module_open libpython. Reason: %s",
g_module_error ());
GST_LOG ("loading libpython by name: %s", PY_LIB_FNAME);
if (!gstpy_module_open (PY_LIB_FNAME)) {
GST_ERROR ("Couldn't load libpython. Reason: %s", gstpy_module_error ());
return FALSE;
}
}
if (!Py_IsInitialized ()) {
GST_LOG ("python wasn't initialized");
GST_LOG ("python wasn't already initialized");
/* set the correct plugin for registering stuff */
Py_Initialize ();
we_initialized = TRUE;

View file

@ -1,7 +1,7 @@
gstpython = library('gstpython',
['gstpythonplugin.c'],
include_directories : [configinc],
dependencies : [gst_dep, pygobject_dep, gstbase_dep, python_dep, gmodule_dep] ,
dependencies : [gst_dep, pygobject_dep, gstbase_dep, python_dep, gmodule_dep, libdl],
install : true,
install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
)