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