Commit 1ab088f0 authored by Christian Hergert's avatar Christian Hergert

config: refactor config providers to be less racey

We had a number of issues in practice with configuration providers where
things would race and as well as some unsafe threading/false-sharing.

This redesigns those components to avoid a number of issues in thread
safety.

There doesn't seem to be any regressions. However, it has pointed out
a few things that are/were broken in the flatpak configuration provider.
We will address those as part of a revamped build preferences that is
more pluggable (See #344 and #352).

Another piece that would be nice to apply on top of this is tracking
the last selected configuration so when restarting Builder we keep
the same config active (See #338).

Fixes #359
parent e84bf1e7
......@@ -30,6 +30,7 @@ src/libide/application/ide-application-shortcuts.c
src/libide/buffers/ide-buffer.c
src/libide/buffers/ide-buffer-manager.c
src/libide/buffers/ide-unsaved-files.c
src/libide/buildconfig/ide-buildconfig-configuration-provider.c
src/libide/buildsystem/ide-build-manager.c
src/libide/buildsystem/ide-build-pipeline.c
src/libide/buildsystem/ide-build-stage-transfer.c
......
......@@ -18,13 +18,7 @@
#pragma once
#include <glib.h>
#include "ide-version-macros.h"
#include "ide-types.h"
#include "buildconfig/ide-buildconfig-configuration.h"
#include "ide-object.h"
G_BEGIN_DECLS
......@@ -32,8 +26,4 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (IdeBuildconfigConfigurationProvider, ide_buildconfig_configuration_provider, IDE, BUILDCONFIG_CONFIGURATION_PROVIDER, IdeObject)
IDE_AVAILABLE_IN_ALL
void ide_buildconfig_configuration_provider_track_config (IdeBuildconfigConfigurationProvider *self,
IdeBuildconfigConfiguration *config);
G_END_DECLS
......@@ -23,6 +23,7 @@
struct _IdeBuildconfigConfiguration
{
IdeConfiguration parent_instance;
gchar **prebuild;
gchar **postbuild;
};
......@@ -113,7 +114,7 @@ ide_buildconfig_configuration_class_init (IdeBuildconfigConfigurationClass *klas
g_param_spec_boxed ("postbuild", NULL, NULL,
G_TYPE_STRV,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
......
......@@ -8,5 +8,3 @@ Depends=editor
Hidden=true
Builtin=true
Embedded=ide_build_tool_register_types
X-Tool-Name=build
X-Tool-Description=Build a project
......@@ -211,10 +211,12 @@ duplicate_configuration (GSimpleAction *action,
if (self->configuration != NULL)
{
g_autoptr(IdeConfiguration) copy = NULL;
IdeContext *context;
IdeConfigurationManager *config_manager;
copy = ide_configuration_duplicate (self->configuration);
ide_configuration_manager_add (self->configuration_manager, copy);
context = ide_widget_get_context (GTK_WIDGET (self));
config_manager = ide_context_get_configuration_manager (context);
ide_configuration_manager_duplicate (config_manager, self->configuration);
}
}
......@@ -236,7 +238,7 @@ delete_configuration (GSimpleAction *action,
* self->configuration will change during this call.
*/
config = g_object_ref (self->configuration);
ide_configuration_manager_remove (self->configuration_manager, config);
ide_configuration_manager_delete (self->configuration_manager, config);
/*
* Switch to the first configuration in the list. The configuration
......
......@@ -27,9 +27,6 @@
void
ide_build_tool_register_types (PeasObjectModule *module)
{
peas_object_module_register_extension_type (module,
IDE_TYPE_APPLICATION_TOOL,
IDE_TYPE_BUILD_TOOL);
peas_object_module_register_extension_type (module,
IDE_TYPE_WORKBENCH_ADDIN,
IDE_TYPE_BUILD_WORKBENCH_ADDIN);
......
/* ide-build-tool.c
*
* Copyright © 2015 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "ide-build-tool"
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <glib/gi18n.h>
#include <ide.h>
#include "buildui/ide-build-tool.h"
struct _IdeBuildTool
{
GObject parent_instance;
gint64 build_start;
};
static gint parallel = -1;
static gchar *configuration_id;
static gchar *device_id;
static gchar *runtime_id;
static void application_tool_init (IdeApplicationToolInterface *iface);
G_DEFINE_TYPE_EXTENDED (IdeBuildTool, ide_build_tool, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (IDE_TYPE_APPLICATION_TOOL, application_tool_init))
static void
ide_build_tool_class_init (IdeBuildToolClass *klass)
{
}
static void
ide_build_tool_init (IdeBuildTool *self)
{
}
static void
ide_build_tool_log_observer (IdeBuildLogStream stream,
const gchar *message,
gssize message_len,
gpointer user_data)
{
if (stream == IDE_BUILD_LOG_STDERR)
g_printerr ("%s", message);
else
g_print ("%s", message);
}
static void
print_build_info (IdeContext *context,
IdeConfiguration *configuration)
{
IdeProject *project;
IdeBuildSystem *build_system;
IdeDevice *device;
IdeVcs *vcs;
g_auto(GStrv) env = NULL;
const gchar *dev_id;
const gchar *project_name;
const gchar *vcs_name;
const gchar *build_system_name;
const gchar *system_type;
g_autofree gchar *build_date = NULL;
GTimeVal tv;
project = ide_context_get_project (context);
project_name = ide_project_get_name (project);
vcs = ide_context_get_vcs (context);
vcs_name = g_type_name (G_TYPE_FROM_INSTANCE (vcs));
build_system = ide_context_get_build_system (context);
build_system_name = g_type_name (G_TYPE_FROM_INSTANCE (build_system));
device = ide_configuration_get_device (configuration);
dev_id = ide_device_get_id (device);
system_type = ide_device_get_system_type (device);
env = ide_configuration_get_environ (configuration);
g_get_current_time (&tv);
build_date = g_time_val_to_iso8601 (&tv);
g_printerr (_("========================\n"));
g_printerr (_(" Project Name: %s\n"), project_name);
g_printerr (_(" Version Control System: %s\n"), vcs_name);
g_printerr (_(" Build System: %s\n"), build_system_name);
g_printerr (_(" Build Date and Time: %s\n"), build_date);
g_printerr (_(" Building for Device: %s (%s)\n"), dev_id, system_type);
if (env && env [0])
{
g_autofree gchar *envstr = g_strjoinv (" ", env);
g_printerr (_(" Environment: %s\n"), envstr);
}
g_printerr (_("========================\n"));
}
static void
ide_build_tool_execute_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuildManager *build_manager = (IdeBuildManager *)object;
g_autoptr(GTask) task = user_data;
g_autoptr(GError) error = NULL;
IdeBuildTool *self;
guint64 completed_at;
guint64 total_usec;
g_assert (G_IS_TASK (task));
g_assert (IDE_IS_BUILD_MANAGER (build_manager));
self = g_task_get_source_object (task);
completed_at = g_get_monotonic_time ();
ide_build_manager_execute_finish (build_manager, result, &error);
total_usec = completed_at - self->build_start;
if (error != NULL)
{
g_printerr (_("===============\n"));
g_printerr (_(" Build Failure: %s\n"), error->message);
g_printerr (_(" Build ran for: %"G_GUINT64_FORMAT".%"G_GUINT64_FORMAT" seconds\n"),
(total_usec / 1000000), ((total_usec % 1000000) / 1000));
g_printerr (_("===============\n"));
g_task_return_error (task, g_steal_pointer (&error));
return;
}
/*
* TODO: We should consider supporting packaging/xdg-app/deployment stuff
* here too. It would be nice if we could say, go build this project,
* for this device, and then deploy.
*/
g_printerr (_("=================\n"));
g_printerr (_(" Build Successful\n"));
g_printerr (_(" Build ran for: %"G_GUINT64_FORMAT".%"G_GUINT64_FORMAT" seconds\n"),
(total_usec / 1000000), ((total_usec % 1000000) / 1000));
g_printerr (_("=================\n"));
g_task_return_boolean (task, TRUE);
}
static void
ide_build_tool_new_context_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(GTask) task = user_data;
g_autoptr(IdeContext) context = NULL;
g_autoptr(IdeConfiguration) configuration = NULL;
IdeConfigurationManager *configuration_manager;
IdeBuildManager *build_manager;
IdeBuildPipeline *pipeline;
GCancellable *cancellable;
g_autoptr(GError) error = NULL;
g_assert (G_IS_TASK (task));
cancellable = g_task_get_cancellable (task);
context = ide_context_new_finish (result, &error);
if (context == NULL)
{
g_task_return_error (task, g_steal_pointer (&error));
return;
}
configuration_manager = ide_context_get_configuration_manager (context);
if (configuration_id != NULL)
configuration = ide_configuration_manager_get_configuration (configuration_manager, configuration_id);
else if (device_id && runtime_id)
configuration = ide_configuration_new (context, "command-line-build", device_id, runtime_id);
else if (device_id)
configuration = ide_configuration_new (context, "command-line-build", device_id, "host");
else if (runtime_id)
configuration = ide_configuration_new (context, "command-line-build", "local", runtime_id);
else
configuration = ide_configuration_manager_get_current (configuration_manager);
if (!ide_configuration_get_device (configuration))
{
/* TODO: Wait for devices to settle. */
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
_("Failed to locate device “%s”"),
device_id);
return;
}
if (!ide_configuration_get_runtime (configuration))
{
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
_("Failed to locate runtime “%s”"),
runtime_id);
return;
}
if (parallel > -1)
{
/* TODO: put this into IdeConfiguration:parallel: */
g_autofree gchar *str = g_strdup_printf ("%d", parallel);
ide_configuration_setenv (configuration, "PARALLEL", str);
}
print_build_info (context, configuration);
build_manager = ide_context_get_build_manager (context);
pipeline = ide_build_manager_get_pipeline (build_manager);
ide_build_pipeline_add_log_observer (pipeline,
ide_build_tool_log_observer,
NULL, NULL);
ide_build_manager_execute_async (build_manager,
IDE_BUILD_PHASE_BUILD,
cancellable,
ide_build_tool_execute_cb,
g_steal_pointer (&task));
}
static void
ide_build_tool_run_async (IdeApplicationTool *tool,
const gchar * const *arguments,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
IdeBuildTool *self = (IdeBuildTool *)tool;
g_autoptr(GTask) task = NULL;
g_autofree gchar *project_path = NULL;
g_autoptr(GFile) project_file = NULL;
g_autoptr(GOptionContext) opt_context = NULL;
g_auto(GStrv) strv = NULL;
g_autoptr(GError) error = NULL;
gboolean clean = FALSE;
const GOptionEntry entries[] = {
{ "clean", 'c', 0, G_OPTION_ARG_NONE, &clean,
N_("Clean the project") },
{ "device", 'd', 0, G_OPTION_ARG_STRING, &device_id,
N_("The ID of the device to build for"),
N_("local") },
{ "runtime", 'r', 0, G_OPTION_ARG_STRING, &runtime_id,
N_("The runtime to use for building"),
N_("host") },
{ "parallel", 'j', 0, G_OPTION_ARG_INT, &parallel,
N_("Number of workers to use when building"),
N_("N") },
{ "configuration", 't', 0, G_OPTION_ARG_STRING, &configuration_id,
N_("The configuration to use from .buildconfig"),
N_("CONFIG_ID") },
{ "project", 'p', 0, G_OPTION_ARG_FILENAME, &project_path,
N_("Path to project file, defaults to current directory"),
N_("PATH") },
{ NULL }
};
g_assert (IDE_IS_BUILD_TOOL (self));
g_assert (arguments != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
opt_context = g_option_context_new ("build [OPTIONS]");
g_option_context_add_main_entries (opt_context, entries, GETTEXT_PACKAGE);
strv = g_strdupv ((gchar **)arguments);
if (!g_option_context_parse_strv (opt_context, &strv, &error))
{
g_task_return_error (task, g_steal_pointer (&error));
return;
}
if (project_path == NULL)
project_path = g_strdup (".");
project_file = g_file_new_for_commandline_arg (project_path);
if (device_id == NULL)
device_id = g_strdup ("local");
if (clean)
{
/* TODO */
}
ide_context_new_async (project_file,
cancellable,
ide_build_tool_new_context_cb,
g_steal_pointer (&task));
}
static gboolean
ide_build_tool_run_finish (IdeApplicationTool *tool,
GAsyncResult *result,
GError **error)
{
g_assert (IDE_IS_BUILD_TOOL (tool));
g_assert (G_IS_TASK (result));
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
application_tool_init (IdeApplicationToolInterface *iface)
{
iface->run_async = ide_build_tool_run_async;
iface->run_finish = ide_build_tool_run_finish;
}
......@@ -12,8 +12,6 @@ buildui_private_sources = [
'ide-build-plugin.c',
'ide-build-stage-row.c',
'ide-build-stage-row.h',
'ide-build-tool.c',
'ide-build-tool.h',
'ide-build-workbench-addin.c',
'ide-build-workbench-addin.h',
'ide-environment-editor-row.c',
......
......@@ -39,12 +39,12 @@ void ide_configuration_manager_set_current (IdeConfigurationM
IDE_AVAILABLE_IN_ALL
IdeConfiguration *ide_configuration_manager_get_configuration (IdeConfigurationManager *self,
const gchar *id);
IDE_AVAILABLE_IN_ALL
void ide_configuration_manager_add (IdeConfigurationManager *self,
IdeConfiguration *configuration);
IDE_AVAILABLE_IN_ALL
void ide_configuration_manager_remove (IdeConfigurationManager *self,
IdeConfiguration *configuration);
IDE_AVAILABLE_IN_3_28
void ide_configuration_manager_duplicate (IdeConfigurationManager *self,
IdeConfiguration *config);
IDE_AVAILABLE_IN_3_28
void ide_configuration_manager_delete (IdeConfigurationManager *self,
IdeConfiguration *config);
IDE_AVAILABLE_IN_ALL
void ide_configuration_manager_save_async (IdeConfigurationManager *self,
GCancellable *cancellable,
......
......@@ -19,21 +19,28 @@
#define G_LOG_DOMAIN "ide-configuration-provider"
#include "application/ide-application.h"
#include "config/ide-configuration.h"
#include "config/ide-configuration-manager.h"
#include "config/ide-configuration-provider.h"
G_DEFINE_INTERFACE (IdeConfigurationProvider, ide_configuration_provider, IDE_TYPE_OBJECT)
enum {
ADDED,
REMOVED,
N_SIGNALS
};
static guint signals [N_SIGNALS];
static void
ide_configuration_provider_real_load_async (IdeConfigurationProvider *self,
IdeConfigurationManager *manager,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_CONFIGURATION_PROVIDER (self));
g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
g_task_report_new_error (self, callback, user_data,
......@@ -58,12 +65,21 @@ ide_configuration_provider_real_load_finish (IdeConfigurationProvider *self,
}
static void
ide_configuration_provider_real_unload (IdeConfigurationProvider *self,
IdeConfigurationManager *manager)
ide_configuration_provider_real_duplicate (IdeConfigurationProvider *self,
IdeConfiguration *config)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_CONFIGURATION_PROVIDER (self));
g_assert (IDE_IS_CONFIGURATION (config));
}
static void
ide_configuration_provider_real_unload (IdeConfigurationProvider *self)
{
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_CONFIGURATION_PROVIDER (self));
g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
}
void
......@@ -102,26 +118,96 @@ ide_configuration_provider_default_init (IdeConfigurationProviderInterface *ifac
{
iface->load_async = ide_configuration_provider_real_load_async;
iface->load_finish = ide_configuration_provider_real_load_finish;
iface->duplicate = ide_configuration_provider_real_duplicate;
iface->unload = ide_configuration_provider_real_unload;
iface->save_async = ide_configuration_provider_real_save_async;
iface->save_finish = ide_configuration_provider_real_save_finish;
/**
* IdeConfigurationProvider:added:
* @self: an #IdeConfigurationProvider
* @config: an #IdeConfiguration
*
* The "added" signal is emitted when a configuration
* has been added to a configuration provider.
*
* Since: 3.28
*/
signals [ADDED] =
g_signal_new ("added",
G_TYPE_FROM_INTERFACE (iface),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (IdeConfigurationProviderInterface, added),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, IDE_TYPE_CONFIGURATION);
g_signal_set_va_marshaller (signals [ADDED],
G_TYPE_FROM_INTERFACE (iface),
g_cclosure_marshal_VOID__OBJECTv);
/**
* IdeConfigurationProvider:removed:
* @self: an #IdeConfigurationProvider
* @config: an #IdeConfiguration
*
* The "removed" signal is emitted when a configuration
* has been removed from a configuration provider.
*
* Since: 3.28
*/
signals [REMOVED] =
g_signal_new ("removed",
G_TYPE_FROM_INTERFACE (iface),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (IdeConfigurationProviderInterface, removed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, IDE_TYPE_CONFIGURATION);
g_signal_set_va_marshaller (signals [REMOVED],
G_TYPE_FROM_INTERFACE (iface),
g_cclosure_marshal_VOID__OBJECTv);
}
/**
* ide_configuration_provider_load_async:
* @self: a #IdeConfigurationProvider
* @cancellable: (nullable): a #GCancellable or %NULL
* @callback: a callback to execute upon completion
* @user_data: closure data for @callback
*
* This function is called to initialize the configuration provider after
* the plugin instance has been created. The provider should locate any
* build configurations within the project and call
* ide_configuration_provider_emit_added() before completing the
* asynchronous function so that the configuration manager may be made
* aware of the configurations.
*
* Since: 3.28
*/
void
ide_configuration_provider_load_async (IdeConfigurationProvider *self,
IdeConfigurationManager *manager,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (manager));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->load_async (self, manager, cancellable, callback, user_data);
IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->load_async (self, cancellable, callback, user_data);
}
/**
* ide_configuration_provider_load_finish:
* @self: a #IdeConfigurationProvider
* @result: a #GAsyncResult
* @error: a location for a #GError, or %NULL
*
* Completes an asynchronous request to ide_configuration_provider_load_async().
*
* Returns: %TRUE if successful; otherwise %FALSE and @error is set.
*/
gboolean
ide_configuration_provider_load_finish (IdeConfigurationProvider *self,
GAsyncResult *result,
......@@ -134,17 +220,39 @@ ide_configuration_provider_load_finish (IdeConfigurationProvider *self,
return IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->load_finish (self, result, error);
}
/**
* ide_configuration_provider_unload:
* @self: a #IdeConfigurationProvider
*
* Requests that the configuration provider unload any state. This is called
* shortly before the configuration provider is finalized.
*
* Implementations of #IdeConfigurationProvider should emit removed
* for every configuration they have registered so that the
* #IdeConfigurationManager has correct information.
*/
void
ide_configuration_provider_unload (IdeConfigurationProvider *self,
IdeConfigurationManager *manager)
ide_configuration_provider_unload (IdeConfigurationProvider *self)
{
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (manager));
IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->unload (self, manager);
IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->unload (self);
}
/**
* ide_configuration_provider_save_async:
* @self: a #IdeConfigurationProvider
* @cancellable: (nullable): a #GCancellable or %NULL
* @callback: a callback to execute upon completion
* @user_data: closure data for @callback
*
* This function is called to request that the configuration provider
* persist any changed configurations back to disk.
*
* This function will be called before unloading the configuration provider
* so that it has a chance to persist any outstanding changes.
*/
void
ide_configuration_provider_save_async (IdeConfigurationProvider *self,
GCancellable *cancellable,
......@@ -158,6 +266,16 @@ ide_configuration_provider_save_async (IdeConfigurationProvider *self,
IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->save_async (self, cancellable, callback, user_data);
}
/**
* ide_configuration_provider_save_finish:
* @self: a #IdeConfigurationProvider
* @result: a #GAsyncResult
* @error: a location for a #GError, or %NULL
*
* Completes an asynchronous request to ide_configuration_provider_save_async().
*
* Returns: %TRUE if successful; otherwise %FALSE and @error is set.
*/
gboolean
ide_configuration_provider_save_finish (IdeConfigurationProvider *self,
GAsyncResult *result,
......@@ -169,3 +287,100 @@ ide_configuration_provider_save_finish (IdeConfigurationProvider *self,
return IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->save_finish (self, result, error);
}
/**
* ide_configuration_provider_emit_added:
* @self: an #IdeConfigurationProvider
* @config: an #IdeConfiguration
*
* #IdeConfigurationProvider implementations should call this function with
* a @config when it has discovered a new configuration.
*
* Since: 3.28
*/
void
ide_configuration_provider_emit_added (IdeConfigurationProvider *self,
IdeConfiguration *config)
{
g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
g_return_if_fail (IDE_IS_CONFIGURATION (config));
g_signal_emit (self, signals [ADDED], 0, config);
}
/**
* ide_configuration_provider_emit_removed:
* @self: an #IdeConfigurationProvider
* @config: an #IdeConfiguration
*
* #IdeConfigurationProvider implementations should call this function with
* a @config when it has discovered it was removed.
*
* Since: 3.28
*/
void
ide_configuration_provider_emit_removed (IdeConfigurationProvider *self,
IdeConfiguration *config)
{
g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
g_return_if_fail (IDE_IS_CONFIGURATION (config));
g_signal_emit (self, signals [REMOVED], 0, config);
}
/**