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.
22 * \author Russell Bryant <russell@digium.com>
24 * \brief A menu-driven system for Asterisk module selection
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;
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.
52 /*! the root of the tree */
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[] = {
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 /*! Force a clean of the source tree */
74 static int force_clean = 0;
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);
88 /*! \brief return a pointer to the first non-whitespace character */
89 static inline char *skip_blanks(char *str)
94 while (*str && *str < 33)
100 /*! \brief Add a category to the category list, ensuring that there are no duplicates */
101 static int add_category(struct category *cat)
103 struct category *tmp;
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);
111 AST_LIST_INSERT_TAIL(&categories, cat, list);
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)
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);
127 AST_LIST_INSERT_TAIL(&cat->members, mem, list);
132 /*! \brief Parse an input makeopts file */
133 static int parse_makeopts_xml(const char *makeopts_xml)
136 struct category *cat;
140 struct conflict *cnf;
147 if (!(f = fopen(makeopts_xml, "r"))) {
148 fprintf(stderr, "Unable to open '%s' for reading!\n", makeopts_xml);
152 if (!(tree = calloc(1, sizeof(*tree)))) {
157 if (!(tree->root = mxmlLoadFile(NULL, f, MXML_OPAQUE_CALLBACK))) {
163 AST_LIST_INSERT_HEAD(&trees, tree, list);
165 menu = mxmlFindElement(tree->root, tree->root, "menu", NULL, NULL, MXML_DESCEND);
166 for (cur = mxmlFindElement(menu, menu, "category", NULL, NULL, MXML_DESCEND);
168 cur = mxmlFindElement(cur, menu, "category", NULL, NULL, MXML_DESCEND))
170 if (!(cat = calloc(1, sizeof(*cat))))
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");
180 if (add_category(cat)) {
185 for (cur2 = mxmlFindElement(cur, cur, "member", NULL, NULL, MXML_DESCEND);
187 cur2 = mxmlFindElement(cur2, cur, "member", NULL, NULL, MXML_DESCEND))
189 if (!(mem = calloc(1, sizeof(*mem))))
192 mem->name = mxmlElementGetAttr(cur2, "name");
193 mem->displayname = mxmlElementGetAttr(cur2, "displayname");
195 if (!cat->positive_output)
198 cur3 = mxmlFindElement(cur2, cur2, "defaultenabled", NULL, NULL, MXML_DESCEND);
199 if (cur3 && cur3->child)
200 mem->defaultenabled = cur3->child->value.opaque;
202 for (cur3 = mxmlFindElement(cur2, cur2, "depend", NULL, NULL, MXML_DESCEND);
204 cur3 = mxmlFindElement(cur3, cur2, "depend", NULL, NULL, MXML_DESCEND))
206 if (!(dep = calloc(1, sizeof(*dep))))
208 if (!strlen_zero(cur3->child->value.opaque)) {
209 dep->name = cur3->child->value.opaque;
210 AST_LIST_INSERT_HEAD(&mem->deps, dep, list);
215 for (cur3 = mxmlFindElement(cur2, cur2, "conflict", NULL, NULL, MXML_DESCEND);
217 cur3 = mxmlFindElement(cur3, cur2, "conflict", NULL, NULL, MXML_DESCEND))
219 if (!(cnf = calloc(1, sizeof(*cnf))))
221 if (!strlen_zero(cur3->child->value.opaque)) {
222 cnf->name = cur3->child->value.opaque;
223 AST_LIST_INSERT_HEAD(&mem->conflicts, cnf, list);
228 if (add_member(mem, cat))
238 /*! \brief Process dependencies against the input dependencies file */
239 static int process_deps(void)
241 struct category *cat;
244 struct conflict *cnf;
249 AST_LIST_ENTRY(dep_file) list;
251 AST_LIST_HEAD_NOLOCK_STATIC(deps_file, dep_file);
256 if (!(f = fopen(MENUSELECT_DEPS, "r"))) {
257 fprintf(stderr, "Unable to open '%s' for reading! Did you run ./configure ?\n", MENUSELECT_DEPS);
261 /* Build a dependency list from the file generated by configure */
262 while (memset(buf, 0, sizeof(buf)), fgets(buf, sizeof(buf), f)) {
267 if (!(dep_file = calloc(1, sizeof(*dep_file))))
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);
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) {
281 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
282 if (!strcasecmp(dep_file->name, dep->name)) {
289 break; /* This dependency is not met, so we can stop now */
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)) {
302 mem->conflictsfailed = 1;
306 if (mem->conflictsfailed)
307 break; /* This conflict was found, so we can stop now */
312 /* Free the dependency list we built from the file */
313 while ((dep_file = AST_LIST_REMOVE_HEAD(&deps_file, list)))
319 /*! \brief Iterate through all of the input makeopts files and call the parse function on them */
320 static int build_member_list(void)
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]);
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)
338 struct category *cat;
341 AST_LIST_TRAVERSE(&categories, cat, list) {
342 if (strcmp(category, cat->name))
344 AST_LIST_TRAVERSE(&cat->members, mem, list) {
345 if (!strcmp(member, mem->name)) {
346 mem->enabled = cat->positive_output;
351 fprintf(stderr, "member '%s' in category '%s' not found, ignoring.\n", member, category);
356 fprintf(stderr, "category '%s' not found! Can't mark '%s' as disabled.\n", category, member);
359 /*! \brief Toggle a member of a category at the specified index to enabled/disabled */
360 void toggle_enabled(struct category *cat, int index)
365 AST_LIST_TRAVERSE(&cat->members, mem, list) {
370 if (mem && !(mem->depsfailed || mem->conflictsfailed)) {
371 mem->enabled = !mem->enabled;
372 if (cat->force_clean_on_change)
377 /*! \brief Process a previously failed dependency
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.
383 static void process_prev_failed_deps(char *buf)
385 const char *cat_name, *mem_name;
386 struct category *cat;
389 cat_name = strsep(&buf, "=");
390 mem_name = strsep(&buf, "\n");
392 if (!cat_name || !mem_name)
395 AST_LIST_TRAVERSE(&categories, cat, list) {
396 if (strcasecmp(cat->name, cat_name))
398 AST_LIST_TRAVERSE(&cat->members, mem, list) {
399 if (strcasecmp(mem->name, mem_name))
402 if (!mem->depsfailed && !mem->conflictsfailed)
411 fprintf(stderr, "Unable to find '%s' in category '%s'\n", mem_name, cat_name);
414 /*! \brief Parse an existing output makeopts file and enable members previously selected */
415 static int parse_existing_config(const char *infile)
419 char *category, *parse, *member;
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);
430 while (fgets(buf, sizeof(buf), f)) {
433 if (strlen_zero(buf))
436 /* skip lines that are not for this tool */
437 if (strncasecmp(buf, "MENUSELECT_", strlen("MENUSELECT_")))
441 parse = skip_blanks(parse);
442 if (strlen_zero(parse))
445 /* Grab the category name */
446 category = strsep(&parse, "=");
448 fprintf(stderr, "Invalid string in '%s' at line '%d'!\n", output_makeopts, lineno);
452 parse = skip_blanks(parse);
454 if (!strcasecmp(category, "MENUSELECT_DEPSFAILED")) {
455 process_prev_failed_deps(parse);
459 while ((member = strsep(&parse, " \n"))) {
460 member = skip_blanks(member);
461 if (strlen_zero(member))
463 mark_as_present(member, category);
472 /*! \brief Create the output makeopts file that results from the user's selections */
473 static int generate_makeopts_file(void)
476 struct category *cat;
479 if (!(f = fopen(output_makeopts, "w"))) {
480 fprintf(stderr, "Unable to open build configuration file (%s) for writing!\n", output_makeopts);
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);
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);
508 #ifdef MENUSELECT_DEBUG
509 /*! \brief Print out all of the information contained in our tree */
510 static void dump_member_list(void)
512 struct category *cat;
515 struct conflict *cnf;
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");
534 /*! \brief Free all categories and their members */
535 static void free_member_list(void)
537 struct category *cat;
540 struct conflict *cnf;
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)))
546 while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
554 /*! \brief Free all of the XML trees */
555 static void free_trees(void)
559 while ((tree = AST_LIST_REMOVE_HEAD(&trees, list))) {
560 mxmlDelete(tree->root);
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)
570 AST_LIST_TRAVERSE(&cat->members, mem, list) {
571 if (!(mem->depsfailed || mem->conflictsfailed))
576 int count_categories(void)
578 struct category *cat;
581 AST_LIST_TRAVERSE(&categories, cat, list)
587 int count_members(struct category *cat)
592 AST_LIST_TRAVERSE(&cat->members, mem, list)
598 /*! \brief Make sure an existing menuselect.makeopts disabled everything it should have */
599 static int sanity_check(void)
601 struct category *cat;
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"
613 " Either run 'make menuselect' or remove the existing \n"
614 " menuselect.makeopts file to resolve this issue. \n"
615 "***********************************************************\n\n", mem->name);
620 return 0; /* all good... */
623 /* \brief Set the forced default values if they exist */
624 static void process_defaults(void)
626 struct category *cat;
629 AST_LIST_TRAVERSE(&categories, cat, list) {
630 AST_LIST_TRAVERSE(&cat->members, mem, list) {
631 if (!mem->defaultenabled)
634 if (!strcasecmp(mem->defaultenabled, "yes"))
636 else if (!strcasecmp(mem->defaultenabled, "no"))
639 fprintf(stderr, "Invalid defaultenabled value for '%s' in category '%s'\n", mem->name, cat->name);
645 int main(int argc, char *argv[])
650 /* Parse the input XML files to build the list of available options */
651 if ((res = build_member_list()))
654 /* Process module dependencies */
655 res = process_deps();
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"))
666 res = parse_existing_config(argv[x]);
667 if (!res && !strcasecmp(argv[x], OUTPUT_MAKEOPTS_DEFAULT))
673 #ifdef MENUSELECT_DEBUG
674 /* Dump the list produced by parsing the various input files */
678 if (!existing_config)
681 res = sanity_check();
683 /* Run the menu to let the user enable/disable options */
684 if (!check_deps && !res)
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.
692 if ((!check_deps || !existing_config) && !res)
693 res = generate_makeopts_file();
695 /* free everything we allocated */
699 /* In some cases, such as modifying the CFLAGS for the build,
700 * a "make clean" needs to be forced. Removing the .lastclean
703 unlink(".lastclean");