various cleanups, remove support for CVS checkouts
[asterisk-bristuff.git] / build_tools / menuselect.c
blob3388ccf0d532a25860601bf7f9bda67ae49ac945
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2005 - 2006, Russell Bryant
6 * Russell Bryant <russell@digium.com>
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.
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.
19 /*!
20 * \file
22 * \author Russell Bryant <russell@digium.com>
24 * \brief A menu-driven system for Asterisk module selection
27 #include "asterisk.h"
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <unistd.h>
34 #include "mxml/mxml.h"
35 #include "menuselect.h"
37 #include "asterisk/linkedlists.h"
39 #undef MENUSELECT_DEBUG
41 /*! The list of categories */
42 struct categories categories = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
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.
51 struct tree {
52 /*! the root of the tree */
53 mxml_node_t *root;
54 /*! for linking */
55 AST_LIST_ENTRY(tree) list;
58 /*! The list of trees from makeopts.xml files */
59 static AST_LIST_HEAD_NOLOCK_STATIC(trees, tree);
61 static const char * const makeopts_files[] = {
62 "makeopts.xml"
65 static char *output_makeopts = OUTPUT_MAKEOPTS_DEFAULT;
67 /*! This is set to 1 if menuselect.makeopts pre-existed the execution of this app */
68 static int existing_config = 0;
70 /*! This is set when the --check-deps argument is provided. */
71 static int check_deps = 0;
73 #if !defined(ast_strdupa) && defined(__GNUC__)
74 #define ast_strdupa(s) \
75 (__extension__ \
76 ({ \
77 const char *__old = (s); \
78 size_t __len = strlen(__old) + 1; \
79 char *__new = __builtin_alloca(__len); \
80 memcpy (__new, __old, __len); \
81 __new; \
82 }))
83 #endif
85 /*! \brief return a pointer to the first non-whitespace character */
86 static inline char *skip_blanks(char *str)
88 if (!str)
89 return NULL;
91 while (*str && *str < 33)
92 str++;
94 return str;
97 /*! \brief Add a category to the category list, ensuring that there are no duplicates */
98 static int add_category(struct category *cat)
100 struct category *tmp;
102 AST_LIST_TRAVERSE(&categories, tmp, list) {
103 if (!strcmp(tmp->name, cat->name)) {
104 fprintf(stderr, "Category '%s' specified more than once!\n", cat->name);
105 return -1;
108 AST_LIST_INSERT_TAIL(&categories, cat, list);
110 return 0;
113 /*! \brief Add a member to the member list of a category, ensuring that there are no duplicates */
114 static int add_member(struct member *mem, struct category *cat)
116 struct member *tmp;
118 AST_LIST_TRAVERSE(&cat->members, tmp, list) {
119 if (!strcmp(tmp->name, mem->name)) {
120 fprintf(stderr, "Member '%s' already exists in category '%s', ignoring.\n", mem->name, cat->name);
121 return -1;
124 AST_LIST_INSERT_TAIL(&cat->members, mem, list);
126 return 0;
129 /*! \brief Free a member structure and all of its members */
130 static void free_member(struct member *mem)
132 struct depend *dep;
133 struct conflict *cnf;
135 while ((dep = AST_LIST_REMOVE_HEAD(&mem->deps, list)))
136 free(dep);
137 while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
138 free(cnf);
139 free(mem);
142 /*! \brief Parse an input makeopts file */
143 static int parse_makeopts_xml(const char *makeopts_xml)
145 FILE *f;
146 struct category *cat;
147 struct tree *tree;
148 struct member *mem;
149 struct depend *dep;
150 struct conflict *cnf;
151 mxml_node_t *cur;
152 mxml_node_t *cur2;
153 mxml_node_t *cur3;
154 mxml_node_t *menu;
155 const char *tmp;
157 if (!(f = fopen(makeopts_xml, "r"))) {
158 fprintf(stderr, "Unable to open '%s' for reading!\n", makeopts_xml);
159 return -1;
162 if (!(tree = calloc(1, sizeof(*tree)))) {
163 fclose(f);
164 return -1;
167 if (!(tree->root = mxmlLoadFile(NULL, f, MXML_OPAQUE_CALLBACK))) {
168 fclose(f);
169 free(tree);
170 return -1;
173 AST_LIST_INSERT_HEAD(&trees, tree, list);
175 menu = mxmlFindElement(tree->root, tree->root, "menu", NULL, NULL, MXML_DESCEND);
176 for (cur = mxmlFindElement(menu, menu, "category", NULL, NULL, MXML_DESCEND);
177 cur;
178 cur = mxmlFindElement(cur, menu, "category", NULL, NULL, MXML_DESCEND))
180 if (!(cat = calloc(1, sizeof(*cat))))
181 return -1;
183 cat->name = mxmlElementGetAttr(cur, "name");
184 cat->displayname = mxmlElementGetAttr(cur, "displayname");
185 if ((tmp = mxmlElementGetAttr(cur, "positive_output")))
186 cat->positive_output = !strcasecmp(tmp, "yes");
187 cat->remove_on_change = mxmlElementGetAttr(cur, "remove_on_change");
189 if (add_category(cat)) {
190 free(cat);
191 continue;
194 for (cur2 = mxmlFindElement(cur, cur, "member", NULL, NULL, MXML_DESCEND);
195 cur2;
196 cur2 = mxmlFindElement(cur2, cur, "member", NULL, NULL, MXML_DESCEND))
198 if (!(mem = calloc(1, sizeof(*mem))))
199 return -1;
201 mem->name = mxmlElementGetAttr(cur2, "name");
202 mem->displayname = mxmlElementGetAttr(cur2, "displayname");
204 mem->remove_on_change = mxmlElementGetAttr(cur2, "remove_on_change");
206 if (!cat->positive_output)
207 mem->was_enabled = mem->enabled = 1;
209 cur3 = mxmlFindElement(cur2, cur2, "defaultenabled", NULL, NULL, MXML_DESCEND);
210 if (cur3 && cur3->child)
211 mem->defaultenabled = cur3->child->value.opaque;
213 for (cur3 = mxmlFindElement(cur2, cur2, "depend", NULL, NULL, MXML_DESCEND);
214 cur3 && cur3->child;
215 cur3 = mxmlFindElement(cur3, cur2, "depend", NULL, NULL, MXML_DESCEND))
217 if (!(dep = calloc(1, sizeof(*dep)))) {
218 free_member(mem);
219 return -1;
221 if (!strlen_zero(cur3->child->value.opaque)) {
222 dep->name = cur3->child->value.opaque;
223 AST_LIST_INSERT_HEAD(&mem->deps, dep, list);
224 } else
225 free(dep);
228 for (cur3 = mxmlFindElement(cur2, cur2, "conflict", NULL, NULL, MXML_DESCEND);
229 cur3 && cur3->child;
230 cur3 = mxmlFindElement(cur3, cur2, "conflict", NULL, NULL, MXML_DESCEND))
232 if (!(cnf = calloc(1, sizeof(*cnf)))) {
233 free_member(mem);
234 return -1;
236 if (!strlen_zero(cur3->child->value.opaque)) {
237 cnf->name = cur3->child->value.opaque;
238 AST_LIST_INSERT_HEAD(&mem->conflicts, cnf, list);
239 } else
240 free(cnf);
243 if (add_member(mem, cat))
244 free_member(mem);
248 fclose(f);
250 return 0;
253 /*! \brief Process dependencies against the input dependencies file */
254 static int process_deps(void)
256 struct category *cat;
257 struct member *mem;
258 struct depend *dep;
259 struct conflict *cnf;
260 FILE *f;
261 struct dep_file {
262 char name[32];
263 int met;
264 AST_LIST_ENTRY(dep_file) list;
265 } *dep_file;
266 AST_LIST_HEAD_NOLOCK_STATIC(deps_file, dep_file);
267 char buf[80];
268 char *p;
269 int res = 0;
271 if (!(f = fopen(MENUSELECT_DEPS, "r"))) {
272 fprintf(stderr, "Unable to open '%s' for reading! Did you run ./configure ?\n", MENUSELECT_DEPS);
273 return -1;
276 /* Build a dependency list from the file generated by configure */
277 while (memset(buf, 0, sizeof(buf)), fgets(buf, sizeof(buf), f)) {
278 p = buf;
279 strsep(&p, "=");
280 if (!p)
281 continue;
282 if (!(dep_file = calloc(1, sizeof(*dep_file))))
283 break;
284 strncpy(dep_file->name, buf, sizeof(dep_file->name) - 1);
285 dep_file->met = atoi(p);
286 AST_LIST_INSERT_TAIL(&deps_file, dep_file, list);
289 fclose(f);
291 /* Process dependencies of all modules */
292 AST_LIST_TRAVERSE(&categories, cat, list) {
293 AST_LIST_TRAVERSE(&cat->members, mem, list) {
294 AST_LIST_TRAVERSE(&mem->deps, dep, list) {
295 mem->depsfailed = 1;
296 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
297 if (!strcasecmp(dep_file->name, dep->name)) {
298 if (dep_file->met)
299 mem->depsfailed = 0;
300 break;
303 if (mem->depsfailed)
304 break; /* This dependency is not met, so we can stop now */
309 /* Process conflicts of all modules */
310 AST_LIST_TRAVERSE(&categories, cat, list) {
311 AST_LIST_TRAVERSE(&cat->members, mem, list) {
312 AST_LIST_TRAVERSE(&mem->conflicts, cnf, list) {
313 mem->conflictsfailed = 0;
314 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
315 if (!strcasecmp(dep_file->name, cnf->name)) {
316 if (dep_file->met)
317 mem->conflictsfailed = 1;
318 break;
321 if (mem->conflictsfailed)
322 break; /* This conflict was found, so we can stop now */
327 /* Free the dependency list we built from the file */
328 while ((dep_file = AST_LIST_REMOVE_HEAD(&deps_file, list)))
329 free(dep_file);
331 return res;
334 /*! \brief Iterate through all of the input makeopts files and call the parse function on them */
335 static int build_member_list(void)
337 int i;
338 int res = -1;
340 for (i = 0; i < (sizeof(makeopts_files) / sizeof(makeopts_files[0])); i++) {
341 if ((res = parse_makeopts_xml(makeopts_files[i]))) {
342 fprintf(stderr, "Error parsing '%s'!\n", makeopts_files[i]);
343 break;
347 return res;
350 /*! \brief Given the string representation of a member and category, mark it as present in a given input file */
351 static void mark_as_present(const char *member, const char *category)
353 struct category *cat;
354 struct member *mem;
356 AST_LIST_TRAVERSE(&categories, cat, list) {
357 if (strcmp(category, cat->name))
358 continue;
359 AST_LIST_TRAVERSE(&cat->members, mem, list) {
360 if (!strcmp(member, mem->name)) {
361 mem->was_enabled = mem->enabled = cat->positive_output;
362 break;
365 if (!mem)
366 fprintf(stderr, "member '%s' in category '%s' not found, ignoring.\n", member, category);
367 break;
370 if (!cat)
371 fprintf(stderr, "category '%s' not found! Can't mark '%s' as disabled.\n", category, member);
374 /*! \brief Toggle a member of a category at the specified index to enabled/disabled */
375 void toggle_enabled(struct category *cat, int index)
377 struct member *mem;
378 int i = 0;
380 AST_LIST_TRAVERSE(&cat->members, mem, list) {
381 if (i++ == index)
382 break;
385 if (mem && !(mem->depsfailed || mem->conflictsfailed)) {
386 mem->enabled = !mem->enabled;
390 /*! \brief Process a previously failed dependency
392 * If a module was previously disabled because of a failed dependency
393 * or a conflict, and not because the user selected it to be that way,
394 * then it needs to be re-enabled by default if the problem is no longer present.
396 static void process_prev_failed_deps(char *buf)
398 const char *cat_name, *mem_name;
399 struct category *cat;
400 struct member *mem;
402 cat_name = strsep(&buf, "=");
403 mem_name = strsep(&buf, "\n");
405 if (!cat_name || !mem_name)
406 return;
408 AST_LIST_TRAVERSE(&categories, cat, list) {
409 if (strcasecmp(cat->name, cat_name))
410 continue;
411 AST_LIST_TRAVERSE(&cat->members, mem, list) {
412 if (strcasecmp(mem->name, mem_name))
413 continue;
415 if (!mem->depsfailed && !mem->conflictsfailed)
416 mem->enabled = 1;
418 break;
420 break;
423 if (!cat || !mem)
424 fprintf(stderr, "Unable to find '%s' in category '%s'\n", mem_name, cat_name);
427 /*! \brief Parse an existing output makeopts file and enable members previously selected */
428 static int parse_existing_config(const char *infile)
430 FILE *f;
431 char buf[2048];
432 char *category, *parse, *member;
433 int lineno = 0;
435 if (!(f = fopen(infile, "r"))) {
436 #ifdef MENUSELECT_DEBUG
437 /* This isn't really an error, so only print the message in debug mode */
438 fprintf(stderr, "Unable to open '%s' for reading existing config.\n", infile);
439 #endif
440 return -1;
443 while (fgets(buf, sizeof(buf), f)) {
444 lineno++;
446 if (strlen_zero(buf))
447 continue;
449 /* skip lines that are not for this tool */
450 if (strncasecmp(buf, "MENUSELECT_", strlen("MENUSELECT_")))
451 continue;
453 parse = buf;
454 parse = skip_blanks(parse);
455 if (strlen_zero(parse))
456 continue;
458 /* Grab the category name */
459 category = strsep(&parse, "=");
460 if (!parse) {
461 fprintf(stderr, "Invalid string in '%s' at line '%d'!\n", output_makeopts, lineno);
462 continue;
465 parse = skip_blanks(parse);
467 if (!strcasecmp(category, "MENUSELECT_DEPSFAILED")) {
468 process_prev_failed_deps(parse);
469 continue;
472 while ((member = strsep(&parse, " \n"))) {
473 member = skip_blanks(member);
474 if (strlen_zero(member))
475 continue;
476 mark_as_present(member, category);
480 fclose(f);
482 return 0;
485 /*! \brief Create the output makeopts file that results from the user's selections */
486 static int generate_makeopts_file(void)
488 FILE *f;
489 struct category *cat;
490 struct member *mem;
492 if (!(f = fopen(output_makeopts, "w"))) {
493 fprintf(stderr, "Unable to open build configuration file (%s) for writing!\n", output_makeopts);
494 return -1;
497 /* Traverse all categories and members and output them as var/val pairs */
498 AST_LIST_TRAVERSE(&categories, cat, list) {
499 fprintf(f, "%s=", cat->name);
500 AST_LIST_TRAVERSE(&cat->members, mem, list) {
501 if ((!cat->positive_output && (!mem->enabled || mem->depsfailed || mem->conflictsfailed)) ||
502 (cat->positive_output && mem->enabled && !mem->depsfailed && !mem->conflictsfailed))
503 fprintf(f, "%s ", mem->name);
505 fprintf(f, "\n");
508 /* Output which members were disabled because of failed dependencies or conflicts */
509 AST_LIST_TRAVERSE(&categories, cat, list) {
510 AST_LIST_TRAVERSE(&cat->members, mem, list) {
511 if (mem->depsfailed || mem->conflictsfailed)
512 fprintf(f, "MENUSELECT_DEPSFAILED=%s=%s\n", cat->name, mem->name);
516 fclose(f);
518 /* Traverse all categories and members and remove any files that are supposed
519 to be removed when an item has been changed */
520 AST_LIST_TRAVERSE(&categories, cat, list) {
521 unsigned int had_changes = 0;
522 char *file, *buf;
524 AST_LIST_TRAVERSE(&cat->members, mem, list) {
525 if (mem->enabled == mem->was_enabled)
526 continue;
528 had_changes = 1;
530 if (mem->remove_on_change) {
531 for (buf = ast_strdupa(mem->remove_on_change), file = strsep(&buf, " ");
532 file;
533 file = strsep(&buf, " "))
534 unlink(file);
538 if (cat->remove_on_change && had_changes) {
539 for (buf = ast_strdupa(cat->remove_on_change), file = strsep(&buf, " ");
540 file;
541 file = strsep(&buf, " "))
542 unlink(file);
546 return 0;
549 #ifdef MENUSELECT_DEBUG
550 /*! \brief Print out all of the information contained in our tree */
551 static void dump_member_list(void)
553 struct category *cat;
554 struct member *mem;
555 struct depend *dep;
556 struct conflict *cnf;
558 AST_LIST_TRAVERSE(&categories, cat, list) {
559 fprintf(stderr, "Category: '%s'\n", cat->name);
560 AST_LIST_TRAVERSE(&cat->members, mem, list) {
561 fprintf(stderr, " ==>> Member: '%s' (%s)", mem->name, mem->enabled ? "Enabled" : "Disabled");
562 fprintf(stderr, " Was %s\n", mem->was_enabled ? "Enabled" : "Disabled");
563 AST_LIST_TRAVERSE(&mem->deps, dep, list)
564 fprintf(stderr, " --> Depends on: '%s'\n", dep->name);
565 if (!AST_LIST_EMPTY(&mem->deps))
566 fprintf(stderr, " --> Dependencies Met: %s\n", mem->depsfailed ? "No" : "Yes");
567 AST_LIST_TRAVERSE(&mem->conflicts, cnf, list)
568 fprintf(stderr, " --> Conflicts with: '%s'\n", cnf->name);
569 if (!AST_LIST_EMPTY(&mem->conflicts))
570 fprintf(stderr, " --> Conflicts Found: %s\n", mem->conflictsfailed ? "Yes" : "No");
574 #endif
576 /*! \brief Free all categories and their members */
577 static void free_member_list(void)
579 struct category *cat;
580 struct member *mem;
581 struct depend *dep;
582 struct conflict *cnf;
584 while ((cat = AST_LIST_REMOVE_HEAD(&categories, list))) {
585 while ((mem = AST_LIST_REMOVE_HEAD(&cat->members, list))) {
586 while ((dep = AST_LIST_REMOVE_HEAD(&mem->deps, list)))
587 free(dep);
588 while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
589 free(cnf);
590 free(mem);
592 free(cat);
596 /*! \brief Free all of the XML trees */
597 static void free_trees(void)
599 struct tree *tree;
601 while ((tree = AST_LIST_REMOVE_HEAD(&trees, list))) {
602 mxmlDelete(tree->root);
603 free(tree);
607 /*! \brief Enable/Disable all members of a category as long as dependencies have been met and no conflicts are found */
608 void set_all(struct category *cat, int val)
610 struct member *mem;
612 AST_LIST_TRAVERSE(&cat->members, mem, list) {
613 if (!(mem->depsfailed || mem->conflictsfailed))
614 mem->enabled = val;
618 int count_categories(void)
620 struct category *cat;
621 int count = 0;
623 AST_LIST_TRAVERSE(&categories, cat, list)
624 count++;
626 return count;
629 int count_members(struct category *cat)
631 struct member *mem;
632 int count = 0;
634 AST_LIST_TRAVERSE(&cat->members, mem, list)
635 count++;
637 return count;
640 /*! \brief Make sure an existing menuselect.makeopts disabled everything it should have */
641 static int sanity_check(void)
643 struct category *cat;
644 struct member *mem;
646 AST_LIST_TRAVERSE(&categories, cat, list) {
647 AST_LIST_TRAVERSE(&cat->members, mem, list) {
648 if ((mem->depsfailed || mem->conflictsfailed) && mem->enabled) {
649 fprintf(stderr, "\n***********************************************************\n"
650 " The existing menuselect.makeopts file did not specify \n"
651 " that '%s' should not be included. However, either some \n"
652 " dependencies for this module were not found or a \n"
653 " conflict exists. \n"
654 " \n"
655 " Either run 'make menuselect' or remove the existing \n"
656 " menuselect.makeopts file to resolve this issue. \n"
657 "***********************************************************\n\n", mem->name);
658 return -1;
662 return 0; /* all good... */
665 /* \brief Set the forced default values if they exist */
666 static void process_defaults(void)
668 struct category *cat;
669 struct member *mem;
671 AST_LIST_TRAVERSE(&categories, cat, list) {
672 AST_LIST_TRAVERSE(&cat->members, mem, list) {
673 if (!mem->defaultenabled)
674 continue;
676 if (!strcasecmp(mem->defaultenabled, "yes"))
677 mem->enabled = 1;
678 else if (!strcasecmp(mem->defaultenabled, "no"))
679 mem->enabled = 0;
680 else
681 fprintf(stderr, "Invalid defaultenabled value for '%s' in category '%s'\n", mem->name, cat->name);
687 int main(int argc, char *argv[])
689 int res = 0;
690 unsigned int x;
692 /* Parse the input XML files to build the list of available options */
693 if ((res = build_member_list()))
694 exit(res);
696 /* Process module dependencies */
697 res = process_deps();
699 /* The --check-deps option is used to ask this application to check to
700 * see if that an existing menuselect.makeopts file contails all of the
701 * modules that have dependencies that have not been met. If this
702 * is not the case, an informative message will be printed to the
703 * user and the build will fail. */
704 for (x = 1; x < argc; x++) {
705 if (!strcmp(argv[x], "--check-deps"))
706 check_deps = 1;
707 else {
708 res = parse_existing_config(argv[x]);
709 if (!res && !strcasecmp(argv[x], OUTPUT_MAKEOPTS_DEFAULT))
710 existing_config = 1;
711 res = 0;
715 #ifdef MENUSELECT_DEBUG
716 /* Dump the list produced by parsing the various input files */
717 dump_member_list();
718 #endif
720 if (!existing_config)
721 process_defaults();
722 else if (check_deps)
723 res = sanity_check();
725 /* Run the menu to let the user enable/disable options */
726 if (!check_deps && !res)
727 res = run_menu();
729 /* Write out the menuselect.makeopts file if
730 * 1) menuselect was not executed with --check-deps
731 * 2) menuselect was executed with --check-deps but menuselect.makeopts
732 * did not already exist.
734 if ((!check_deps || !existing_config) && !res)
735 res = generate_makeopts_file();
737 /* free everything we allocated */
738 free_trees();
739 free_member_list();
741 exit(res);