From: Philippe Proulx Date: Wed, 8 Mar 2017 19:00:07 +0000 (-0500) Subject: Put Python plugin support in a separate shared object X-Git-Tag: v2.0.0-pre1~440 X-Git-Url: http://git.efficios.com/?p=babeltrace.git;a=commitdiff_plain;h=6fbd4105b92d0da8b3c5818a5b7c5b07850f4a01 Put Python plugin support in a separate shared object This is to make the work of packagers easier for libbabeltrace not to depend on libpython, even if you need Python plugin support. With this patch, python.c's constructor tries to open the new Python plugin provider shared object. If it fails, Python plugin support is disabled, but shared object plugins can still be loaded. The new BUILT_IN_PYTHON_PLUGIN_SUPPORT configure variable, if set to 1, makes the build embed the Python plugin provider into libbabeltrace (like before this patch). Signed-off-by: Philippe Proulx Signed-off-by: Jérémie Galarneau --- diff --git a/Makefile.am b/Makefile.am index 4b517db7..1d66b1d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,25 @@ AM_CFLAGS = $(PACKAGE_CFLAGS) -I$(top_srcdir)/include ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = include common types compat lib formats plugins converter bindings tests doc extras +SUBDIRS = \ + include \ + common \ + types \ + compat + +if WITH_PYTHON_PLUGINS +SUBDIRS += python-plugin-provider +endif + +SUBDIRS += \ + lib \ + formats \ + plugins \ + converter \ + bindings \ + tests \ + doc \ + extras dist_doc_DATA = ChangeLog LICENSE mit-license.txt gpl-2.0.txt \ std-ext-lib.txt README diff --git a/configure.ac b/configure.ac index d48be53b..0f318214 100644 --- a/configure.ac +++ b/configure.ac @@ -376,6 +376,16 @@ AS_IF([test "x$BUILT_IN_PLUGINS" != x], [ ]) AM_CONDITIONAL([BUILT_IN_PLUGINS], [test "x$built_in_plugins" = "xyes"]) +AC_ARG_VAR([BUILT_IN_PYTHON_PLUGIN_SUPPORT], [Statically-link Python plugin support into the babeltrace binary]) +AS_IF([test "x$BUILT_IN_PYTHON_PLUGIN_SUPPORT" != x], [ +# Built-in plug-ins are only available when the --disable-shared --enable-static options are used. + AS_IF([test "x$enable_static" != "xyes"], [AC_MSG_ERROR(--enable-static must be used to bundle Python plugin support in the babeltrace executable)]) + AS_IF([test "x$enable_shared" = "xyes"], [AC_MSG_ERROR(--disable-shared must be used to bundle Python plugin support in the babeltrace executable)]) + built_in_python_plugin_support=yes + AC_DEFINE([BT_BUILT_IN_PYTHON_PLUGIN_SUPPORT], [1], [Define to 1 to register plug-in attributes in static executable sections]) +]) +AM_CONDITIONAL([BUILT_IN_PYTHON_PLUGIN_SUPPORT], [test "x$built_in_python_plugin_support" = "xyes"]) + PKG_CHECK_MODULES(GMODULE, [gmodule-2.0 >= 2.0.0]) LIBS="$LIBS $GMODULE_LIBS" @@ -496,6 +506,7 @@ AC_CONFIG_FILES([ plugins/utils/Makefile plugins/utils/dummy/Makefile plugins/utils/trimmer/Makefile + python-plugin-provider/Makefile babeltrace.pc babeltrace-ctf.pc ]) @@ -571,6 +582,10 @@ PPRINT_PROP_BOOL([Python bindings tests], $value) test "x$enable_python_plugins" = "xyes" && value=1 || value=0 PPRINT_PROP_BOOL([Python plugin support], $value) +# built-in Python plugin support enabled/disabled +test "x$built_in_python_plugin_support" = "xyes" && value=1 || value=0 +PPRINT_PROP_BOOL([Built-in Python plugin support], $value) + # debug info enabled/disabled test "x$_enable_debug_info" = "xyes" && value=1 || value=0 PPRINT_PROP_BOOL([Debug information output], $value) diff --git a/include/Makefile.am b/include/Makefile.am index ba2ad2bf..b4d86ecc 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -126,8 +126,6 @@ noinst_HEADERS = \ babeltrace/mmap-align.h \ babeltrace/plugin/plugin-internal.h \ babeltrace/plugin/plugin-so-internal.h \ - babeltrace/plugin/plugin-python-enabled-internal.h \ - babeltrace/plugin/plugin-python-disabled-internal.h \ babeltrace/component/component-class-internal.h \ babeltrace/component/connection-internal.h \ babeltrace/component/port-internal.h \ diff --git a/include/babeltrace/babeltrace-internal.h b/include/babeltrace/babeltrace-internal.h index c3ed6f97..535f9909 100644 --- a/include/babeltrace/babeltrace-internal.h +++ b/include/babeltrace/babeltrace-internal.h @@ -180,6 +180,9 @@ extern bool babeltrace_verbose, babeltrace_debug; #define BT_CTF_MAJOR 1 #define BT_CTF_MINOR 8 +#define __STRINGIFY(x) #x +#define TOSTRING(x) __STRINGIFY(x) + struct bt_trace_descriptor; struct trace_collection { GPtrArray *array; /* struct bt_trace_descriptor */ diff --git a/include/babeltrace/plugin/plugin-internal.h b/include/babeltrace/plugin/plugin-internal.h index eb72e944..9b66836f 100644 --- a/include/babeltrace/plugin/plugin-internal.h +++ b/include/babeltrace/plugin/plugin-internal.h @@ -69,10 +69,111 @@ struct bt_plugin { /* Value depends on the specific plugin type */ void *spec_data; + void (*destroy_spec_data)(struct bt_plugin *); }; -BT_HIDDEN -struct bt_plugin *bt_plugin_create_empty(enum bt_plugin_type type); +static inline +void bt_plugin_destroy(struct bt_object *obj) +{ + struct bt_plugin *plugin; + + assert(obj); + plugin = container_of(obj, struct bt_plugin, base); + + if (plugin->destroy_spec_data) { + plugin->destroy_spec_data(plugin); + } + + if (plugin->comp_classes) { + g_ptr_array_free(plugin->comp_classes, TRUE); + } + + if (plugin->info.name) { + g_string_free(plugin->info.name, TRUE); + } + + if (plugin->info.path) { + g_string_free(plugin->info.path, TRUE); + } + + if (plugin->info.description) { + g_string_free(plugin->info.description, TRUE); + } + + if (plugin->info.author) { + g_string_free(plugin->info.author, TRUE); + } + + if (plugin->info.license) { + g_string_free(plugin->info.license, TRUE); + } + + if (plugin->info.version.extra) { + g_string_free(plugin->info.version.extra, TRUE); + } + + g_free(plugin); +} + +static inline +struct bt_plugin *bt_plugin_create_empty(enum bt_plugin_type type) +{ + struct bt_plugin *plugin = NULL; + + plugin = g_new0(struct bt_plugin, 1); + if (!plugin) { + goto error; + } + + bt_object_init(plugin, bt_plugin_destroy); + plugin->type = type; + + /* Create empty array of component classes */ + plugin->comp_classes = + g_ptr_array_new_with_free_func((GDestroyNotify) bt_put); + if (!plugin->comp_classes) { + goto error; + } + + /* Create empty info */ + plugin->info.name = g_string_new(NULL); + if (!plugin->info.name) { + goto error; + } + + plugin->info.path = g_string_new(NULL); + if (!plugin->info.path) { + goto error; + } + + plugin->info.description = g_string_new(NULL); + if (!plugin->info.description) { + goto error; + } + + plugin->info.author = g_string_new(NULL); + if (!plugin->info.author) { + goto error; + } + + plugin->info.license = g_string_new(NULL); + if (!plugin->info.license) { + goto error; + } + + plugin->info.version.extra = g_string_new(NULL); + if (!plugin->info.version.extra) { + goto error; + } + + goto end; + +error: + BT_PUT(plugin); + +end: + return plugin; +} static inline void bt_plugin_set_path(struct bt_plugin *plugin, const char *path) diff --git a/include/babeltrace/plugin/plugin-python-disabled-internal.h b/include/babeltrace/plugin/plugin-python-disabled-internal.h deleted file mode 100644 index dec6fc13..00000000 --- a/include/babeltrace/plugin/plugin-python-disabled-internal.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef BABELTRACE_PLUGIN_PLUGIN_PYTHON_DISABLED_INTERNAL_H -#define BABELTRACE_PLUGIN_PLUGIN_PYTHON_DISABLED_INTERNAL_H - -/* - * BabelTrace - Babeltrace Plug-in Interface - * - * Copyright 2017 Philippe Proulx - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -struct bt_plugin; - -static inline -struct bt_plugin **bt_plugin_python_create_all_from_file(const char *path) -{ - return NULL; -} - -static inline -void bt_plugin_python_destroy_spec_data(struct bt_plugin *plugin) -{ -} - -#endif /* BABELTRACE_PLUGIN_PLUGIN_PYTHON_DISABLED_INTERNAL_H */ diff --git a/include/babeltrace/plugin/plugin-python-enabled-internal.h b/include/babeltrace/plugin/plugin-python-enabled-internal.h deleted file mode 100644 index b1d0345f..00000000 --- a/include/babeltrace/plugin/plugin-python-enabled-internal.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef BABELTRACE_PLUGIN_PLUGIN_PYTHON_ENABLED_INTERNAL_H -#define BABELTRACE_PLUGIN_PLUGIN_PYTHON_ENABLED_INTERNAL_H - -/* - * BabelTrace - Babeltrace Plug-in Interface - * - * Copyright 2017 Philippe Proulx - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -struct bt_plugin; - -BT_HIDDEN -struct bt_plugin **bt_plugin_python_create_all_from_file(const char *path); - -static inline -void bt_plugin_python_destroy_spec_data(struct bt_plugin *plugin) -{ -} - -#endif /* BABELTRACE_PLUGIN_PLUGIN_PYTHON_ENABLED_INTERNAL_H */ diff --git a/include/babeltrace/plugin/plugin-so-internal.h b/include/babeltrace/plugin/plugin-so-internal.h index 4e3bc866..e2dd51c0 100644 --- a/include/babeltrace/plugin/plugin-so-internal.h +++ b/include/babeltrace/plugin/plugin-so-internal.h @@ -64,7 +64,4 @@ BT_HIDDEN int bt_plugin_so_on_add_component_class(struct bt_plugin *plugin, struct bt_component_class *comp_class); -BT_HIDDEN -void bt_plugin_so_destroy_spec_data(struct bt_plugin *plugin); - #endif /* BABELTRACE_PLUGIN_PLUGIN_SO_INTERNAL_H */ diff --git a/include/babeltrace/plugin/python-plugin-provider-internal.h b/include/babeltrace/plugin/python-plugin-provider-internal.h new file mode 100644 index 00000000..acf913ff --- /dev/null +++ b/include/babeltrace/plugin/python-plugin-provider-internal.h @@ -0,0 +1,31 @@ +#ifndef BABELTRACE_PLUGIN_PYTHON_PLUGIN_PROVIDER_INTERNAL_H +#define BABELTRACE_PLUGIN_PYTHON_PLUGIN_PROVIDER_INTERNAL_H + +/* * + * Copyright 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +extern +struct bt_plugin **bt_plugin_python_create_all_from_file(const char *path); + +#endif /* BABELTRACE_PLUGIN_PYTHON_PLUGIN_PROVIDER_INTERNAL_H */ diff --git a/lib/Makefile.am b/lib/Makefile.am index c47d581b..0ba33f39 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -34,3 +34,7 @@ libbabeltrace_la_LIBADD = \ component/libcomponent.la \ plugin/libplugin.la \ $(top_builddir)/common/libbabeltrace-common.la + +if BUILT_IN_PYTHON_PLUGIN_SUPPORT +libbabeltrace_la_LIBADD += $(top_builddir)/python-plugin-provider/libbabeltrace-python-plugin-provider.la +endif diff --git a/lib/plugin/Makefile.am b/lib/plugin/Makefile.am index 355a31f8..467bbc52 100644 --- a/lib/plugin/Makefile.am +++ b/lib/plugin/Makefile.am @@ -1,4 +1,4 @@ -AM_CFLAGS = $(PYTHON_INCLUDE) $(PACKAGE_CFLAGS) -I$(top_srcdir)/include +AM_CFLAGS = $(PACKAGE_CFLAGS) -I$(top_srcdir)/include noinst_LTLIBRARIES = libplugin.la @@ -6,8 +6,3 @@ noinst_LTLIBRARIES = libplugin.la libplugin_la_SOURCES = \ plugin.c \ plugin-so.c - -if WITH_PYTHON_PLUGINS -libplugin_la_SOURCES += plugin-python.c -libplugin_la_LDFLAGS = $(PYTHON_LIBS) -endif diff --git a/lib/plugin/plugin-python.c b/lib/plugin/plugin-python.c deleted file mode 100644 index fc897ee3..00000000 --- a/lib/plugin/plugin-python.c +++ /dev/null @@ -1,454 +0,0 @@ -/* - * plugin-python.c - * - * Babeltrace Plugin (Python) - * - * Copyright 2017 Philippe Proulx - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PYTHON_PLUGIN_FILE_PREFIX "bt_plugin_" -#define PYTHON_PLUGIN_FILE_PREFIX_LEN (sizeof(PYTHON_PLUGIN_FILE_PREFIX) - 1) -#define PYTHON_PLUGIN_FILE_EXT ".py" -#define PYTHON_PLUGIN_FILE_EXT_LEN (sizeof(PYTHON_PLUGIN_FILE_EXT) - 1) - -enum python_state { - /* init_python() not called yet */ - PYTHON_STATE_NOT_INITED, - - /* init_python() called once with success */ - PYTHON_STATE_FULLY_INITIALIZED, - - /* init_python() called once without success */ - PYTHON_STATE_CANNOT_INITIALIZE, -} python_state = PYTHON_STATE_NOT_INITED; - -static PyObject *py_try_load_plugin_module_func = NULL; - -static -void print_python_traceback_verbose(void) -{ - if (Py_IsInitialized() && PyErr_Occurred() && babeltrace_verbose) { - PyErr_Print(); - } -} - -static -void pyerr_clear(void) -{ - if (Py_IsInitialized()) { - PyErr_Clear(); - } -} - -static -void init_python(void) -{ - PyObject *py_bt2_py_plugin_mod = NULL; - const char *dis_python_env; - sighandler_t old_sigint = signal(SIGINT, SIG_DFL); - - if (python_state != PYTHON_STATE_NOT_INITED) { - goto end; - } - - /* - * User can disable Python plugin support with the - * BABELTRACE_DISABLE_PYTHON_PLUGINS environment variable set to - * 1. - */ - dis_python_env = getenv("BABELTRACE_DISABLE_PYTHON_PLUGINS"); - if (dis_python_env && dis_python_env[0] == '1' && - dis_python_env[1] == '\0') { - printf_verbose("Python plugin support is disabled by BABELTRACE_DISABLE_PYTHON_PLUGINS environment variable\n"); - python_state = PYTHON_STATE_CANNOT_INITIALIZE; - goto end; - } - - if (!Py_IsInitialized()) { - Py_InitializeEx(0); - printf_verbose("Initialized Python:\n%s\n", Py_GetVersion()); - } - - py_bt2_py_plugin_mod = PyImport_ImportModule("bt2.py_plugin"); - if (!py_bt2_py_plugin_mod) { - printf_verbose("Cannot import bt2.py_plugin Python module\n"); - python_state = PYTHON_STATE_CANNOT_INITIALIZE; - goto end; - } - - py_try_load_plugin_module_func = - PyObject_GetAttrString(py_bt2_py_plugin_mod, "_try_load_plugin_module"); - if (!py_try_load_plugin_module_func) { - printf_verbose("Cannot get _try_load_plugin_module attribute from bt2.py_plugin Python module\n"); - python_state = PYTHON_STATE_CANNOT_INITIALIZE; - goto end; - } - - python_state = PYTHON_STATE_FULLY_INITIALIZED; - -end: - if (old_sigint != SIG_ERR) { - (void) signal(SIGINT, old_sigint); - } - - print_python_traceback_verbose(); - pyerr_clear(); - Py_XDECREF(py_bt2_py_plugin_mod); - return; -} - -__attribute__((destructor)) static -void fini_python(void) { - if (Py_IsInitialized()) { - if (py_try_load_plugin_module_func) { - Py_DECREF(py_try_load_plugin_module_func); - py_try_load_plugin_module_func = NULL; - } - - Py_Finalize(); - } - - python_state = PYTHON_STATE_NOT_INITED; -} - -static -struct bt_plugin *bt_plugin_from_python_plugin_info(PyObject *plugin_info) -{ - struct bt_plugin *plugin = NULL; - PyObject *py_name = NULL; - PyObject *py_author = NULL; - PyObject *py_description = NULL; - PyObject *py_license = NULL; - PyObject *py_version = NULL; - PyObject *py_comp_class_addrs = NULL; - const char *name = NULL; - const char *author = NULL; - const char *description = NULL; - const char *license = NULL; - unsigned int major = 0, minor = 0, patch = 0; - const char *version_extra = NULL; - int ret; - - assert(plugin_info); - assert(python_state == PYTHON_STATE_FULLY_INITIALIZED); - py_name = PyObject_GetAttrString(plugin_info, "name"); - if (!py_name) { - printf_verbose("Cannot find `name` attribute in plugin info\n"); - goto error; - } - - py_author = PyObject_GetAttrString(plugin_info, "author"); - if (!py_author) { - printf_verbose("Cannot find `author` attribute in plugin info\n"); - goto error; - } - - py_description = PyObject_GetAttrString(plugin_info, "description"); - if (!py_description) { - printf_verbose("Cannot find `description` attribute in plugin info\n"); - goto error; - } - - py_license = PyObject_GetAttrString(plugin_info, "license"); - if (!py_license) { - printf_verbose("Cannot find `license` attribute in plugin info\n"); - goto error; - } - - py_version = PyObject_GetAttrString(plugin_info, "version"); - if (!py_version) { - printf_verbose("Cannot find `version` attribute in plugin info\n"); - goto error; - } - - py_comp_class_addrs = PyObject_GetAttrString(plugin_info, - "comp_class_addrs"); - if (!py_comp_class_addrs) { - printf_verbose("Cannot find `comp_class_addrs` attribute in plugin info\n"); - goto error; - } - - if (PyUnicode_Check(py_name)) { - name = PyUnicode_AsUTF8(py_name); - if (!name) { - printf_verbose("Cannot decode plugin name string\n"); - goto error; - } - } else { - /* Plugin name is mandatory */ - printf_verbose("Plugin name is not a string\n"); - goto error; - } - - if (PyUnicode_Check(py_author)) { - author = PyUnicode_AsUTF8(py_author); - if (!author) { - printf_verbose("Cannot decode plugin author string\n"); - goto error; - } - } - - if (PyUnicode_Check(py_description)) { - description = PyUnicode_AsUTF8(py_description); - if (!description) { - printf_verbose("Cannot decode plugin description string\n"); - goto error; - } - } - - if (PyUnicode_Check(py_license)) { - license = PyUnicode_AsUTF8(py_license); - if (!license) { - printf_verbose("Cannot decode plugin license string\n"); - goto error; - } - } - - if (PyTuple_Check(py_version)) { - if (PyTuple_Size(py_version) >= 3) { - PyObject *py_major = PyTuple_GetItem(py_version, 0); - PyObject *py_minor = PyTuple_GetItem(py_version, 1); - PyObject *py_patch = PyTuple_GetItem(py_version, 2); - - assert(py_major); - assert(py_minor); - assert(py_patch); - - if (PyLong_Check(py_major)) { - major = PyLong_AsUnsignedLong(py_major); - } - - if (PyLong_Check(py_minor)) { - minor = PyLong_AsUnsignedLong(py_minor); - } - - if (PyLong_Check(py_patch)) { - patch = PyLong_AsUnsignedLong(py_patch); - } - - if (PyErr_Occurred()) { - /* Overflow error, most probably */ - printf_verbose("Invalid plugin version format\n"); - goto error; - } - } - - if (PyTuple_Size(py_version) >= 4) { - PyObject *py_extra = PyTuple_GetItem(py_version, 3); - - assert(py_extra); - - if (PyUnicode_Check(py_extra)) { - version_extra = PyUnicode_AsUTF8(py_extra); - if (!version_extra) { - printf_verbose("Cannot decode plugin version's extra string\n"); - goto error; - } - } - } - } - - plugin = bt_plugin_create_empty(BT_PLUGIN_TYPE_PYTHON); - if (!plugin) { - goto error; - } - - bt_plugin_set_name(plugin, name); - - if (description) { - bt_plugin_set_description(plugin, description); - } - - if (author) { - bt_plugin_set_author(plugin, author); - } - - if (license) { - bt_plugin_set_license(plugin, license); - } - - bt_plugin_set_version(plugin, major, minor, patch, version_extra); - - if (PyList_Check(py_comp_class_addrs)) { - size_t i; - - for (i = 0; i < PyList_Size(py_comp_class_addrs); i++) { - struct bt_component_class *comp_class; - PyObject *py_comp_class_addr; - - py_comp_class_addr = - PyList_GetItem(py_comp_class_addrs, i); - assert(py_comp_class_addr); - if (PyLong_Check(py_comp_class_addr)) { - comp_class = (struct bt_component_class *) - PyLong_AsUnsignedLongLong(py_comp_class_addr); - } else { - printf_verbose("Component class address #%zu: not an integer\n", - i); - continue; - } - - ret = bt_plugin_add_component_class(plugin, comp_class); - if (ret < 0) { - printf_verbose("Cannot add component class #%zu\n", - i); - continue; - } - } - } - - bt_plugin_freeze(plugin); - - goto end; - -error: - print_python_traceback_verbose(); - pyerr_clear(); - BT_PUT(plugin); - -end: - Py_XDECREF(py_name); - Py_XDECREF(py_author); - Py_XDECREF(py_description); - Py_XDECREF(py_license); - Py_XDECREF(py_version); - Py_XDECREF(py_comp_class_addrs); - return plugin; -} - -BT_HIDDEN -struct bt_plugin **bt_plugin_python_create_all_from_file(const char *path) -{ - struct bt_plugin **plugins = NULL; - PyObject *py_plugin_info = NULL; - gchar *basename = NULL; - size_t path_len; - - assert(path); - - if (python_state == PYTHON_STATE_CANNOT_INITIALIZE) { - /* - * We do not even care about the rest of the function - * here because we already know Python cannot be fully - * initialized. - */ - goto error; - } - - path_len = strlen(path); - - /* File name ends with `.py` */ - if (strncmp(path + path_len - PYTHON_PLUGIN_FILE_EXT_LEN, - PYTHON_PLUGIN_FILE_EXT, - PYTHON_PLUGIN_FILE_EXT_LEN) != 0) { - printf_verbose("Skipping non-Python file: `%s`\n", - path); - goto error; - } - - /* File name starts with `bt_plugin_` */ - basename = g_path_get_basename(path); - if (!basename) { - goto error; - } - - if (strncmp(basename, PYTHON_PLUGIN_FILE_PREFIX, - PYTHON_PLUGIN_FILE_PREFIX_LEN) != 0) { - printf_verbose("Skipping Python file not starting with `%s`: `%s`\n", - PYTHON_PLUGIN_FILE_PREFIX, path); - goto error; - } - - /* - * Initialize Python now. - * - * This is not done in the library contructor because the - * interpreter is somewhat slow to initialize. If you don't - * have any potential Python plugins, you don't need to endure - * this waiting time everytime you load the library. - */ - init_python(); - if (python_state != PYTHON_STATE_FULLY_INITIALIZED) { - /* - * For some reason we cannot initialize Python, - * import the required modules, and get the required - * attributes from them. - */ - goto error; - } - - /* - * Call bt2.py_plugin._try_load_plugin_module() with this path - * to get plugin info if the plugin is loadable and complete. - * This function returns None when there's an error, but just in - * case we also manually clear the last Python error state. - */ - py_plugin_info = PyObject_CallFunction(py_try_load_plugin_module_func, - "(s)", path); - if (!py_plugin_info || py_plugin_info == Py_None) { - printf_verbose("Cannot load Python plugin `%s`:\n", path); - print_python_traceback_verbose(); - PyErr_Clear(); - goto error; - } - - /* - * Get bt_plugin from plugin info object. - * - * calloc(2, ...) because a single Python plugin file always - * provides a single Babeltrace plugin (second item is the - * sentinel). - */ - plugins = calloc(2, sizeof(*plugins)); - if (!plugins) { - goto error; - } - - plugins[0] = bt_plugin_from_python_plugin_info(py_plugin_info); - if (!plugins[0]) { - goto error; - } - - bt_plugin_set_path(plugins[0], path); - goto end; - -error: - if (plugins) { - BT_PUT(plugins[0]); - free(plugins); - plugins = NULL; - } - -end: - Py_XDECREF(py_plugin_info); - g_free(basename); - return plugins; -} diff --git a/lib/plugin/plugin-so.c b/lib/plugin/plugin-so.c index 24478f1f..96f21458 100644 --- a/lib/plugin/plugin-so.c +++ b/lib/plugin/plugin-so.c @@ -188,7 +188,7 @@ end: return shared_lib_handle; } -BT_HIDDEN +static void bt_plugin_so_destroy_spec_data(struct bt_plugin *plugin) { struct bt_plugin_so_spec_data *spec = plugin->spec_data; @@ -642,6 +642,7 @@ struct bt_plugin *bt_plugin_so_create_empty( goto error; } + plugin->destroy_spec_data = bt_plugin_so_destroy_spec_data; plugin->spec_data = g_new0(struct bt_plugin_so_spec_data, 1); if (!plugin->spec_data) { goto error; diff --git a/lib/plugin/plugin.c b/lib/plugin/plugin.c index 0e628890..83f628cd 100644 --- a/lib/plugin/plugin.c +++ b/lib/plugin/plugin.c @@ -27,6 +27,7 @@ * SOFTWARE. */ +#include #include #include #include @@ -38,118 +39,46 @@ #include #include -#ifdef WITH_PYTHON_PLUGINS -# include -#else -# include -#endif +#define PYTHON_PLUGIN_PROVIDER_FILENAME "libbabeltrace-python-plugin-provider." G_MODULE_SUFFIX +#define PYTHON_PLUGIN_PROVIDER_SYM_NAME bt_plugin_python_create_all_from_file +#define PYTHON_PLUGIN_PROVIDER_SYM_NAME_STR TOSTRING(PYTHON_PLUGIN_PROVIDER_SYM_NAME) +#ifdef BT_BUILT_IN_PYTHON_PLUGIN_SUPPORT +#include static -void bt_plugin_destroy(struct bt_object *obj) -{ - struct bt_plugin *plugin; - - assert(obj); - plugin = container_of(obj, struct bt_plugin, base); - - if (plugin->type == BT_PLUGIN_TYPE_SO) { - bt_plugin_so_destroy_spec_data(plugin); - } else if (plugin->type == BT_PLUGIN_TYPE_PYTHON) { - bt_plugin_python_destroy_spec_data(plugin); - } else { - assert(false); - } - - if (plugin->comp_classes) { - g_ptr_array_free(plugin->comp_classes, TRUE); - } - - if (plugin->info.name) { - g_string_free(plugin->info.name, TRUE); - } - - if (plugin->info.path) { - g_string_free(plugin->info.path, TRUE); - } - - if (plugin->info.description) { - g_string_free(plugin->info.description, TRUE); - } - - if (plugin->info.author) { - g_string_free(plugin->info.author, TRUE); - } +struct bt_plugin **(*bt_plugin_python_create_all_from_file_sym)(const char *path) = + bt_plugin_python_create_all_from_file; +#else /* BT_BUILT_IN_PYTHON_PLUGIN_SUPPORT */ +static GModule *python_plugin_provider_module; +static +struct bt_plugin **(*bt_plugin_python_create_all_from_file_sym)(const char *path); - if (plugin->info.license) { - g_string_free(plugin->info.license, TRUE); +__attribute__((constructor)) static +void init_python_plugin_provider(void) { + python_plugin_provider_module = + g_module_open(PYTHON_PLUGIN_PROVIDER_FILENAME, + G_MODULE_BIND_LOCAL); + if (!python_plugin_provider_module) { + printf_warning("Cannot find `%s`: Python plugin support is disabled\n", + PYTHON_PLUGIN_PROVIDER_FILENAME); + return; } - if (plugin->info.version.extra) { - g_string_free(plugin->info.version.extra, TRUE); + if (!g_module_symbol(python_plugin_provider_module, + PYTHON_PLUGIN_PROVIDER_SYM_NAME_STR, + (gpointer) &bt_plugin_python_create_all_from_file_sym)) { + printf_warning("Cannot find the Python plugin provider loading symbole: Python plugin support is disabled\n"); } - - g_free(plugin); } -BT_HIDDEN -struct bt_plugin *bt_plugin_create_empty(enum bt_plugin_type type) -{ - struct bt_plugin *plugin = NULL; - - plugin = g_new0(struct bt_plugin, 1); - if (!plugin) { - goto error; - } - - bt_object_init(plugin, bt_plugin_destroy); - plugin->type = type; - - /* Create empty array of component classes */ - plugin->comp_classes = - g_ptr_array_new_with_free_func((GDestroyNotify) bt_put); - if (!plugin->comp_classes) { - goto error; - } - - /* Create empty info */ - plugin->info.name = g_string_new(NULL); - if (!plugin->info.name) { - goto error; - } - - plugin->info.path = g_string_new(NULL); - if (!plugin->info.path) { - goto error; - } - - plugin->info.description = g_string_new(NULL); - if (!plugin->info.description) { - goto error; - } - - plugin->info.author = g_string_new(NULL); - if (!plugin->info.author) { - goto error; - } - - plugin->info.license = g_string_new(NULL); - if (!plugin->info.license) { - goto error; - } - - plugin->info.version.extra = g_string_new(NULL); - if (!plugin->info.version.extra) { - goto error; +__attribute__((destructor)) static +void fini_python_plugin_provider(void) { + if (python_plugin_provider_module) { + (void) g_module_close(python_plugin_provider_module); + python_plugin_provider_module = NULL; } - - goto end; - -error: - BT_PUT(plugin); - -end: - return plugin; } +#endif struct bt_plugin **bt_plugin_create_all_from_static(void) { @@ -172,9 +101,12 @@ struct bt_plugin **bt_plugin_create_all_from_file(const char *path) goto end; } - plugins = bt_plugin_python_create_all_from_file(path); - if (plugins) { - goto end; + /* Try Python plugins if support is available */ + if (bt_plugin_python_create_all_from_file_sym) { + plugins = bt_plugin_python_create_all_from_file_sym(path); + if (plugins) { + goto end; + } } end: diff --git a/python-plugin-provider/Makefile.am b/python-plugin-provider/Makefile.am new file mode 100644 index 00000000..f4776760 --- /dev/null +++ b/python-plugin-provider/Makefile.am @@ -0,0 +1,7 @@ +AM_CFLAGS = $(PYTHON_INCLUDE) $(PACKAGE_CFLAGS) -I$(top_srcdir)/include + +lib_LTLIBRARIES = libbabeltrace-python-plugin-provider.la + +libbabeltrace_python_plugin_provider_la_SOURCES = python-plugin-provider.c +libbabeltrace_python_plugin_provider_la_LDFLAGS = \ + -version-info $(BABELTRACE_LIBRARY_VERSION) $(PYTHON_LIBS) diff --git a/python-plugin-provider/python-plugin-provider.c b/python-plugin-provider/python-plugin-provider.c new file mode 100644 index 00000000..20892fca --- /dev/null +++ b/python-plugin-provider/python-plugin-provider.c @@ -0,0 +1,454 @@ +/* + * python-plugin-provider.c + * + * Babeltrace Python plugin provider + * + * Copyright 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PYTHON_PLUGIN_FILE_PREFIX "bt_plugin_" +#define PYTHON_PLUGIN_FILE_PREFIX_LEN (sizeof(PYTHON_PLUGIN_FILE_PREFIX) - 1) +#define PYTHON_PLUGIN_FILE_EXT ".py" +#define PYTHON_PLUGIN_FILE_EXT_LEN (sizeof(PYTHON_PLUGIN_FILE_EXT) - 1) + +enum python_state { + /* init_python() not called yet */ + PYTHON_STATE_NOT_INITED, + + /* init_python() called once with success */ + PYTHON_STATE_FULLY_INITIALIZED, + + /* init_python() called once without success */ + PYTHON_STATE_CANNOT_INITIALIZE, +} python_state = PYTHON_STATE_NOT_INITED; + +static PyObject *py_try_load_plugin_module_func = NULL; + +static +void print_python_traceback_verbose(void) +{ + if (Py_IsInitialized() && PyErr_Occurred() && babeltrace_verbose) { + PyErr_Print(); + } +} + +static +void pyerr_clear(void) +{ + if (Py_IsInitialized()) { + PyErr_Clear(); + } +} + +static +void init_python(void) +{ + PyObject *py_bt2_py_plugin_mod = NULL; + const char *dis_python_env; + sighandler_t old_sigint = signal(SIGINT, SIG_DFL); + + if (python_state != PYTHON_STATE_NOT_INITED) { + goto end; + } + + /* + * User can disable Python plugin support with the + * BABELTRACE_DISABLE_PYTHON_PLUGINS environment variable set to + * 1. + */ + dis_python_env = getenv("BABELTRACE_DISABLE_PYTHON_PLUGINS"); + if (dis_python_env && dis_python_env[0] == '1' && + dis_python_env[1] == '\0') { + printf_verbose("Python plugin support is disabled by BABELTRACE_DISABLE_PYTHON_PLUGINS environment variable\n"); + python_state = PYTHON_STATE_CANNOT_INITIALIZE; + goto end; + } + + if (!Py_IsInitialized()) { + Py_InitializeEx(0); + printf_verbose("Initialized Python:\n%s\n", Py_GetVersion()); + } + + py_bt2_py_plugin_mod = PyImport_ImportModule("bt2.py_plugin"); + if (!py_bt2_py_plugin_mod) { + printf_verbose("Cannot import bt2.py_plugin Python module\n"); + python_state = PYTHON_STATE_CANNOT_INITIALIZE; + goto end; + } + + py_try_load_plugin_module_func = + PyObject_GetAttrString(py_bt2_py_plugin_mod, "_try_load_plugin_module"); + if (!py_try_load_plugin_module_func) { + printf_verbose("Cannot get _try_load_plugin_module attribute from bt2.py_plugin Python module\n"); + python_state = PYTHON_STATE_CANNOT_INITIALIZE; + goto end; + } + + python_state = PYTHON_STATE_FULLY_INITIALIZED; + +end: + if (old_sigint != SIG_ERR) { + (void) signal(SIGINT, old_sigint); + } + + print_python_traceback_verbose(); + pyerr_clear(); + Py_XDECREF(py_bt2_py_plugin_mod); + return; +} + +__attribute__((destructor)) static +void fini_python(void) { + if (Py_IsInitialized()) { + if (py_try_load_plugin_module_func) { + Py_DECREF(py_try_load_plugin_module_func); + py_try_load_plugin_module_func = NULL; + } + + Py_Finalize(); + } + + python_state = PYTHON_STATE_NOT_INITED; +} + +static +struct bt_plugin *bt_plugin_from_python_plugin_info(PyObject *plugin_info) +{ + struct bt_plugin *plugin = NULL; + PyObject *py_name = NULL; + PyObject *py_author = NULL; + PyObject *py_description = NULL; + PyObject *py_license = NULL; + PyObject *py_version = NULL; + PyObject *py_comp_class_addrs = NULL; + const char *name = NULL; + const char *author = NULL; + const char *description = NULL; + const char *license = NULL; + unsigned int major = 0, minor = 0, patch = 0; + const char *version_extra = NULL; + int ret; + + assert(plugin_info); + assert(python_state == PYTHON_STATE_FULLY_INITIALIZED); + py_name = PyObject_GetAttrString(plugin_info, "name"); + if (!py_name) { + printf_verbose("Cannot find `name` attribute in plugin info\n"); + goto error; + } + + py_author = PyObject_GetAttrString(plugin_info, "author"); + if (!py_author) { + printf_verbose("Cannot find `author` attribute in plugin info\n"); + goto error; + } + + py_description = PyObject_GetAttrString(plugin_info, "description"); + if (!py_description) { + printf_verbose("Cannot find `description` attribute in plugin info\n"); + goto error; + } + + py_license = PyObject_GetAttrString(plugin_info, "license"); + if (!py_license) { + printf_verbose("Cannot find `license` attribute in plugin info\n"); + goto error; + } + + py_version = PyObject_GetAttrString(plugin_info, "version"); + if (!py_version) { + printf_verbose("Cannot find `version` attribute in plugin info\n"); + goto error; + } + + py_comp_class_addrs = PyObject_GetAttrString(plugin_info, + "comp_class_addrs"); + if (!py_comp_class_addrs) { + printf_verbose("Cannot find `comp_class_addrs` attribute in plugin info\n"); + goto error; + } + + if (PyUnicode_Check(py_name)) { + name = PyUnicode_AsUTF8(py_name); + if (!name) { + printf_verbose("Cannot decode plugin name string\n"); + goto error; + } + } else { + /* Plugin name is mandatory */ + printf_verbose("Plugin name is not a string\n"); + goto error; + } + + if (PyUnicode_Check(py_author)) { + author = PyUnicode_AsUTF8(py_author); + if (!author) { + printf_verbose("Cannot decode plugin author string\n"); + goto error; + } + } + + if (PyUnicode_Check(py_description)) { + description = PyUnicode_AsUTF8(py_description); + if (!description) { + printf_verbose("Cannot decode plugin description string\n"); + goto error; + } + } + + if (PyUnicode_Check(py_license)) { + license = PyUnicode_AsUTF8(py_license); + if (!license) { + printf_verbose("Cannot decode plugin license string\n"); + goto error; + } + } + + if (PyTuple_Check(py_version)) { + if (PyTuple_Size(py_version) >= 3) { + PyObject *py_major = PyTuple_GetItem(py_version, 0); + PyObject *py_minor = PyTuple_GetItem(py_version, 1); + PyObject *py_patch = PyTuple_GetItem(py_version, 2); + + assert(py_major); + assert(py_minor); + assert(py_patch); + + if (PyLong_Check(py_major)) { + major = PyLong_AsUnsignedLong(py_major); + } + + if (PyLong_Check(py_minor)) { + minor = PyLong_AsUnsignedLong(py_minor); + } + + if (PyLong_Check(py_patch)) { + patch = PyLong_AsUnsignedLong(py_patch); + } + + if (PyErr_Occurred()) { + /* Overflow error, most probably */ + printf_verbose("Invalid plugin version format\n"); + goto error; + } + } + + if (PyTuple_Size(py_version) >= 4) { + PyObject *py_extra = PyTuple_GetItem(py_version, 3); + + assert(py_extra); + + if (PyUnicode_Check(py_extra)) { + version_extra = PyUnicode_AsUTF8(py_extra); + if (!version_extra) { + printf_verbose("Cannot decode plugin version's extra string\n"); + goto error; + } + } + } + } + + plugin = bt_plugin_create_empty(BT_PLUGIN_TYPE_PYTHON); + if (!plugin) { + goto error; + } + + bt_plugin_set_name(plugin, name); + + if (description) { + bt_plugin_set_description(plugin, description); + } + + if (author) { + bt_plugin_set_author(plugin, author); + } + + if (license) { + bt_plugin_set_license(plugin, license); + } + + bt_plugin_set_version(plugin, major, minor, patch, version_extra); + + if (PyList_Check(py_comp_class_addrs)) { + size_t i; + + for (i = 0; i < PyList_Size(py_comp_class_addrs); i++) { + struct bt_component_class *comp_class; + PyObject *py_comp_class_addr; + + py_comp_class_addr = + PyList_GetItem(py_comp_class_addrs, i); + assert(py_comp_class_addr); + if (PyLong_Check(py_comp_class_addr)) { + comp_class = (struct bt_component_class *) + PyLong_AsUnsignedLongLong(py_comp_class_addr); + } else { + printf_verbose("Component class address #%zu: not an integer\n", + i); + continue; + } + + ret = bt_plugin_add_component_class(plugin, comp_class); + if (ret < 0) { + printf_verbose("Cannot add component class #%zu\n", + i); + continue; + } + } + } + + bt_plugin_freeze(plugin); + + goto end; + +error: + print_python_traceback_verbose(); + pyerr_clear(); + BT_PUT(plugin); + +end: + Py_XDECREF(py_name); + Py_XDECREF(py_author); + Py_XDECREF(py_description); + Py_XDECREF(py_license); + Py_XDECREF(py_version); + Py_XDECREF(py_comp_class_addrs); + return plugin; +} + +G_MODULE_EXPORT +struct bt_plugin **bt_plugin_python_create_all_from_file(const char *path) +{ + struct bt_plugin **plugins = NULL; + PyObject *py_plugin_info = NULL; + gchar *basename = NULL; + size_t path_len; + + assert(path); + + if (python_state == PYTHON_STATE_CANNOT_INITIALIZE) { + /* + * We do not even care about the rest of the function + * here because we already know Python cannot be fully + * initialized. + */ + goto error; + } + + path_len = strlen(path); + + /* File name ends with `.py` */ + if (strncmp(path + path_len - PYTHON_PLUGIN_FILE_EXT_LEN, + PYTHON_PLUGIN_FILE_EXT, + PYTHON_PLUGIN_FILE_EXT_LEN) != 0) { + printf_verbose("Skipping non-Python file: `%s`\n", + path); + goto error; + } + + /* File name starts with `bt_plugin_` */ + basename = g_path_get_basename(path); + if (!basename) { + goto error; + } + + if (strncmp(basename, PYTHON_PLUGIN_FILE_PREFIX, + PYTHON_PLUGIN_FILE_PREFIX_LEN) != 0) { + printf_verbose("Skipping Python file not starting with `%s`: `%s`\n", + PYTHON_PLUGIN_FILE_PREFIX, path); + goto error; + } + + /* + * Initialize Python now. + * + * This is not done in the library contructor because the + * interpreter is somewhat slow to initialize. If you don't + * have any potential Python plugins, you don't need to endure + * this waiting time everytime you load the library. + */ + init_python(); + if (python_state != PYTHON_STATE_FULLY_INITIALIZED) { + /* + * For some reason we cannot initialize Python, + * import the required modules, and get the required + * attributes from them. + */ + goto error; + } + + /* + * Call bt2.py_plugin._try_load_plugin_module() with this path + * to get plugin info if the plugin is loadable and complete. + * This function returns None when there's an error, but just in + * case we also manually clear the last Python error state. + */ + py_plugin_info = PyObject_CallFunction(py_try_load_plugin_module_func, + "(s)", path); + if (!py_plugin_info || py_plugin_info == Py_None) { + printf_verbose("Cannot load Python plugin `%s`:\n", path); + print_python_traceback_verbose(); + PyErr_Clear(); + goto error; + } + + /* + * Get bt_plugin from plugin info object. + * + * calloc(2, ...) because a single Python plugin file always + * provides a single Babeltrace plugin (second item is the + * sentinel). + */ + plugins = calloc(2, sizeof(*plugins)); + if (!plugins) { + goto error; + } + + plugins[0] = bt_plugin_from_python_plugin_info(py_plugin_info); + if (!plugins[0]) { + goto error; + } + + bt_plugin_set_path(plugins[0], path); + goto end; + +error: + if (plugins) { + BT_PUT(plugins[0]); + free(plugins); + plugins = NULL; + } + +end: + Py_XDECREF(py_plugin_info); + g_free(basename); + return plugins; +}