lib: introduce WpComponentLoader and remove WpModule

The component loader is a more generic and extensible mechanism
of loading components; modules are one type of component...
The idea is to make scripts and config files also be components,
loaded by plugins that inherit WpComponentLoader
This commit is contained in:
George Kiagiadakis
2021-01-31 23:29:55 +02:00
parent f61e292959
commit 0d072874a1
27 changed files with 289 additions and 80 deletions

159
lib/wp/component-loader.c Normal file
View File

@@ -0,0 +1,159 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* SECTION: component-loader
* @title: Components
*/
#define G_LOG_DOMAIN "wp-comp-loader"
#include "component-loader.h"
#include "error.h"
#include "private/registry.h"
#include <pipewire/impl.h>
#define WP_MODULE_INIT_SYMBOL "wireplumber__module_init"
typedef gboolean (*WpModuleInitFunc) (WpCore *, GVariant *, GError **);
G_DEFINE_ABSTRACT_TYPE (WpComponentLoader, wp_component_loader, WP_TYPE_PLUGIN)
static void
wp_component_loader_init (WpComponentLoader * self)
{
}
static void
wp_component_loader_class_init (WpComponentLoaderClass * klass)
{
}
static const gchar *
get_module_dir (void)
{
static const gchar *module_dir = NULL;
if (!module_dir) {
module_dir = g_getenv ("WIREPLUMBER_MODULE_DIR");
if (!module_dir)
module_dir = WIREPLUMBER_DEFAULT_MODULE_DIR;
}
return module_dir;
}
static gboolean
load_module (WpCore * core, const gchar * module_name,
GVariant * args, GError ** error)
{
g_autofree gchar *module_path = NULL;
GModule *gmodule;
gpointer module_init;
module_path = g_module_build_path (get_module_dir (), module_name);
gmodule = g_module_open (module_path, G_MODULE_BIND_LOCAL);
if (!gmodule) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to open module %s: %s", module_path, g_module_error ());
return FALSE;
}
if (!g_module_symbol (gmodule, WP_MODULE_INIT_SYMBOL, &module_init)) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to locate symbol " WP_MODULE_INIT_SYMBOL " in %s",
module_path);
g_module_close (gmodule);
return FALSE;
}
return ((WpModuleInitFunc) module_init) (core, args, error);
}
static gboolean
load_pw_module (WpCore * core, const gchar * module_name,
GVariant * args, GError ** error)
{
const gchar *args_str = NULL;
if (args) {
if (g_variant_is_of_type (args, G_VARIANT_TYPE_STRING))
args_str = g_variant_get_string (args, NULL);
//TODO if it proves to be useful
//else if (g_variant_is_of_type (args, G_VARIANT_TYPE_DICTIONARY))
}
struct pw_impl_module *module = pw_context_load_module (
wp_core_get_pw_context (core), module_name, args_str, NULL);
if (!module) {
int res = errno;
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to load pipewire module: %s", g_strerror (res));
return FALSE;
}
return TRUE;
}
static gboolean
find_component_loader_func (gpointer cl, gpointer type)
{
if (WP_IS_COMPONENT_LOADER (cl) &&
(WP_COMPONENT_LOADER_GET_CLASS (cl)->supports_type (
WP_COMPONENT_LOADER (cl), (const gchar *) type)))
return TRUE;
return FALSE;
}
static WpComponentLoader *
wp_component_loader_find (WpCore * core, const gchar * type)
{
g_return_val_if_fail (WP_IS_CORE (core), NULL);
GObject *c = wp_registry_find_object (wp_core_get_registry (core),
(GEqualFunc) find_component_loader_func, type);
return c ? WP_COMPONENT_LOADER (c) : NULL;
}
static gboolean
wp_component_loader_load (WpComponentLoader * self, const gchar * component,
const gchar * type, GVariant * args, GError ** error)
{
g_return_val_if_fail (WP_IS_COMPONENT_LOADER (self), FALSE);
return WP_COMPONENT_LOADER_GET_CLASS (self)->load (self, component, type,
args, error);
}
/**
* wp_core_load_component:
* @self: the core
* @component: the module name or file name
* @type: the type of the component
* @args: (transfer floating)(nullable): additional arguments for the component,
* usually a dict or a string
* @error: (out) (optional): return location for errors, or NULL to ignore
*
* Returns: %TRUE if loaded, %FALSE if there was an error
*/
gboolean
wp_core_load_component (WpCore * self, const gchar * component,
const gchar * type, GVariant * args, GError ** error)
{
g_autoptr (GVariant) args_ref = args ? g_variant_ref_sink (args) : NULL;
if (!g_strcmp0 (type, "module"))
return load_module (self, component, args_ref, error);
else if (!g_strcmp0 (type, "pw_module"))
return load_pw_module (self, component, args_ref, error);
else {
g_autoptr (WpComponentLoader) c = wp_component_loader_find (self, type);
if (c)
return wp_component_loader_load (c, component, type, args, error);
else {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"No component loader was found for components of type '%s'", type);
return FALSE;
}
}
}

