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 (¤t_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, ®ex_error);
332 if (regex_error == NULL)
333 {
334 g_autofree gchar *previous_pkgconfigs = g_steal_pointer (¤t_pkgconfigs);
335
336 current_pkgconfigs = g_regex_replace_literal (regex, previous_pkgconfigs, (gssize) -1, 0, path, 0, ®ex_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