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