1 /* gbp-sysroot-manager.c
2  *
3  * Copyright 2018 Corentin Noël <corentin.noel@collabora.com>
4  * Copyright 2018 Collabora Ltd.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * SPDX-License-Identifier: GPL-3.0-or-later
20  */
21 
22 #define G_LOG_DOMAIN "gbp-sysroot-manager"
23 #define BASIC_LIBDIRS "/usr/lib/pkgconfig:/usr/share/pkgconfig"
24 
25 #include "gbp-sysroot-manager.h"
26 
27 struct _GbpSysrootManager
28 {
29   GObject parent_instance;
30   GKeyFile *key_file;
31 };
32 
33 G_DEFINE_FINAL_TYPE (GbpSysrootManager, gbp_sysroot_manager, G_TYPE_OBJECT)
34 
35 enum {
36   TARGET_MODIFIED,
37   TARGET_NAME_CHANGED,
38   TARGET_ARCH_CHANGED,
39   N_SIGNALS
40 };
41 
42 static guint signals [N_SIGNALS];
43 
44 static gchar *
sysroot_manager_get_path(void)45 sysroot_manager_get_path (void)
46 {
47   g_autofree gchar *directory_path = NULL;
48   g_autofree gchar *conf_file = NULL;
49 
50   directory_path = g_build_filename (g_get_user_config_dir (),
51                                      ide_get_program_name (),
52                                      "sysroot",
53                                      NULL);
54 
55   g_mkdir_with_parents (directory_path, 0750);
56   conf_file = g_build_filename (directory_path, "general.conf", NULL);
57   return g_steal_pointer (&conf_file);
58 }
59 
60 /**
61  * sysroot_manager_find_additional_pkgconfig_paths:
62  *
63  * Returns a colon-separated list of additional pkgconfig paths
64  *
65  * Returns: (transfer full) (nullable): additional guessed paths
66  */
67 static gchar *
sysroot_manager_find_additional_pkgconfig_paths(GbpSysrootManager * self,const gchar * target)68 sysroot_manager_find_additional_pkgconfig_paths (GbpSysrootManager *self,
69                                                  const gchar       *target)
70 {
71   g_autofree gchar *path = NULL;
72   g_autofree gchar *lib64_path = NULL;
73   g_autofree gchar *target_arch = NULL;
74   g_autofree gchar *libmultiarch_path = NULL;
75   g_autofree gchar *returned_paths = NULL;
76 
77   g_assert (GBP_IS_SYSROOT_MANAGER (self));
78   g_assert (self->key_file != NULL);
79   g_assert (target != NULL);
80 
81   path = gbp_sysroot_manager_get_target_path (self, target);
82   lib64_path = g_build_filename (path, "usr", "lib64", "pkgconfig", NULL);
83   target_arch = gbp_sysroot_manager_get_target_arch (self, target);
84   libmultiarch_path = g_build_filename (path, "usr", "lib", target_arch, "pkgconfig", NULL);
85 
86   if (g_file_test (lib64_path, G_FILE_TEST_EXISTS))
87     returned_paths = g_steal_pointer (&lib64_path);
88 
89   if (g_file_test (libmultiarch_path, G_FILE_TEST_EXISTS))
90     {
91       g_autofree gchar *previous_returned_path = g_steal_pointer (&returned_paths);
92       returned_paths = g_strjoin (":", libmultiarch_path, previous_returned_path, NULL);
93     }
94 
95   return g_strdup (returned_paths);
96 }
97 
98 static void
sysroot_manager_save(GbpSysrootManager * self)99 sysroot_manager_save (GbpSysrootManager *self)
100 {
101   g_autofree gchar *conf_file = NULL;
102   g_autoptr(GError) error = NULL;
103 
104   g_assert (GBP_IS_SYSROOT_MANAGER (self));
105   g_assert (self->key_file != NULL);
106 
107   conf_file = sysroot_manager_get_path ();
108 
109   if (!g_key_file_save_to_file (self->key_file, conf_file, &error))
110     g_warning ("Error saving the sysroot configuration: %s", error->message);
111 }
112 
113 /**
114  * gbp_sysroot_manager_get_default:
115  *
116  * Returns the default #GbpSysrootManager instance.
117  *
118  * Returns: (transfer none): the common sysroot manager
119  */
120 GbpSysrootManager *
gbp_sysroot_manager_get_default(void)121 gbp_sysroot_manager_get_default (void)
122 {
123   static GbpSysrootManager *instance;
124 
125   /* TODO: This needs to be attached to the IdeContext somehow, as this is
126    *       not ideal when two contexts are loaded and sharing occurs.
127    */
128 
129   if (instance == NULL)
130     {
131       instance = g_object_new (GBP_TYPE_SYSROOT_MANAGER, NULL);
132       g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
133     }
134 
135   return instance;
136 }
137 
138 /**
139  * gbp_sysroot_manager_create_target:
140  * @self: a #GbpSysrootManager
141  *
142  * This creates a new target and initializes its fields to the default parameters.
143  *
144  * Returns: (transfer full): the unique identifier of the new target
145  */
146 gchar *
gbp_sysroot_manager_create_target(GbpSysrootManager * self)147 gbp_sysroot_manager_create_target (GbpSysrootManager *self)
148 {
149   g_return_val_if_fail (GBP_IS_SYSROOT_MANAGER (self), NULL);
150   g_return_val_if_fail (self->key_file != NULL, NULL);
151 
152   for (guint i = 0; i < UINT_MAX; i++)
153     {
154       gchar *result;
155       g_autoptr(GString) sysroot_name = g_string_new (NULL);
156 
157       g_string_printf (sysroot_name, "Sysroot %u", i);
158       result = sysroot_name->str;
159       if (!g_key_file_has_group (self->key_file, result))
160         {
161           g_key_file_set_string (self->key_file, result, "Name", result);
162           g_key_file_set_string (self->key_file, result, "Path", "/");
163           sysroot_manager_save (self);
164           g_signal_emit (self, signals[TARGET_MODIFIED], 0, result, GBP_SYSROOT_MANAGER_TARGET_CREATED);
165           return g_string_free (g_steal_pointer (&sysroot_name), FALSE);
166         }
167     }
168 
169   return NULL;
170 }
171 
172 void
gbp_sysroot_manager_remove_target(GbpSysrootManager * self,const gchar * target)173 gbp_sysroot_manager_remove_target (GbpSysrootManager *self,
174                                    const gchar       *target)
175 {
176   g_autoptr(GError) error = NULL;
177 
178   g_return_if_fail (GBP_IS_SYSROOT_MANAGER (self));
179   g_return_if_fail (self->key_file != NULL);
180   g_return_if_fail (target != NULL);
181 
182   if (!g_key_file_remove_group (self->key_file, target, &error))
183     g_warning ("Error removing target \"%s\": %s", target, error->message);
184 
185   g_signal_emit (self, signals[TARGET_MODIFIED], 0, target, GBP_SYSROOT_MANAGER_TARGET_REMOVED);
186   sysroot_manager_save (self);
187 }
188 
189 /**
190  * gbp_sysroot_manager_set_target_name:
191  * @self: a #GbpSysrootManager
192  * @target: the unique identifier of the target
193  * @name: the displayable name of the target
194  *
195  * Sets the displayable name of the target.
196  */
197 void
gbp_sysroot_manager_set_target_name(GbpSysrootManager * self,const gchar * target,const gchar * name)198 gbp_sysroot_manager_set_target_name (GbpSysrootManager *self,
199                                      const gchar       *target,
200                                      const gchar       *name)
201 {
202   g_return_if_fail (GBP_IS_SYSROOT_MANAGER (self));
203   g_return_if_fail (self->key_file != NULL);
204   g_return_if_fail (target != NULL);
205 
206   g_key_file_set_string (self->key_file, target, "Name", name);
207   g_signal_emit (self, signals[TARGET_MODIFIED], 0, target, GBP_SYSROOT_MANAGER_TARGET_CHANGED);
208   g_signal_emit (self, signals[TARGET_NAME_CHANGED], 0, target, name);
209   sysroot_manager_save (self);
210 }
211 
212 /**
213  * gbp_sysroot_manager_get_target_name:
214  * @self: a #GbpSysrootManager
215  * @target: the unique identifier of the target
216  *
217  * Gets the displayable name of the target.
218  *
219  * Returns: (transfer full): the name of the target to display.
220  */
221 gchar *
gbp_sysroot_manager_get_target_name(GbpSysrootManager * self,const gchar * target)222 gbp_sysroot_manager_get_target_name (GbpSysrootManager *self,
223                                      const gchar       *target)
224 {
225   g_return_val_if_fail (GBP_IS_SYSROOT_MANAGER (self), NULL);
226   g_return_val_if_fail (self->key_file != NULL, NULL);
227   g_return_val_if_fail (target != NULL, NULL);
228 
229   return g_key_file_get_string (self->key_file, target, "Name", NULL);
230 }
231 
232 /**
233  * gbp_sysroot_manager_set_target_arch:
234  * @self: a #GbpSysrootManager
235  * @target: the unique identifier of the target
236  * @name: the architecture of the target
237  *
238  * Sets the architecture of the target.
239  */
240 void
gbp_sysroot_manager_set_target_arch(GbpSysrootManager * self,const gchar * target,const gchar * arch)241 gbp_sysroot_manager_set_target_arch (GbpSysrootManager *self,
242                                      const gchar       *target,
243                                      const gchar       *arch)
244 {
245   g_return_if_fail (GBP_IS_SYSROOT_MANAGER (self));
246   g_return_if_fail (self->key_file != NULL);
247   g_return_if_fail (target != NULL);
248 
249   g_key_file_set_string (self->key_file, target, "Arch", arch);
250   g_signal_emit (self, signals[TARGET_MODIFIED], 0, target, GBP_SYSROOT_MANAGER_TARGET_CHANGED);
251   g_signal_emit (self, signals[TARGET_ARCH_CHANGED], 0, target, arch);
252   sysroot_manager_save (self);
253 }
254 
255 /**
256  * gbp_sysroot_manager_get_target_arch:
257  * @self: a #GbpSysrootManager
258  * @target: the unique identifier of the target
259  *
260  * Gets the architecture of the target.
261  *
262  * Returns: (transfer full): the architecture of the target.
263  */
264 gchar *
gbp_sysroot_manager_get_target_arch(GbpSysrootManager * self,const gchar * target)265 gbp_sysroot_manager_get_target_arch (GbpSysrootManager *self,
266                                      const gchar       *target)
267 {
268   g_return_val_if_fail (GBP_IS_SYSROOT_MANAGER (self), NULL);
269   g_return_val_if_fail (self->key_file != NULL, NULL);
270   g_return_val_if_fail (target != NULL, NULL);
271 
272   return g_key_file_get_string (self->key_file, target, "Arch", NULL);
273 }
274 
275 /**
276  * gbp_sysroot_manager_set_target_path:
277  * @self: a #GbpSysrootManager
278  * @target: the unique identifier of the target
279  * @path: the sysroot path of the target
280  *
281  * Sets the sysroot path of the target.
282  */
283 void
gbp_sysroot_manager_set_target_path(GbpSysrootManager * self,const gchar * target,const gchar * path)284 gbp_sysroot_manager_set_target_path (GbpSysrootManager *self,
285                                      const gchar       *target,
286                                      const gchar       *path)
287 {
288   g_autofree gchar *current_path = NULL;
289   g_autofree gchar *current_pkgconfigs = NULL;
290 
291   g_return_if_fail (GBP_IS_SYSROOT_MANAGER (self));
292   g_return_if_fail (self->key_file != NULL);
293   g_return_if_fail (target != NULL);
294   g_return_if_fail (path != NULL);
295 
296   current_path = gbp_sysroot_manager_get_target_path (self, target);
297   g_key_file_set_string (self->key_file, target, "Path", path);
298   g_signal_emit (self, signals[TARGET_MODIFIED], 0, target, GBP_SYSROOT_MANAGER_TARGET_CHANGED);
299   sysroot_manager_save (self);
300 
301   current_pkgconfigs = gbp_sysroot_manager_get_target_pkg_config_path (self, target);
302   if (current_pkgconfigs == NULL || g_strcmp0 (current_pkgconfigs, "") == 0)
303     {
304       g_auto(GStrv) path_parts = NULL;
305       g_autofree gchar *additional_paths = NULL;
306       g_autofree gchar *found_pkgconfigs = g_steal_pointer (&current_pkgconfigs);
307 
308       // Prepend the sysroot path to the BASIC_LIBDIRS values
309       path_parts = g_strsplit (BASIC_LIBDIRS, ":", 0);
310       for (gint i = g_strv_length (path_parts) - 1; i >= 0; i--)
311         {
312           g_autofree gchar *path_i = NULL;
313           g_autofree gchar *previous_pkgconfigs = g_steal_pointer (&found_pkgconfigs);
314 
315           path_i = g_build_path (G_DIR_SEPARATOR_S, path, path_parts[i], NULL);
316           found_pkgconfigs = g_strjoin (":", path_i, previous_pkgconfigs, NULL);
317         }
318 
319       additional_paths = sysroot_manager_find_additional_pkgconfig_paths (self, target);
320       current_pkgconfigs = g_strjoin (":", found_pkgconfigs, additional_paths, NULL);
321 
322       gbp_sysroot_manager_set_target_pkg_config_path (self, target, current_pkgconfigs);
323     }
324   else
325     {
326       g_autoptr(GError) regex_error = NULL;
327       g_autoptr(GRegex) regex = NULL;
328       g_autofree gchar *current_path_escaped = NULL;
329 
330       current_path_escaped = g_regex_escape_string (current_path, -1);
331       regex = g_regex_new (current_path_escaped, 0, 0, &regex_error);
332       if (regex_error == NULL)
333         {
334           g_autofree gchar *previous_pkgconfigs = g_steal_pointer (&current_pkgconfigs);
335 
336           current_pkgconfigs = g_regex_replace_literal (regex, previous_pkgconfigs, (gssize) -1, 0, path, 0, &regex_error);
337           if (regex_error == NULL)
338             gbp_sysroot_manager_set_target_pkg_config_path (self, target, current_pkgconfigs);
339           else
340             g_warning ("Regex error: %s", regex_error->message);
341         }
342       else
343         g_warning ("Regex error: %s", regex_error->message);
344     }
345 }
346 
347 /**
348  * gbp_sysroot_manager_get_target_path:
349  * @self: a #GbpSysrootManager
350  * @target: the unique identifier of the target
351  *
352  * Gets the sysroot path of the target.
353  *
354  * Returns: (transfer full): the sysroot path of the target.
355  */
356 gchar *
gbp_sysroot_manager_get_target_path(GbpSysrootManager * self,const gchar * target)357 gbp_sysroot_manager_get_target_path (GbpSysrootManager *self,
358                                      const gchar       *target)
359 {
360   g_return_val_if_fail (GBP_IS_SYSROOT_MANAGER (self), NULL);
361   g_return_val_if_fail (self->key_file != NULL, NULL);
362   g_return_val_if_fail (target != NULL, NULL);
363 
364   return g_key_file_get_string (self->key_file, target, "Path", NULL);
365 }
366 
367 
368 /**
369  * gbp_sysroot_manager_set_target_pkg_config_path:
370  * @self: a #GbpSysrootManager
371  * @target: the unique identifier of the target
372  * @path: (nullable): the additional Pkg-Config paths of the target or %NULL
373  *
374  * Sets the additional Pkg-Config paths of the target.
375  * It is possible to use several paths by separating them with a colon character.
376  */
377 void
gbp_sysroot_manager_set_target_pkg_config_path(GbpSysrootManager * self,const gchar * target,const gchar * path)378 gbp_sysroot_manager_set_target_pkg_config_path (GbpSysrootManager *self,
379                                                 const gchar       *target,
380                                                 const gchar       *path)
381 {
382   g_return_if_fail (GBP_IS_SYSROOT_MANAGER (self));
383   g_return_if_fail (self->key_file != NULL);
384   g_return_if_fail (target != NULL);
385 
386   g_key_file_set_string (self->key_file, target, "PkgConfigPath", path);
387   g_signal_emit (self, signals[TARGET_MODIFIED], 0, target, GBP_SYSROOT_MANAGER_TARGET_CHANGED);
388   sysroot_manager_save (self);
389 }
390 
391 /**
392  * gbp_sysroot_manager_get_target_pkg_config_path:
393  * @self: a #GbpSysrootManager
394  * @target: the unique identifier of the target
395  *
396  * Gets the additional Pkg-Config paths of the target.
397  *
398  * This is often used when the target has its libraries in an architecture-specific folder.
399  *
400  * Returns: (transfer full) (nullable): the additional paths to pkg-config, using a colon separator.
401  */
402 gchar *
gbp_sysroot_manager_get_target_pkg_config_path(GbpSysrootManager * self,const gchar * target)403 gbp_sysroot_manager_get_target_pkg_config_path (GbpSysrootManager *self,
404                                                 const gchar       *target)
405 {
406   g_return_val_if_fail (GBP_IS_SYSROOT_MANAGER (self), NULL);
407   g_return_val_if_fail (self->key_file != NULL, NULL);
408   g_return_val_if_fail (target != NULL, NULL);
409 
410   return g_key_file_get_string (self->key_file, target, "PkgConfigPath", NULL);
411 }
412 
413 /**
414  * gbp_sysroot_manager_list:
415  * @self: a #GbpSysrootManager
416  *
417  * Retrieves the list of all the available sysroot unique identifiers.
418  *
419  * Returns: (transfer full) (nullable): the %NULL-terminated list of all the available sysroot
420  * unique identifiers.
421  */
422 gchar **
gbp_sysroot_manager_list(GbpSysrootManager * self)423 gbp_sysroot_manager_list (GbpSysrootManager *self)
424 {
425   g_return_val_if_fail (GBP_IS_SYSROOT_MANAGER (self), NULL);
426   g_return_val_if_fail (self->key_file != NULL, NULL);
427 
428   return g_key_file_get_groups (self->key_file, NULL);
429 }
430 
431 static void
gbp_sysroot_manager_finalize(GObject * object)432 gbp_sysroot_manager_finalize (GObject *object)
433 {
434   GbpSysrootManager *self = GBP_SYSROOT_MANAGER(object);
435 
436   g_clear_pointer (&self->key_file, g_key_file_free);
437 
438   G_OBJECT_CLASS (gbp_sysroot_manager_parent_class)->finalize (object);
439 }
440 
441 void
gbp_sysroot_manager_class_init(GbpSysrootManagerClass * klass)442 gbp_sysroot_manager_class_init (GbpSysrootManagerClass *klass)
443 {
444   G_OBJECT_CLASS (klass)->finalize = gbp_sysroot_manager_finalize;
445 
446   signals [TARGET_MODIFIED] =
447     g_signal_new_class_handler ("target-changed",
448                                 G_TYPE_FROM_CLASS (klass),
449                                 G_SIGNAL_RUN_FIRST,
450                                 NULL, NULL, NULL, NULL,
451                                 G_TYPE_NONE,
452                                 2,
453                                 G_TYPE_STRING,
454                                 G_TYPE_INT);
455 
456   signals [TARGET_NAME_CHANGED] =
457     g_signal_new_class_handler ("target-name-changed",
458                                 G_TYPE_FROM_CLASS (klass),
459                                 G_SIGNAL_RUN_FIRST,
460                                 NULL, NULL, NULL, NULL,
461                                 G_TYPE_NONE,
462                                 2,
463                                 G_TYPE_STRING,
464                                 G_TYPE_STRING);
465 
466   signals [TARGET_ARCH_CHANGED] =
467     g_signal_new_class_handler ("target-arch-changed",
468                                 G_TYPE_FROM_CLASS (klass),
469                                 G_SIGNAL_RUN_FIRST,
470                                 NULL, NULL, NULL, NULL,
471                                 G_TYPE_NONE,
472                                 2,
473                                 G_TYPE_STRING,
474                                 G_TYPE_STRING);
475 }
476 
477 static void
gbp_sysroot_manager_init(GbpSysrootManager * self)478 gbp_sysroot_manager_init (GbpSysrootManager *self)
479 {
480   g_autofree gchar *conf_file = NULL;
481   g_autoptr(GError) error = NULL;
482 
483   conf_file = sysroot_manager_get_path ();
484   self->key_file = g_key_file_new ();
485 
486   if (!g_key_file_load_from_file (self->key_file, conf_file, G_KEY_FILE_KEEP_COMMENTS, &error) &&
487       !g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
488     g_warning ("Error loading the sysroot configuration: %s", error->message);
489 }
490