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