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