1 /* ide-autotools-pipeline-addin.c
2  *
3  * Copyright 2016-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 "ide-autotools-pipeline-addin"
22 
23 #include <glib/gi18n.h>
24 
25 #include "ide-autotools-autogen-stage.h"
26 #include "ide-autotools-build-system.h"
27 #include "ide-autotools-make-stage.h"
28 #include "ide-autotools-makecache-stage.h"
29 #include "ide-autotools-pipeline-addin.h"
30 
31 static gboolean
register_autoreconf_stage(IdeAutotoolsPipelineAddin * self,IdePipeline * pipeline,GError ** error)32 register_autoreconf_stage (IdeAutotoolsPipelineAddin  *self,
33                            IdePipeline           *pipeline,
34                            GError                    **error)
35 {
36   g_autofree gchar *configure_path = NULL;
37   g_autoptr(IdePipelineStage) stage = NULL;
38   const gchar *srcdir;
39   gboolean completed;
40   guint stage_id;
41 
42   g_assert (IDE_IS_AUTOTOOLS_PIPELINE_ADDIN (self));
43   g_assert (IDE_IS_PIPELINE (pipeline));
44 
45   configure_path = ide_pipeline_build_srcdir_path (pipeline, "configure", NULL);
46   completed = g_file_test (configure_path, G_FILE_TEST_IS_REGULAR);
47   srcdir = ide_pipeline_get_srcdir (pipeline);
48 
49   stage = g_object_new (IDE_TYPE_AUTOTOOLS_AUTOGEN_STAGE,
50                         "name", _("Bootstrapping build system"),
51                         "completed", completed,
52                         "srcdir", srcdir,
53                         NULL);
54 
55   stage_id = ide_pipeline_attach (pipeline, IDE_PIPELINE_PHASE_AUTOGEN, 0, stage);
56 
57   ide_pipeline_addin_track (IDE_PIPELINE_ADDIN (self), stage_id);
58 
59   return TRUE;
60 }
61 
62 static gint
compare_mtime(const gchar * path_a,const gchar * path_b)63 compare_mtime (const gchar *path_a,
64                const gchar *path_b)
65 {
66   g_autoptr(GFile) file_a = g_file_new_for_path (path_a);
67   g_autoptr(GFile) file_b = g_file_new_for_path (path_b);
68   g_autoptr(GFileInfo) info_a = NULL;
69   g_autoptr(GFileInfo) info_b = NULL;
70   gint64 ret = 0;
71 
72   info_a = g_file_query_info (file_a,
73                               G_FILE_ATTRIBUTE_TIME_MODIFIED,
74                               G_FILE_QUERY_INFO_NONE,
75                               NULL,
76                               NULL);
77 
78   info_b = g_file_query_info (file_b,
79                               G_FILE_ATTRIBUTE_TIME_MODIFIED,
80                               G_FILE_QUERY_INFO_NONE,
81                               NULL,
82                               NULL);
83 
84   ret = (gint64)g_file_info_get_attribute_uint64 (info_a, G_FILE_ATTRIBUTE_TIME_MODIFIED) -
85         (gint64)g_file_info_get_attribute_uint64 (info_b, G_FILE_ATTRIBUTE_TIME_MODIFIED);
86 
87   if (ret < 0)
88     return -1;
89   else if (ret > 0)
90     return 1;
91   return 0;
92 }
93 
94 static void
check_configure_status(IdeAutotoolsPipelineAddin * self,IdePipeline * pipeline,GPtrArray * targets,GCancellable * cancellable,IdePipelineStage * stage)95 check_configure_status (IdeAutotoolsPipelineAddin *self,
96                         IdePipeline          *pipeline,
97                         GPtrArray                 *targets,
98                         GCancellable              *cancellable,
99                         IdePipelineStage             *stage)
100 {
101   g_autofree gchar *configure_ac = NULL;
102   g_autofree gchar *configure = NULL;
103   g_autofree gchar *config_status = NULL;
104   g_autofree gchar *makefile = NULL;
105 
106   IDE_ENTRY;
107 
108   g_assert (IDE_IS_AUTOTOOLS_PIPELINE_ADDIN (self));
109   g_assert (IDE_IS_PIPELINE (pipeline));
110   g_assert (IDE_IS_PIPELINE_STAGE (stage));
111   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
112 
113   configure = ide_pipeline_build_srcdir_path (pipeline, "configure", NULL);
114   configure_ac = ide_pipeline_build_srcdir_path (pipeline, "configure.ac", NULL);
115   config_status = ide_pipeline_build_builddir_path (pipeline, "config.status", NULL);
116   makefile = ide_pipeline_build_builddir_path (pipeline, "Makefile", NULL);
117 
118   IDE_TRACE_MSG (" configure.ac is at %s", configure_ac);
119   IDE_TRACE_MSG (" configure is at %s", configure);
120   IDE_TRACE_MSG (" config.status is at %s", config_status);
121   IDE_TRACE_MSG (" makefile is at %s", makefile);
122 
123   /*
124    * First make sure some essential files exist. If not, we need to run the
125    * configure process.
126    *
127    * TODO: This may take some tweaking if we ever try to reuse existing builds
128    *       that were performed in-tree.
129    */
130   if (!g_file_test (configure_ac, G_FILE_TEST_IS_REGULAR) ||
131       !g_file_test (configure, G_FILE_TEST_IS_REGULAR) ||
132       !g_file_test (config_status, G_FILE_TEST_IS_REGULAR) ||
133       !g_file_test (makefile, G_FILE_TEST_IS_REGULAR))
134     {
135       ide_pipeline_stage_set_completed (stage, FALSE);
136       IDE_EXIT;
137     }
138 
139   /*
140    * Now make sure that config.status and Makefile are indeed newer than
141    * our configure script.
142    */
143   if (compare_mtime (configure_ac, configure) < 0 &&
144       compare_mtime (configure, config_status) < 0 &&
145       compare_mtime (configure, makefile) < 0)
146     {
147       /*
148        * TODO: It would be fancy if we could look at '^ac_cs_config=' to determine
149        * if the configure args match what we expect. But this is a bit more
150        * complicated than simply a string comparison.
151        */
152       ide_pipeline_stage_set_completed (stage, TRUE);
153       IDE_EXIT;
154     }
155 
156   ide_pipeline_stage_set_completed (stage, FALSE);
157 
158   IDE_EXIT;
159 }
160 
161 static const gchar *
compiler_environment_from_language(gchar * language)162 compiler_environment_from_language (gchar *language)
163 {
164   if (g_strcmp0 (language, IDE_TOOLCHAIN_LANGUAGE_C) == 0)
165     return "CC";
166 
167   if (g_strcmp0 (language, IDE_TOOLCHAIN_LANGUAGE_CPLUSPLUS) == 0)
168     return "CXX";
169 
170   if (g_strcmp0 (language, IDE_TOOLCHAIN_LANGUAGE_PYTHON) == 0)
171     return "PYTHON";
172 
173   if (g_strcmp0 (language, IDE_TOOLCHAIN_LANGUAGE_FORTRAN) == 0)
174     return "FC";
175 
176   if (g_strcmp0 (language, IDE_TOOLCHAIN_LANGUAGE_D) == 0)
177     return "DC";
178 
179   if (g_strcmp0 (language, IDE_TOOLCHAIN_LANGUAGE_VALA) == 0)
180     return "VALAC";
181 
182   return NULL;
183 }
184 
185 static void
add_compiler_env_variables(gpointer key,gpointer value,gpointer user_data)186 add_compiler_env_variables (gpointer key,
187                             gpointer value,
188                             gpointer user_data)
189 {
190   IdeSubprocessLauncher *launcher = (IdeSubprocessLauncher *)user_data;
191   const gchar *env = compiler_environment_from_language (key);
192   if (env == NULL)
193     return;
194 
195   ide_subprocess_launcher_setenv (launcher, env, value, TRUE);
196 }
197 
198 static gboolean
register_configure_stage(IdeAutotoolsPipelineAddin * self,IdePipeline * pipeline,GError ** error)199 register_configure_stage (IdeAutotoolsPipelineAddin  *self,
200                           IdePipeline           *pipeline,
201                           GError                    **error)
202 {
203   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
204   g_autoptr(IdePipelineStage) stage = NULL;
205   IdeConfig *configuration;
206   IdeToolchain *toolchain;
207   g_autofree gchar *configure_path = NULL;
208   g_autofree gchar *host_arg = NULL;
209   g_autoptr(IdeTriplet) triplet = NULL;
210   const gchar *config_opts;
211   const gchar *prefix;
212   guint stage_id;
213 
214   g_assert (IDE_IS_AUTOTOOLS_PIPELINE_ADDIN (self));
215   g_assert (IDE_IS_PIPELINE (pipeline));
216 
217   if (NULL == (launcher = ide_pipeline_create_launcher (pipeline, error)))
218     return FALSE;
219 
220   ide_subprocess_launcher_set_flags (launcher,
221                                      G_SUBPROCESS_FLAGS_STDIN_PIPE |
222                                      G_SUBPROCESS_FLAGS_STDOUT_PIPE |
223                                      G_SUBPROCESS_FLAGS_STDERR_PIPE);
224 
225   configure_path = ide_pipeline_build_srcdir_path (pipeline, "configure", NULL);
226   ide_subprocess_launcher_push_argv (launcher, configure_path);
227 
228   /* --host=triplet */
229   configuration = ide_pipeline_get_config (pipeline);
230   toolchain = ide_pipeline_get_toolchain (pipeline);
231   triplet = ide_toolchain_get_host_triplet (toolchain);
232   host_arg = g_strdup_printf ("--host=%s", ide_triplet_get_full_name (triplet));
233   ide_subprocess_launcher_push_argv (launcher, host_arg);
234 
235   if (g_strcmp0 (ide_toolchain_get_id (toolchain), "default") != 0)
236     {
237       GHashTable *compilers = ide_toolchain_get_tools_for_id (toolchain,
238                                                               IDE_TOOLCHAIN_TOOL_CC);
239       const gchar *tool_path;
240 
241       g_hash_table_foreach (compilers, add_compiler_env_variables, launcher);
242 
243       tool_path = ide_toolchain_get_tool_for_language (toolchain,
244                                                        IDE_TOOLCHAIN_LANGUAGE_ANY,
245                                                        IDE_TOOLCHAIN_TOOL_AR);
246       if (tool_path != NULL)
247         ide_subprocess_launcher_setenv (launcher, "AR", tool_path, TRUE);
248 
249       tool_path = ide_toolchain_get_tool_for_language (toolchain,
250                                                        IDE_TOOLCHAIN_LANGUAGE_ANY,
251                                                        IDE_TOOLCHAIN_TOOL_STRIP);
252       if (tool_path != NULL)
253         ide_subprocess_launcher_setenv (launcher, "STRIP", tool_path, TRUE);
254 
255       tool_path = ide_toolchain_get_tool_for_language (toolchain,
256                                                        IDE_TOOLCHAIN_LANGUAGE_ANY,
257                                                        IDE_TOOLCHAIN_TOOL_PKG_CONFIG);
258       if (tool_path != NULL)
259         ide_subprocess_launcher_setenv (launcher, "PKG_CONFIG", tool_path, TRUE);
260     }
261 
262   /*
263    * Parse the configure options as defined in the build configuration and append
264    * them to configure.
265    */
266 
267   config_opts = ide_config_get_config_opts (configuration);
268   prefix = ide_config_get_prefix (configuration);
269 
270   if (prefix != NULL)
271     {
272       g_autofree gchar *prefix_arg = g_strdup_printf ("--prefix=%s", prefix);
273       ide_subprocess_launcher_push_argv (launcher, prefix_arg);
274     }
275 
276   if (!dzl_str_empty0 (config_opts))
277     {
278       g_auto(GStrv) argv = NULL;
279       gint argc = 0;
280 
281       if (!g_shell_parse_argv (config_opts, &argc, &argv, error))
282         return FALSE;
283 
284       for (gint i = 0; i < argc; i++)
285         ide_subprocess_launcher_push_argv (launcher, argv[i]);
286     }
287 
288   stage = g_object_new (IDE_TYPE_PIPELINE_STAGE_LAUNCHER,
289                         "name", _("Configuring project"),
290                         "launcher", launcher,
291                         NULL);
292 
293   /*
294    * If the Makefile exists within the builddir, we will assume the
295    * project has been initially configured correctly. Otherwise, every
296    * time the user opens the project they have to go through a full
297    * re-configure and build.
298    *
299    * Should the user need to perform an autogen, a manual rebuild is
300    * easily achieved so this seems to be the sensible default.
301    *
302    * If we were to do this "correctly", we would look at config.status to
303    * match the "ac_cs_config" variable to what we set. However, that is
304    * influenced by environment variables, so its a bit non-trivial.
305    */
306   g_signal_connect_object (stage,
307                            "query",
308                            G_CALLBACK (check_configure_status),
309                            self,
310                            G_CONNECT_SWAPPED);
311 
312   stage_id = ide_pipeline_attach (pipeline, IDE_PIPELINE_PHASE_CONFIGURE, 0, stage);
313 
314   ide_pipeline_addin_track (IDE_PIPELINE_ADDIN (self), stage_id);
315 
316   return TRUE;
317 }
318 
319 static gboolean
register_make_stage(IdeAutotoolsPipelineAddin * self,IdePipeline * pipeline,IdePipelinePhase phase,GError ** error,const gchar * target,const gchar * clean_target)320 register_make_stage (IdeAutotoolsPipelineAddin  *self,
321                      IdePipeline           *pipeline,
322                      IdePipelinePhase               phase,
323                      GError                    **error,
324                      const gchar                *target,
325                      const gchar                *clean_target)
326 {
327   g_autoptr(IdePipelineStage) stage = NULL;
328   IdeConfig *config;
329   guint stage_id;
330   gint parallel;
331 
332   g_assert (IDE_IS_AUTOTOOLS_PIPELINE_ADDIN (self));
333   g_assert (IDE_IS_PIPELINE (pipeline));
334 
335   config = ide_pipeline_get_config (pipeline);
336   parallel = ide_config_get_parallelism (config);
337 
338   stage = g_object_new (IDE_TYPE_AUTOTOOLS_MAKE_STAGE,
339                         "name", _("Building project"),
340                         "clean-target", clean_target,
341                         "parallel", parallel,
342                         "target", target,
343                         NULL);
344 
345   stage_id = ide_pipeline_attach (pipeline, phase, 0, stage);
346   ide_pipeline_addin_track (IDE_PIPELINE_ADDIN (self), stage_id);
347 
348   return TRUE;
349 }
350 
351 static gboolean
register_makecache_stage(IdeAutotoolsPipelineAddin * self,IdePipeline * pipeline,GError ** error)352 register_makecache_stage (IdeAutotoolsPipelineAddin  *self,
353                           IdePipeline           *pipeline,
354                           GError                    **error)
355 {
356   g_autoptr(IdePipelineStage) stage = NULL;
357   guint stage_id;
358 
359   g_assert (IDE_IS_AUTOTOOLS_PIPELINE_ADDIN (self));
360   g_assert (IDE_IS_PIPELINE (pipeline));
361 
362   if (NULL == (stage = ide_autotools_makecache_stage_new_for_pipeline (pipeline, error)))
363     return FALSE;
364 
365   ide_pipeline_stage_set_name (stage, _("Caching build commands"));
366 
367   stage_id = ide_pipeline_attach (pipeline,
368                                         IDE_PIPELINE_PHASE_CONFIGURE | IDE_PIPELINE_PHASE_AFTER,
369                                         0,
370                                         stage);
371   ide_pipeline_addin_track (IDE_PIPELINE_ADDIN (self), stage_id);
372 
373   return TRUE;
374 }
375 
376 static void
ide_autotools_pipeline_addin_load(IdePipelineAddin * addin,IdePipeline * pipeline)377 ide_autotools_pipeline_addin_load (IdePipelineAddin *addin,
378                                    IdePipeline      *pipeline)
379 {
380   IdeAutotoolsPipelineAddin *self = (IdeAutotoolsPipelineAddin *)addin;
381   g_autoptr(GError) error = NULL;
382   IdeBuildSystem *build_system;
383   IdeContext *context;
384 
385   g_assert (IDE_IS_AUTOTOOLS_PIPELINE_ADDIN (self));
386   g_assert (IDE_IS_PIPELINE (pipeline));
387 
388   context = ide_object_get_context (IDE_OBJECT (addin));
389   build_system = ide_build_system_from_context (context);
390 
391   if (!IDE_IS_AUTOTOOLS_BUILD_SYSTEM (build_system))
392     return;
393 
394   if (!register_autoreconf_stage (self, pipeline, &error) ||
395       !register_configure_stage (self, pipeline, &error) ||
396       !register_makecache_stage (self, pipeline, &error) ||
397       !register_make_stage (self, pipeline, IDE_PIPELINE_PHASE_BUILD, &error, "all", "clean") ||
398       !register_make_stage (self, pipeline, IDE_PIPELINE_PHASE_INSTALL, &error, "install", NULL))
399     {
400       g_assert (error != NULL);
401       g_warning ("Failed to create autotools launcher: %s", error->message);
402       return;
403     }
404 }
405 
406 /* GObject Boilerplate */
407 
408 static void
addin_iface_init(IdePipelineAddinInterface * iface)409 addin_iface_init (IdePipelineAddinInterface *iface)
410 {
411   iface->load = ide_autotools_pipeline_addin_load;
412 }
413 
414 struct _IdeAutotoolsPipelineAddin { IdeObject parent; };
415 
G_DEFINE_FINAL_TYPE_WITH_CODE(IdeAutotoolsPipelineAddin,ide_autotools_pipeline_addin,IDE_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (IDE_TYPE_PIPELINE_ADDIN,addin_iface_init))416 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeAutotoolsPipelineAddin, ide_autotools_pipeline_addin, IDE_TYPE_OBJECT,
417                          G_IMPLEMENT_INTERFACE (IDE_TYPE_PIPELINE_ADDIN, addin_iface_init))
418 
419 static void
420 ide_autotools_pipeline_addin_class_init (IdeAutotoolsPipelineAddinClass *klass)
421 {
422 }
423 
424 static void
ide_autotools_pipeline_addin_init(IdeAutotoolsPipelineAddin * self)425 ide_autotools_pipeline_addin_init (IdeAutotoolsPipelineAddin *self)
426 {
427 }
428