1
/* ide-autotools-build-task.c
3
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
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.
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.
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/>.
24
#include <glib/gi18n.h>
27
#include "ide-autotools-build-task.h"
28
#include "ide-context.h"
29
#include "ide-device.h"
30
#include "ide-project.h"
37
guint require_autogen : 1;
38
guint require_configure : 1;
40
} IdeAutotoolsBuildTaskPrivate;
44
gchar *directory_path;
48
gchar **configure_argv;
50
guint require_autogen : 1;
51
guint require_configure : 1;
52
guint bootstrap_only : 1;
55
typedef gboolean (*WorkStep) (GTask *task,
56
IdeAutotoolsBuildTask *self,
58
GCancellable *cancellable);
60
G_DEFINE_TYPE_WITH_PRIVATE (IdeAutotoolsBuildTask, ide_autotools_build_task,
61
IDE_TYPE_BUILD_RESULT)
69
PROP_REQUIRE_CONFIGURE,
73
static GSubprocess *log_and_spawn (IdeAutotoolsBuildTask *self,
74
GSubprocessLauncher *launcher,
77
...) G_GNUC_NULL_TERMINATED;
78
static gboolean step_mkdirs (GTask *task,
79
IdeAutotoolsBuildTask *self,
81
GCancellable *cancellable);
82
static gboolean step_autogen (GTask *task,
83
IdeAutotoolsBuildTask *self,
85
GCancellable *cancellable);
86
static gboolean step_configure (GTask *task,
87
IdeAutotoolsBuildTask *self,
89
GCancellable *cancellable);
90
static gboolean step_make_all (GTask *task,
91
IdeAutotoolsBuildTask *self,
93
GCancellable *cancellable);
95
static GParamSpec *gParamSpecs [LAST_PROP];
96
static WorkStep gWorkSteps [] = {
105
ide_autotools_build_task_get_require_autogen (IdeAutotoolsBuildTask *task)
107
IdeAutotoolsBuildTaskPrivate *priv;
109
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task), FALSE);
111
priv = ide_autotools_build_task_get_instance_private (task);
113
return priv->require_autogen;
117
ide_autotools_build_task_set_require_autogen (IdeAutotoolsBuildTask *task,
118
gboolean require_autogen)
120
IdeAutotoolsBuildTaskPrivate *priv;
122
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task));
124
priv = ide_autotools_build_task_get_instance_private (task);
126
priv->require_autogen = !!require_autogen;
130
ide_autotools_build_task_get_require_configure (IdeAutotoolsBuildTask *task)
132
IdeAutotoolsBuildTaskPrivate *priv;
134
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task), FALSE);
136
priv = ide_autotools_build_task_get_instance_private (task);
138
return priv->require_configure;
142
ide_autotools_build_task_set_require_configure (IdeAutotoolsBuildTask *task,
143
gboolean require_configure)
145
IdeAutotoolsBuildTaskPrivate *priv;
147
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (task));
149
priv = ide_autotools_build_task_get_instance_private (task);
151
priv->require_autogen = !!require_configure;
155
* ide_autotools_build_task_get_config:
156
* @self: A #IdeAutotoolsBuildTask.
158
* Gets the "config" property of the task. This is the overlay config to be
159
* applied on top of the device config when compiling.
161
* Returns: (transfer none) (nullable): A #GKeyFile or %NULL.
164
ide_autotools_build_task_get_config (IdeAutotoolsBuildTask *self)
166
IdeAutotoolsBuildTaskPrivate *priv;
168
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
170
priv = ide_autotools_build_task_get_instance_private (self);
176
ide_autotools_build_task_set_config (IdeAutotoolsBuildTask *self,
179
IdeAutotoolsBuildTaskPrivate *priv;
181
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
183
priv = ide_autotools_build_task_get_instance_private (self);
185
if (priv->config != config)
187
g_clear_pointer (&priv->config, g_key_file_unref);
188
priv->config = config ? g_key_file_ref (config) : NULL;
189
g_object_notify_by_pspec (G_OBJECT (self),
190
gParamSpecs [PROP_CONFIG]);
195
* ide_autotools_build_task_get_device:
196
* @self: A #IdeAutotoolsBuildTask.
198
* Gets the "device" property. This is the device we are compiling for,
199
* which may involve cross-compiling.
201
* Returns: (transfer none): An #IdeDevice.
204
ide_autotools_build_task_get_device (IdeAutotoolsBuildTask *self)
206
IdeAutotoolsBuildTaskPrivate *priv;
208
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
210
priv = ide_autotools_build_task_get_instance_private (self);
216
ide_autotools_build_task_set_device (IdeAutotoolsBuildTask *self,
219
IdeAutotoolsBuildTaskPrivate *priv;
221
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
223
priv = ide_autotools_build_task_get_instance_private (self);
225
if (g_set_object (&priv->device, device))
226
g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_DEVICE]);
230
* ide_autotools_build_task_get_directory:
232
* Fetches the build directory that was used.
234
* Returns: (transfer none): A #GFile.
237
ide_autotools_build_task_get_directory (IdeAutotoolsBuildTask *self)
239
IdeAutotoolsBuildTaskPrivate *priv;
241
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
243
priv = ide_autotools_build_task_get_instance_private (self);
245
return priv->directory;
249
ide_autotools_build_task_set_directory (IdeAutotoolsBuildTask *self,
252
IdeAutotoolsBuildTaskPrivate *priv;
254
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
255
g_return_if_fail (!directory || G_IS_FILE (directory));
257
priv = ide_autotools_build_task_get_instance_private (self);
260
* We require a build directory that is accessable via a native path.
264
g_autofree gchar *path = NULL;
266
path = g_file_get_path (directory);
270
g_warning (_("Directory must be on a locally mounted filesystem."));
275
if (priv->directory != directory)
276
if (g_set_object (&priv->directory, directory))
277
g_object_notify_by_pspec (G_OBJECT (self),
278
gParamSpecs [PROP_DIRECTORY]);
282
ide_autotools_build_task_finalize (GObject *object)
284
IdeAutotoolsBuildTask *self = (IdeAutotoolsBuildTask *)object;
285
IdeAutotoolsBuildTaskPrivate *priv;
287
priv = ide_autotools_build_task_get_instance_private (self);
289
g_clear_object (&priv->device);
290
g_clear_object (&priv->directory);
291
g_clear_pointer (&priv->config, g_key_file_unref);
293
G_OBJECT_CLASS (ide_autotools_build_task_parent_class)->finalize (object);
297
ide_autotools_build_task_get_property (GObject *object,
302
IdeAutotoolsBuildTask *self = IDE_AUTOTOOLS_BUILD_TASK (object);
307
g_value_set_object (value, ide_autotools_build_task_get_config (self));
311
g_value_set_object (value, ide_autotools_build_task_get_device (self));
315
g_value_set_object (value, ide_autotools_build_task_get_directory (self));
318
case PROP_REQUIRE_AUTOGEN:
319
g_value_set_boolean (value, ide_autotools_build_task_get_require_autogen (self));
322
case PROP_REQUIRE_CONFIGURE:
323
g_value_set_boolean (value, ide_autotools_build_task_get_require_configure (self));
327
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
332
ide_autotools_build_task_set_property (GObject *object,
337
IdeAutotoolsBuildTask *self = IDE_AUTOTOOLS_BUILD_TASK (object);
342
ide_autotools_build_task_set_config (self, g_value_get_boxed (value));
346
ide_autotools_build_task_set_device (self, g_value_get_object (value));
350
ide_autotools_build_task_set_directory (self, g_value_get_object (value));
353
case PROP_REQUIRE_AUTOGEN:
354
ide_autotools_build_task_set_require_autogen (self, g_value_get_boolean (value));
357
case PROP_REQUIRE_CONFIGURE:
358
ide_autotools_build_task_set_require_configure (self, g_value_get_boolean (value));
362
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
367
ide_autotools_build_task_class_init (IdeAutotoolsBuildTaskClass *klass)
369
GObjectClass *object_class = G_OBJECT_CLASS (klass);
371
object_class->finalize = ide_autotools_build_task_finalize;
372
object_class->get_property = ide_autotools_build_task_get_property;
373
object_class->set_property = ide_autotools_build_task_set_property;
375
gParamSpecs [PROP_CONFIG] =
376
g_param_spec_boxed ("config",
378
_("The overlay config for the compilation."),
381
G_PARAM_CONSTRUCT_ONLY |
382
G_PARAM_STATIC_STRINGS));
384
gParamSpecs [PROP_DEVICE] =
385
g_param_spec_object ("device",
387
_("The device to build for."),
390
G_PARAM_CONSTRUCT_ONLY |
391
G_PARAM_STATIC_STRINGS));
393
gParamSpecs [PROP_DIRECTORY] =
394
g_param_spec_object ("directory",
396
_("The directory to perform the build within."),
399
G_PARAM_CONSTRUCT_ONLY |
400
G_PARAM_STATIC_STRINGS));
402
gParamSpecs [PROP_REQUIRE_AUTOGEN] =
403
g_param_spec_boolean ("require-autogen",
404
_("Require Autogen"),
405
_("If autogen.sh should be forced to execute."),
408
G_PARAM_CONSTRUCT_ONLY |
409
G_PARAM_STATIC_STRINGS));
411
gParamSpecs [PROP_REQUIRE_CONFIGURE] =
412
g_param_spec_boolean ("require-configure",
413
_("Require Configure"),
414
_("If configure should be forced to execute."),
417
G_PARAM_CONSTRUCT_ONLY |
418
G_PARAM_STATIC_STRINGS));
420
g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
424
ide_autotools_build_task_init (IdeAutotoolsBuildTask *self)
429
gen_configure_argv (IdeAutotoolsBuildTask *self,
432
IdeAutotoolsBuildTaskPrivate *priv;
434
const gchar *system_type;
435
GKeyFile *configs[2];
440
gchar *configure_path;
443
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
445
priv = ide_autotools_build_task_get_instance_private (self);
447
ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
449
configs [0] = ide_device_get_config (priv->device);
450
configs [1] = priv->config;
452
for (j = 0; j < G_N_ELEMENTS (configs); j++)
454
GKeyFile *config = configs [j];
458
if (g_key_file_has_group (config, "autoconf"))
464
keys = g_key_file_get_keys (config, "autoconf", &len, NULL);
466
for (i = 0; i < len; i++)
470
if (*keys [i] == '-')
472
value = g_key_file_get_string (config,
473
"autoconf", keys [i],
476
g_hash_table_replace (ht, g_strdup (keys [i]), value);
485
ar = g_ptr_array_new ();
486
configure_path = g_build_filename (state->project_path, "configure", NULL);
487
g_ptr_array_add (ar, configure_path);
489
g_hash_table_iter_init (&iter, ht);
491
while (g_hash_table_iter_next (&iter, &k, &v))
493
g_ptr_array_add (ar, g_strdup (k));
494
if (v && *(gchar *)v)
495
g_ptr_array_add (ar, g_strdup (v));
498
if (!g_hash_table_lookup (ht, "--prefix"))
502
prefix = g_build_filename (state->project_path, "_install", NULL);
503
g_ptr_array_add (ar, g_strdup_printf ("--prefix=%s", prefix));
507
device = ide_autotools_build_task_get_device (self);
508
system_type = ide_device_get_system_type (device);
509
g_ptr_array_add (ar, g_strdup_printf ("--host=%s", system_type));
511
g_ptr_array_add (ar, NULL);
512
g_hash_table_unref (ht);
514
return (gchar **)g_ptr_array_free (ar, FALSE);
518
worker_state_new (IdeAutotoolsBuildTask *self)
520
IdeAutotoolsBuildTaskPrivate *priv;
521
g_autofree gchar *name = NULL;
523
GPtrArray *make_targets;
529
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
531
priv = ide_autotools_build_task_get_instance_private (self);
533
context = ide_object_get_context (IDE_OBJECT (self));
534
project_file = ide_context_get_project_file (context);
536
name = g_file_get_basename (project_file);
538
if (g_str_has_prefix (name, "configure."))
539
project_dir = g_file_get_parent (project_file);
541
project_dir = g_object_ref (project_file);
543
state = g_slice_new0 (WorkerState);
544
state->require_autogen = priv->require_autogen;
545
state->require_configure = priv->require_configure;
546
state->directory_path = g_file_get_path (priv->directory);
547
state->project_path = g_file_get_path (project_dir);
548
state->system_type = g_strdup (ide_device_get_system_type (priv->device));
550
if ((val32 = g_key_file_get_integer (priv->config, "parallel", "workers", NULL)))
551
state->parallel = g_strdup_printf ("-j%u", val32);
553
state->parallel = g_strdup ("-j1");
555
make_targets = g_ptr_array_new ();
557
if (priv->config && g_key_file_get_boolean (priv->config, "autotools", "rebuild", NULL))
559
state->require_autogen = TRUE;
560
state->require_configure = TRUE;
561
g_ptr_array_add (make_targets, g_strdup ("clean"));
564
g_ptr_array_add (make_targets, g_strdup ("all"));
565
g_ptr_array_add (make_targets, NULL);
566
state->make_targets = (gchar **)g_ptr_array_free (make_targets, FALSE);
568
if (g_key_file_get_boolean (priv->config, "autotools", "bootstrap-only", NULL))
570
state->require_autogen = TRUE;
571
state->require_configure = TRUE;
572
state->bootstrap_only = TRUE;
573
g_clear_pointer (&state->make_targets, (GDestroyNotify)g_strfreev);
576
state->configure_argv = gen_configure_argv (self, state);
582
worker_state_free (void *data)
584
WorkerState *state = data;
586
g_free (state->directory_path);
587
g_free (state->project_path);
588
g_free (state->system_type);
589
g_free (state->parallel);
590
g_strfreev (state->configure_argv);
591
g_strfreev (state->make_targets);
592
g_slice_free (WorkerState, state);
596
ide_autotools_build_task_execute_worker (GTask *task,
597
gpointer source_object,
599
GCancellable *cancellable)
601
IdeAutotoolsBuildTask *self = source_object;
602
WorkerState *state = task_data;
605
g_return_if_fail (G_IS_TASK (task));
606
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
607
g_return_if_fail (state);
608
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
610
for (i = 0; gWorkSteps [i]; i++)
612
if (g_cancellable_is_cancelled (cancellable) ||
613
!gWorkSteps [i] (task, self, state, cancellable))
617
g_task_return_boolean (task, TRUE);
621
ide_autotools_build_task_execute_async (IdeAutotoolsBuildTask *self,
622
GCancellable *cancellable,
623
GAsyncReadyCallback callback,
626
IdeAutotoolsBuildTaskPrivate *priv;
627
g_autoptr(GTask) task = NULL;
630
g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
631
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
633
priv = ide_autotools_build_task_get_instance_private (self);
637
g_task_report_new_error (self, callback, user_data,
638
ide_autotools_build_task_execute_async,
641
_("Cannot execute build task more than once."));
645
priv->executed = TRUE;
647
state = worker_state_new (self);
649
task = g_task_new (self, cancellable, callback, user_data);
650
g_task_set_task_data (task, state, worker_state_free);
651
g_task_run_in_thread (task, ide_autotools_build_task_execute_worker);
655
ide_autotools_build_task_execute_finish (IdeAutotoolsBuildTask *self,
656
GAsyncResult *result,
659
GTask *task = (GTask *)result;
661
g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), FALSE);
662
g_return_val_if_fail (G_IS_TASK (result), FALSE);
663
g_return_val_if_fail (G_IS_TASK (task), FALSE);
665
return g_task_propagate_boolean (task, error);
669
log_and_spawn (IdeAutotoolsBuildTask *self,
670
GSubprocessLauncher *launcher,
681
log = g_string_new (NULL);
682
g_string_append (log, argv0);
684
argv = g_ptr_array_new ();
685
g_ptr_array_add (argv, (gchar *)argv0);
687
va_start (args, argv0);
688
while ((item = va_arg (args, gchar *)))
690
g_ptr_array_add (argv, item);
691
g_string_append_printf (log, " '%s'", item);
695
g_ptr_array_add (argv, NULL);
697
ide_build_result_log_stdout (IDE_BUILD_RESULT (self), "%s", log->str);
698
ret = g_subprocess_launcher_spawnv (launcher,
699
(const gchar * const *)argv->pdata,
702
g_string_free (log, TRUE);
703
g_ptr_array_unref (argv);
709
step_mkdirs (GTask *task,
710
IdeAutotoolsBuildTask *self,
712
GCancellable *cancellable)
714
g_assert (G_IS_TASK (task));
715
g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
717
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
719
if (!g_file_test (state->directory_path, G_FILE_TEST_EXISTS))
721
if (g_mkdir_with_parents (state->directory_path, 0750) != 0)
723
g_task_return_new_error (task,
726
_("Failed to create build directory."));
730
else if (!g_file_test (state->directory_path, G_FILE_TEST_IS_DIR))
732
g_task_return_new_error (task,
734
G_IO_ERROR_NOT_DIRECTORY,
735
_("'%s' is not a directory."),
736
state->directory_path);
744
step_autogen (GTask *task,
745
IdeAutotoolsBuildTask *self,
747
GCancellable *cancellable)
749
g_autofree gchar *autogen_sh_path = NULL;
750
g_autofree gchar *configure_path = NULL;
751
g_autoptr(GSubprocessLauncher) launcher = NULL;
752
g_autoptr(GSubprocess) process = NULL;
753
GError *error = NULL;
755
g_assert (G_IS_TASK (task));
756
g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
758
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
760
configure_path = g_build_filename (state->project_path, "configure", NULL);
762
if (!state->require_autogen)
764
if (g_file_test (configure_path, G_FILE_TEST_IS_REGULAR))
768
autogen_sh_path = g_build_filename (state->project_path, "autogen.sh", NULL);
769
if (!g_file_test (autogen_sh_path, G_FILE_TEST_EXISTS))
771
g_task_return_new_error (task,
774
_("autogen.sh is missing from project directory (%s)."),
775
state->project_path);
779
if (!g_file_test (autogen_sh_path, G_FILE_TEST_IS_EXECUTABLE))
781
g_task_return_new_error (task,
784
_("autogen.sh is not executable."));
788
launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDOUT_PIPE |
789
G_SUBPROCESS_FLAGS_STDERR_PIPE));
790
g_subprocess_launcher_set_cwd (launcher, state->project_path);
791
g_subprocess_launcher_setenv (launcher, "NOCONFIGURE", "1", TRUE);
793
process = log_and_spawn (self, launcher, &error, autogen_sh_path, NULL);
797
g_task_return_error (task, error);
801
ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
803
if (!g_subprocess_wait_check (process, cancellable, &error))
805
g_task_return_error (task, error);
809
if (!g_file_test (configure_path, G_FILE_TEST_IS_EXECUTABLE))
811
g_task_return_new_error (task,
814
_("autogen.sh failed to create configure (%s)"),
823
step_configure (GTask *task,
824
IdeAutotoolsBuildTask *self,
826
GCancellable *cancellable)
828
g_autoptr(GSubprocessLauncher) launcher = NULL;
829
g_autoptr(GSubprocess) process = NULL;
830
g_autofree gchar *makefile_path = NULL;
831
g_autofree gchar *config_log = NULL;
832
GError *error = NULL;
834
g_assert (G_IS_TASK (task));
835
g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
837
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
839
if (!state->require_configure)
842
* Skip configure if we already have a makefile.
844
makefile_path = g_build_filename (state->directory_path, "Makefile", NULL);
845
if (g_file_test (makefile_path, G_FILE_TEST_EXISTS))
849
launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDERR_PIPE |
850
G_SUBPROCESS_FLAGS_STDOUT_PIPE));
851
g_subprocess_launcher_set_cwd (launcher, state->directory_path);
853
config_log = g_strjoinv (" ", state->configure_argv);
854
ide_build_result_log_stdout (IDE_BUILD_RESULT (self), "%s", config_log);
856
process = g_subprocess_launcher_spawnv (
858
(const gchar * const *)state->configure_argv,
863
g_task_return_error (task, error);
867
ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
869
if (!g_subprocess_wait_check (process, cancellable, &error))
871
g_task_return_error (task, error);
875
if (state->bootstrap_only)
877
g_task_return_boolean (task, TRUE);
885
step_make_all (GTask *task,
886
IdeAutotoolsBuildTask *self,
888
GCancellable *cancellable)
890
g_autoptr(GSubprocessLauncher) launcher = NULL;
891
g_autoptr(GSubprocess) process = NULL;
892
const gchar * const *targets;
893
gchar *default_targets[] = { "all", NULL };
894
GError *error = NULL;
897
g_assert (G_IS_TASK (task));
898
g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
900
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
902
launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDERR_PIPE |
903
G_SUBPROCESS_FLAGS_STDOUT_PIPE));
904
g_subprocess_launcher_set_cwd (launcher, state->directory_path);
906
if (!g_strv_length (state->make_targets))
907
targets = (const gchar * const *)default_targets;
909
targets = (const gchar * const *)state->make_targets;
911
for (i = 0; targets [i]; i++)
913
const gchar *target = targets [i];
915
process = log_and_spawn (self, launcher, &error, GNU_MAKE_NAME, target, state->parallel, NULL);
919
g_task_return_error (task, error);
923
ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
925
if (!g_subprocess_wait_check (process, cancellable, &error))
927
g_task_return_error (task, error);