38
lib/wp/component-loader.h Normal file
View File

@@ -0,0 +1,38 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_COMPONENT_LOADER_H__
#define __WIREPLUMBER_COMPONENT_LOADER_H__
#include "plugin.h"
G_BEGIN_DECLS
/**
* WP_TYPE_COMPONENT_LOADER:
*
* The #WpComponentLoader #GType
*/
#define WP_TYPE_COMPONENT_LOADER (wp_component_loader_get_type ())
WP_API
G_DECLARE_DERIVABLE_TYPE (WpComponentLoader, wp_component_loader,
WP, COMPONENT_LOADER, WpPlugin)
struct _WpComponentLoaderClass
{
WpPluginClass parent_class;
gboolean (*supports_type) (WpComponentLoader * self, const gchar * type);
gboolean (*load) (WpComponentLoader * self, const gchar * component,
const gchar * type, GVariant * args, GError ** error);
};
G_END_DECLS
#endif

View File

@@ -40,6 +40,10 @@ struct pw_context * wp_core_get_pw_context (WpCore * self);
WP_API
struct pw_core * wp_core_get_pw_core (WpCore * self);
WP_API
gboolean wp_core_load_component (WpCore * self, const gchar * component,
const gchar * type, GVariant * args, GError ** error);
/* Connection */
WP_API

View File

@@ -1,6 +1,7 @@
wp_lib_sources = files(
'private/pipewire-object-mixin.c',
'client.c',
'component-loader.c',
'configuration.c',
'core.c',
'debug.c',
@@ -13,7 +14,6 @@ wp_lib_sources = files(
'iterator.c',
'link.c',
'metadata.c',
'module.c',
'node.c',
'object.c',
'object-interest.c',
@@ -37,6 +37,7 @@ wp_lib_sources = files(
wp_lib_headers = files(
'client.h',
'component-loader.h',
'configuration.h',
'core.h',
'debug.h',
@@ -50,7 +51,6 @@ wp_lib_headers = files(
'iterator.h',
'link.h',
'metadata.h',
'module.h',
'node.h',
'object.h',
'object-interest.h',

View File

@@ -10,6 +10,7 @@
#define __WIREPLUMBER_WP_H__
#include "client.h"
#include "component-loader.h"
#include "configuration.h"
#include "core.h"
#include "debug.h"

View File

@@ -74,11 +74,12 @@ wp_client_permissions_class_init (WpClientPermissionsClass * klass)
plugin_class->disable = wp_client_permissions_disable;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_client_permissions_get_type (),
"name", "client-permissions",
"core", core,
NULL));
return TRUE;
}

View File

@@ -402,11 +402,12 @@ wp_default_metadata_class_init (WpDefaultMetadataClass * klass)
plugin_class->disable = wp_default_metadata_disable;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_default_metadata_get_type (),
"name", "default-metadata",
"core", core,
NULL));
return TRUE;
}

View File

@@ -332,11 +332,12 @@ wp_default_profile_class_init (WpDefaultProfileClass * klass)
NULL, G_TYPE_NONE, 2, WP_TYPE_DEVICE, G_TYPE_POINTER);
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_default_profile_get_type (),
"name", STATE_NAME,
"core", core,
NULL));
return TRUE;
}

View File

@@ -182,12 +182,12 @@ wp_device_activation_class_init (WpDeviceActivationClass * klass)
plugin_class->disable = wp_device_activation_disable;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_device_activation_get_type (),
"name", "device-activation",
"core", core,
NULL));
return TRUE;
}

View File

