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 (&copy), 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