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