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
27 #include "autoconfig.h"
34 #include "mxml/mxml.h"
35 #include "menuselect.h"
39 #include "asterisk/linkedlists.h"
41 #undef MENUSELECT_DEBUG
44 /*! the name of the dependency */
47 AST_LIST_ENTRY(depend) list;
51 /*! the name of the conflict */
54 AST_LIST_ENTRY(conflict) list;
57 /*! The list of categories */
58 struct categories categories = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
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.
68 /*! the root of the tree */
71 AST_LIST_ENTRY(tree) list;
74 /*! The list of trees from makeopts.xml files */
75 AST_LIST_HEAD_NOLOCK_STATIC(trees, tree);
77 const char * const makeopts_files[] = {
81 char *output_makeopts = OUTPUT_MAKEOPTS_DEFAULT;
83 /*! This is set to 1 if menuselect.makeopts pre-existed the execution of this app */
84 int existing_config = 0;
86 /*! This is set when the --check-deps argument is provided. */
89 /*! Force a clean of the source tree */
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);
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)
108 tmp = calloc(num, len);
111 fprintf(stderr, "Memory allocation error!\n");
116 /*! \brief return a pointer to the first non-whitespace character */
117 static inline char *skip_blanks(char *str)
122 while (*str && *str < 33)
128 /*! \brief Add a category to the category list, ensuring that there are no duplicates */
129 int add_category(struct category *cat)
131 struct category *tmp;
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);
139 AST_LIST_INSERT_TAIL(&categories, cat, list);
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)
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);
155 AST_LIST_INSERT_TAIL(&cat->members, mem, list);
160 /*! \brief Parse an input makeopts file */
161 int parse_makeopts_xml(const char *makeopts_xml)
164 struct category *cat;
168 struct conflict *cnf;
175 if (!(f = fopen(makeopts_xml, "r"))) {
176 fprintf(stderr, "Unable to open '%s' for reading!\n", makeopts_xml);
180 if (!(tree = my_calloc(1, sizeof(*tree)))) {
185 if (!(tree->root = mxmlLoadFile(NULL, f, MXML_OPAQUE_CALLBACK))) {
191 AST_LIST_INSERT_HEAD(&trees, tree, list);
193 menu = mxmlFindElement(tree->root, tree->root, "menu", NULL, NULL, MXML_DESCEND);
194 for (cur = mxmlFindElement(menu, menu, "category", NULL, NULL, MXML_DESCEND);
196 cur = mxmlFindElement(cur, menu, "category", NULL, NULL, MXML_DESCEND))
198 if (!(cat = my_calloc(1, sizeof(*cat))))
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");
208 if (add_category(cat)) {
213 for (cur2 = mxmlFindElement(cur, cur, "member", NULL, NULL, MXML_DESCEND);
215 cur2 = mxmlFindElement(cur2, cur, "member", NULL, NULL, MXML_DESCEND))
217 if (!(mem = my_calloc(1, sizeof(*mem))))
220 if (!cat->positive_output)
221 mem->enabled = 1; /* Enabled by default */
223 mem->name = mxmlElementGetAttr(cur2, "name");
225 cur3 = mxmlFindElement(cur2, cur2, "defaultenabled", NULL, NULL, MXML_DESCEND);
226 if (cur3 && cur3->child) {
227 if (!strcasecmp("no", cur3->child->value.opaque))
229 else if (!strcasecmp("yes", cur3->child->value.opaque))
232 fprintf(stderr, "Invalid value '%s' for <defaultenabled> !\n", cur3->child->value.opaque);
235 for (cur3 = mxmlFindElement(cur2, cur2, "depend", NULL, NULL, MXML_DESCEND);
237 cur3 = mxmlFindElement(cur3, cur2, "depend", NULL, NULL, MXML_DESCEND))
239 if (!(dep = my_calloc(1, sizeof(*dep))))
241 if (!strlen_zero(cur3->child->value.opaque)) {
242 dep->name = cur3->child->value.opaque;
243 AST_LIST_INSERT_HEAD(&mem->deps, dep, list);
248 for (cur3 = mxmlFindElement(cur2, cur2, "conflict", NULL, NULL, MXML_DESCEND);
250 cur3 = mxmlFindElement(cur3, cur2, "conflict", NULL, NULL, MXML_DESCEND))
252 if (!(cnf = my_calloc(1, sizeof(*cnf))))
254 if (!strlen_zero(cur3->child->value.opaque)) {
255 cnf->name = cur3->child->value.opaque;
256 AST_LIST_INSERT_HEAD(&mem->conflicts, cnf, list);
261 if (add_member(mem, cat))
271 /*! \brief Process dependencies against the input dependencies file */
272 int process_deps(void)
274 struct category *cat;
277 struct conflict *cnf;
282 AST_LIST_ENTRY(dep_file) list;
284 AST_LIST_HEAD_NOLOCK_STATIC(deps_file, dep_file);
289 if (!(f = fopen(MENUSELECT_DEPS, "r"))) {
290 fprintf(stderr, "Unable to open '%s' for reading! Did you run ./configure ?\n", MENUSELECT_DEPS);
294 /* Build a dependency list from the file generated by configure */
295 while (memset(buf, 0, sizeof(buf)), fgets(buf, sizeof(buf), f)) {
300 if (!(dep_file = my_calloc(1, sizeof(*dep_file))))
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);
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) {
314 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
315 if (!strcasecmp(dep_file->name, dep->name)) {
322 break; /* This dependency is not met, so we can stop now */
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. */
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");
336 mem->enabled = 0; /* Automatically disable it if dependencies not met */
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)) {
349 mem->conflictsfailed = 1;
353 if (mem->conflictsfailed)
354 break; /* This conflict was found, so we can stop now */
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. */
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");
368 mem->enabled = 0; /* Automatically disable it if conflicts exist */
375 /* Free the dependency list we built from the file */
376 while ((dep_file = AST_LIST_REMOVE_HEAD(&deps_file, list)))
382 /*! \brief Iterate through all of the input makeopts files and call the parse function on them */
383 int build_member_list(void)
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]);
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)
401 struct category *cat;
404 AST_LIST_TRAVERSE(&categories, cat, list) {
405 if (strcmp(category, cat->name))
407 AST_LIST_TRAVERSE(&cat->members, mem, list) {
408 if (!strcmp(member, mem->name)) {
409 mem->enabled = cat->positive_output;
414 fprintf(stderr, "member '%s' in category '%s' not found, ignoring.\n", member, category);
419 fprintf(stderr, "category '%s' not found! Can't mark '%s' as disabled.\n", category, member);
422 /*! \brief Toggle a member of a category at the specified index to enabled/disabled */
423 void toggle_enabled(struct category *cat, int index)
428 AST_LIST_TRAVERSE(&cat->members, mem, list) {
433 if (mem && !(mem->depsfailed || mem->conflictsfailed)) {
434 mem->enabled = !mem->enabled;
435 if (cat->force_clean_on_change)
440 /*! \brief Parse an existing output makeopts file and enable members previously selected */
441 int parse_existing_config(const char *infile)
445 char *category, *parse, *member;
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);
456 while (fgets(buf, sizeof(buf), f)) {
459 if (strlen_zero(buf))
462 /* skip lines that are not for this tool */
463 if (strncasecmp(buf, "MENUSELECT_", strlen("MENUSELECT_")))
467 parse = skip_blanks(parse);
468 if (strlen_zero(parse))
471 /* Grab the category name */
472 category = strsep(&parse, "=");
474 fprintf(stderr, "Invalid string in '%s' at line '%d'!\n", output_makeopts, lineno);
478 parse = skip_blanks(parse);
479 while ((member = strsep(&parse, " \n"))) {
480 member = skip_blanks(member);
481 if (strlen_zero(member))
484 mark_as_present(member, category);
493 /*! \brief Create the output makeopts file that results from the user's selections */
494 int generate_makeopts_file(void)
497 struct category *cat;
500 if (!(f = fopen(output_makeopts, "w"))) {
501 fprintf(stderr, "Unable to open build configuration file (%s) for writing!\n", output_makeopts);
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);
521 #ifdef MENUSELECT_DEBUG
522 /*! \brief Print out all of the information contained in our tree */
523 void dump_member_list(void)
525 struct category *cat;
528 struct conflict *cnf;
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");
547 /*! \brief Free all categories and their members */
548 void free_member_list(void)
550 struct category *cat;
553 struct conflict *cnf;
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)))
559 while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
567 /*! \brief Free all of the XML trees */
568 void free_trees(void)
572 while ((tree = AST_LIST_REMOVE_HEAD(&trees, list))) {
573 mxmlDelete(tree->root);
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)
583 AST_LIST_TRAVERSE(&cat->members, mem, list) {
584 if (!(mem->depsfailed || mem->conflictsfailed))
589 int count_categories(void)
591 struct category *cat;
594 AST_LIST_TRAVERSE(&categories, cat, list)
600 int count_members(struct category *cat)
605 AST_LIST_TRAVERSE(&cat->members, mem, list)
611 int main(int argc, char *argv[])
616 /* Parse the input XML files to build the list of available options */
617 if ((res = build_member_list()))
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"))
629 res = parse_existing_config(argv[x]);
630 if (!res && !strcasecmp(argv[x], OUTPUT_MAKEOPTS_DEFAULT))
635 /* Process module dependencies */
636 res = process_deps();
638 #ifdef MENUSELECT_DEBUG
639 /* Dump the list produced by parsing the various input files */
643 /* Run the menu to let the user enable/disable options */
644 if (!check_deps && !res)
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.
652 if ((!check_deps || !existing_config) && !res)
653 res = generate_makeopts_file();
655 /* free everything we allocated */
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");
668 unlink(".lastclean");