Thanks to the fine work of Russell Bryant and Dancho Lazarov, we now have autoconf...
[asterisk/asterisk.git] / build_tools / menuselect.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005 - 2006, Russell Bryant
5  *
6  * Russell Bryant <russell@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*
20  * \file
21  *
22  * \author Russell Bryant <russell@digium.com>
23  * 
24  * \brief A menu-driven system for Asterisk module selection
25  */
26
27 #include "autoconfig.h"
28
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 #include "mxml/mxml.h"
35 #include "menuselect.h"
36
37 #include "asterisk.h"
38
39 #include "asterisk/linkedlists.h"
40
41 #undef MENUSELECT_DEBUG
42
43 struct depend {
44         /*! the name of the dependency */
45         const char *name;
46         /*! for linking */
47         AST_LIST_ENTRY(depend) list;
48 };
49
50 struct conflict {
51         /*! the name of the conflict */
52         const char *name;
53         /*! for linking */
54         AST_LIST_ENTRY(conflict) list;
55 };
56
57 /*! The list of categories */
58 struct categories categories = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
59
60 /*!
61    We have to maintain a pointer to the root of the trees generated from reading
62    the build options XML files so that we can free it when we're done.  We don't
63    copy any of the information over from these trees. Our list is just a 
64    convenient mapping to the information contained in these lists with one
65    additional piece of information - whether the build option is enabled or not.
66 */
67 struct tree {
68         /*! the root of the tree */
69         mxml_node_t *root;
70         /*! for linking */
71         AST_LIST_ENTRY(tree) list;
72 };
73
74 /*! The list of trees from makeopts.xml files */
75 AST_LIST_HEAD_NOLOCK_STATIC(trees, tree);
76
77 const char * const makeopts_files[] = {
78         "makeopts.xml"
79 };
80
81 char *output_makeopts = OUTPUT_MAKEOPTS_DEFAULT;
82
83 /*! This is set to 1 if menuselect.makeopts pre-existed the execution of this app */
84 int existing_config = 0;
85
86 /*! This is set when the --check-deps argument is provided. */
87 int check_deps = 0;
88
89 /*! Force a clean of the source tree */
90 int force_clean = 0;
91
92 int add_category(struct category *cat);
93 int add_member(struct member *mem, struct category *cat);
94 int parse_makeopts_xml(const char *makeopts_xml);
95 int process_deps(void);
96 int build_member_list(void);
97 void mark_as_present(const char *member, const char *category);
98 int parse_existing_config(const char *infile);
99 int generate_makeopts_file(void);
100 void free_member_list(void);
101 void free_trees(void);
102
103 /*! \brief a wrapper for calloc() that generates an error message if the allocation fails */
104 static inline void *my_calloc(size_t num, size_t len)
105 {
106         void *tmp;
107
108         tmp = calloc(num, len);
109         
110         if (!tmp)
111                 fprintf(stderr, "Memory allocation error!\n");
112
113         return tmp;
114 }
115
116 /*! \brief return a pointer to the first non-whitespace character */
117 static inline char *skip_blanks(char *str)
118 {
119         if (!str)
120                 return NULL;
121
122         while (*str && *str < 33)
123                 str++;
124
125         return str;
126 }
127
128 /*! \brief Add a category to the category list, ensuring that there are no duplicates */
129 int add_category(struct category *cat)
130 {
131         struct category *tmp;
132
133         AST_LIST_TRAVERSE(&categories, tmp, list) {
134                 if (!strcmp(tmp->name, cat->name)) {
135                         fprintf(stderr, "Category '%s' specified more than once!\n", cat->name);
136                         return -1;
137                 }
138         }
139         AST_LIST_INSERT_TAIL(&categories, cat, list);
140
141         return 0;
142 }
143
144 /*! \brief Add a member to the member list of a category, ensuring that there are no duplicates */
145 int add_member(struct member *mem, struct category *cat)
146 {
147         struct member *tmp;
148
149         AST_LIST_TRAVERSE(&cat->members, tmp, list) {
150                 if (!strcmp(tmp->name, mem->name)) {
151                         fprintf(stderr, "Member '%s' already exists in category '%s', ignoring.\n", mem->name, cat->name);
152                         return -1;
153                 }
154         }
155         AST_LIST_INSERT_TAIL(&cat->members, mem, list);
156
157         return 0;
158 }
159
160 /*! \brief Parse an input makeopts file */
161 int parse_makeopts_xml(const char *makeopts_xml)
162 {
163         FILE *f;
164         struct category *cat;
165         struct tree *tree;
166         struct member *mem;
167         struct depend *dep;
168         struct conflict *cnf;
169         mxml_node_t *cur;
170         mxml_node_t *cur2;
171         mxml_node_t *cur3;
172         mxml_node_t *menu;
173         const char *tmp;
174
175         if (!(f = fopen(makeopts_xml, "r"))) {
176                 fprintf(stderr, "Unable to open '%s' for reading!\n", makeopts_xml);
177                 return -1;
178         }
179
180         if (!(tree = my_calloc(1, sizeof(*tree)))) {
181                 fclose(f);
182                 return -1;
183         }
184
185         if (!(tree->root = mxmlLoadFile(NULL, f, MXML_OPAQUE_CALLBACK))) {
186                 fclose(f);
187                 free(tree);
188                 return -1;
189         }
190
191         AST_LIST_INSERT_HEAD(&trees, tree, list);
192
193         menu = mxmlFindElement(tree->root, tree->root, "menu", NULL, NULL, MXML_DESCEND);
194         for (cur = mxmlFindElement(menu, menu, "category", NULL, NULL, MXML_DESCEND);
195              cur;
196              cur = mxmlFindElement(cur, menu, "category", NULL, NULL, MXML_DESCEND))
197         {
198                 if (!(cat = my_calloc(1, sizeof(*cat))))
199                         return -1;
200
201                 cat->name = mxmlElementGetAttr(cur, "name");
202                 cat->displayname = mxmlElementGetAttr(cur, "displayname");
203                 if ((tmp = mxmlElementGetAttr(cur, "positive_output")))
204                         cat->positive_output = !strcasecmp(tmp, "yes");
205                 if ((tmp = mxmlElementGetAttr(cur, "force_clean_on_change")))
206                         cat->force_clean_on_change = !strcasecmp(tmp, "yes");
207
208                 if (add_category(cat)) {
209                         free(cat);
210                         continue;
211                 }
212
213                 for (cur2 = mxmlFindElement(cur, cur, "member", NULL, NULL, MXML_DESCEND);
214                      cur2;
215                      cur2 = mxmlFindElement(cur2, cur, "member", NULL, NULL, MXML_DESCEND))
216                 {
217                         if (!(mem = my_calloc(1, sizeof(*mem))))
218                                 return -1;
219                         
220                         if (!cat->positive_output)
221                                 mem->enabled = 1; /* Enabled by default */
222
223                         mem->name = mxmlElementGetAttr(cur2, "name");
224                         
225                         cur3 = mxmlFindElement(cur2, cur2, "defaultenabled", NULL, NULL, MXML_DESCEND);
226                         if (cur3 && cur3->child) {
227                                 if (!strcasecmp("no", cur3->child->value.opaque))
228                                         mem->enabled = 0;
229                                 else if (!strcasecmp("yes", cur3->child->value.opaque))
230                                         mem->enabled = 1;
231                                 else
232                                         fprintf(stderr, "Invalid value '%s' for <defaultenabled> !\n", cur3->child->value.opaque);
233                         }
234                         
235                         for (cur3 = mxmlFindElement(cur2, cur2, "depend", NULL, NULL, MXML_DESCEND);
236                              cur3 && cur3->child;
237                              cur3 = mxmlFindElement(cur3, cur2, "depend", NULL, NULL, MXML_DESCEND))
238                         {
239                                 if (!(dep = my_calloc(1, sizeof(*dep))))
240                                         return -1;
241                                 if (!strlen_zero(cur3->child->value.opaque)) {
242                                         dep->name = cur3->child->value.opaque;
243                                         AST_LIST_INSERT_HEAD(&mem->deps, dep, list);
244                                 } else
245                                         free(dep);
246                         }
247
248                         for (cur3 = mxmlFindElement(cur2, cur2, "conflict", NULL, NULL, MXML_DESCEND);
249                              cur3 && cur3->child;
250                              cur3 = mxmlFindElement(cur3, cur2, "conflict", NULL, NULL, MXML_DESCEND))
251                         {
252                                 if (!(cnf = my_calloc(1, sizeof(*cnf))))
253                                         return -1;
254                                 if (!strlen_zero(cur3->child->value.opaque)) {
255                                         cnf->name = cur3->child->value.opaque;
256                                         AST_LIST_INSERT_HEAD(&mem->conflicts, cnf, list);
257                                 } else
258                                         free(cnf);
259                         }
260
261                         if (add_member(mem, cat))
262                                 free(mem);
263                 }
264         }
265
266         fclose(f);
267
268         return 0;
269 }
270
271 /*! \brief Process dependencies against the input dependencies file */
272 int process_deps(void)
273 {
274         struct category *cat;
275         struct member *mem;
276         struct depend *dep;
277         struct conflict *cnf;
278         FILE *f;
279         struct dep_file {
280                 char name[32];
281                 int met;
282                 AST_LIST_ENTRY(dep_file) list;
283         } *dep_file;
284         AST_LIST_HEAD_NOLOCK_STATIC(deps_file, dep_file);
285         char buf[80];
286         char *p;
287         int res = 0;
288
289         if (!(f = fopen(MENUSELECT_DEPS, "r"))) {
290                 fprintf(stderr, "Unable to open '%s' for reading!  Did you run ./configure ?\n", MENUSELECT_DEPS);
291                 return -1;
292         }
293
294         /* Build a dependency list from the file generated by configure */      
295         while (memset(buf, 0, sizeof(buf)), fgets(buf, sizeof(buf), f)) {
296                 p = buf;
297                 strsep(&p, "=");
298                 if (!p)
299                         continue;
300                 if (!(dep_file = my_calloc(1, sizeof(*dep_file))))
301                         break;
302                 strncpy(dep_file->name, buf, sizeof(dep_file->name) - 1);
303                 dep_file->met = atoi(p);
304                 AST_LIST_INSERT_TAIL(&deps_file, dep_file, list);
305         }
306
307         fclose(f);
308
309         /* Process dependencies of all modules */
310         AST_LIST_TRAVERSE(&categories, cat, list) {
311                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
312                         AST_LIST_TRAVERSE(&mem->deps, dep, list) {
313                                 mem->depsfailed = 1;
314                                 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
315                                         if (!strcasecmp(dep_file->name, dep->name)) {
316                                                 if (dep_file->met)
317                                                         mem->depsfailed = 0;
318                                                 break;
319                                         }
320                                 }
321                                 if (mem->depsfailed)
322                                         break; /* This dependency is not met, so we can stop now */
323                         }
324                         if (mem->depsfailed) {
325                                 if (check_deps && existing_config && mem->enabled) {
326                                         /* Config already existed, but this module was not disabled.
327                                          * However, according to our current list of dependencies that
328                                          * have been met, this can not be built. */
329                                         res = -1;
330                                         fprintf(stderr, "\nThe existing menuselect.makeopts did not specify that %s should not be built\n", mem->name);
331                                         fprintf(stderr, "However, menuselect-deps indicates that dependencies for this module have not\n");
332                                         fprintf(stderr, "been met.  So, either remove the existing menuselect.makeopts file, or run\n");
333                                         fprintf(stderr, "'make menuselect' to generate a file that is correct.\n\n");
334                                         goto deps_file_free;
335                                 }
336                                 mem->enabled = 0; /* Automatically disable it if dependencies not met */
337                         }
338                 }
339         }
340
341         /* Process conflicts of all modules */
342         AST_LIST_TRAVERSE(&categories, cat, list) {
343                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
344                         AST_LIST_TRAVERSE(&mem->conflicts, cnf, list) {
345                                 mem->conflictsfailed = 0;
346                                 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
347                                         if (!strcasecmp(dep_file->name, cnf->name)) {
348                                                 if (dep_file->met)
349                                                         mem->conflictsfailed = 1;
350                                                 break;
351                                         }
352                                 }
353                                 if (mem->conflictsfailed)
354                                         break; /* This conflict was found, so we can stop now */
355                         }
356                         if (mem->conflictsfailed) {
357                                 if (check_deps && existing_config && mem->enabled) {
358                                         /* Config already existed, but this module was not disabled.
359                                          * However, according to our current list of conflicts that
360                                          * exist, this can not be built. */
361                                         res = -1;
362                                         fprintf(stderr, "\nThe existing menuselect.makeopts did not specify that %s should not be built\n", mem->name);
363                                         fprintf(stderr, "However, menuselect-deps indicates that conflicts for this module exist.\n");
364                                         fprintf(stderr, "So, either remove the existing menuselect.makeopts file, or run\n");
365                                         fprintf(stderr, "'make menuselect' to generate a file that is correct.\n\n");
366                                         goto deps_file_free;
367                                 }
368                                 mem->enabled = 0; /* Automatically disable it if conflicts exist */
369                         }
370                 }
371         }
372
373 deps_file_free:
374
375         /* Free the dependency list we built from the file */
376         while ((dep_file = AST_LIST_REMOVE_HEAD(&deps_file, list)))
377                 free(dep_file);
378
379         return res;
380 }
381
382 /*! \brief Iterate through all of the input makeopts files and call the parse function on them */
383 int build_member_list(void)
384 {
385         int i;
386         int res = -1;
387
388         for (i = 0; i < (sizeof(makeopts_files) / sizeof(makeopts_files[0])); i++) {
389                 if ((res = parse_makeopts_xml(makeopts_files[i]))) {
390                         fprintf(stderr, "Error parsing '%s'!\n", makeopts_files[i]);
391                         break;
392                 }
393         }
394
395         return res;
396 }
397
398 /*! \brief Given the string representation of a member and category, mark it as present in a given input file */
399 void mark_as_present(const char *member, const char *category)
400 {
401         struct category *cat;
402         struct member *mem;
403
404         AST_LIST_TRAVERSE(&categories, cat, list) {
405                 if (strcmp(category, cat->name))
406                         continue;
407                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
408                         if (!strcmp(member, mem->name)) {
409                                 mem->enabled = cat->positive_output;
410                                 break;
411                         }
412                 }
413                 if (!mem)
414                         fprintf(stderr, "member '%s' in category '%s' not found, ignoring.\n", member, category);
415                 break;
416         }
417
418         if (!cat)
419                 fprintf(stderr, "category '%s' not found! Can't mark '%s' as disabled.\n", category, member);
420 }
421
422 /*! \brief Toggle a member of a category at the specified index to enabled/disabled */
423 void toggle_enabled(struct category *cat, int index)
424 {
425         struct member *mem;
426         int i = 0;
427
428         AST_LIST_TRAVERSE(&cat->members, mem, list) {
429                 if (i++ == index)
430                         break;
431         }
432
433         if (mem && !(mem->depsfailed || mem->conflictsfailed)) {
434                 mem->enabled = !mem->enabled;
435                 if (cat->force_clean_on_change)
436                         force_clean = 1;
437         }
438 }
439
440 /*! \brief Parse an existing output makeopts file and enable members previously selected */
441 int parse_existing_config(const char *infile)
442 {
443         FILE *f;
444         char buf[2048];
445         char *category, *parse, *member;
446         int lineno = 0;
447
448         if (!(f = fopen(infile, "r"))) {
449 #ifdef MENUSELECT_DEBUG
450                 /* This isn't really an error, so only print the message in debug mode */
451                 fprintf(stderr, "Unable to open '%s' for reading existing config.\n", infile);
452 #endif  
453                 return -1;
454         }
455
456         while (fgets(buf, sizeof(buf), f)) {
457                 lineno++;
458
459                 if (strlen_zero(buf))
460                         continue;
461
462                 /* skip lines that are not for this tool */
463                 if (strncasecmp(buf, "MENUSELECT_", strlen("MENUSELECT_")))
464                         continue;
465
466                 parse = buf;
467                 parse = skip_blanks(parse);
468                 if (strlen_zero(parse))
469                         continue;
470
471                 /* Grab the category name */    
472                 category = strsep(&parse, "=");
473                 if (!parse) {
474                         fprintf(stderr, "Invalid string in '%s' at line '%d'!\n", output_makeopts, lineno);
475                         continue;
476                 }
477
478                 parse = skip_blanks(parse);
479                 while ((member = strsep(&parse, " \n"))) {
480                         member = skip_blanks(member);
481                         if (strlen_zero(member))
482                                 continue;
483
484                         mark_as_present(member, category);
485                 }
486         }
487
488         fclose(f);
489
490         return 0;
491 }
492
493 /*! \brief Create the output makeopts file that results from the user's selections */
494 int generate_makeopts_file(void)
495 {
496         FILE *f;
497         struct category *cat;
498         struct member *mem;
499
500         if (!(f = fopen(output_makeopts, "w"))) {
501                 fprintf(stderr, "Unable to open build configuration file (%s) for writing!\n", output_makeopts);
502                 return -1;
503         }
504
505         /* Traverse all categories and members and output them as var/val pairs */
506         AST_LIST_TRAVERSE(&categories, cat, list) {
507                 fprintf(f, "%s=", cat->name);
508                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
509                         if ((!cat->positive_output && (!mem->enabled || mem->depsfailed || mem->conflictsfailed)) ||
510                             (cat->positive_output && mem->enabled && !mem->depsfailed && !mem->conflictsfailed))
511                                 fprintf(f, "%s ", mem->name);
512                 }
513                 fprintf(f, "\n");
514         }
515
516         fclose(f);
517
518         return 0;
519 }
520
521 #ifdef MENUSELECT_DEBUG
522 /*! \brief Print out all of the information contained in our tree */
523 void dump_member_list(void)
524 {
525         struct category *cat;
526         struct member *mem;
527         struct depend *dep;
528         struct conflict *cnf;
529
530         AST_LIST_TRAVERSE(&categories, cat, list) {
531                 fprintf(stderr, "Category: '%s'\n", cat->name);
532                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
533                         fprintf(stderr, "   ==>> Member: '%s'  (%s)\n", mem->name, mem->enabled ? "Enabled" : "Disabled");
534                         AST_LIST_TRAVERSE(&mem->deps, dep, list)
535                                 fprintf(stderr, "      --> Depends on: '%s'\n", dep->name);
536                         if (!AST_LIST_EMPTY(&mem->deps))
537                                 fprintf(stderr, "      --> Dependencies Met: %s\n", mem->depsfailed ? "No" : "Yes");    
538                         AST_LIST_TRAVERSE(&mem->conflicts, cnf, list)
539                                 fprintf(stderr, "      --> Conflicts with: '%s'\n", cnf->name);
540                         if (!AST_LIST_EMPTY(&mem->conflicts))
541                                 fprintf(stderr, "      --> Conflicts Found: %s\n", mem->conflictsfailed ? "Yes" : "No");
542                 }
543         }
544 }
545 #endif
546
547 /*! \brief Free all categories and their members */
548 void free_member_list(void)
549 {
550         struct category *cat;
551         struct member *mem;
552         struct depend *dep;
553         struct conflict *cnf;
554
555         while ((cat = AST_LIST_REMOVE_HEAD(&categories, list))) {
556                 while ((mem = AST_LIST_REMOVE_HEAD(&cat->members, list))) {
557                         while ((dep = AST_LIST_REMOVE_HEAD(&mem->deps, list)))
558                                 free(dep);
559                         while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
560                                 free(cnf);
561                         free(mem);
562                 }
563                 free(cat);
564         }
565 }
566
567 /*! \brief Free all of the XML trees */
568 void free_trees(void)
569 {
570         struct tree *tree;
571
572         while ((tree = AST_LIST_REMOVE_HEAD(&trees, list))) {
573                 mxmlDelete(tree->root);
574                 free(tree);
575         }
576 }
577
578 /*! \brief Enable/Disable all members of a category as long as dependencies have been met and no conflicts are found */
579 void set_all(struct category *cat, int val)
580 {
581         struct member *mem;
582
583         AST_LIST_TRAVERSE(&cat->members, mem, list) {
584                 if (!(mem->depsfailed || mem->conflictsfailed))
585                         mem->enabled = val;
586         }
587 }
588
589 int count_categories(void)
590 {
591         struct category *cat;
592         int count = 0;
593
594         AST_LIST_TRAVERSE(&categories, cat, list)
595                 count++;
596
597         return count;           
598 }
599
600 int count_members(struct category *cat)
601 {
602         struct member *mem;
603         int count = 0;
604
605         AST_LIST_TRAVERSE(&cat->members, mem, list)
606                 count++;
607
608         return count;           
609 }
610
611 int main(int argc, char *argv[])
612 {
613         int res = 0;
614         unsigned int x;
615
616         /* Parse the input XML files to build the list of available options */
617         if ((res = build_member_list()))
618                 exit(res);
619
620         /* The --check-deps option is used to ask this application to check to
621          * see if that an existing menuselect.makeopts file contails all of the
622          * modules that have dependencies that have not been met.  If this
623          * is not the case, an informative message will be printed to the
624          * user and the build will fail. */
625         for (x = 1; x < argc; x++) {
626                 if (!strcmp(argv[x], "--check-deps"))
627                         check_deps = 1;
628                 else {
629                         res = parse_existing_config(argv[x]);
630                         if (!res && !strcasecmp(argv[x], OUTPUT_MAKEOPTS_DEFAULT))
631                                 existing_config = 1;
632                 }
633         }
634
635         /* Process module dependencies */
636         res = process_deps();
637
638 #ifdef MENUSELECT_DEBUG
639         /* Dump the list produced by parsing the various input files */
640         dump_member_list();
641 #endif
642
643         /* Run the menu to let the user enable/disable options */
644         if (!check_deps && !res)
645                 res = run_menu();
646
647         /* Write out the menuselect.makeopts file if
648          * 1) menuselect was not executed with --check-deps
649          * 2) menuselect was executed with --check-deps but menuselect.makeopts
650          *    did not already exist.
651          */
652         if ((!check_deps || !existing_config) && !res)
653                 res = generate_makeopts_file();
654         
655         /* free everything we allocated */
656         free_trees();
657         free_member_list();
658
659         if (check_deps && !existing_config && !res) {
660                 fprintf(stderr, "\n***********************************************************\n");
661                 fprintf(stderr, "* menuselect.makeopts file generated with default values! *\n");
662                 fprintf(stderr, "* Please rerun make to build Asterisk.                    *\n");
663                 fprintf(stderr, "***********************************************************\n\n");
664                 res = -1;
665         }
666
667         if (force_clean)
668                 unlink(".lastclean");
669
670         exit(res);
671 }