1 /* gbp-flatpak-build-system-discovery.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-flatpak-build-system-discovery"
22
23 #include <json-glib/json-glib.h>
24
25 #include "gbp-flatpak-build-system-discovery.h"
26
27 #define DISCOVERY_MAX_DEPTH 3
28
29 /*
30 * TODO: It would be nice if this could share more code with GbpFlatpakConfigurationProvider.
31 */
32
33 struct _GbpFlatpakBuildSystemDiscovery
34 {
35 GObject parent_instance;
36 };
37
38 /* Returns whether @filename seems to be a JSON file, naively detected. */
39 static gboolean
maybe_is_json_file(const char * filename)40 maybe_is_json_file (const char *filename)
41 {
42 return strlen (filename) >= strlen (".json") && g_str_has_suffix (filename, ".json");
43 }
44
45 static void
gbp_flatpak_build_system_discovery_find_manifests(GFile * directory,GPtrArray * results,gint depth,GCancellable * cancellable)46 gbp_flatpak_build_system_discovery_find_manifests (GFile *directory,
47 GPtrArray *results,
48 gint depth,
49 GCancellable *cancellable)
50 {
51 g_autoptr(GFileEnumerator) enumerator = NULL;
52 g_autoptr(GPtrArray) child_dirs = NULL;
53 gpointer infoptr;
54
55 g_assert (G_IS_FILE (directory));
56 g_assert (results != NULL);
57 g_assert (depth < DISCOVERY_MAX_DEPTH);
58
59 if (g_cancellable_is_cancelled (cancellable))
60 return;
61
62 enumerator = g_file_enumerate_children (directory,
63 G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK","
64 G_FILE_ATTRIBUTE_STANDARD_NAME","
65 G_FILE_ATTRIBUTE_STANDARD_TYPE,
66 G_FILE_QUERY_INFO_NONE,
67 cancellable,
68 NULL);
69
70 if (enumerator == NULL)
71 return;
72
73 while (NULL != (infoptr = g_file_enumerator_next_file (enumerator, cancellable, NULL)))
74 {
75 g_autoptr(GFileInfo) info = infoptr;
76 g_autoptr(GFile) file = NULL;
77 GFileType file_type;
78 const gchar *name;
79 g_autofree gchar *app_id = NULL;
80
81 if (g_file_info_get_is_symlink (info))
82 continue;
83
84 if (NULL == (name = g_file_info_get_name (info)))
85 continue;
86
87 file_type = g_file_info_get_file_type (info);
88 file = g_file_get_child (directory, name);
89
90 if (file_type == G_FILE_TYPE_DIRECTORY)
91 {
92 /* TODO: Use a global ignored-file filter from libide */
93 if (g_strcmp0 (name, ".flatpak-builder") == 0 || g_strcmp0 (name, ".git") == 0)
94 continue;
95
96 if (depth < DISCOVERY_MAX_DEPTH - 1)
97 {
98 if (child_dirs == NULL)
99 child_dirs = g_ptr_array_new_with_free_func (g_object_unref);
100 g_ptr_array_add (child_dirs, g_steal_pointer (&file));
101 continue;
102 }
103 }
104
105 if (!maybe_is_json_file (name))
106 continue;
107
108 app_id = g_strndup (name, strlen (name) - strlen (".json"));
109 if (!g_application_id_is_valid (app_id))
110 continue;
111
112 g_ptr_array_add (results, g_steal_pointer (&file));
113 }
114
115 if (child_dirs != NULL)
116 {
117 for (guint i = 0; i < child_dirs->len; i++)
118 {
119 GFile *file = g_ptr_array_index (child_dirs, i);
120
121 if (g_cancellable_is_cancelled (cancellable))
122 return;
123
124 gbp_flatpak_build_system_discovery_find_manifests (file, results, depth + 1, cancellable);
125 }
126 }
127 }
128
129 static gchar *
gbp_flatpak_build_system_discovery_discover(IdeBuildSystemDiscovery * discovery,GFile * project_file,GCancellable * cancellable,gint * priority,GError ** error)130 gbp_flatpak_build_system_discovery_discover (IdeBuildSystemDiscovery *discovery,
131 GFile *project_file,
132 GCancellable *cancellable,
133 gint *priority,
134 GError **error)
135 {
136 g_autoptr(GPtrArray) manifests = NULL;
137
138 IDE_ENTRY;
139
140 g_assert (GBP_IS_FLATPAK_BUILD_SYSTEM_DISCOVERY (discovery));
141 g_assert (G_IS_FILE (project_file));
142 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
143 g_assert (priority != NULL);
144
145 manifests = g_ptr_array_new_with_free_func (g_object_unref);
146 gbp_flatpak_build_system_discovery_find_manifests (project_file, manifests, 0, cancellable);
147
148 IDE_TRACE_MSG ("We found %u potential manifests", manifests->len);
149
150 if (priority)
151 *priority = 0;
152
153 for (guint i = 0; i < manifests->len; i++)
154 {
155 GFile *file = g_ptr_array_index (manifests, i);
156 g_autofree gchar *path = NULL;
157 g_autofree gchar *base = NULL;
158 const gchar *buildsystem;
159 const gchar *app_id_str;
160 g_autoptr(JsonParser) parser = NULL;
161 JsonObject *root_object;
162 JsonNode *root_node;
163 JsonNode *app_id;
164 JsonNode *modules_node;
165 JsonArray *modules_array;
166 JsonNode *source_node;
167 JsonObject *source_object;
168 JsonNode *buildsystem_node;
169 guint len;
170
171 if (NULL == (path = g_file_get_path (file)))
172 continue;
173
174 IDE_TRACE_MSG ("Checking potential manifest \"%s\"", path);
175
176 base = g_file_get_basename (file);
177 parser = json_parser_new ();
178
179 if (!json_parser_load_from_file (parser, path, NULL))
180 continue;
181
182 root_node = json_parser_get_root (parser);
183
184 if (NULL != (root_object = json_node_get_object (root_node)) &&
185 NULL != (app_id = json_object_get_member (root_object, "app-id")) &&
186 JSON_NODE_HOLDS_VALUE (app_id) &&
187 NULL != (app_id_str = json_node_get_string (app_id)) &&
188 g_str_has_prefix (base, app_id_str) &&
189 NULL != (modules_node = json_object_get_member (root_object, "modules")) &&
190 JSON_NODE_HOLDS_ARRAY (modules_node) &&
191 NULL != (modules_array = json_node_get_array (modules_node)) &&
192 /* TODO: Discovery matching source element */
193 (len = json_array_get_length (modules_array)) > 0 &&
194 NULL != (source_node = json_array_get_element (modules_array, len - 1)) &&
195 JSON_NODE_HOLDS_OBJECT (source_node) &&
196 NULL != (source_object = json_node_get_object (source_node)) &&
197 json_object_has_member (source_object, "buildsystem") &&
198 NULL != (buildsystem_node = json_object_get_member (source_object, "buildsystem")) &&
199 JSON_NODE_HOLDS_VALUE (buildsystem_node) &&
200 NULL != (buildsystem = json_node_get_string (buildsystem_node)) &&
201 *buildsystem != '\0')
202 {
203 gchar *ret;
204
205 if (dzl_str_equal0 (buildsystem, "cmake-ninja"))
206 buildsystem = "cmake";
207 else if (dzl_str_equal0 (buildsystem, "simple"))
208 {
209 JsonNode *sdk_extensions;
210 JsonArray *sdk_extensions_array;
211
212 buildsystem = "directory";
213
214 /* Check for a cargo project */
215 sdk_extensions = json_object_get_member (root_object, "sdk-extensions");
216 sdk_extensions_array = json_node_get_array (sdk_extensions);
217 len = json_array_get_length (sdk_extensions_array);
218 for (guint j = 0; j < len; j++)
219 {
220 const gchar *extension;
221 extension = json_array_get_string_element (sdk_extensions_array, j);
222 if (ide_str_equal0 (extension, "org.freedesktop.Sdk.Extension.rust-stable") ||
223 ide_str_equal0 (extension, "org.freedesktop.Sdk.Extension.rust-nightly"))
224 buildsystem = "cargo_plugin";
225 }
226 }
227
228 /* Set priority higher than normal discoveries */
229 if (priority != NULL)
230 *priority = -1000;
231
232 ret = g_strdup (buildsystem);
233 IDE_TRACE_MSG ("Discovered buildsystem of type \"%s\"", ret);
234 IDE_RETURN (ret);
235 }
236 }
237
238 IDE_RETURN (NULL);
239 }
240
241 static void
build_system_discovery_iface_init(IdeBuildSystemDiscoveryInterface * iface)242 build_system_discovery_iface_init (IdeBuildSystemDiscoveryInterface *iface)
243 {
244 iface->discover = gbp_flatpak_build_system_discovery_discover;
245 }
246
G_DEFINE_FINAL_TYPE_WITH_CODE(GbpFlatpakBuildSystemDiscovery,gbp_flatpak_build_system_discovery,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (IDE_TYPE_BUILD_SYSTEM_DISCOVERY,build_system_discovery_iface_init))247 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpFlatpakBuildSystemDiscovery,
248 gbp_flatpak_build_system_discovery,
249 G_TYPE_OBJECT,
250 G_IMPLEMENT_INTERFACE (IDE_TYPE_BUILD_SYSTEM_DISCOVERY, build_system_discovery_iface_init))
251
252 static void
253 gbp_flatpak_build_system_discovery_class_init (GbpFlatpakBuildSystemDiscoveryClass *klass)
254 {
255 }
256
257 static void
gbp_flatpak_build_system_discovery_init(GbpFlatpakBuildSystemDiscovery * self)258 gbp_flatpak_build_system_discovery_init (GbpFlatpakBuildSystemDiscovery *self)
259 {
260 }
261