jbsrc/lib/src/core/jb-action.c (19679B) - raw
1 /*
2 * JB, the Jean-Yves Lefort's Build System
3 * Copyright (C) 2008 Jean-Yves Lefort <jylefort@brutele.be>
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 along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <string.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include "jb-group.h"
27 #include "jb-resource.h"
28 #include "jb-tests.h"
29 #include "jb-feature.h"
30 #include "jb-config.h"
31 #include "jb-action.h"
32
33 static const char *standard_distfiles[] = {
34 "jb",
35 "jbsrc/jb.c",
36 "jbsrc/lib/COPYING",
37 "jbsrc/lib/README",
38 "jbsrc/tools/config.guess",
39 "jbsrc/tools/config.sub",
40 JB_PACKAGE_SOURCES,
41 JB_SOURCES
42 };
43
44 static const char *optional_distfiles[] = {
45 "AUTHORS",
46 "COPYING",
47 "COPYING-DOCS",
48 "INSTALL",
49 "NEWS",
50 "README",
51 "TODO",
52 "TRANSLATING",
53 "po/POTFILES.in",
54 "po/POTFILES.skip"
55 };
56
57 static const char *distcleanfiles[] = {
58 JB_CONFIG_FILE,
59 "build/jb",
60 "build/jb.c",
61 "build/jbsrc/jb",
62 "build/jbsrc/jb.c"
63 };
64
65 static const char *maintainercleanfiles[] = {
66 "jb",
67 "jbsrc/lib",
68 "jbsrc/tools/config.guess",
69 "jbsrc/tools/config.sub"
70 };
71
72 static GSList *
73 get_groups (GSList *group_names)
74 {
75 GSList *l;
76 GSList *groups = NULL;
77
78 if (group_names == NULL)
79 groups = g_slist_copy(jb_groups);
80 else
81 JB_LIST_FOREACH(l, group_names)
82 {
83 const char *name = l->data;
84 JBGroup *group;
85
86 group = jb_group_get(name);
87 if (group == NULL)
88 jb_error("unknown group \"%s\"", name);
89
90 groups = g_slist_append(groups, group);
91 }
92
93 return groups;
94 }
95
96 static void
97 perform_action (GSList *groups, void (*perform) (JBResource *res))
98 {
99 GSList *la;
100
101 JB_LIST_FOREACH(la, groups)
102 {
103 JBGroup *group = la->data;
104 GSList *lb;
105
106 JB_LIST_FOREACH(lb, group->resources)
107 {
108 JBResource *res = lb->data;
109
110 perform(res);
111 }
112 }
113 }
114
115 static void
116 core_configure (void)
117 {
118 jb_check_cc_dependency_style();
119 }
120
121 static void
122 report_variable_group (const JBVariableGroup *group, int varname_len)
123 {
124 GSList *l;
125
126 jb_message("");
127 jb_message(" %s:", group->name);
128 jb_message("");
129
130 JB_LIST_FOREACH(l, jb_variables)
131 {
132 JBVariable *variable = l->data;
133
134 if (variable->group == group && (variable->flags & JB_VARIABLE_NO_REPORT) == 0)
135 {
136 char *varname;
137 char *value;
138
139 varname = g_strdup_printf("%s:", variable->name);
140 value = jb_variable_to_string(variable);
141
142 jb_message(" %-*s %s", varname_len, varname, value);
143
144 g_free(varname);
145 g_free(value);
146 }
147 }
148 }
149
150 static int
151 get_longest_displayable_variable_name_len (void)
152 {
153 GSList *l;
154 int longest = 0;
155
156 JB_LIST_FOREACH(l, jb_variables)
157 {
158 JBVariable *variable = l->data;
159 int len;
160
161 if (variable->group == NULL)
162 continue;
163
164 len = strlen(variable->name) + 1; /* +1 for the semicolon */
165 if (len > longest)
166 longest = len;
167 }
168
169 return longest;
170 }
171
172 static void
173 print_configure_report (void)
174 {
175 int varname_len;
176 GSList *l;
177
178 jb_message("");
179 jb_message_expand("$human-package $version was configured successfully.", NULL);
180 jb_message("The following variables are in effect:");
181
182 varname_len = get_longest_displayable_variable_name_len();
183
184 JB_LIST_FOREACH(l, jb_variable_groups)
185 report_variable_group(l->data, varname_len);
186
187 jb_message("");
188 jb_message_expand("Type \"./jb build\" to build $human-package $version.", NULL);
189 }
190
191 static void
192 show_variable_group_help (const JBVariableGroup *group)
193 {
194 GSList *l;
195
196 jb_message("");
197 jb_message(" %s:", group->name);
198
199 JB_LIST_FOREACH(l, jb_variables)
200 {
201 JBVariable *variable = l->data;
202 char *value;
203
204 if (variable->group != group)
205 continue;
206
207 jb_message("");
208 jb_message(" variable: %s (%s)", variable->name, jb_variable_get_type_name(variable));
209 jb_message(" description: %s", variable->description);
210
211 value = jb_variable_to_string(variable);
212 jb_message(" default value: %s", value);
213 g_free(value);
214 }
215 }
216
217 void
218 jb_action_help (void)
219 {
220 GSList *l;
221
222 jb_message("Usage:");
223 jb_message("");
224 jb_message(" ./jb help");
225 jb_message(" ./jb configure [VARIABLE=VALUE...]");
226 jb_message(" ./jb build [GROUP...]");
227 jb_message(" ./jb install [GROUP...]");
228 jb_message(" ./jb makedist");
229 jb_message(" ./jb clean [GROUP...]");
230 jb_message(" ./jb distclean [GROUP...]");
231 jb_message(" ./jb maintainerclean [GROUP...]");
232 jb_message("");
233 jb_message("Variables:");
234
235 JB_LIST_FOREACH(l, jb_variable_groups)
236 show_variable_group_help(l->data);
237 }
238
239 static void
240 configure_real (void)
241 {
242 jb_set_log_file("build/configure.log");
243
244 jb_feature_configure();
245 core_configure();
246 jb_package_configure();
247
248 /* remove our temporary test files */
249 unlink("build/test");
250 unlink("build/test.c");
251 unlink("build/test.o");
252 unlink("build/test.o.deps");
253
254 jb_config_save();
255 }
256
257 void
258 jb_action_configure (void)
259 {
260 configure_real();
261
262 print_configure_report();
263 }
264
265 static void
266 ensure_configure (void)
267 {
268 if (g_file_test(JB_CONFIG_FILE, G_FILE_TEST_EXISTS))
269 jb_config_load();
270 else
271 {
272 jb_message_expand("configuring $human-package $version with default values since \"./jb configure\" was not run...", NULL);
273 configure_real();
274 }
275 }
276
277 static void
278 build_real (GSList *groups)
279 {
280 jb_set_log_file("build/build.log");
281
282 perform_action(groups, jb_resource_pre_build);
283 perform_action(groups, jb_resource_build);
284 }
285
286 void
287 jb_action_build (GSList *group_names)
288 {
289 GSList *groups;
290
291 ensure_configure();
292 jb_package_add_resources();
293
294 groups = get_groups(group_names);
295
296 build_real(groups);
297
298 jb_message("");
299 jb_message_expand("$human-package $version was built successfully.", NULL);
300 jb_message_expand("Type \"sudo ./jb install\" to install $human-package $version.", NULL);
301
302 g_slist_free(groups);
303 }
304
305 static void
306 install_real (GSList *groups)
307 {
308 jb_set_log_file("build/install.log");
309
310 perform_action(groups, jb_resource_install);
311 }
312
313 void
314 jb_action_install (GSList *group_names)
315 {
316 GSList *groups;
317
318 ensure_configure();
319 jb_package_add_resources();
320
321 groups = get_groups(group_names);
322
323 build_real(groups);
324 install_real(groups);
325
326 jb_message("");
327 jb_message_expand("$human-package $version was installed successfully.", NULL);
328
329 g_slist_free(groups);
330 }
331
332 static void
333 add_standard_distfiles_to_dist (void)
334 {
335 int i;
336
337 for (i = 0; i < G_N_ELEMENTS(standard_distfiles); i++)
338 jb_action_add_to_dist_string_list(standard_distfiles[i]);
339 }
340
341 static void
342 add_optional_distfiles_to_dist (void)
343 {
344 int i;
345
346 for (i = 0; i < G_N_ELEMENTS(optional_distfiles); i++)
347 {
348 const char *file = optional_distfiles[i];
349
350 if (g_file_test(file, G_FILE_TEST_EXISTS))
351 jb_action_add_to_dist(file);
352 }
353 }
354
355 static void
356 makedist_real (GSList *groups)
357 {
358 jb_set_log_file("build/makedist.log");
359
360 /* just in case */
361 jb_rmtree(jb_action_get_distdir());
362
363 add_standard_distfiles_to_dist();
364 add_optional_distfiles_to_dist();
365
366 perform_action(groups, jb_resource_makedist);
367 }
368
369 void
370 jb_action_makedist (void)
371 {
372 GSList *groups;
373 char *tarball;
374
375 ensure_configure();
376 jb_package_add_resources();
377
378 groups = get_groups(NULL);
379
380 build_real(groups);
381 makedist_real(groups);
382
383 tarball = jb_variable_expand("${package}-$version.tar.bz2", NULL);
384
385 jb_message("creating build/%s", tarball);
386
387 jb_action_exec("cd build && tar -chof - ${package}-$version | bzip2 -9 -c >$tarball",
388 "tarball", tarball,
389 NULL);
390
391 jb_rmtree(jb_action_get_distdir());
392
393 jb_message("");
394 jb_message("build/%s was created successfully.", tarball);
395
396 g_slist_free(groups);
397 g_free(tarball);
398 }
399
400 static void
401 clean_real (GSList *groups)
402 {
403 jb_set_log_file("build/clean.log");
404
405 perform_action(groups, jb_resource_clean);
406 }
407
408 void
409 jb_action_clean (GSList *group_names)
410 {
411 GSList *groups;
412
413 ensure_configure();
414 jb_package_add_resources();
415
416 groups = get_groups(group_names);
417
418 clean_real(groups);
419
420 jb_message("");
421 jb_message_expand("$human-package $version was cleaned successfully.", NULL);
422
423 g_slist_free(groups);
424 }
425
426 static void
427 distclean_real (GSList *groups)
428 {
429 jb_set_log_file("build/distclean.log");
430
431 perform_action(groups, jb_resource_distclean);
432
433 jb_action_rm_array((char **) distcleanfiles, G_N_ELEMENTS(distcleanfiles));
434 }
435
436 void
437 jb_action_distclean (GSList *group_names)
438 {
439 GSList *groups;
440
441 ensure_configure();
442 jb_package_add_resources();
443
444 groups = get_groups(group_names);
445
446 clean_real(groups);
447 distclean_real(groups);
448
449 jb_message("");
450 jb_message_expand("$human-package $version was dist-cleaned successfully.", NULL);
451
452 g_slist_free(groups);
453 }
454
455 static void
456 maintainerclean_real (GSList *groups)
457 {
458 jb_set_log_file("build/maintainerclean.log");
459
460 perform_action(groups, jb_resource_maintainerclean);
461
462 jb_action_rm_array((char **) maintainercleanfiles, G_N_ELEMENTS(maintainercleanfiles));
463 }
464
465 void
466 jb_action_maintainerclean (GSList *group_names)
467 {
468 GSList *groups;
469
470 ensure_configure();
471 jb_package_add_resources();
472
473 groups = get_groups(group_names);
474
475 clean_real(groups);
476 distclean_real(groups);
477 maintainerclean_real(groups);
478
479 jb_message("");
480 jb_message_expand("$human-package $version was maintainer-cleaned successfully.", NULL);
481
482 g_slist_free(groups);
483 }
484
485 void
486 jb_action_exec (const char *str, ...)
487 {
488 va_list args;
489 char *command;
490 gboolean can_fail = FALSE;
491 char *standard_output;
492 char *standard_error;
493
494 g_return_if_fail(str != NULL);
495
496 va_start(args, str);
497 command = jb_variable_expandv(str, args);
498 va_end(args);
499
500 if (command[0] == '-')
501 {
502 char *tmp;
503
504 can_fail = TRUE;
505
506 tmp = g_strdup(command + 1);
507 g_free(command);
508 command = tmp;
509 }
510
511 if (! jb_exec(&standard_output, &standard_error, "%s", command) && ! can_fail)
512 {
513 g_printerr("%s\n", command);
514
515 if (*standard_output != '\0' && *standard_error != '\0')
516 {
517 g_printerr("standard output:\n");
518 g_printerr("%s\n", standard_output);
519
520 g_printerr("standard error:\n");
521 g_printerr("%s\n", standard_error);
522 }
523 else if (*standard_output != '\0')
524 g_printerr("%s\n", standard_output);
525 else if (*standard_error != '\0')
526 g_printerr("%s\n", standard_error);
527
528 jb_error("command failed");
529 }
530
531 g_free(command);
532 }
533
534 /*
535 * Doing this internally is much faster than using an external program
536 * (install or cp), and also more portable.
537 */
538 static gboolean
539 install_file_real (const char *srcfile,
540 const char *dstfile,
541 const char *owner,
542 const char *group,
543 mode_t mode,
544 GError **err)
545 {
546 int in;
547 int out;
548
549 in = open(srcfile, O_RDONLY);
550 if (in < 0)
551 {
552 g_set_error(err, 0, 0, "cannot open %s for reading: %s", srcfile, g_strerror(errno));
553 return FALSE;
554 }
555
556 out = open(dstfile, O_CREAT | O_WRONLY | O_TRUNC, mode);
557 if (out < 0)
558 {
559 /*
560 * Unlink the file and try again, in case the file could not be
561 * opened because it already existed and was not writable. Do
562 * this unconditionally without testing EPERM, since it might
563 * not be portable.
564 */
565 unlink(dstfile);
566
567 out = open(dstfile, O_CREAT | O_WRONLY | O_TRUNC, mode);
568 if (out < 0)
569 {
570 g_set_error(err, 0, 0, "cannot open %s for writing: %s", dstfile, g_strerror(errno));
571 goto error;
572 }
573 }
574
575 while (TRUE)
576 {
577 char buf[4096];
578 ssize_t bytes_read;
579 ssize_t bytes_written;
580
581 bytes_read = read(in, buf, sizeof(buf));
582 if (bytes_read < 0)
583 {
584 g_set_error(err, 0, 0, "cannot read from %s: %s", srcfile, g_strerror(errno));
585 goto error;
586 }
587 if (bytes_read == 0)
588 break;
589
590 bytes_written = write(out, buf, bytes_read);
591 if (bytes_written < 0)
592 {
593 g_set_error(err, 0, 0, "cannot write to %s: %s", dstfile, g_strerror(errno));
594 goto error;
595 }
596 if (bytes_written != bytes_read)
597 {
598 g_set_error(err, 0, 0, "cannot write to %s", dstfile);
599 goto error;
600 }
601 }
602
603 /*
604 * The Linux manpage of fchmod() mentions that "as a security
605 * measure, depending on the file system, the set-user-ID and
606 * set-group-ID execution bits may be turned off if a file is
607 * written", so set the ownership and permissions after writing the
608 * file.
609 */
610
611 if (owner != NULL || group != NULL)
612 {
613 GError *tmp_err = NULL;
614
615 if (! jb_fchown_by_name(out, owner, group, &tmp_err))
616 {
617 if (owner != NULL && group != NULL)
618 g_set_error(err, 0, 0, "cannot chown %s to %s:%s: %s", dstfile, owner, group, tmp_err->message);
619 else if (owner != NULL)
620 g_set_error(err, 0, 0, "cannot chown %s to owner %s: %s", dstfile, owner, tmp_err->message);
621 else
622 g_set_error(err, 0, 0, "cannot chown %s to group %s: %s", dstfile, group, tmp_err->message);
623
624 g_error_free(tmp_err);
625 goto error;
626 }
627 }
628
629 /*
630 * Set the permissions after chowning the file, since the chown can
631 * clear the setuid/setgid bits.
632 */
633
634 if (fchmod(out, mode) < 0)
635 {
636 g_set_error(err, 0, 0, "cannot chmod %s to " JB_MODE_FORMAT ": %s", dstfile, (unsigned int) mode, g_strerror(errno));
637 goto error;
638 }
639
640 if (close(out) < 0)
641 {
642 g_set_error(err, 0, 0, "cannot close %s: %s", dstfile, g_strerror(errno));
643 goto error;
644 }
645
646 close(in);
647
648 return TRUE;
649
650 error:
651 close(in);
652 close(out);
653
654 return FALSE;
655 }
656
657 void
658 jb_action_install_file (const char *srcfile,
659 const char *dstdir,
660 const char *owner,
661 const char *group,
662 mode_t mode)
663 {
664 char *srcfile_basename;
665 char *dstfile;
666
667 g_return_if_fail(srcfile != NULL);
668 g_return_if_fail(dstdir != NULL);
669
670 srcfile_basename = g_path_get_basename(srcfile);
671 dstfile = g_strdup_printf("%s/%s", dstdir, srcfile_basename);
672 g_free(srcfile_basename);
673
674 jb_action_install_to_file(srcfile, dstfile, owner, group, mode);
675 g_free(dstfile);
676 }
677
678 void
679 jb_action_install_to_file (const char *srcfile,
680 const char *dstfile,
681 const char *owner,
682 const char *group,
683 mode_t mode)
684 {
685 char *real_dstfile;
686 GError *err = NULL;
687
688 g_return_if_fail(srcfile != NULL);
689 g_return_if_fail(dstfile != NULL);
690
691 real_dstfile = jb_variable_expand("$destdir$dstfile",
692 "dstfile", dstfile,
693 NULL);
694
695 jb_message("installing %s", real_dstfile);
696
697 jb_mkdir_of_file(real_dstfile);
698
699 if (! install_file_real(srcfile, real_dstfile, owner, group, mode, &err))
700 jb_error("%s", err->message);
701
702 g_free(real_dstfile);
703 }
704
705 void
706 jb_action_install_data (const char *srcfile, const char *dstdir)
707 {
708 g_return_if_fail(srcfile != NULL);
709 g_return_if_fail(dstdir != NULL);
710
711 jb_action_install_file(srcfile,
712 dstdir,
713 jb_variable_get_string_or_null("data-owner"),
714 jb_variable_get_string_or_null("data-group"),
715 jb_variable_get_mode("data-mode"));
716 }
717
718 void
719 jb_action_install_data_to_file (const char *srcfile, const char *dstfile)
720 {
721 g_return_if_fail(srcfile != NULL);
722 g_return_if_fail(dstfile != NULL);
723
724 jb_action_install_to_file(srcfile,
725 dstfile,
726 jb_variable_get_string_or_null("data-owner"),
727 jb_variable_get_string_or_null("data-group"),
728 jb_variable_get_mode("data-mode"));
729 }
730
731 void
732 jb_action_install_data_list (GSList *srcfiles, const char *dstdir)
733 {
734 GSList *l;
735
736 g_return_if_fail(dstdir != NULL);
737
738 JB_LIST_FOREACH(l, srcfiles)
739 {
740 const char *srcfile = l->data;
741
742 jb_action_install_data(srcfile, dstdir);
743 }
744 }
745
746 void
747 jb_action_install_program (const char *srcfile, const char *dstdir)
748 {
749 g_return_if_fail(srcfile != NULL);
750 g_return_if_fail(dstdir != NULL);
751
752 jb_action_install_file(srcfile,
753 dstdir,
754 jb_variable_get_string_or_null("program-owner"),
755 jb_variable_get_string_or_null("program-group"),
756 jb_variable_get_mode("program-mode"));
757 }
758
759 void
760 jb_action_install_library (const char *srcfile, const char *dstdir)
761 {
762 g_return_if_fail(srcfile != NULL);
763 g_return_if_fail(dstdir != NULL);
764
765 jb_action_install_file(srcfile,
766 dstdir,
767 jb_variable_get_string_or_null("library-owner"),
768 jb_variable_get_string_or_null("library-group"),
769 jb_variable_get_mode("library-mode"));
770 }
771
772 void
773 jb_action_rm (const char *file)
774 {
775 g_return_if_fail(file != NULL);
776
777 if (g_file_test(file, G_FILE_TEST_EXISTS))
778 {
779 jb_message("removing %s", file);
780 unlink(file);
781 }
782
783 /* if the parent directories were created by JB, also remove them */
784
785 if (g_str_has_prefix(file, "jbsrc/tools/"))
786 jb_action_rmdir("jbsrc/tools");
787 else if (g_str_has_prefix(file, "build/"))
788 {
789 GSList *parentdirs = NULL;
790 char *dir;
791
792 dir = g_path_get_dirname(file);
793
794 while (TRUE)
795 {
796 parentdirs = g_slist_append(parentdirs, dir);
797
798 if (! strcmp(dir, "build"))
799 break;
800
801 dir = g_path_get_dirname(dir);
802 g_assert(strcmp(dir, "/"));
803 }
804
805 jb_action_rmdir_list(parentdirs);
806
807 jb_g_slist_free_deep(parentdirs);
808 }
809 }
810
811 void
812 jb_action_rm_array (char **files, int len)
813 {
814 int i;
815
816 for (i = 0; i < len; i++)
817 jb_action_rm(files[i]);
818 }
819
820 void
821 jb_action_rm_list (GSList *files)
822 {
823 GSList *l;
824
825 JB_LIST_FOREACH(l, files)
826 jb_action_rm(l->data);
827 }
828
829 static gboolean
830 dir_is_empty (const char *dir)
831 {
832 GDir *gdir;
833 gboolean is_empty;
834
835 gdir = g_dir_open(dir, 0, NULL);
836 if (gdir == NULL)
837 return TRUE;
838
839 is_empty = g_dir_read_name(gdir) == NULL;
840
841 g_dir_close(gdir);
842
843 return is_empty;
844 }
845
846 void
847 jb_action_rmdir (const char *dir)
848 {
849 g_return_if_fail(dir != NULL);
850
851 if (g_file_test(dir, G_FILE_TEST_EXISTS) && dir_is_empty(dir))
852 {
853 jb_message("removing directory %s", dir);
854 rmdir(dir);
855 }
856 }
857
858 void
859 jb_action_rmdir_list (GSList *dirs)
860 {
861 GSList *l;
862
863 JB_LIST_FOREACH(l, dirs)
864 jb_action_rmdir(l->data);
865 }
866
867 static gboolean
868 add_to_hash_set (JBStringHashSet **hash_set, const char *value)
869 {
870 if (*hash_set == NULL)
871 *hash_set = jb_string_hash_set_new();
872
873 if (! jb_string_hash_set_add(*hash_set, value))
874 return FALSE;
875
876 return TRUE;
877 }
878
879 void
880 jb_action_add_to_dist (const char *file)
881 {
882 static JBStringHashSet *distfiles = NULL;
883 char *dir;
884 char *distdir;
885
886 g_return_if_fail(file != NULL);
887 g_return_if_fail(*file != '\0');
888
889 if (! add_to_hash_set(&distfiles, file))
890 return;
891
892 jb_message("adding %s to dist", file);
893
894 dir = g_path_get_dirname(file);
895 distdir = g_strdup_printf("%s/%s", jb_action_get_distdir(), dir);
896 g_free(dir);
897
898 jb_mkdir(distdir);
899
900 jb_action_exec("cp -p $file $distdir",
901 "file", file,
902 "distdir", distdir,
903 NULL);
904
905 g_free(distdir);
906 }
907
908 void
909 jb_action_add_to_dist_list (GSList *files)
910 {
911 GSList *l;
912
913 JB_LIST_FOREACH(l, files)
914 {
915 const char *file = l->data;
916 jb_action_add_to_dist(file);
917 }
918 }
919
920 void
921 jb_action_add_to_dist_string_list (const char *files)
922 {
923 int i;
924 char **array;
925
926 g_return_if_fail(files != NULL);
927
928 array = g_strsplit(files, " ", 0);
929
930 for (i = 0; array[i] != NULL; i++)
931 {
932 const char *file = array[i];
933
934 /* the files array can contain extra spaces */
935 if (*file != '\0')
936 jb_action_add_to_dist(array[i]);
937 }
938
939 g_strfreev(array);
940 }
941
942 const char *
943 jb_action_get_distdir (void)
944 {
945 static char *distdir = NULL;
946
947 if (distdir == NULL)
948 distdir = jb_variable_expand("build/${package}-$version", NULL);
949
950 return distdir;
951 }