@@ -168,14 +168,15 @@ wp_lua_scripting_plugin_class_init (WpLuaScriptingPluginClass * klass)
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
const gchar *profile;
if (!g_variant_lookup (args, "profile", "&s", &profile)) {
wp_warning_object (module, "module-lua-scripting requires a 'profile'");
return;
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"module-lua-scripting requires a 'profile'");
return FALSE;
}
wp_plugin_register (g_object_new (wp_lua_scripting_plugin_get_type (),
@@ -183,4 +184,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
"core", core,
"profile", profile,
NULL));
return TRUE;
}

View File

@@ -70,11 +70,12 @@ wp_metadata_plugin_class_init (WpMetadataPluginClass * klass)
plugin_class->disable = wp_metadata_plugin_disable;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_metadata_plugin_get_type (),
"name", "metadata",
"core", core,
NULL));
return TRUE;
}

View File

@@ -130,11 +130,12 @@ wp_node_suspension_class_init (WpNodeSuspensionClass * klass)
plugin_class->disable = wp_node_suspension_disable;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_node_suspension_get_type (),
"name", "node-suspension",
"core", core,
NULL));
return TRUE;
}

View File

@@ -271,11 +271,12 @@ wp_reserve_device_plugin_class_init (WpReserveDevicePluginClass * klass)
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_plugin_register (g_object_new (wp_reserve_device_plugin_get_type (),
"name", "reserve-device",
"core", core,
NULL));
return TRUE;
}

View File

