gem-graph-client/libide/foundry/ide-build-system.c

820 lines
26 KiB
C

/* ide-build-system.c
*
* Copyright 2015-2019 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/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-build-system"
#include "config.h"
#include <libide-code.h>
#include <libide-io.h>
#include <libide-projects.h>
#include <libide-threading.h>
#include <libide-vcs.h>
#include <string.h>
#include "ide-gfile-private.h"
#include "ide-build-manager.h"
#include "ide-pipeline.h"
#include "ide-build-system.h"
#include "ide-config.h"
#include "ide-device.h"
#include "ide-foundry-compat.h"
#include "ide-toolchain.h"
G_DEFINE_INTERFACE (IdeBuildSystem, ide_build_system, IDE_TYPE_OBJECT)
enum {
PROP_0,
PROP_PROJECT_FILE,
N_PROPS
};
typedef struct
{
GPtrArray *files;
GHashTable *flags;
guint index;
} GetBuildFlagsData;
static GParamSpec *properties [N_PROPS];
static void
get_build_flags_data_free (GetBuildFlagsData *data)
{
g_clear_pointer (&data->files, g_ptr_array_unref);
g_clear_pointer (&data->flags, g_hash_table_unref);
g_slice_free (GetBuildFlagsData, data);
}
gint
ide_build_system_get_priority (IdeBuildSystem *self)
{
IdeBuildSystemInterface *iface;
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), 0);
iface = IDE_BUILD_SYSTEM_GET_IFACE (self);
if (iface->get_priority != NULL)
return iface->get_priority (self);
return 0;
}
static void
ide_build_system_real_get_build_flags_async (IdeBuildSystem *self,
GFile *file,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeContext) context = NULL;
g_autoptr(IdeTask) task = NULL;
g_autoptr(GError) error = NULL;
g_auto(GStrv) parsed_flags = NULL;
IdeBuildManager *build_manager;
IdeEnvironment *env;
const gchar *flags = NULL;
const gchar *path;
IdePipeline *pipeline;
IdeConfig *config;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_BUILD_SYSTEM (self));
g_assert (G_IS_FILE (file));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_build_system_real_get_build_flags_async);
/* Avoid work immediately if we can */
if (ide_task_return_error_if_cancelled (task))
return;
if (!g_file_is_native (file) || !(path = g_file_peek_path (file)))
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"Cannot get build flags for non-native file");
return;
}
if (!(context = ide_object_ref_context (IDE_OBJECT (self))) ||
!ide_context_has_project (context) ||
!(build_manager = ide_build_manager_from_context (context)) ||
!(pipeline = ide_build_manager_get_pipeline (build_manager)) ||
!(config = ide_pipeline_get_config (pipeline)) ||
!(env = ide_config_get_environment (config)))
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_INITIALIZED,
"Cannot access build flags without build config");
return;
}
if (ide_path_is_cpp_like (path))
{
flags = ide_environment_getenv (env, "CXXFLAGS");
}
else if (ide_path_is_c_like (path))
{
if (!(flags = ide_environment_getenv (env, "CFLAGS")))
flags = ide_environment_getenv (env, "CXXFLAGS");
}
else
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"Cannot extract build flags for unknown file type: \"%s\"",
path);
return;
}
if (flags == NULL)
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"No CFLAGS or CXXFLAGS environment variables were specified");
return;
}
if (!g_shell_parse_argv (flags, NULL, &parsed_flags, &error))
ide_task_return_error (task, g_steal_pointer (&error));
else
ide_task_return_pointer (task, g_steal_pointer (&parsed_flags), g_strfreev);
}
static gchar **
ide_build_system_real_get_build_flags_finish (IdeBuildSystem *self,
GAsyncResult *result,
GError **error)
{
return ide_task_propagate_pointer (IDE_TASK (result), error);
}
static void
ide_build_system_get_build_flags_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuildSystem *self = (IdeBuildSystem *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
g_auto(GStrv) flags = NULL;
GetBuildFlagsData *data;
GFile *file;
g_assert (IDE_IS_BUILD_SYSTEM (self));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
data = ide_task_get_task_data (task);
g_assert (data != NULL);
g_assert (data->files != NULL);
g_assert (data->files->len > 0);
g_assert (data->index < data->files->len);
g_assert (data->flags != NULL);
file = g_ptr_array_index (data->files, data->index);
g_assert (G_IS_FILE (file));
data->index++;
flags = ide_build_system_get_build_flags_finish (self, result, &error);
if (error != NULL)
g_debug ("Failed to load build flags for \"%s\": %s",
g_file_peek_path (file),
error->message);
else
g_hash_table_insert (data->flags, g_object_ref (file), g_steal_pointer (&flags));
if (ide_task_return_error_if_cancelled (task))
return;
if (data->index < data->files->len)
{
GCancellable *cancellable = ide_task_get_cancellable (task);
file = g_ptr_array_index (data->files, data->index);
g_assert (G_IS_FILE (file));
ide_build_system_get_build_flags_async (self,
file,
cancellable,
ide_build_system_get_build_flags_cb,
g_steal_pointer (&task));
return;
}
ide_task_return_pointer (task,
g_steal_pointer (&data->flags),
g_hash_table_unref);
}
static GPtrArray *
copy_files (GPtrArray *in)
{
GPtrArray *out = g_ptr_array_new_full (in->len, g_object_unref);
for (guint i = 0; i < in->len; i++)
g_ptr_array_add (out, g_file_dup (g_ptr_array_index (in, i)));
return g_steal_pointer (&out);
}
static void
ide_build_system_real_get_build_flags_for_files_async (IdeBuildSystem *self,
GPtrArray *files,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
GetBuildFlagsData *data;
g_return_if_fail (IDE_IS_BUILD_SYSTEM (self));
g_return_if_fail (files != NULL);
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_build_system_real_get_build_flags_for_files_async);
ide_task_set_priority (task, G_PRIORITY_LOW);
if (files->len == 0)
{
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_INVAL,
"No files were provided");
return;
}
g_assert (files->len > 0);
g_assert (G_IS_FILE (g_ptr_array_index (files, 0)));
if (ide_task_return_error_if_cancelled (task))
return;
data = g_slice_new0 (GetBuildFlagsData);
data->files = copy_files (files);
data->flags = g_hash_table_new_full ((GHashFunc)g_file_hash,
(GEqualFunc)g_file_equal,
g_object_unref,
(GDestroyNotify)g_strfreev);
ide_task_set_task_data (task, data, get_build_flags_data_free);
ide_build_system_get_build_flags_async (self,
g_ptr_array_index (files, 0),
cancellable,
ide_build_system_get_build_flags_cb,
g_steal_pointer (&task));
}
static GHashTable *
ide_build_system_real_get_build_flags_for_files_finish (IdeBuildSystem *self,
GAsyncResult *result,
GError **error)
{
return ide_task_propagate_pointer (IDE_TASK (result), error);
}
static void
ide_build_system_default_init (IdeBuildSystemInterface *iface)
{
iface->get_build_flags_async = ide_build_system_real_get_build_flags_async;
iface->get_build_flags_finish = ide_build_system_real_get_build_flags_finish;
iface->get_build_flags_for_files_async = ide_build_system_real_get_build_flags_for_files_async;
iface->get_build_flags_for_files_finish = ide_build_system_real_get_build_flags_for_files_finish;
properties [PROP_PROJECT_FILE] =
g_param_spec_object ("project-file",
"Project File",
"The project file.",
G_TYPE_FILE,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_interface_install_property (iface, properties [PROP_PROJECT_FILE]);
}
static gchar *
ide_build_system_translate (IdeBuildSystem *self,
IdePipeline *pipeline,
const gchar *prefix,
const gchar *path)
{
g_autofree gchar *freeme = NULL;
g_autofree gchar *translated_path = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GFile) translated = NULL;
IdeRuntime *runtime;
g_assert (IDE_IS_BUILD_SYSTEM (self));
g_assert (!pipeline || IDE_IS_PIPELINE (pipeline));
g_assert (prefix != NULL);
g_assert (path != NULL);
if (NULL == pipeline ||
NULL == (runtime = ide_pipeline_get_runtime (pipeline)))
return g_strdup_printf ("%s%s", prefix, path);
if (!g_path_is_absolute (path))
path = freeme = ide_pipeline_build_builddir_path (pipeline, path, NULL);
file = g_file_new_for_path (path);
translated = ide_runtime_translate_file (runtime, file);
translated_path = g_file_get_path (translated);
return g_strdup_printf ("%s%s", prefix, translated_path);
}
static void
ide_build_system_post_process_build_flags (IdeBuildSystem *self,
gchar **flags)
{
IdePipeline *pipeline;
IdeBuildManager *build_manager;
IdeContext *context;
g_assert (IDE_IS_BUILD_SYSTEM (self));
if (flags == NULL || flags[0] == NULL)
return;
context = ide_object_get_context (IDE_OBJECT (self));
build_manager = ide_build_manager_from_context (context);
pipeline = ide_build_manager_get_pipeline (build_manager);
for (guint i = 0; flags[i] != NULL; i++)
{
gchar *flag = flags[i];
gchar *translated;
if (flag[0] != '-')
continue;
switch (flag[1])
{
case 'I':
if (flag[2] == '\0')
{
if (flags[i+1] != NULL)
{
translated = ide_build_system_translate (self, pipeline, "", flags[++i]);
flags[i] = translated;
g_free (flag);
}
}
else
{
translated = ide_build_system_translate (self, pipeline, "-I", &flag[2]);
flags[i] = translated;
g_free (flag);
}
break;
case 'D':
case 'x':
if (strlen (flag) == 2)
i++;
break;
case 'f': /* -fPIC */
case 'W': /* -Werror... */
case 'm': /* -m64 -mtune=native */
default:
break;
}
}
}
void
ide_build_system_get_build_flags_async (IdeBuildSystem *self,
GFile *file,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
IDE_ENTRY;
g_return_if_fail (IDE_IS_BUILD_SYSTEM (self));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_flags_async (self, file, cancellable, callback, user_data);
IDE_EXIT;
}
/**
* ide_build_system_get_build_flags_finish:
*
* Returns: (transfer full):
*
* Since: 3.32
*/
gchar **
ide_build_system_get_build_flags_finish (IdeBuildSystem *self,
GAsyncResult *result,
GError **error)
{
gchar **ret;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
ret = IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_flags_finish (self, result, error);
if (ret != NULL)
ide_build_system_post_process_build_flags (self, ret);
#ifdef IDE_ENABLE_TRACE
if (ret != NULL)
{
g_autoptr(GString) str = g_string_new (NULL);
for (guint i = 0; ret[i]; i++)
{
g_autofree char *escaped = g_shell_quote (ret[i]);
g_string_append (str, escaped);
g_string_append_c (str, ' ');
}
IDE_TRACE_MSG ("build_flags = %s", str->str);
}
#endif
IDE_RETURN (ret);
}
/**
* ide_build_system_get_build_flags_for_files_async:
* @self: An #IdeBuildSystem instance.
* @files: (element-type GFile) (transfer none): array of files whose build flags has to be retrieved.
* @cancellable: (allow-none): a #GCancellable to cancel getting build flags.
* @callback: function to be called after getting build flags.
* @user_data: data to pass to @callback.
*
* This function will get build flags for all files and returns
* map of file and its build flags as #GHashTable.
*
* Since: 3.32
*/
void
ide_build_system_get_build_flags_for_files_async (IdeBuildSystem *self,
GPtrArray *files,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
IDE_ENTRY;
g_return_if_fail (IDE_IS_BUILD_SYSTEM (self));
g_return_if_fail ( files != NULL);
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_flags_for_files_async (self, files, cancellable, callback, user_data);
IDE_EXIT;
}
/**
* ide_build_system_get_build_flags_for_files_finish:
* @self: an #IdeBuildSystem
* @result: a #GAsyncResult
* @error: a location for a #GError or %NULL
*
* Returns: (element-type Ide.File GStrv) (transfer full): a #GHashTable or #GFile to #GStrv
*
* Since: 3.32
*/
GHashTable *
ide_build_system_get_build_flags_for_files_finish (IdeBuildSystem *self,
GAsyncResult *result,
GError **error)
{
GHashTable *ret;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
ret = IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_flags_for_files_finish (self, result, error);
if (ret != NULL)
{
GHashTableIter iter;
gchar **flags;
g_hash_table_iter_init (&iter, ret);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&flags))
ide_build_system_post_process_build_flags (self, flags);
}
IDE_RETURN (ret);
}
gchar *
ide_build_system_get_builddir (IdeBuildSystem *self,
IdePipeline *pipeline)
{
gchar *ret = NULL;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
g_return_val_if_fail (IDE_IS_PIPELINE (pipeline), NULL);
if (IDE_BUILD_SYSTEM_GET_IFACE (self)->get_builddir)
ret = IDE_BUILD_SYSTEM_GET_IFACE (self)->get_builddir (self, pipeline);
if (ret == NULL)
{
g_autofree gchar *name = NULL;
g_autofree gchar *branch = NULL;
g_autoptr(GFile) base = NULL;
g_autoptr(GFile) nosymlink = NULL;
IdeConfig *config;
const gchar *config_id;
const gchar *runtime_id;
const gchar *arch;
IdeRuntime *runtime;
IdeContext *context;
IdeVcs *vcs;
context = ide_object_get_context (IDE_OBJECT (self));
vcs = ide_vcs_from_context (context);
config = ide_pipeline_get_config (pipeline);
config_id = ide_config_get_id (config);
runtime = ide_pipeline_get_runtime (pipeline);
runtime_id = ide_runtime_get_short_id (runtime);
branch = ide_vcs_get_branch_name (vcs);
arch = ide_pipeline_get_arch (pipeline);
if (branch != NULL)
name = g_strdup_printf ("%s-%s-%s-%s", config_id, runtime_id, arch, branch);
else
name = g_strdup_printf ("%s-%s-%s", config_id, runtime_id, arch);
g_strdelimit (name, "@:/ ", '-');
/* Avoid symlink's when we can, so that paths with symlinks have at least
* a chance of working.
*/
base = ide_context_cache_file (context, "builds", name, NULL);
nosymlink = _ide_g_file_readlink (base);
ret = g_file_get_path (nosymlink);
}
IDE_RETURN (ret);
}
gchar *
ide_build_system_get_id (IdeBuildSystem *self)
{
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
if (IDE_BUILD_SYSTEM_GET_IFACE (self)->get_id)
return IDE_BUILD_SYSTEM_GET_IFACE (self)->get_id (self);
return g_strdup (G_OBJECT_TYPE_NAME (self));
}
gchar *
ide_build_system_get_display_name (IdeBuildSystem *self)
{
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
if (IDE_BUILD_SYSTEM_GET_IFACE (self)->get_display_name)
return IDE_BUILD_SYSTEM_GET_IFACE (self)->get_display_name (self);
return ide_build_system_get_id (self);
}
static void
ide_build_system_get_build_flags_for_dir_cb2 (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuildSystem *build_system = (IdeBuildSystem *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
g_autoptr(GHashTable) ret = NULL;
g_assert (IDE_IS_BUILD_SYSTEM (build_system));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
ret = ide_build_system_get_build_flags_for_files_finish (build_system, result, &error);
if (ret == NULL)
ide_task_return_error (task, g_steal_pointer (&error));
else
ide_task_return_pointer (task,
g_steal_pointer (&ret),
g_hash_table_unref);
}
static void
ide_build_system_get_build_flags_for_dir_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GFile *dir = (GFile *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
g_autoptr(GPtrArray) infos = NULL;
g_autoptr(GPtrArray) files = NULL;
IdeBuildSystem *self;
GCancellable *cancellable;
IdeContext *context;
IdeVcs *vcs;
g_assert (G_IS_FILE (dir));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
infos = ide_g_file_get_children_finish (dir, result, &error);
IDE_PTR_ARRAY_SET_FREE_FUNC (infos, g_object_unref);
if (infos == NULL)
{
ide_task_return_error (task, g_steal_pointer (&error));
return;
}
self = ide_task_get_source_object (task);
context = ide_object_get_context (IDE_OBJECT (self));
vcs = ide_vcs_from_context (context);
cancellable = ide_task_get_cancellable (task);
files = g_ptr_array_new_with_free_func (g_object_unref);
for (guint i = 0; i < infos->len; i++)
{
GFileInfo *file_info = g_ptr_array_index (infos, i);
GFileType file_type = g_file_info_get_file_type (file_info);
if (file_type == G_FILE_TYPE_REGULAR)
{
const gchar *name = g_file_info_get_name (file_info);
g_autoptr(GFile) child = g_file_get_child (dir, name);
if (!ide_vcs_is_ignored (vcs, child, NULL))
g_ptr_array_add (files, g_steal_pointer (&child));
}
}
ide_build_system_get_build_flags_for_files_async (self,
files,
cancellable,
ide_build_system_get_build_flags_for_dir_cb2,
g_steal_pointer (&task));
}
void
ide_build_system_get_build_flags_for_dir_async (IdeBuildSystem *self,
GFile *directory,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
g_return_if_fail (IDE_IS_BUILD_SYSTEM (self));
g_return_if_fail (G_IS_FILE (directory));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_build_system_get_build_flags_for_dir_async);
ide_task_set_priority (task, G_PRIORITY_LOW);
ide_g_file_get_children_async (directory,
G_FILE_ATTRIBUTE_STANDARD_NAME","
G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_LOW,
cancellable,
ide_build_system_get_build_flags_for_dir_cb,
g_steal_pointer (&task));
}
/**
* ide_build_system_get_build_flags_for_dir_finish:
* @self: an #IdeBuildSystem
* @result: a #GAsyncResult
* @error: a location for a #GError or %NULL
*
* Returns: (element-type Ide.File GStrv) (transfer full): a #GHashTable of #GFile to #GStrv
*
* Since: 3.32
*/
GHashTable *
ide_build_system_get_build_flags_for_dir_finish (IdeBuildSystem *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
return ide_task_propagate_pointer (IDE_TASK (result), error);
}
/**
* ide_build_system_supports_toolchain:
* @self: an #IdeBuildSystem
* @toolchain: a #IdeToolchain
*
* Checks whether the build system supports the given toolchain.
*
* Returns: %TRUE if the toolchain is supported by the build system, %FALSE otherwise
*
* Since: 3.32
*/
gboolean
ide_build_system_supports_toolchain (IdeBuildSystem *self,
IdeToolchain *toolchain)
{
const gchar *toolchain_id;
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), FALSE);
g_return_val_if_fail (IDE_IS_TOOLCHAIN (toolchain), FALSE);
toolchain_id = ide_toolchain_get_id (toolchain);
if (g_strcmp0 (toolchain_id, "default") == 0)
return TRUE;
if (IDE_BUILD_SYSTEM_GET_IFACE (self)->supports_toolchain)
return IDE_BUILD_SYSTEM_GET_IFACE (self)->supports_toolchain (self, toolchain);
return FALSE;
}
/**
* ide_build_system_get_project_version:
* @self: a #IdeBuildSystem
*
* If the build system supports it, gets the project version as configured
* in the build system's configuration files.
*
* Returns: (transfer full) (nullable): a string containing the project version
*
* Since: 3.32
*/
gchar *
ide_build_system_get_project_version (IdeBuildSystem *self)
{
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
if (IDE_BUILD_SYSTEM_GET_IFACE (self)->get_project_version)
return IDE_BUILD_SYSTEM_GET_IFACE (self)->get_project_version (self);
return NULL;
}
/**
* ide_build_system_supports_language:
* @self: a #IdeBuildSystem
* @language: the language identifier
*
* Returns %TRUE if @self in it's current configuration is known to support @language.
*
* Returns: %TRUE if @language is supported, otherwise %FALSE.
*
* Since: 41.0
*/
gboolean
ide_build_system_supports_language (IdeBuildSystem *self,
const char *language)
{
g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), FALSE);
g_return_val_if_fail (language != NULL, FALSE);
if (IDE_BUILD_SYSTEM_GET_IFACE (self)->supports_language)
return IDE_BUILD_SYSTEM_GET_IFACE (self)->supports_language (self, language);
return FALSE;
}