various menuselect fixes as a result of boredom during a 9 hour flight and
[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                 
210                         if (!cat->positive_output)
211                                 mem->enabled = 1;
212         
213                         cur3 = mxmlFindElement(cur2, cur2, "defaultenabled", NULL, NULL, MXML_DESCEND);
214                         if (cur3 && cur3->child)
215                                 mem->defaultenabled = cur3->child->value.opaque;
216                         
217                         for (cur3 = mxmlFindElement(cur2, cur2, "depend", NULL, NULL, MXML_DESCEND);
218                              cur3 && cur3->child;
219                              cur3 = mxmlFindElement(cur3, cur2, "depend", NULL, NULL, MXML_DESCEND))
220                         {
221                                 if (!(dep = calloc(1, sizeof(*dep))))
222                                         return -1;
223                                 if (!strlen_zero(cur3->child->value.opaque)) {
224                                         dep->name = cur3->child->value.opaque;
225                                         AST_LIST_INSERT_HEAD(&mem->deps, dep, list);
226                                 } else
227                                         free(dep);
228                         }
229
230                         for (cur3 = mxmlFindElement(cur2, cur2, "conflict", NULL, NULL, MXML_DESCEND);
231                              cur3 && cur3->child;
232                              cur3 = mxmlFindElement(cur3, cur2, "conflict", NULL, NULL, MXML_DESCEND))
233                         {
234                                 if (!(cnf = calloc(1, sizeof(*cnf))))
235                                         return -1;
236                                 if (!strlen_zero(cur3->child->value.opaque)) {
237                                         cnf->name = cur3->child->value.opaque;
238                                         AST_LIST_INSERT_HEAD(&mem->conflicts, cnf, list);
239                                 } else
240                                         free(cnf);
241                         }
242
243                         if (add_member(mem, cat))
244                                 free(mem);
245                 }
246         }
247
248         fclose(f);
249
250         return 0;
251 }
252
253 /*! \brief Process dependencies against the input dependencies file */
254 static int process_deps(void)
255 {
256         struct category *cat;
257         struct member *mem;
258         struct depend *dep;
259         struct conflict *cnf;
260         FILE *f;
261         struct dep_file {
262                 char name[32];
263                 int met;
264                 AST_LIST_ENTRY(dep_file) list;
265         } *dep_file;
266         AST_LIST_HEAD_NOLOCK_STATIC(deps_file, dep_file);
267         char buf[80];
268         char *p;
269         int res = 0;
270
271         if (!(f = fopen(MENUSELECT_DEPS, "r"))) {
272                 fprintf(stderr, "Unable to open '%s' for reading!  Did you run ./configure ?\n", MENUSELECT_DEPS);
273                 return -1;
274         }
275
276         /* Build a dependency list from the file generated by configure */      
277         while (memset(buf, 0, sizeof(buf)), fgets(buf, sizeof(buf), f)) {
278                 p = buf;
279                 strsep(&p, "=");
280                 if (!p)
281                         continue;
282                 if (!(dep_file = calloc(1, sizeof(*dep_file))))
283                         break;
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);
287         }
288
289         fclose(f);
290
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) {
295                                 mem->depsfailed = 1;
296                                 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
297                                         if (!strcasecmp(dep_file->name, dep->name)) {
298                                                 if (dep_file->met)
299                                                         mem->depsfailed = 0;
300                                                 break;
301                                         }
302                                 }
303                                 if (mem->depsfailed)
304                                         break; /* This dependency is not met, so we can stop now */
305                         }
306                 }
307         }
308
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)) {
316                                                 if (dep_file->met)
317                                                         mem->conflictsfailed = 1;
318                                                 break;
319                                         }
320                                 }
321                                 if (mem->conflictsfailed)
322                                         break; /* This conflict was found, so we can stop now */
323                         }
324                 }
325         }
326
327         /* Free the dependency list we built from the file */
328         while ((dep_file = AST_LIST_REMOVE_HEAD(&deps_file, list)))
329                 free(dep_file);
330
331         return res;
332 }
333
334 /*! \brief Iterate through all of the input makeopts files and call the parse function on them */
335 static int build_member_list(void)
336 {
337         int i;
338         int res = -1;
339
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]);
343                         break;
344                 }
345         }
346
347         return res;
348 }
349
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)
352 {
353         struct category *cat;
354         struct member *mem;
355
356         AST_LIST_TRAVERSE(&categories, cat, list) {
357                 if (strcmp(category, cat->name))
358                         continue;
359                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
360                         if (!strcmp(member, mem->name)) {
361                                 mem->enabled = cat->positive_output;
362                                 break;
363                         }
364                 }
365                 if (!mem)
366                         fprintf(stderr, "member '%s' in category '%s' not found, ignoring.\n", member, category);
367                 break;
368         }
369
370         if (!cat)
371                 fprintf(stderr, "category '%s' not found! Can't mark '%s' as disabled.\n", category, member);
372 }
373
374 /*! \brief Toggle a member of a category at the specified index to enabled/disabled */
375 void toggle_enabled(struct category *cat, int index)
376 {
377         struct member *mem;
378         int i = 0;
379
380         AST_LIST_TRAVERSE(&cat->members, mem, list) {
381                 if (i++ == index)
382                         break;
383         }
384
385         if (mem && !(mem->depsfailed || mem->conflictsfailed)) {
386                 mem->enabled = !mem->enabled;
387                 if (cat->force_clean_on_change)
388                         force_clean = 1;
389         }
390 }
391
392 /*! \brief Process a previously failed dependency
393  *
394  * If a module was previously disabled because of a failed dependency
395  * or a conflict, and not because the user selected it to be that way,
396  * then it needs to be re-enabled by default if the problem is no longer present.
397  */
398 static void process_prev_failed_deps(char *buf)
399 {
400         const char *cat_name, *mem_name;
401         struct category *cat;
402         struct member *mem;
403
404         cat_name = strsep(&buf, "=");
405         mem_name = strsep(&buf, "\n");
406
407         if (!cat_name || !mem_name)
408                 return;
409
410         AST_LIST_TRAVERSE(&categories, cat, list) {
411                 if (strcasecmp(cat->name, cat_name))
412                         continue;
413                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
414                         if (strcasecmp(mem->name, mem_name))
415                                 continue;
416
417                         if (!mem->depsfailed && !mem->conflictsfailed)
418                                 mem->enabled = 1;                       
419         
420                         break;
421                 }
422                 break;  
423         }
424
425         if (!cat || !mem)
426                 fprintf(stderr, "Unable to find '%s' in category '%s'\n", mem_name, cat_name);
427 }
428
429 /*! \brief Parse an existing output makeopts file and enable members previously selected */
430 static int parse_existing_config(const char *infile)
431 {
432         FILE *f;
433         char buf[2048];
434         char *category, *parse, *member;
435         int lineno = 0;
436
437         if (!(f = fopen(infile, "r"))) {
438 #ifdef MENUSELECT_DEBUG
439                 /* This isn't really an error, so only print the message in debug mode */
440                 fprintf(stderr, "Unable to open '%s' for reading existing config.\n", infile);
441 #endif  
442                 return -1;
443         }
444
445         while (fgets(buf, sizeof(buf), f)) {
446                 lineno++;
447
448                 if (strlen_zero(buf))
449                         continue;
450
451                 /* skip lines that are not for this tool */
452                 if (strncasecmp(buf, "MENUSELECT_", strlen("MENUSELECT_")))
453                         continue;
454
455                 parse = buf;
456                 parse = skip_blanks(parse);
457                 if (strlen_zero(parse))
458                         continue;
459
460                 /* Grab the category name */    
461                 category = strsep(&parse, "=");
462                 if (!parse) {
463                         fprintf(stderr, "Invalid string in '%s' at line '%d'!\n", output_makeopts, lineno);
464                         continue;
465                 }
466                 
467                 parse = skip_blanks(parse);
468         
469                 if (!strcasecmp(category, "MENUSELECT_DEPSFAILED")) {
470                         process_prev_failed_deps(parse);
471                         continue;
472                 }
473         
474                 while ((member = strsep(&parse, " \n"))) {
475                         member = skip_blanks(member);
476                         if (strlen_zero(member))
477                                 continue;
478                         mark_as_present(member, category);
479                 }
480         }
481
482         fclose(f);
483
484         return 0;
485 }
486
487 /*! \brief Create the output makeopts file that results from the user's selections */
488 static int generate_makeopts_file(void)
489 {
490         FILE *f;
491         struct category *cat;
492         struct member *mem;
493
494         if (!(f = fopen(output_makeopts, "w"))) {
495                 fprintf(stderr, "Unable to open build configuration file (%s) for writing!\n", output_makeopts);
496                 return -1;
497         }
498
499         /* Traverse all categories and members and output them as var/val pairs */
500         AST_LIST_TRAVERSE(&categories, cat, list) {
501                 fprintf(f, "%s=", cat->name);
502                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
503                         if ((!cat->positive_output && (!mem->enabled || mem->depsfailed || mem->conflictsfailed)) ||
504                             (cat->positive_output && mem->enabled && !mem->depsfailed && !mem->conflictsfailed))
505                                 fprintf(f, "%s ", mem->name);
506                 }
507                 fprintf(f, "\n");
508         }
509
510         /* Output which members were disabled because of failed dependencies or conflicts */
511         AST_LIST_TRAVERSE(&categories, cat, list) {
512                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
513                         if (mem->depsfailed || mem->conflictsfailed)
514                                 fprintf(f, "MENUSELECT_DEPSFAILED=%s=%s\n", cat->name, mem->name);
515                 }
516         }
517
518         fclose(f);
519
520         return 0;
521 }
522
523 #ifdef MENUSELECT_DEBUG
524 /*! \brief Print out all of the information contained in our tree */
525 static void dump_member_list(void)
526 {
527         struct category *cat;
528         struct member *mem;
529         struct depend *dep;
530         struct conflict *cnf;
531
532         AST_LIST_TRAVERSE(&categories, cat, list) {
533                 fprintf(stderr, "Category: '%s'\n", cat->name);
534                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
535                         fprintf(stderr, "   ==>> Member: '%s'  (%s)\n", mem->name, mem->enabled ? "Enabled" : "Disabled");
536                         AST_LIST_TRAVERSE(&mem->deps, dep, list)
537                                 fprintf(stderr, "      --> Depends on: '%s'\n", dep->name);
538                         if (!AST_LIST_EMPTY(&mem->deps))
539                                 fprintf(stderr, "      --> Dependencies Met: %s\n", mem->depsfailed ? "No" : "Yes");    
540                         AST_LIST_TRAVERSE(&mem->conflicts, cnf, list)
541                                 fprintf(stderr, "      --> Conflicts with: '%s'\n", cnf->name);
542                         if (!AST_LIST_EMPTY(&mem->conflicts))
543                                 fprintf(stderr, "      --> Conflicts Found: %s\n", mem->conflictsfailed ? "Yes" : "No");
544                 }
545         }
546 }
547 #endif
548
549 /*! \brief Free all categories and their members */
550 static void free_member_list(void)
551 {
552         struct category *cat;
553         struct member *mem;
554         struct depend *dep;
555         struct conflict *cnf;
556
557         while ((cat = AST_LIST_REMOVE_HEAD(&categories, list))) {
558                 while ((mem = AST_LIST_REMOVE_HEAD(&cat->members, list))) {
559                         while ((dep = AST_LIST_REMOVE_HEAD(&mem->deps, list)))
560                                 free(dep);
561                         while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
562                                 free(cnf);
563                         free(mem);
564                 }
565                 free(cat);
566         }
567 }
568
569 /*! \brief Free all of the XML trees */
570 static void free_trees(void)
571 {
572         struct tree *tree;
573
574         while ((tree = AST_LIST_REMOVE_HEAD(&trees, list))) {
575                 mxmlDelete(tree->root);
576                 free(tree);
577         }
578 }
579
580 /*! \brief Enable/Disable all members of a category as long as dependencies have been met and no conflicts are found */
581 void set_all(struct category *cat, int val)
582 {
583         struct member *mem;
584
585         AST_LIST_TRAVERSE(&cat->members, mem, list) {
586                 if (!(mem->depsfailed || mem->conflictsfailed))
587                         mem->enabled = val;
588         }
589 }
590
591 int count_categories(void)
592 {
593         struct category *cat;
594         int count = 0;
595
596         AST_LIST_TRAVERSE(&categories, cat, list)
597                 count++;
598
599         return count;           
600 }
601
602 int count_members(struct category *cat)
603 {
604         struct member *mem;
605         int count = 0;
606
607         AST_LIST_TRAVERSE(&cat->members, mem, list)
608                 count++;
609
610         return count;           
611 }
612
613 /*! \brief Make sure an existing menuselect.makeopts disabled everything it should have */
614 static int sanity_check(void)
615 {
616         struct category *cat;
617         struct member *mem;
618
619         AST_LIST_TRAVERSE(&categories, cat, list) {
620                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
621                         if ((mem->depsfailed || mem->conflictsfailed) && mem->enabled) {
622                                 fprintf(stderr, "\n***********************************************************\n"
623                                                 "  The existing menuselect.makeopts file did not specify    \n"
624                                                 "  that '%s' should not be included.  However, either some  \n"
625                                                 "  dependencies for this module were not found or a         \n"
626                                                 "  conflict exists.                                         \n"
627                                                 "                                                           \n"
628                                                 "  Either run 'make menuselect' or remove the existing      \n"
629                                                 "  menuselect.makeopts file to resolve this issue.          \n"
630                                                 "***********************************************************\n\n", mem->name);
631                                 return -1;
632                         }
633                 }
634         }
635 }
636
637 /* \brief Set the forced default values if they exist */
638 static void process_defaults(void)
639 {
640         struct category *cat;
641         struct member *mem;
642
643         AST_LIST_TRAVERSE(&categories, cat, list) {
644                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
645                         if (!mem->defaultenabled)
646                                 continue;
647                         
648                         if (!strcasecmp(mem->defaultenabled, "yes"))
649                                 mem->enabled = 1;
650                         else if (!strcasecmp(mem->defaultenabled, "no"))
651                                 mem->enabled = 0;
652                         else
653                                 fprintf(stderr, "Invalid defaultenabled value for '%s' in category '%s'\n", mem->name, cat->name);      
654                 }
655         }
656
657 }
658
659 int main(int argc, char *argv[])
660 {
661         int res = 0;
662         unsigned int x;
663
664         /* Parse the input XML files to build the list of available options */
665         if ((res = build_member_list()))
666                 exit(res);
667         
668         /* Process module dependencies */
669         res = process_deps();
670         
671         /* The --check-deps option is used to ask this application to check to
672          * see if that an existing menuselect.makeopts file contails all of the
673          * modules that have dependencies that have not been met.  If this
674          * is not the case, an informative message will be printed to the
675          * user and the build will fail. */
676         for (x = 1; x < argc; x++) {
677                 if (!strcmp(argv[x], "--check-deps"))
678                         check_deps = 1;
679                 else {
680                         res = parse_existing_config(argv[x]);
681                         if (!res && !strcasecmp(argv[x], OUTPUT_MAKEOPTS_DEFAULT))
682                                 existing_config = 1;
683                         res = 0;
684                 }
685         }
686
687 #ifdef MENUSELECT_DEBUG
688         /* Dump the list produced by parsing the various input files */
689         dump_member_list();
690 #endif
691
692         if (!existing_config)
693                 process_defaults();
694         else if (check_deps)
695                 res = sanity_check();
696
697         /* Run the menu to let the user enable/disable options */
698         if (!check_deps && !res)
699                 res = run_menu();
700
701         /* Write out the menuselect.makeopts file if
702          * 1) menuselect was not executed with --check-deps
703          * 2) menuselect was executed with --check-deps but menuselect.makeopts
704          *    did not already exist.
705          */
706         if ((!check_deps || !existing_config) && !res)
707                 res = generate_makeopts_file();
708         
709         /* free everything we allocated */
710         free_trees();
711         free_member_list();
712
713         if (check_deps && !existing_config && !res) {
714                 fprintf(stderr, "\n***********************************************************\n");
715                 fprintf(stderr, "* menuselect.makeopts file generated with default values! *\n");
716                 fprintf(stderr, "* Please rerun make to build Asterisk.                    *\n");
717                 fprintf(stderr, "***********************************************************\n\n");
718                 res = -1;
719         }
720
721         /* In some cases, such as modifying the CFLAGS for the build,
722          * a "make clean" needs to be forced.  Removing the .lastclean 
723          * file does this. */
724         if (force_clean)
725                 unlink(".lastclean");
726
727         exit(res);
728 }