@@ -563,8 +563,8 @@ si_adapter_port_info_init (WpSiPortInfoInterface * iface)
iface->get_ports = si_adapter_get_ports;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -590,4 +590,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_si_factory_register (core, wp_si_factory_new_simple (
"si-adapter", si_adapter_get_type (), g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -290,8 +290,8 @@ si_audio_softdsp_endpoint_class_init (WpSiAudioSoftdspEndpointClass * klass)
si_class->activate_rollback = si_audio_softdsp_endpoint_activate_rollback;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -302,4 +302,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_si_factory_register (core, wp_si_factory_new_simple (
"si-audio-softdsp-endpoint", si_audio_softdsp_endpoint_get_type (),
g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -540,8 +540,8 @@ si_bluez5_endpoint_stream_acquisition_init (
iface->release = si_bluez5_endpoint_stream_acquisition_release;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -568,4 +568,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_si_factory_register (core, wp_si_factory_new_simple (
"si-bluez5-endpoint", si_bluez5_endpoint_get_type (),
g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -495,8 +495,8 @@ si_convert_class_init (WpSiConvertClass * klass)
si_class->activate_rollback = si_convert_activate_rollback;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -510,4 +510,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_si_factory_register (core, wp_si_factory_new_simple (
"si-convert", si_convert_get_type (), g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -174,8 +174,8 @@ si_fake_stream_port_info_init (WpSiPortInfoInterface * iface)
iface->get_ports = si_fake_stream_get_ports;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -185,4 +185,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_si_factory_register (core, wp_si_factory_new_simple (
"si-fake-stream", si_fake_stream_get_type (), g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -287,8 +287,8 @@ si_monitor_endpoint_port_info_init (WpSiPortInfoInterface * iface)
iface->get_ports = si_monitor_endpoint_get_ports;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -299,4 +299,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_si_factory_register (core, wp_si_factory_new_simple (
"si-monitor-endpoint", si_monitor_endpoint_get_type (),
g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -415,8 +415,8 @@ si_simple_node_endpoint_port_info_init (WpSiPortInfoInterface * iface)
iface->get_ports = si_simple_node_endpoint_get_ports;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -436,4 +436,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
wp_si_factory_register (core, wp_si_factory_new_simple (
"si-simple-node-endpoint", si_simple_node_endpoint_get_type (),
g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -497,8 +497,8 @@ si_standard_link_link_init (WpSiLinkInterface * iface)
iface->get_in_stream = si_standard_link_get_in_stream;
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
WP_PLUGIN_EXPORT gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
GVariantBuilder b;
@@ -520,4 +520,5 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
"si-standard-link",
si_standard_link_get_type (),
g_variant_builder_end (&b)));
return TRUE;
}

View File

@@ -251,22 +251,21 @@ parse_commands_file (struct WpDaemonData *d, GInputStream * stream,
properties = g_variant_new_parsed ("@a{sv} {}");
}
if (!wp_module_load (d->core, abi, module, properties, error)) {
if (!wp_core_load_component (d->core, module, "module", properties,
error))
return FALSE;
}
} else if (!g_strcmp0 (cmd, "load-pipewire-module")) {
gchar *module, *props;
module = strtok_r (NULL, " ", &saveptr);
props = module + strlen(module) + 1;
properties = g_variant_new_string (props);
if (!pw_context_load_module (wp_core_get_pw_context (d->core), module,
props, NULL)) {
g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_OPERATION_FAILED,
"failed to load pipewire module '%s': %s", module,
g_strerror (errno));
if (!wp_core_load_component (d->core, module, "pw_module", properties,
error))
return FALSE;
}
} else if (!g_strcmp0 (cmd, "add-spa-lib")) {
gchar *regex, *lib;
gint ret;

View File

@@ -307,7 +307,6 @@ on_session_ready (WpObject * session, GAsyncResult * res, AppData * d)
static gboolean
appdata_init (AppData * d, GError ** error)
{
WpModule *module;
WpImplSession *session;
/* setup the internal test PipeWire server */
@@ -353,24 +352,24 @@ appdata_init (AppData * d, GError ** error)
NULL));
/* load wireplumber modules (wireplumber.conf) */
if (!(module = wp_module_load (d->core, "C",
"libwireplumber-module-si-simple-node-endpoint", NULL, error)))
if (!(wp_core_load_component (d->core,
"libwireplumber-module-si-simple-node-endpoint", "module", NULL, error)))
return FALSE;
if (!(module = wp_module_load (d->core, "C",
"libwireplumber-module-si-audio-softdsp-endpoint", NULL, error)))
if (!(wp_core_load_component (d->core,
"libwireplumber-module-si-audio-softdsp-endpoint", "module", NULL, error)))
return FALSE;
if (!(module = wp_module_load (d->core, "C",
"libwireplumber-module-si-adapter", NULL, error)))
if (!(wp_core_load_component (d->core,
"libwireplumber-module-si-adapter", "module", NULL, error)))
return FALSE;
if (!(module = wp_module_load (d->core, "C",
"libwireplumber-module-si-convert", NULL, error)))
if (!(wp_core_load_component (d->core,
"libwireplumber-module-si-convert", "module", NULL, error)))
return FALSE;
if (!(module = wp_module_load (d->core, "C",
"libwireplumber-module-si-standard-link", NULL, error)))
if (!(wp_core_load_component (d->core,
"libwireplumber-module-si-standard-link", "module", NULL, error)))
return FALSE;
/* connect */

View File

@@ -28,17 +28,15 @@ test_rd_setup (RdTestFixture *f, gconstpointer data)
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.core, "C",
"libwireplumber-module-reserve-device", NULL, &error);
wp_core_load_component (f->base.core,
"libwireplumber-module-reserve-device", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.client_core, "C",
"libwireplumber-module-reserve-device", NULL, &error);
wp_core_load_component (f->base.client_core,
"libwireplumber-module-reserve-device", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
f->rd_plugin_1 = wp_plugin_find (f->base.core, "reserve-device");

View File

@@ -35,24 +35,21 @@ test_si_audio_softdsp_endpoint_setup (TestFixture * f, gconstpointer user_data)
}
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.core, "C",
"libwireplumber-module-si-adapter", NULL, &error);
wp_core_load_component (f->base.core,
"libwireplumber-module-si-adapter", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.core, "C",
"libwireplumber-module-si-convert", NULL, &error);
wp_core_load_component (f->base.core,
"libwireplumber-module-si-convert", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.core, "C",
"libwireplumber-module-si-audio-softdsp-endpoint", NULL, &error);
wp_core_load_component (f->base.core,
"libwireplumber-module-si-audio-softdsp-endpoint", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
}

View File

@@ -39,10 +39,9 @@ test_si_simple_node_endpoint_setup (TestFixture * f, gconstpointer user_data)
}
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.core, "C",
"libwireplumber-module-si-simple-node-endpoint", NULL, &error);
wp_core_load_component (f->base.core,
"libwireplumber-module-si-simple-node-endpoint", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
}

View File

@@ -86,14 +86,13 @@ test_si_standard_link_setup (TestFixture * f, gconstpointer user_data)
}
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.core, "C",
"libwireplumber-module-si-simple-node-endpoint", NULL, &error);
wp_core_load_component (f->base.core,
"libwireplumber-module-si-simple-node-endpoint", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
module = wp_module_load (f->base.core, "C",
"libwireplumber-module-si-standard-link", NULL, &error);
wp_core_load_component (f->base.core,
"libwireplumber-module-si-standard-link", "module", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
g_assert_nonnull (