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 #if !defined(ast_strdupa) && defined(__GNUC__)
74 #define ast_strdupa(s) \
77 const char *__old = (s); \
78 size_t __len = strlen(__old) + 1; \
79 char *__new = __builtin_alloca(__len); \
80 memcpy (__new, __old, __len); \
85 /*! \brief return a pointer to the first non-whitespace character */
86 static inline char *skip_blanks(char *str
)
91 while (*str
&& *str
< 33)
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
);
108 AST_LIST_INSERT_TAIL(&categories
, cat
, list
);
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
)
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
);
124 AST_LIST_INSERT_TAIL(&cat
->members
, mem
, list
);
129 /*! \brief Free a member structure and all of its members */
130 static void free_member(struct member
*mem
)
133 struct conflict
*cnf
;
135 while ((dep
= AST_LIST_REMOVE_HEAD(&mem
->deps
, list
)))
137 while ((cnf
= AST_LIST_REMOVE_HEAD(&mem
->conflicts
, list
)))
142 /*! \brief Parse an input makeopts file */
143 static int parse_makeopts_xml(const char *makeopts_xml
)
146 struct category
*cat
;
150 struct conflict
*cnf
;
157 if (!(f
= fopen(makeopts_xml
, "r"))) {
158 fprintf(stderr
, "Unable to open '%s' for reading!\n", makeopts_xml
);
162 if (!(tree
= calloc(1, sizeof(*tree
)))) {
167 if (!(tree
->root
= mxmlLoadFile(NULL
, f
, MXML_OPAQUE_CALLBACK
))) {
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
);
178 cur
= mxmlFindElement(cur
, menu
, "category", NULL
, NULL
, MXML_DESCEND
))
180 if (!(cat
= calloc(1, sizeof(*cat
))))
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
)) {
194 for (cur2
= mxmlFindElement(cur
, cur
, "member", NULL
, NULL
, MXML_DESCEND
);
196 cur2
= mxmlFindElement(cur2
, cur
, "member", NULL
, NULL
, MXML_DESCEND
))
198 if (!(mem
= calloc(1, sizeof(*mem
))))
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
);
215 cur3
= mxmlFindElement(cur3
, cur2
, "depend", NULL
, NULL
, MXML_DESCEND
))
217 if (!(dep
= calloc(1, sizeof(*dep
)))) {
221 if (!strlen_zero(cur3
->child
->value
.opaque
)) {
222 dep
->name
= cur3
->child
->value
.opaque
;
223 AST_LIST_INSERT_HEAD(&mem
->deps
, dep
, list
);
228 for (cur3
= mxmlFindElement(cur2
, cur2
, "conflict", NULL
, NULL
, MXML_DESCEND
);
230 cur3
= mxmlFindElement(cur3
, cur2
, "conflict", NULL
, NULL
, MXML_DESCEND
))
232 if (!(cnf
= calloc(1, sizeof(*cnf
)))) {
236 if (!strlen_zero(cur3
->child
->value
.opaque
)) {
237 cnf
->name
= cur3
->child
->value
.opaque
;
238 AST_LIST_INSERT_HEAD(&mem
->conflicts
, cnf
, list
);
243 if (add_member(mem
, cat
))
253 /*! \brief Process dependencies against the input dependencies file */
254 static int process_deps(void)
256 struct category
*cat
;
259 struct conflict
*cnf
;
264 AST_LIST_ENTRY(dep_file
) list
;
266 AST_LIST_HEAD_NOLOCK_STATIC(deps_file
, dep_file
);
271 if (!(f
= fopen(MENUSELECT_DEPS
, "r"))) {
272 fprintf(stderr
, "Unable to open '%s' for reading! Did you run ./configure ?\n", MENUSELECT_DEPS
);
276 /* Build a dependency list from the file generated by configure */
277 while (memset(buf
, 0, sizeof(buf
)), fgets(buf
, sizeof(buf
), f
)) {
282 if (!(dep_file
= calloc(1, sizeof(*dep_file
))))
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
);
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
) {
296 AST_LIST_TRAVERSE(&deps_file
, dep_file
, list
) {
297 if (!strcasecmp(dep_file
->name
, dep
->name
)) {
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
)) {
317 mem
->conflictsfailed
= 1;
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
)))
334 /*! \brief Iterate through all of the input makeopts files and call the parse function on them */
335 static int build_member_list(void)
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
]);
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
;
356 AST_LIST_TRAVERSE(&categories
, cat
, list
) {
357 if (strcmp(category
, cat
->name
))
359 AST_LIST_TRAVERSE(&cat
->members
, mem
, list
) {
360 if (!strcmp(member
, mem
->name
)) {
361 mem
->was_enabled
= mem
->enabled
= cat
->positive_output
;
366 fprintf(stderr
, "member '%s' in category '%s' not found, ignoring.\n", member
, category
);
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
)
380 AST_LIST_TRAVERSE(&cat
->members
, mem
, list
) {
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
;
402 cat_name
= strsep(&buf
, "=");
403 mem_name
= strsep(&buf
, "\n");
405 if (!cat_name
|| !mem_name
)
408 AST_LIST_TRAVERSE(&categories
, cat
, list
) {
409 if (strcasecmp(cat
->name
, cat_name
))
411 AST_LIST_TRAVERSE(&cat
->members
, mem
, list
) {
412 if (strcasecmp(mem
->name
, mem_name
))
415 if (!mem
->depsfailed
&& !mem
->conflictsfailed
)
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
)
432 char *category
, *parse
, *member
;
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
);
443 while (fgets(buf
, sizeof(buf
), f
)) {
446 if (strlen_zero(buf
))
449 /* skip lines that are not for this tool */
450 if (strncasecmp(buf
, "MENUSELECT_", strlen("MENUSELECT_")))
454 parse
= skip_blanks(parse
);
455 if (strlen_zero(parse
))
458 /* Grab the category name */
459 category
= strsep(&parse
, "=");
461 fprintf(stderr
, "Invalid string in '%s' at line '%d'!\n", output_makeopts
, lineno
);
465 parse
= skip_blanks(parse
);
467 if (!strcasecmp(category
, "MENUSELECT_DEPSFAILED")) {
468 process_prev_failed_deps(parse
);
472 while ((member
= strsep(&parse
, " \n"))) {
473 member
= skip_blanks(member
);
474 if (strlen_zero(member
))
476 mark_as_present(member
, category
);
485 /*! \brief Create the output makeopts file that results from the user's selections */
486 static int generate_makeopts_file(void)
489 struct category
*cat
;
492 if (!(f
= fopen(output_makeopts
, "w"))) {
493 fprintf(stderr
, "Unable to open build configuration file (%s) for writing!\n", output_makeopts
);
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
);
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
);
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;
524 AST_LIST_TRAVERSE(&cat
->members
, mem
, list
) {
525 if (mem
->enabled
== mem
->was_enabled
)
530 if (mem
->remove_on_change
) {
531 for (buf
= ast_strdupa(mem
->remove_on_change
), file
= strsep(&buf
, " ");
533 file
= strsep(&buf
, " "))
538 if (cat
->remove_on_change
&& had_changes
) {
539 for (buf
= ast_strdupa(cat
->remove_on_change
), file
= strsep(&buf
, " ");
541 file
= strsep(&buf
, " "))
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
;
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");
576 /*! \brief Free all categories and their members */
577 static void free_member_list(void)
579 struct category
*cat
;
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
)))
588 while ((cnf
= AST_LIST_REMOVE_HEAD(&mem
->conflicts
, list
)))
596 /*! \brief Free all of the XML trees */
597 static void free_trees(void)
601 while ((tree
= AST_LIST_REMOVE_HEAD(&trees
, list
))) {
602 mxmlDelete(tree
->root
);
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
)
612 AST_LIST_TRAVERSE(&cat
->members
, mem
, list
) {
613 if (!(mem
->depsfailed
|| mem
->conflictsfailed
))
618 int count_categories(void)
620 struct category
*cat
;
623 AST_LIST_TRAVERSE(&categories
, cat
, list
)
629 int count_members(struct category
*cat
)
634 AST_LIST_TRAVERSE(&cat
->members
, mem
, list
)
640 /*! \brief Make sure an existing menuselect.makeopts disabled everything it should have */
641 static int sanity_check(void)
643 struct category
*cat
;
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"
655 " Either run 'make menuselect' or remove the existing \n"
656 " menuselect.makeopts file to resolve this issue. \n"
657 "***********************************************************\n\n", mem
->name
);
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
;
671 AST_LIST_TRAVERSE(&categories
, cat
, list
) {
672 AST_LIST_TRAVERSE(&cat
->members
, mem
, list
) {
673 if (!mem
->defaultenabled
)
676 if (!strcasecmp(mem
->defaultenabled
, "yes"))
678 else if (!strcasecmp(mem
->defaultenabled
, "no"))
681 fprintf(stderr
, "Invalid defaultenabled value for '%s' in category '%s'\n", mem
->name
, cat
->name
);
687 int main(int argc
, char *argv
[])
692 /* Parse the input XML files to build the list of available options */
693 if ((res
= build_member_list()))
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"))
708 res
= parse_existing_config(argv
[x
]);
709 if (!res
&& !strcasecmp(argv
[x
], OUTPUT_MAKEOPTS_DEFAULT
))
715 #ifdef MENUSELECT_DEBUG
716 /* Dump the list produced by parsing the various input files */
720 if (!existing_config
)
723 res
= sanity_check();
725 /* Run the menu to let the user enable/disable options */
726 if (!check_deps
&& !res
)
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 */