1 /* gbp-meson-build-system.c
2 *
3 * Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "gbp-meson-build-system"
22
23 #include <glib/gi18n.h>
24 #include <json-glib/json-glib.h>
25
26 #include "gbp-meson-build-system.h"
27 #include "gbp-meson-build-target.h"
28 #include "gbp-meson-toolchain.h"
29
30 struct _GbpMesonBuildSystem
31 {
32 IdeObject parent_instance;
33 GFile *project_file;
34 IdeCompileCommands *compile_commands;
35 GFileMonitor *monitor;
36 gchar *project_version;
37 gchar **languages;
38 };
39
40 static void async_initable_iface_init (GAsyncInitableIface *iface);
41 static void build_system_iface_init (IdeBuildSystemInterface *iface);
42
43 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpMesonBuildSystem, gbp_meson_build_system, IDE_TYPE_OBJECT,
44 G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
45 G_IMPLEMENT_INTERFACE (IDE_TYPE_BUILD_SYSTEM, build_system_iface_init))
46
47 enum {
48 PROP_0,
49 PROP_PROJECT_FILE,
50 N_PROPS
51 };
52
53 static GParamSpec *properties [N_PROPS];
54
55 static void
gbp_meson_build_system_ensure_config_cb(GObject * object,GAsyncResult * result,gpointer user_data)56 gbp_meson_build_system_ensure_config_cb (GObject *object,
57 GAsyncResult *result,
58 gpointer user_data)
59 {
60 IdeBuildManager *build_manager = (IdeBuildManager *)object;
61 g_autoptr(IdeTask) task = user_data;
62 g_autoptr(GError) error = NULL;
63
64 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
65 g_assert (G_IS_ASYNC_RESULT (result));
66 g_assert (IDE_IS_TASK (task));
67
68 if (!ide_build_manager_build_finish (build_manager, result, &error))
69 ide_task_return_error (task, g_steal_pointer (&error));
70 else
71 ide_task_return_boolean (task, TRUE);
72 }
73
74 static void
gbp_meson_build_system_ensure_config_async(GbpMesonBuildSystem * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)75 gbp_meson_build_system_ensure_config_async (GbpMesonBuildSystem *self,
76 GCancellable *cancellable,
77 GAsyncReadyCallback callback,
78 gpointer user_data)
79 {
80 g_autoptr(IdeTask) task = NULL;
81 IdeBuildManager *build_manager;
82 IdeContext *context;
83
84 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
85 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
86
87 task = ide_task_new (self, cancellable, callback, user_data);
88 ide_task_set_source_tag (task, gbp_meson_build_system_ensure_config_async);
89 ide_task_set_priority (task, G_PRIORITY_LOW);
90
91 context = ide_object_get_context (IDE_OBJECT (self));
92 build_manager = ide_build_manager_from_context (context);
93
94 ide_build_manager_build_async (build_manager,
95 IDE_PIPELINE_PHASE_CONFIGURE,
96 NULL,
97 cancellable,
98 gbp_meson_build_system_ensure_config_cb,
99 g_steal_pointer (&task));
100 }
101
102 static gboolean
gbp_meson_build_system_ensure_config_finish(GbpMesonBuildSystem * self,GAsyncResult * result,GError ** error)103 gbp_meson_build_system_ensure_config_finish (GbpMesonBuildSystem *self,
104 GAsyncResult *result,
105 GError **error)
106 {
107 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
108 g_assert (IDE_IS_TASK (result));
109
110 return ide_task_propagate_boolean (IDE_TASK (result), error);
111 }
112
113 static void
gbp_meson_build_system_monitor_changed(GbpMesonBuildSystem * self,GFile * file,GFile * other_file,GFileMonitorEvent event,GFileMonitor * monitor)114 gbp_meson_build_system_monitor_changed (GbpMesonBuildSystem *self,
115 GFile *file,
116 GFile *other_file,
117 GFileMonitorEvent event,
118 GFileMonitor *monitor)
119 {
120 IDE_ENTRY;
121
122 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
123 g_assert (!file || G_IS_FILE (file));
124 g_assert (!other_file || G_IS_FILE (other_file));
125 g_assert (G_IS_FILE_MONITOR (monitor));
126
127 /* Release our previous compile commands */
128 g_clear_object (&self->compile_commands);
129 g_file_monitor_cancel (monitor);
130 g_clear_object (&self->monitor);
131
132 IDE_EXIT;
133 }
134
135 static void
gbp_meson_build_system_monitor(GbpMesonBuildSystem * self,GFile * file)136 gbp_meson_build_system_monitor (GbpMesonBuildSystem *self,
137 GFile *file)
138 {
139 g_autoptr(GFileMonitor) monitor = NULL;
140
141 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
142 g_assert (G_IS_FILE (file));
143
144 monitor = g_file_monitor_file (file,
145 G_FILE_MONITOR_NONE,
146 NULL,
147 NULL);
148 g_signal_connect_object (monitor,
149 "changed",
150 G_CALLBACK (gbp_meson_build_system_monitor_changed),
151 self,
152 G_CONNECT_SWAPPED);
153 g_set_object (&self->monitor, monitor);
154 }
155
156 static void
gbp_meson_build_system_load_commands_load_cb(GObject * object,GAsyncResult * result,gpointer user_data)157 gbp_meson_build_system_load_commands_load_cb (GObject *object,
158 GAsyncResult *result,
159 gpointer user_data)
160 {
161 IdeCompileCommands *compile_commands = (IdeCompileCommands *)object;
162 GbpMesonBuildSystem *self;
163 g_autoptr(IdeTask) task = user_data;
164 g_autoptr(GError) error = NULL;
165
166 g_assert (IDE_IS_COMPILE_COMMANDS (compile_commands));
167 g_assert (G_IS_ASYNC_RESULT (result));
168 g_assert (IDE_IS_TASK (task));
169
170 self = ide_task_get_source_object (task);
171
172 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
173
174 if (!ide_compile_commands_load_finish (compile_commands, result, &error))
175 {
176 ide_task_return_error (task, g_steal_pointer (&error));
177 return;
178 }
179
180 g_set_object (&self->compile_commands, compile_commands);
181 ide_task_return_pointer (task, g_object_ref (compile_commands), g_object_unref);
182 }
183
184 static void
gbp_meson_build_system_load_commands_config_cb(GObject * object,GAsyncResult * result,gpointer user_data)185 gbp_meson_build_system_load_commands_config_cb (GObject *object,
186 GAsyncResult *result,
187 gpointer user_data)
188 {
189 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)object;
190 g_autoptr(IdeCompileCommands) compile_commands = NULL;
191 g_autoptr(IdeTask) task = user_data;
192 g_autoptr(GError) error = NULL;
193 g_autoptr(GFile) file = NULL;
194 g_autofree gchar *path = NULL;
195 IdeBuildManager *build_manager;
196 IdePipeline *pipeline;
197 GCancellable *cancellable;
198 IdeContext *context;
199
200 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
201 g_assert (G_IS_ASYNC_RESULT (result));
202 g_assert (IDE_IS_TASK (task));
203
204 if (!gbp_meson_build_system_ensure_config_finish (self, result, &error))
205 {
206 ide_task_return_error (task, g_steal_pointer (&error));
207 return;
208 }
209
210 context = ide_object_get_context (IDE_OBJECT (self));
211 build_manager = ide_build_manager_from_context (context);
212 pipeline = ide_build_manager_get_pipeline (build_manager);
213
214 if (pipeline == NULL)
215 {
216 /* Unlikely, but possible */
217 ide_task_return_new_error (task,
218 G_IO_ERROR,
219 G_IO_ERROR_FAILED,
220 "No build pipeline is available");
221 return;
222 }
223
224 path = ide_pipeline_build_builddir_path (pipeline, "compile_commands.json", NULL);
225
226 if (!g_file_test (path, G_FILE_TEST_IS_REGULAR))
227 {
228 /* Unlikely, but possible */
229 ide_task_return_new_error (task,
230 G_IO_ERROR,
231 G_IO_ERROR_NOT_FOUND,
232 "Failed to locate compile_commands.json");
233 return;
234 }
235
236 compile_commands = ide_compile_commands_new ();
237 file = g_file_new_for_path (path);
238 cancellable = ide_task_get_cancellable (task);
239
240 ide_compile_commands_load_async (compile_commands,
241 file,
242 cancellable,
243 gbp_meson_build_system_load_commands_load_cb,
244 g_steal_pointer (&task));
245
246 gbp_meson_build_system_monitor (self, file);
247 }
248
249 static void
gbp_meson_build_system_load_commands_async(GbpMesonBuildSystem * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)250 gbp_meson_build_system_load_commands_async (GbpMesonBuildSystem *self,
251 GCancellable *cancellable,
252 GAsyncReadyCallback callback,
253 gpointer user_data)
254 {
255 g_autoptr(IdeTask) task = NULL;
256 g_autofree gchar *path = NULL;
257 IdeBuildManager *build_manager;
258 IdePipeline *pipeline;
259 IdeContext *context;
260
261 IDE_ENTRY;
262
263 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
264 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
265
266 task = ide_task_new (self, cancellable, callback, user_data);
267 ide_task_set_source_tag (task, gbp_meson_build_system_load_commands_async);
268
269 /*
270 * If we've already load the compile commands database, use it and
271 * short circuit as early as we can to avoid progressing the build
272 * pipeline unnecessarily.
273 */
274
275 if (self->compile_commands != NULL)
276 {
277 ide_task_return_pointer (task,
278 g_object_ref (self->compile_commands),
279 g_object_unref);
280 IDE_EXIT;
281 }
282
283 /*
284 * If the build pipeline has been previously configured, we might
285 * already have a "compile_commands.json" file in the build directory
286 * that we can reuse.
287 */
288
289 context = ide_object_get_context (IDE_OBJECT (self));
290 build_manager = ide_build_manager_from_context (context);
291 pipeline = ide_build_manager_get_pipeline (build_manager);
292
293 /*
294 * Because we're accessing the pipeline directly, we need to be careful
295 * here about whether or not it is setup fully. It may be delayed due
296 * to device initialization.
297 */
298 if (pipeline == NULL || !ide_pipeline_is_ready (pipeline))
299 {
300 ide_task_return_new_error (task,
301 G_IO_ERROR,
302 G_IO_ERROR_NOT_INITIALIZED,
303 "There is no pipeline to access");
304 IDE_EXIT;
305 }
306
307 path = ide_pipeline_build_builddir_path (pipeline, "compile_commands.json", NULL);
308
309 if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
310 {
311 g_autoptr(IdeCompileCommands) compile_commands = NULL;
312 g_autoptr(GFile) file = NULL;
313
314 compile_commands = ide_compile_commands_new ();
315 file = g_file_new_for_path (path);
316
317 ide_compile_commands_load_async (compile_commands,
318 file,
319 cancellable,
320 gbp_meson_build_system_load_commands_load_cb,
321 g_steal_pointer (&task));
322
323 gbp_meson_build_system_monitor (self, file);
324
325 IDE_EXIT;
326 }
327
328 /*
329 * Because we're accessing the pipeline directly, we need to be careful
330 * here about whether or not it is setup fully. It may be delayed due
331 * to device initialization.
332 */
333 if (!ide_pipeline_is_ready (pipeline))
334 {
335 ide_task_return_new_error (task,
336 G_IO_ERROR,
337 G_IO_ERROR_NOT_INITIALIZED,
338 "The pipeline is not yet ready to handle requests");
339 IDE_EXIT;
340 }
341
342 /*
343 * It looks like we need to ensure the build pipeline advances to the the
344 * CONFIGURE phase so that meson has generated a new compile_commands.json
345 * that we can load.
346 */
347
348 gbp_meson_build_system_ensure_config_async (self,
349 cancellable,
350 gbp_meson_build_system_load_commands_config_cb,
351 g_steal_pointer (&task));
352
353 IDE_EXIT;
354 }
355
356 static IdeCompileCommands *
gbp_meson_build_system_load_commands_finish(GbpMesonBuildSystem * self,GAsyncResult * result,GError ** error)357 gbp_meson_build_system_load_commands_finish (GbpMesonBuildSystem *self,
358 GAsyncResult *result,
359 GError **error)
360 {
361 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
362 g_assert (IDE_IS_TASK (result));
363
364 return ide_task_propagate_pointer (IDE_TASK (result), error);
365 }
366
367 static void
gbp_meson_build_system_set_project_file(GbpMesonBuildSystem * self,GFile * file)368 gbp_meson_build_system_set_project_file (GbpMesonBuildSystem *self,
369 GFile *file)
370 {
371 g_autofree gchar *name = NULL;
372
373 g_assert (IDE_IS_MAIN_THREAD ());
374 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
375 g_assert (G_IS_FILE (file));
376
377 name = g_file_get_basename (file);
378
379 if (ide_str_equal0 (name, "meson.build"))
380 self->project_file = g_file_dup (file);
381 else
382 self->project_file = g_file_get_child (file, "meson.build");
383 }
384
385 static void
gbp_meson_build_system_finalize(GObject * object)386 gbp_meson_build_system_finalize (GObject *object)
387 {
388 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)object;
389
390 g_clear_object (&self->project_file);
391 g_clear_object (&self->compile_commands);
392 g_clear_object (&self->monitor);
393 g_clear_pointer (&self->project_version, g_free);
394 g_clear_pointer (&self->languages, g_strfreev);
395
396 G_OBJECT_CLASS (gbp_meson_build_system_parent_class)->finalize (object);
397 }
398
399 static void
gbp_meson_build_system_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)400 gbp_meson_build_system_get_property (GObject *object,
401 guint prop_id,
402 GValue *value,
403 GParamSpec *pspec)
404 {
405 GbpMesonBuildSystem *self = GBP_MESON_BUILD_SYSTEM (object);
406
407 switch (prop_id)
408 {
409 case PROP_PROJECT_FILE:
410 g_value_set_object (value, self->project_file);
411 break;
412
413 default:
414 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
415 }
416 }
417
418 static void
gbp_meson_build_system_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)419 gbp_meson_build_system_set_property (GObject *object,
420 guint prop_id,
421 const GValue *value,
422 GParamSpec *pspec)
423 {
424 GbpMesonBuildSystem *self = GBP_MESON_BUILD_SYSTEM (object);
425
426 switch (prop_id)
427 {
428 case PROP_PROJECT_FILE:
429 gbp_meson_build_system_set_project_file (self, g_value_get_object (value));
430 break;
431
432 default:
433 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
434 }
435 }
436
437 static void
gbp_meson_build_system_class_init(GbpMesonBuildSystemClass * klass)438 gbp_meson_build_system_class_init (GbpMesonBuildSystemClass *klass)
439 {
440 GObjectClass *object_class = G_OBJECT_CLASS (klass);
441
442 object_class->finalize = gbp_meson_build_system_finalize;
443 object_class->get_property = gbp_meson_build_system_get_property;
444 object_class->set_property = gbp_meson_build_system_set_property;
445
446 properties [PROP_PROJECT_FILE] =
447 g_param_spec_object ("project-file",
448 "Project File",
449 "The primary meson.build for the project",
450 G_TYPE_FILE,
451 (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
452
453 g_object_class_install_properties (object_class, N_PROPS, properties);
454 }
455
456 static void
gbp_meson_build_system_init(GbpMesonBuildSystem * self)457 gbp_meson_build_system_init (GbpMesonBuildSystem *self)
458 {
459 }
460
461 static gchar *
gbp_meson_build_system_get_id(IdeBuildSystem * build_system)462 gbp_meson_build_system_get_id (IdeBuildSystem *build_system)
463 {
464 return g_strdup ("meson");
465 }
466
467 static gchar *
gbp_meson_build_system_get_display_name(IdeBuildSystem * build_system)468 gbp_meson_build_system_get_display_name (IdeBuildSystem *build_system)
469 {
470 return g_strdup (_("Meson"));
471 }
472
473 static gint
gbp_meson_build_system_get_priority(IdeBuildSystem * build_system)474 gbp_meson_build_system_get_priority (IdeBuildSystem *build_system)
475 {
476 return -400;
477 }
478
479 static void
gbp_meson_build_system_get_build_flags_for_files_cb(GObject * object,GAsyncResult * result,gpointer user_data)480 gbp_meson_build_system_get_build_flags_for_files_cb (GObject *object,
481 GAsyncResult *result,
482 gpointer user_data)
483 {
484 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)object;
485 g_autoptr(IdeCompileCommands) compile_commands = NULL;
486 g_autoptr(IdeTask) task = user_data;
487 g_autoptr(GError) error = NULL;
488 g_autoptr(GHashTable) ret = NULL;
489 g_auto(GStrv) system_includes = NULL;
490 IdeConfigManager *config_manager;
491 IdeConfig *config;
492 IdeContext *context;
493 IdeRuntime *runtime;
494 GPtrArray *files;
495
496 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
497 g_assert (G_IS_ASYNC_RESULT (result));
498 g_assert (IDE_IS_TASK (task));
499
500 if (!(compile_commands = gbp_meson_build_system_load_commands_finish (self, result, &error)))
501 {
502 ide_task_return_error (task, g_steal_pointer (&error));
503 return;
504 }
505
506 files = ide_task_get_task_data (task);
507 g_assert (files != NULL);
508
509 /* Get non-standard system includes */
510 context = ide_object_get_context (IDE_OBJECT (self));
511 config_manager = ide_config_manager_from_context (context);
512 config = ide_config_manager_get_current (config_manager);
513 if (NULL != (runtime = ide_config_get_runtime (config)))
514 system_includes = ide_runtime_get_system_include_dirs (runtime);
515
516 ret = g_hash_table_new_full (g_file_hash,
517 (GEqualFunc)g_file_equal,
518 g_object_unref,
519 (GDestroyNotify)g_strfreev);
520
521 for (guint i = 0; i < files->len; i++)
522 {
523 GFile *file = g_ptr_array_index (files, i);
524 g_auto(GStrv) flags = NULL;
525
526 flags = ide_compile_commands_lookup (compile_commands, file,
527 (const gchar * const *)system_includes,
528 NULL, NULL);
529 g_hash_table_insert (ret, g_object_ref (file), g_steal_pointer (&flags));
530 }
531
532 ide_task_return_pointer (task, g_steal_pointer (&ret), g_hash_table_unref);
533 }
534
535 static void
gbp_meson_build_system_get_build_flags_cb(GObject * object,GAsyncResult * result,gpointer user_data)536 gbp_meson_build_system_get_build_flags_cb (GObject *object,
537 GAsyncResult *result,
538 gpointer user_data)
539 {
540 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)object;
541 g_autoptr(IdeCompileCommands) compile_commands = NULL;
542 g_autoptr(IdeTask) task = user_data;
543 g_autoptr(GError) error = NULL;
544 g_autoptr(GFile) directory = NULL;
545 g_auto(GStrv) system_includes = NULL;
546 g_auto(GStrv) ret = NULL;
547 IdeConfigManager *config_manager;
548 IdeContext *context;
549 IdeConfig *config;
550 IdeRuntime *runtime;
551 GFile *file;
552
553 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
554 g_assert (G_IS_ASYNC_RESULT (result));
555 g_assert (IDE_IS_TASK (task));
556
557 compile_commands = gbp_meson_build_system_load_commands_finish (self, result, &error);
558
559 if (compile_commands == NULL)
560 {
561 ide_task_return_error (task, g_steal_pointer (&error));
562 return;
563 }
564
565 file = ide_task_get_task_data (task);
566 g_assert (G_IS_FILE (file));
567
568 /* Get non-standard system includes */
569 context = ide_object_get_context (IDE_OBJECT (self));
570 config_manager = ide_config_manager_from_context (context);
571 config = ide_config_manager_get_current (config_manager);
572 if (NULL != (runtime = ide_config_get_runtime (config)))
573 system_includes = ide_runtime_get_system_include_dirs (runtime);
574
575 ret = ide_compile_commands_lookup (compile_commands,
576 file,
577 (const gchar * const *)system_includes,
578 &directory,
579 &error);
580
581 if (ret == NULL)
582 ide_task_return_error (task, g_steal_pointer (&error));
583 else
584 ide_task_return_pointer (task, g_steal_pointer (&ret), g_strfreev);
585 }
586
587 static void
gbp_meson_build_system_get_build_flags_async(IdeBuildSystem * build_system,GFile * file,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)588 gbp_meson_build_system_get_build_flags_async (IdeBuildSystem *build_system,
589 GFile *file,
590 GCancellable *cancellable,
591 GAsyncReadyCallback callback,
592 gpointer user_data)
593 {
594 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)build_system;
595 g_autoptr(IdeTask) task = NULL;
596
597 IDE_ENTRY;
598
599 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
600 g_assert (G_IS_FILE (file));
601 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
602
603 task = ide_task_new (self, cancellable, callback, user_data);
604 ide_task_set_priority (task, G_PRIORITY_LOW);
605 ide_task_set_source_tag (task, gbp_meson_build_system_get_build_flags_async);
606 ide_task_set_task_data (task, g_object_ref (file), g_object_unref);
607
608 gbp_meson_build_system_load_commands_async (self,
609 cancellable,
610 gbp_meson_build_system_get_build_flags_cb,
611 g_steal_pointer (&task));
612
613 IDE_EXIT;
614 }
615
616 static gchar **
gbp_meson_build_system_get_build_flags_finish(IdeBuildSystem * build_system,GAsyncResult * result,GError ** error)617 gbp_meson_build_system_get_build_flags_finish (IdeBuildSystem *build_system,
618 GAsyncResult *result,
619 GError **error)
620 {
621 g_assert (GBP_IS_MESON_BUILD_SYSTEM (build_system));
622 g_assert (IDE_IS_TASK (result));
623
624 return ide_task_propagate_pointer (IDE_TASK (result), error);
625 }
626
627 static void
gbp_meson_build_system_get_build_flags_for_files_async(IdeBuildSystem * build_system,GPtrArray * files,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)628 gbp_meson_build_system_get_build_flags_for_files_async (IdeBuildSystem *build_system,
629 GPtrArray *files,
630 GCancellable *cancellable,
631 GAsyncReadyCallback callback,
632 gpointer user_data)
633 {
634 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)build_system;
635 g_autoptr(IdeTask) task = NULL;
636 g_autoptr(GPtrArray) copy = NULL;
637
638 IDE_ENTRY;
639
640 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
641 g_assert (files != NULL);
642 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
643
644 task = ide_task_new (self, cancellable, callback, user_data);
645 ide_task_set_source_tag (task, gbp_meson_build_system_get_build_flags_async);
646 ide_task_set_priority (task, G_PRIORITY_LOW);
647
648 /* Make our own copy of the array */
649 copy = g_ptr_array_new_with_free_func (g_object_unref);
650 for (guint i = 0; i < files->len; i++)
651 g_ptr_array_add (copy, g_object_ref (g_ptr_array_index (files, i)));
652 ide_task_set_task_data (task, g_steal_pointer (©), g_ptr_array_unref);
653
654 gbp_meson_build_system_load_commands_async (self,
655 cancellable,
656 gbp_meson_build_system_get_build_flags_for_files_cb,
657 g_steal_pointer (&task));
658
659 IDE_EXIT;
660 }
661
662 static GHashTable *
gbp_meson_build_system_get_build_flags_for_files_finish(IdeBuildSystem * build_system,GAsyncResult * result,GError ** error)663 gbp_meson_build_system_get_build_flags_for_files_finish (IdeBuildSystem *build_system,
664 GAsyncResult *result,
665 GError **error)
666 {
667 g_assert (GBP_IS_MESON_BUILD_SYSTEM (build_system));
668 g_assert (IDE_IS_TASK (result));
669
670 return ide_task_propagate_pointer (IDE_TASK (result), error);
671 }
672
673 static gchar *
gbp_meson_build_system_get_builddir(IdeBuildSystem * build_system,IdePipeline * pipeline)674 gbp_meson_build_system_get_builddir (IdeBuildSystem *build_system,
675 IdePipeline *pipeline)
676 {
677 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)build_system;
678 IdeConfig *config;
679 IdeBuildLocality locality;
680
681 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
682 g_assert (IDE_IS_PIPELINE (pipeline));
683
684 /*
685 * If the build configuration requires that we do an in tree build (yuck),
686 * then use "_build" as our build directory to build in-tree.
687 */
688
689 config = ide_pipeline_get_config (pipeline);
690 locality = ide_config_get_locality (config);
691
692 if ((locality & IDE_BUILD_LOCALITY_OUT_OF_TREE) == 0)
693 {
694 g_autoptr(GFile) parent = g_file_get_parent (self->project_file);
695 g_autofree gchar *path = g_file_get_path (parent);
696 g_autofree gchar *builddir = g_build_filename (path, "_build", NULL);
697
698 return g_steal_pointer (&builddir);
699 }
700
701 return NULL;
702 }
703
704 static gboolean
gbp_meson_build_system_supports_toolchain(IdeBuildSystem * self,IdeToolchain * toolchain)705 gbp_meson_build_system_supports_toolchain (IdeBuildSystem *self,
706 IdeToolchain *toolchain)
707 {
708 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
709 g_assert (IDE_IS_TOOLCHAIN (toolchain));
710
711 if (GBP_IS_MESON_TOOLCHAIN (toolchain))
712 return TRUE;
713
714 return FALSE;
715 }
716
717 static gchar *
gbp_meson_build_system_get_project_version(IdeBuildSystem * build_system)718 gbp_meson_build_system_get_project_version (IdeBuildSystem *build_system)
719 {
720 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)build_system;
721
722 g_assert (IDE_IS_MAIN_THREAD ());
723 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
724
725 return g_strdup (self->project_version);
726 }
727
728 static gboolean
gbp_meson_build_system_supports_language(IdeBuildSystem * system,const char * language)729 gbp_meson_build_system_supports_language (IdeBuildSystem *system,
730 const char *language)
731 {
732 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)system;
733
734 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
735 g_assert (language != NULL);
736
737 if (self->languages != NULL)
738 return g_strv_contains ((const char * const *)self->languages, language);
739
740 return FALSE;
741 }
742
743 static void
build_system_iface_init(IdeBuildSystemInterface * iface)744 build_system_iface_init (IdeBuildSystemInterface *iface)
745 {
746 iface->get_id = gbp_meson_build_system_get_id;
747 iface->get_display_name = gbp_meson_build_system_get_display_name;
748 iface->get_priority = gbp_meson_build_system_get_priority;
749 iface->get_build_flags_async = gbp_meson_build_system_get_build_flags_async;
750 iface->get_build_flags_finish = gbp_meson_build_system_get_build_flags_finish;
751 iface->get_build_flags_for_files_async = gbp_meson_build_system_get_build_flags_for_files_async;
752 iface->get_build_flags_for_files_finish = gbp_meson_build_system_get_build_flags_for_files_finish;
753 iface->get_builddir = gbp_meson_build_system_get_builddir;
754 iface->get_project_version = gbp_meson_build_system_get_project_version;
755 iface->supports_toolchain = gbp_meson_build_system_supports_toolchain;
756 iface->supports_language = gbp_meson_build_system_supports_language;
757 }
758
759
760 static char **
split_language(gchar * raw_language_string)761 split_language (gchar *raw_language_string)
762 {
763 g_autofree gchar *copy = NULL;
764 GString *str = g_string_new (raw_language_string);
765 g_string_replace (str, "'", "", -1);
766 g_string_replace (str, " ", "", -1);
767 g_string_replace (str, "\n", "", -1);
768 copy = g_string_free (str, FALSE);
769
770 return g_strsplit (copy, ",", -1);
771 }
772
773 /**
774 * This could be
775 * 1) without language
776 * 2) 'projectname', 'c' with only one language
777 * 3) 'projectname', 'c', 'c++' with variadic as languages
778 * 4) 'projectname', ['c', 'c++'] with an list as languages
779 */
780 char **
_gbp_meson_build_system_parse_languages(const gchar * raw_language_string)781 _gbp_meson_build_system_parse_languages (const gchar *raw_language_string)
782 {
783 g_autofree gchar *language_string = NULL;
784 gchar *cur = (gchar *) raw_language_string;
785 gchar *cur2;
786 cur = g_strstr_len (cur, -1, ",");
787 if (cur == NULL) goto failure;
788 cur++;
789 cur2 = cur;
790 while (*cur2 != ':' || *cur2 == '\0')
791 {
792 if (*cur2 == '[')
793 {
794 cur2 = g_strstr_len (cur2, -1, "]");
795 if (cur2 == NULL) goto failure;
796 cur2++;
797 break;
798 }
799 cur2++;
800 }
801 if (*cur2 == ':') while(*cur2 != ',') cur2--;
802 if (cur2-cur <= 0) goto failure;
803 language_string = g_strndup (cur, cur2-cur);
804
805 if (strstr(language_string, "[") || strstr(language_string, "]"))
806 {
807 gchar *begin = NULL;
808 gchar *end = NULL;
809 g_autofree gchar *copy = NULL;
810
811 if ((begin = strstr(language_string, "[")) == NULL) goto failure;
812 if ((end = strstr(language_string, "]")) == NULL) goto failure;
813 copy = g_strndup (begin + 1, end-begin - 1);
814
815 return split_language (copy);
816 }
817
818 return split_language (language_string);
819
820 failure:
821 return NULL;
822 }
823
824 static void
extract_metadata(GbpMesonBuildSystem * self,const gchar * contents)825 extract_metadata (GbpMesonBuildSystem *self,
826 const gchar *contents)
827 {
828 const gchar *ptr;
829 g_autoptr(GRegex) regex = NULL;
830 g_autoptr(GMatchInfo) match_info = NULL;
831
832 g_assert (IDE_IS_MAIN_THREAD ());
833 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
834 g_assert (contents != NULL);
835
836 ptr = strstr (contents, "version:");
837
838 if (ptr > contents)
839 {
840 const gchar *prev = ptr - 1;
841 gunichar ch = g_utf8_get_char (prev);
842
843 if (g_unichar_isspace (ch) || ch == ',')
844 {
845 const gchar *begin;
846 const gchar *end;
847
848 ptr++;
849
850 for (ptr++; *ptr && *ptr != '\''; ptr = g_utf8_next_char (ptr)) ;
851 if (!*ptr)
852 goto failure;
853
854 ptr++;
855 begin = ptr;
856
857 for (ptr++; *ptr && *ptr != '\''; ptr = g_utf8_next_char (ptr)) ;
858 if (!*ptr)
859 goto failure;
860
861 end = ptr;
862
863 g_free (self->project_version);
864 self->project_version = g_strndup (begin, end - begin);
865 }
866 }
867
868 regex = g_regex_new ("^project\\((.*)\\)", G_REGEX_DOTALL | G_REGEX_UNGREEDY, 0, NULL);
869 g_regex_match (regex, contents, 0, &match_info);
870 while (g_match_info_matches (match_info))
871 {
872 const gchar *str = g_match_info_fetch (match_info, 1);
873 self->languages = _gbp_meson_build_system_parse_languages (str);
874
875 g_match_info_next (match_info, NULL);
876 }
877
878 failure:
879
880 return;
881 }
882
883 static void
gbp_meson_build_system_notify_pipeline(GbpMesonBuildSystem * self,GParamSpec * pspec,IdeBuildManager * build_manager)884 gbp_meson_build_system_notify_pipeline (GbpMesonBuildSystem *self,
885 GParamSpec *pspec,
886 IdeBuildManager *build_manager)
887 {
888 IDE_ENTRY;
889
890 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
891 g_assert (pspec != NULL);
892 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
893
894 /*
895 * We need to regenerate compile commands when the build pipeline
896 * changes so that we get the updated commands.
897 */
898 g_clear_object (&self->compile_commands);
899
900 IDE_EXIT;
901 }
902
903 static void
gbp_meson_build_system_init_async(GAsyncInitable * initable,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)904 gbp_meson_build_system_init_async (GAsyncInitable *initable,
905 gint io_priority,
906 GCancellable *cancellable,
907 GAsyncReadyCallback callback,
908 gpointer user_data)
909 {
910 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)initable;
911 g_autoptr(IdeTask) task = NULL;
912 g_autofree gchar *contents = NULL;
913 IdeBuildManager *build_manager;
914 IdeContext *context;
915 gsize len = 0;
916
917 IDE_ENTRY;
918
919 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
920 g_assert (G_IS_FILE (self->project_file));
921 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
922
923 context = ide_object_get_context (IDE_OBJECT (self));
924 g_assert (IDE_IS_CONTEXT (context));
925
926 build_manager = ide_build_manager_from_context (context);
927 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
928
929 task = ide_task_new (self, cancellable, callback, user_data);
930 ide_task_set_source_tag (task, gbp_meson_build_system_init_async);
931 ide_task_set_priority (task, io_priority);
932 ide_task_set_task_data (task, g_object_ref (self->project_file), g_object_unref);
933
934 if (g_file_load_contents (self->project_file, cancellable, &contents, &len, NULL, NULL))
935 extract_metadata (self, contents);
936
937 /*
938 * We want to be notified of any changes to the current build manager.
939 * This will let us invalidate our compile_commands.json when it changes.
940 */
941 g_signal_connect_object (build_manager,
942 "notify::pipeline",
943 G_CALLBACK (gbp_meson_build_system_notify_pipeline),
944 self,
945 G_CONNECT_SWAPPED);
946
947 ide_task_return_boolean (task, TRUE);
948
949 IDE_EXIT;
950 }
951
952 static gboolean
gbp_meson_build_system_init_finish(GAsyncInitable * initable,GAsyncResult * result,GError ** error)953 gbp_meson_build_system_init_finish (GAsyncInitable *initable,
954 GAsyncResult *result,
955 GError **error)
956 {
957 GbpMesonBuildSystem *self = (GbpMesonBuildSystem *)initable;
958 g_autoptr(GFile) project_file = NULL;
959
960 IDE_ENTRY;
961
962 g_assert (GBP_IS_MESON_BUILD_SYSTEM (self));
963 g_assert (IDE_IS_TASK (result));
964
965 project_file = ide_task_propagate_pointer (IDE_TASK (result), error);
966 if (g_set_object (&self->project_file, project_file))
967 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROJECT_FILE]);
968
969 IDE_RETURN (project_file != NULL);
970 }
971
972 static void
async_initable_iface_init(GAsyncInitableIface * iface)973 async_initable_iface_init (GAsyncInitableIface *iface)
974 {
975 iface->init_async = gbp_meson_build_system_init_async;
976 iface->init_finish = gbp_meson_build_system_init_finish;
977 }
978
979 const gchar * const *
gbp_meson_build_system_get_languages(GbpMesonBuildSystem * self)980 gbp_meson_build_system_get_languages (GbpMesonBuildSystem *self)
981 {
982 g_return_val_if_fail (GBP_IS_MESON_BUILD_SYSTEM (self), NULL);
983
984 return (const gchar * const *)self->languages;
985 }
986