comparison lwcc/driver/main.c @ 288:fc76f1a0dc49 ccdev

Initial version of lwcc compiler driver and Makefile infrastructure Implement an initial version of the lwcc compiler front end and arrange for the Makefile to understand how to compile it.
author William Astle <lost@l-w.ca>
date Sun, 08 Sep 2013 15:53:06 -0600
parents
children 83f682ed4d65
comparison
equal deleted inserted replaced
287:d2f04fa0103b 288:fc76f1a0dc49
1 /*
2 lwcc/driver/main.c
3
4 Copyright © 2013 William Astle
5
6 This file is part of LWTOOLS.
7
8 LWTOOLS is free software: you can redistribute it and/or modify it under the
9 terms of the GNU General Public License as published by the Free Software
10 Foundation, either version 3 of the License, or (at your option) any later
11 version.
12
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 more details.
17
18 You should have received a copy of the GNU General Public License along with
19 this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <errno.h>
23 #include <signal.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <unistd.h>
31
32 #include <lw_alloc.h>
33 #include <lw_string.h>
34 #include <lw_stringlist.h>
35
36 #define VERSTRING "lwcc from " PACKAGE_STRING
37
38 /* list of compilation phases */
39 enum phase_t {
40 PHASE_DEFAULT = 0,
41 PHASE_PREPROCESS,
42 PHASE_COMPILE,
43 PHASE_ASSEMBLE,
44 PHASE_LINK
45 };
46
47 /* these are the names of various programs the compiler calls */
48 const char *linker_program_name = "lwlink";
49 const char *compiler_program_name = "lwcc1";
50 const char *assembler_program_name = "lwasm";
51 const char *preprocessor_program_name = "lwcpp";
52
53 /* this will be set to the directory where temporary files get created */
54 const char *temp_directory = NULL;
55
56 /* these are for book keeping if we get interrupted - the volatile and atomic
57 types are needed because they are accessed in a signal handler */
58 static volatile sig_atomic_t sigterm_received = 0;
59 static volatile sig_atomic_t child_pid = 0;
60
61 /* path specified with --sysroot */
62 const char *sysroot = "";
63 /* path specified with -isysroot */
64 const char *isysroot = NULL;
65
66 /* record which phase to stop after for -c, -E, and -S */
67 /* default is to stop after PHASE_LINK */
68 static int stop_after = PHASE_DEFAULT;
69
70 int nostdinc = 0; // set if -nostdinc is specified
71 int nostartfiles = 0; // set if -nostartfiles is specified
72 int nostdlib = 0; // set if -nostdlib is specified
73 int verbose_mode = 0; // set to number of --verbose arguments
74 int save_temps = 0; // set if -save-temps is specified
75 int debug_mode = 0; // set if -g specified
76 int pic_mode = 0; // set to 1 if -fpic, 2 if -fPIC; last one specified wins
77 const char *output_file; // set to the value of the -o option (output file)
78
79 /* compiler base directory - from -B */
80 const char *basedir = NULL;
81
82 /* used to ensure a unique temporary file at every stage */
83 static int file_counter = 0;
84
85 /* these are various string lists used to keep track of things, mostly
86 command line arguments. */
87
88 lw_stringlist_t input_files; // input files from command line
89 lw_stringlist_t runtime_dirs; // directories to search for runtime files
90 lw_stringlist_t lib_dirs; // directories to search for library files
91 lw_stringlist_t program_dirs; // directories to search for compiler program components
92 lw_stringlist_t preproc_args; // recorded arguments to pass through to the preprocessor
93 lw_stringlist_t include_dirs; // include paths specified with -I
94 lw_stringlist_t includes; // include paths specified with -include
95 lw_stringlist_t user_sysincdirs; // include paths specified with -isystem
96 lw_stringlist_t asm_args; // recorded arguments to pass through to the assembler
97 lw_stringlist_t linker_args; // recorded arguments to pass through to the linker
98 lw_stringlist_t sysincdirs; // the standard system include directories
99 lw_stringlist_t tempfiles; // a list of temporary files created which need to be cleaned up
100 lw_stringlist_t compiler_args; // recorded arguments to pass through to the compiler
101
102 /* forward delcarations */
103 static void parse_command_line(int, char **);
104
105 /* signal handler for SIGTERM - all it does is record the fact that
106 SIGTERM happened and propagate the signal to whatever child process
107 might currently be running */
108 static void exit_on_signal(int sig)
109 {
110 sigterm_received = 1;
111 if (child_pid)
112 kill(child_pid, SIGTERM);
113 }
114
115 /* utility function to carp about an error condition and bail */
116 void do_error(const char *f, ...)
117 {
118 va_list arg;
119 va_start(arg, f);
120 fprintf(stderr, "ERROR: ");
121 vfprintf(stderr, f, arg);
122 putc('\n', stderr);
123 va_end(arg);
124 exit(1);
125 }
126
127 /* utility function to carp about some condition; do not bail */
128 void do_warning(const char *f, ...)
129 {
130 va_list arg;
131 va_start(arg, f);
132 fprintf(stderr, "WARNING: ");
133 vfprintf(stderr, f, arg);
134 putc('\n', stderr);
135 va_end(arg);
136 }
137
138 /* utility function to print out an array of strings - stops at the first
139 NULL string pointer. */
140 static void print_array(char **arr)
141 {
142 int c = 0;
143 while (*arr)
144 {
145 if (c)
146 printf(" ");
147 printf("%s", *arr);
148 arr++;
149 c = 1;
150 }
151 }
152
153 /* expand any search path entries to reflect the sysroot and
154 isysroot settings. Note that it does NOT apply to the compiler
155 program search path */
156 static void expand_sysroot(void)
157 {
158 /* list of path lists to process for replacements of = */
159 lw_stringlist_t *lists[] = { &runtime_dirs, &sysincdirs, &include_dirs, &user_sysincdirs, &lib_dirs, NULL };
160 /* list of replacement strings for = in the same order */
161 const char *sysroots[] = { sysroot, isysroot, isysroot, isysroot, sysroot, NULL };
162 size_t i, sysroot_len, value_len;
163 char *path;
164 lw_stringlist_t newlist;
165 lw_stringlist_t working;
166 char *s;
167
168 /* for each list, run through entry by entry, do any needed replacement
169 and add the entry to a new list. Then replace the old list with the
170 new one. */
171 for (i = 0; lists[i] != NULL; i++)
172 {
173 working = *lists[i];
174 newlist = lw_stringlist_create();
175
176 lw_stringlist_reset(working);
177 for (s = lw_stringlist_current(working); s; s = lw_stringlist_next(working))
178 {
179 if (s[0] == '=')
180 {
181 sysroot_len = strlen(sysroots[i]);
182 value_len = strlen(s);
183 /* note that the skipped = will make up for the trailing NUL */
184 path = lw_alloc(sysroot_len + value_len);
185 memcpy(path, sysroots[i], sysroot_len);
186 /* the +1 here will copy the trailing NUL */
187 memcpy(path + sysroot_len, s + 1, value_len);
188 lw_stringlist_addstring(newlist, path);
189 lw_free(path);
190 }
191 else
192 {
193 lw_stringlist_addstring(newlist, s);
194 }
195 }
196 lw_stringlist_destroy(working);
197 *lists[i] = newlist;
198 }
199 }
200
201 /* look for file fn in path list p which is okay for access mode mode.
202 Return a string allocated by lw_alloc. */
203 static char *find_file(const char *fn, lw_stringlist_t p, int mode)
204 {
205 char *s;
206 char *f;
207 size_t lf, lp;
208 int need_slash;
209
210 lf = strlen(fn);
211 lw_stringlist_reset(p);
212 for (s = lw_stringlist_current(p); s; s = lw_stringlist_next(p))
213 {
214 lp = strlen(s);
215 need_slash = 0;
216 if (lp && s[lp - 1] == '/')
217 need_slash = 1;
218 f = lw_alloc(lp + lf + need_slash + 1);
219 memcpy(f, s, lp);
220 if (need_slash)
221 f[lp] = '/';
222 /* +1 gets the NUL */
223 memcpy(f + lp + need_slash, fn, lf + 1);
224 if (access(f, mode) == 0)
225 return f;
226 lw_free(f);
227 }
228 /* if not found anywhere, try the bare filename - it might work */
229 return lw_strdup(fn);
230 }
231
232 /* take a string list which contains an argv and execute the specified
233 program */
234 static int execute_program(lw_stringlist_t args)
235 {
236 int argc;
237 char **argv;
238 int result;
239 char *s;
240
241 argc = lw_stringlist_nstrings(args);
242 argv = lw_alloc(sizeof(char *) * (argc + 1));
243 lw_stringlist_reset(args);
244 for (result = 0, s = lw_stringlist_current(args); s; s = lw_stringlist_next(args))
245 {
246 argv[result] = s;
247 }
248 argv[result] = NULL;
249
250 if (verbose_mode)
251 {
252 printf("Executing ");
253 print_array(argv);
254 printf("\n");
255 }
256
257 /* bail now if a signal happened */
258 if (sigterm_received)
259 {
260 lw_free(argv);
261 return 1;
262 }
263
264 /* make sure stdio has flushed everything so that output from the
265 child process doesn't get intermingled */
266 fflush(NULL);
267
268 /* now make the child process */
269 child_pid = fork();
270 if (child_pid == 0)
271 {
272 /* child process */
273 /* try executing program */
274 execvp(argv[0], argv);
275 /* only way to get here is if execvp() failed so carp about it and exit */
276 fprintf(stderr, "Exec of %s failed: %s", argv[0], strerror(errno));
277 /* exit with failure but don't call any atexit(), etc., functions */
278 _exit(127);
279 }
280 else if (child_pid == -1)
281 {
282 /* failure to make child process */
283 do_error("Failed to execute program %s: %s", argv[0], strerror(errno));
284 }
285 /* clean up argv */
286 lw_free(argv);
287
288 /* parent process - wait for child to exit */
289 while (waitpid(child_pid, &result, 0) == -1 && errno == EINTR)
290 /* do nothing */;
291 /* fetch actual return status */
292 result = WEXITSTATUS(result);
293 if (result)
294 {
295 /* carp about non-zero return status */
296 do_error("%s terminated with status %d", argv[0], result);
297 }
298 /* return nonzero if signalled to exit */
299 return sigterm_received;
300 }
301
302 /*
303 construct an output file name as follows:
304
305 1. if it is the last phase of compilation and an output file name is
306 specified, use that if not specified
307 2. if it is the last phase or we are saving temporary files, any suffix
308 on f is removed and replaced with nsuffix
309 3. otherwise, a temporary file is created. If necessary, a temporary
310 directory is created to hold the temporary file. The name of the temporary
311 file is recorded in the tempfiles string list for later cleanup. The name
312 of the temporary directory is recorded in temp_directory for later cleanup.
313 */
314 static char *output_name(const char *f, const char *nsuffix, int last)
315 {
316 const char *osuffix;
317 char *name;
318 size_t lf, ls, len;
319 int counter_len;
320
321 /* get a new file counter */
322 file_counter++;
323
324 /* if the output was specified, use it */
325 if (last && output_file)
326 {
327 return lw_strdup(output_file);
328 }
329
330 /* find the start of the old suffix */
331 osuffix = strrchr(f, '.');
332 if (osuffix != NULL && strchr(osuffix, '/') != NULL)
333 osuffix = NULL;
334 if (osuffix == NULL)
335 osuffix = f + strlen(f);
336
337 ls = strlen(nsuffix);
338
339 /* if this is the last stage or we're saving temps, use a name derived
340 from the original file name by replacing the suffix with nsuffix */
341 if (save_temps || last)
342 {
343 lf = osuffix - f;
344 name = lw_alloc(lf + ls + 1);
345 memcpy(name, f, lf);
346 /* note that the +1 will copy the trailing NUL */
347 memcpy(name + lf, nsuffix, ls + 1);
348 return name;
349 }
350
351 /* finally, use a temporary file */
352 if (temp_directory == NULL)
353 {
354 /* if we haven't already made a temporary directory, do so */
355 const char *dirtempl;
356 char *path;
357 size_t dirtempl_len;
358 int need_slash;
359
360 /* look for a TMPFIR environment variable and use that if present
361 but use /tmp as a fallback */
362 dirtempl = getenv("TMPDIR");
363 if (dirtempl == NULL)
364 dirtempl = "/tmp";
365 dirtempl_len = strlen(dirtempl);
366 /* work out if we need to add a slash on the end of the directory */
367 if (dirtempl_len && dirtempl[dirtempl_len - 1] == '/')
368 need_slash = 0;
369 else
370 need_slash = 1;
371 /* make a string of the form <tempdir>/lwcc-XXXXXX */
372 path = lw_alloc(dirtempl_len + need_slash + 11 + 1);
373 memcpy(path, dirtempl, dirtempl_len);
374 if (need_slash)
375 path[dirtempl_len] = '/';
376 memcpy(path + dirtempl_len + need_slash, "lwcc-XXXXXX", 12);
377 /* now make a temporary directory */
378 if (mkdtemp(path) == NULL)
379 do_error("mkdtemp failed: %s", strerror(errno));
380 /* record the temporary directory name */
381 temp_directory = path;
382 }
383 /* now create a file name in the temporary directory. The strategy here
384 uses a counter that is passed along and is guaranteed to be unique for
385 every file requested. */
386 lf = strlen(temp_directory);
387 /* this gets the length of the counter as a string but doesn't actually
388 allocate anything so we can make a string long enough */
389 counter_len = snprintf(NULL, 0, "%d", file_counter);
390 if (counter_len < 1)
391 do_error("snprintf failure: %s", strerror(errno));
392 len = lf + 1 + (size_t)counter_len + ls + 1;
393 name = lw_alloc(len);
394 /* it should be impossible for ths snprintf call to fail */
395 snprintf(name, len, "%s/%d%s", temp_directory, file_counter, nsuffix);
396
397 /* record the temporary file name for later */
398 lw_stringlist_addstring(tempfiles, name);
399 return name;
400 }
401
402 /* this calls the actual compiler, passing the contents of compiler_args
403 as arguments. It also adds the input file and output file. */
404 static int compile_file(const char *file, char *input, char **output, const char *suffix)
405 {
406 lw_stringlist_t args;
407 char *out;
408 int retval;
409 char *s;
410
411 args = lw_stringlist_create();
412
413 /* find the compiler executable and make that argv[0] */
414 s = find_file(compiler_program_name, program_dirs, X_OK);
415 lw_stringlist_addstring(args, s);
416 lw_free(s);
417
418 /* add all the saved compiler arguments to argv */
419 lw_stringlist_reset(compiler_args);
420 for (s = lw_stringlist_current(compiler_args); s; s = lw_stringlist_next(compiler_args))
421 {
422 lw_stringlist_addstring(args, s);
423 }
424 /* work out the output file name and add that to argv */
425 out = output_name(file, suffix, stop_after == PHASE_COMPILE);
426 lw_stringlist_addstring(args, "-o");
427 lw_stringlist_addstring(args, out);
428 /* add the input file to argv */
429 lw_stringlist_addstring(args, input);
430 /* if the input file name and the output file name pointers are the same
431 free the input one */
432 if (*output == input)
433 lw_free(input);
434 /* tell the caller what the output name is */
435 *output = out;
436 /* actually run the compiler */
437 retval = execute_program(args);
438
439 lw_stringlist_destroy(args);
440 return retval;
441 }
442
443 /* this calls the actual assembler, passing the contents of asm_args
444 as arguments. It also adds the input file and output file. */
445 static int assemble_file(const char *file, char *input, char **output, const char *suffix)
446 {
447 lw_stringlist_t args;
448 char *out;
449 int retval;
450 char *s;
451
452 args = lw_stringlist_create();
453
454 /* find the assembler binary and add that as argv[0] */
455 s = find_file(assembler_program_name, program_dirs, X_OK);
456 lw_stringlist_addstring(args, s);
457 lw_free(s);
458
459 /* add asm_args to argv */
460 lw_stringlist_reset(asm_args);
461 for (s = lw_stringlist_current(asm_args); s; s = lw_stringlist_next(asm_args))
462 {
463 lw_stringlist_addstring(args, s);
464 }
465 /* get an output file name and add that to argv */
466 out = output_name(file, ".o", stop_after == PHASE_ASSEMBLE);
467 lw_stringlist_addstring(args, "-o");
468 lw_stringlist_addstring(args, out);
469 /* finally, add the input file */
470 lw_stringlist_addstring(args, input);
471 /* clean up input file name if same as output pointer */
472 if (*output == input)
473 lw_free(input);
474 /* tell caller what file we made */
475 *output = out;
476 /* actually run the assembler */
477 retval = execute_program(args);
478
479 lw_stringlist_destroy(args);
480 return retval;
481 }
482
483 /* run the preprocessor. Pass along preproc_args and appropriate options
484 for all the include directories */
485 static int preprocess_file(const char *file, char *input, char **output, const char *suffix)
486 {
487 lw_stringlist_t args;
488 char *s;
489 char *out;
490 int retval;
491
492 args = lw_stringlist_create();
493
494 /* find the linker binary and make that argv[0] */
495 s = find_file(preprocessor_program_name, program_dirs, X_OK);
496 lw_stringlist_addstring(args, s);
497 lw_free(s);
498
499 /* add preproc_args to argv */
500 lw_stringlist_reset(preproc_args);
501 for (s = lw_stringlist_current(preproc_args); s; s = lw_stringlist_next(preproc_args))
502 {
503 lw_stringlist_addstring(args, s);
504 }
505
506 /* add the include files specified by -i */
507 lw_stringlist_reset(includes);
508 for (s = lw_stringlist_current(includes); s; s = lw_stringlist_next(includes))
509 {
510 lw_stringlist_addstring(args, "-i");
511 lw_stringlist_addstring(args, s);
512 }
513
514 /* add the include directories specified by -I */
515 lw_stringlist_reset(include_dirs);
516 for (s = lw_stringlist_current(include_dirs); s; s = lw_stringlist_next(include_dirs))
517 {
518 lw_stringlist_addstring(args, "-I");
519 lw_stringlist_addstring(args, s);
520 }
521
522 /* add the user specified system include directories (-isystem) */
523 lw_stringlist_reset(user_sysincdirs);
524 for (s = lw_stringlist_current(user_sysincdirs); s; s = lw_stringlist_next(user_sysincdirs))
525 {
526 lw_stringlist_addstring(args, "-S");
527 lw_stringlist_addstring(args, s);
528 }
529
530 /* and, if not -nostdinc, the standard system include directories */
531 if (!nostdinc)
532 {
533 lw_stringlist_reset(sysincdirs);
534 for (s = lw_stringlist_current(sysincdirs); s; s = lw_stringlist_next(sysincdirs))
535 {
536 lw_stringlist_addstring(args, "-S");
537 lw_stringlist_addstring(args, s);
538 }
539 }
540
541 /* if we stop after preprocessing, output to stdout if no output file */
542 if (stop_after == PHASE_PREPROCESS && output_file == NULL)
543 {
544 out = lw_strdup("-");
545 }
546 else
547 {
548 /* otherwise, make an output file */
549 out = output_name(file, suffix, stop_after == PHASE_PREPROCESS);
550 }
551 /* if not stdout, add the output file to argv */
552 if (strcmp(out, "-") != 0)
553 {
554 lw_stringlist_addstring(args, "-o");
555 lw_stringlist_addstring(args, out);
556 }
557 /* add the input file name to argv */
558 lw_stringlist_addstring(args, input);
559
560 /* if input and output pointers are same, clean up input */
561 if (*output == input)
562 lw_free(input);
563 /* tell caller what our output file is */
564 *output = out;
565 /* finally, actually run the preprocessor */
566 retval = execute_program(args);
567
568 lw_stringlist_destroy(args);
569 return retval;
570 }
571
572 /*
573 handle an input file through the various stages of compilation. If any
574 stage decides to handle an input file, that fact is recorded. If control
575 reaches the end of the function without a file being handled, that
576 fact is mentioned to the user. Unknown files are passed to the linker
577 if nothing handles them and linking is to be done. It's possible the linker
578 will actually know what to do with them.
579 */
580 static int handle_input_file(const char *f)
581 {
582 const char *suffix;
583 char *src;
584 int handled, retval;
585
586 /* note: this needs to handle -x but for now, assume c for stdin */
587 if (strcmp(f, "-") == 0)
588 {
589 suffix = ".c";
590 }
591 else
592 {
593 /* work out the suffix on the file */
594 suffix = strrchr(f, '.');
595 if (suffix != NULL && strchr(suffix, '/') != NULL)
596 suffix = NULL;
597 if (suffix == NULL)
598 suffix = "";
599 }
600
601 /* make a copy of the file */
602 src = lw_strdup(f);
603
604 /* preprocess if appropriate */
605 if (strcmp(suffix, ".c") == 0)
606 {
607 /* preprocessed c input source goes to .i */
608 suffix = ".i";
609 retval = preprocess_file(f, src, &src, suffix);
610 if (retval)
611 goto done;
612 handled = 1;
613 }
614 else if (strcmp(suffix, ".S") == 0)
615 {
616 /* preprocessed asm source goes to .s */
617 suffix = ".s";
618 retval = preprocess_file(f, src, &src, suffix);
619 if (retval)
620 goto done;
621 handled = 1;
622 }
623 /* if we're only preprocessing, bail */
624 if (stop_after == PHASE_PREPROCESS)
625 goto done;
626
627 /* now on to compile if appropriate */
628 if (strcmp(suffix, ".i") == 0)
629 {
630 /* preprocessed c source goes to .s after compiling */
631 suffix = ".s";
632 retval = compile_file(f, src, &src, suffix);
633 if (retval)
634 goto done;
635 handled = 1;
636 }
637 /* bail if we're only compiling, not assembling */
638 if (stop_after == PHASE_COMPILE)
639 goto done;
640
641 /* assemble if appropriate */
642 if (strcmp(suffix, ".s") == 0)
643 {
644 /* assembler output is an object file */
645 suffix = ".o";
646 retval = assemble_file(f, src, &src, suffix);
647 if (retval)
648 goto done;
649 handled = 1;
650 }
651 /* bail if we're not linking */
652 if (stop_after == PHASE_ASSEMBLE)
653 goto done;
654
655 /* if we get here with a .o unhandled, pretend it is handled */
656 if (strcmp(suffix, ".o") == 0)
657 handled = 1;
658
659 /* add the final file name to the linker args */
660 lw_stringlist_addstring(linker_args, src);
661 done:
662 if (!handled && !retval)
663 {
664 /* carp about unhandled files if there is no error */
665 if (stop_after == PHASE_LINK)
666 {
667 do_warning("unknown suffix %s; passing file down to linker", suffix);
668 }
669 else
670 {
671 do_warning("unknown suffix %s; skipped", suffix);
672 }
673 }
674 /* clean up the file name */
675 lw_free(src);
676
677 return retval;
678 }
679
680 /*
681 This actually runs the linker. Along the way, all the files the linker
682 is supposed to handle will have been added to linker_args.
683 */
684 static int handle_linking(void)
685 {
686 lw_stringlist_t linker_flags;
687 char *s;
688 int retval;
689
690 linker_flags = lw_stringlist_create();
691
692 /* find the linker binary and make that argv[0] */
693 s = find_file(linker_program_name, program_dirs, X_OK);
694 lw_stringlist_addstring(linker_flags, s);
695 lw_free(s);
696
697 /* tell the linker about the output file name, if specified */
698 if (output_file)
699 {
700 lw_stringlist_addstring(linker_flags, "-o");
701 lw_stringlist_addstring(linker_flags, (char *)output_file);
702 }
703
704 /* add the standard library options if not -nostdlib */
705 if (!nostdlib)
706 {
707 }
708
709 /* add the standard startup files if not -nostartfiles */
710 if (!nostartfiles)
711 {
712 }
713
714 /* pass along the various input files, etc., to the linker */
715 lw_stringlist_reset(linker_args);
716 for (s = lw_stringlist_current(linker_args); s; s = lw_stringlist_next(linker_args))
717 {
718 lw_stringlist_addstring(linker_flags, s);
719 }
720
721 /* actually run the linker */
722 retval = execute_program(linker_flags);
723
724 lw_stringlist_destroy(linker_flags);
725 return retval;
726 }
727
728 /*
729 Do various setup tasks, process the command line, handle the input files,
730 and clean up.
731 */
732 int main(int argc, char **argv)
733 {
734 char *ap;
735 int retval;
736
737 input_files = lw_stringlist_create();
738 runtime_dirs = lw_stringlist_create();
739 lib_dirs = lw_stringlist_create();
740 program_dirs = lw_stringlist_create();
741 preproc_args = lw_stringlist_create();
742 include_dirs = lw_stringlist_create();
743 user_sysincdirs = lw_stringlist_create();
744 asm_args = lw_stringlist_create();
745 linker_args = lw_stringlist_create();
746 sysincdirs = lw_stringlist_create();
747 includes = lw_stringlist_create();
748 tempfiles = lw_stringlist_create();
749 compiler_args = lw_stringlist_create();
750
751 parse_command_line(argc, argv);
752 if (stop_after == PHASE_DEFAULT)
753 stop_after = PHASE_LINK;
754
755 if (verbose_mode)
756 printf("%s\n", VERSTRING);
757
758 if (isysroot == NULL)
759 isysroot = sysroot;
760 expand_sysroot();
761
762 if (stop_after != PHASE_LINK && output_file && lw_stringlist_nstrings(input_files) > 1)
763 {
764 do_error("-o cannot be specified with multiple inputs unless linking");
765 }
766
767 // default to stdout for preprocessing
768 if (stop_after == PHASE_PREPROCESS && output_file == NULL)
769 output_file = "-";
770
771 if (lw_stringlist_nstrings(input_files) == 0)
772 do_error("No input files specified");
773
774 /* handle -B here */
775
776 retval = 0;
777 /* make sure we exit if interrupted */
778 signal(SIGTERM, exit_on_signal);
779
780 /* handle input files */
781 lw_stringlist_reset(input_files);
782 for (ap = lw_stringlist_current(input_files); ap; ap = lw_stringlist_next(input_files))
783 {
784 if (handle_input_file(ap))
785 retval = 1;
786 }
787
788 if (!retval && stop_after >= PHASE_LINK)
789 {
790 retval = handle_linking();
791 }
792
793 /* if a signal nixed us, mention the fact */
794 if (sigterm_received)
795 do_warning("Terminating on signal");
796
797 /* clean up temporary files */
798 if (!save_temps)
799 {
800 lw_stringlist_reset(tempfiles);
801 for (ap = lw_stringlist_current(tempfiles); ap; ap = lw_stringlist_next(tempfiles))
802 {
803 if (unlink(ap) == -1)
804 {
805 do_warning("Removal of %s failed: %s", ap, strerror(errno));
806 }
807 }
808 if (temp_directory)
809 {
810 if (rmdir(temp_directory) == -1)
811 {
812 do_warning("Removal of temporary directory %s failed: %s", temp_directory, strerror(errno));
813 }
814 }
815 }
816
817 /* be polite and clean up all the string lists */
818 lw_stringlist_destroy(input_files);
819 lw_stringlist_destroy(runtime_dirs);
820 lw_stringlist_destroy(lib_dirs);
821 lw_stringlist_destroy(program_dirs);
822 lw_stringlist_destroy(preproc_args);
823 lw_stringlist_destroy(include_dirs);
824 lw_stringlist_destroy(user_sysincdirs);
825 lw_stringlist_destroy(asm_args);
826 lw_stringlist_destroy(linker_args);
827 lw_stringlist_destroy(sysincdirs);
828 lw_stringlist_destroy(includes);
829 lw_stringlist_destroy(tempfiles);
830 lw_stringlist_destroy(compiler_args);
831
832 return retval;
833 }
834
835 struct option_e
836 {
837 char *optbase; // base name of option, with -
838 int needarg; // nonzero if option needs argument
839 int noextra; // nonzero if there must not be anything after optbase
840 int optcode; // option code (passed to fn)
841 void *optptr; // pointer for opt (passed to fn)
842 int (*fn)(char *, char *, int, void *); // function to handle argument, NULL to ignore it
843 };
844
845 enum CMD_MISC {
846 CMD_MISC_VERSION,
847 CMD_MISC_OPTIMIZE,
848 };
849
850 enum OPT_ARG {
851 OPT_ARG_OPT = 0, // argument is optional
852 OPT_ARG_SEP = 1, // argument may be separate
853 OPT_ARG_INC = 2, // argument must not be separate
854 };
855
856 /* set an integer at *optptr to optcode */
857 static int cmdline_set_int(char *opt, char *optarg, int optcode, void *optptr)
858 {
859 *((int *)optptr) = optcode;
860 return 0;
861 }
862
863 /* set a string at *optptr to optarg */
864 static int cmdline_set_string(char *opt, char *optarg, int optcode, void *optptr)
865 {
866 char **s = (char **)optptr;
867 *s = optarg;
868
869 return 0;
870 }
871
872 /* set a string at *optptr to optarg */
873 static int cmdline_set_stringifnull(char *opt, char *optarg, int optcode, void *optptr)
874 {
875 char **s = (char **)optptr;
876
877 if (*s)
878 do_error("Multiple %.*s options specified", optcode ? optcode : strlen(opt), opt);
879 *s = optarg;
880
881 return 0;
882 }
883
884 /* split arg on commas and add the results to string list *optptr */
885 static int cmdline_argsplit(char *opt, char *arg, int optcode, void *optptr)
886 {
887 lw_stringlist_t l = *(lw_stringlist_t *)optptr;
888 char *next;
889
890 for (; arg != NULL; arg = next)
891 {
892 next = strchr(arg, ',');
893 if (next != NULL)
894 *next++ = '\0';
895 lw_stringlist_addstring(l, arg);
896 }
897 return 0;
898 }
899
900 /* add opt to string list *optptr */
901 static int cmdline_arglist(char *opt, char *arg, int optcode, void *optptr)
902 {
903 lw_stringlist_t l = *(lw_stringlist_t *)optptr;
904
905 lw_stringlist_addstring(l, opt);
906 return 0;
907 }
908
909 /* add optarg to string list *optptr */
910 static int cmdline_optarglist(char *opt, char *optarg, int optcode, void *optptr)
911 {
912 lw_stringlist_t l = *(lw_stringlist_t *)optptr;
913
914 lw_stringlist_addstring(l, optarg);
915 return 0;
916 }
917
918 static int cmdline_misc(char *opt, char *optarg, int optcode, void *optptr)
919 {
920 switch (optcode)
921 {
922 case CMD_MISC_VERSION:
923 printf("%s\n", VERSTRING);
924 exit(0);
925
926 case CMD_MISC_OPTIMIZE:
927 if (!optarg)
928 return 0;
929 switch (*optarg)
930 {
931 case '0':
932 case '1':
933 case '2':
934 case '3':
935 case 's':
936 return 0;
937 }
938 return -1;
939
940 default:
941 return -1;
942 }
943 return 0;
944 }
945
946 static int cmdline_set_intifzero(char *opt, char *optarg, int optcode, void *optptr)
947 {
948 int *iv = (int *)optptr;
949
950 if (*iv && *iv != optcode)
951 {
952 do_error("conflicting compiler option specified: %s", opt);
953 }
954 *iv = optcode;
955 return 0;
956 }
957
958 struct option_e optionlist[] =
959 {
960 { "--version", OPT_ARG_OPT, 1, CMD_MISC_VERSION, NULL, cmdline_misc },
961 { "--sysroot=", OPT_ARG_INC, 0, 0, &sysroot, cmdline_set_string },
962 { "-B", OPT_ARG_INC, 0, 0, &basedir, cmdline_set_string },
963 { "-C", OPT_ARG_OPT, 1, 0, &preproc_args, cmdline_arglist },
964 { "-c", OPT_ARG_OPT, 1, PHASE_COMPILE, &stop_after, cmdline_set_intifzero },
965 { "-D", OPT_ARG_INC, 0, 0, &preproc_args, cmdline_arglist },
966 { "-E", OPT_ARG_OPT, 1, PHASE_PREPROCESS, &stop_after, cmdline_set_intifzero },
967 { "-fPIC", OPT_ARG_OPT, 1, 2, &pic_mode, cmdline_set_int },
968 { "-fpic", OPT_ARG_OPT, 1, 1, &pic_mode, cmdline_set_int },
969 { "-g", OPT_ARG_OPT, 1, 1, &debug_mode, cmdline_set_int },
970 { "-I", OPT_ARG_SEP, 0, 0, &include_dirs, cmdline_optarglist },
971 { "-include", OPT_ARG_SEP, 1, 0, &includes, cmdline_optarglist },
972 { "-isysroot", OPT_ARG_SEP, 1, 0, &isysroot, cmdline_set_string },
973 { "-isystem", OPT_ARG_SEP, 1, 0, &user_sysincdirs, cmdline_optarglist },
974 { "-M", OPT_ARG_OPT, 1, 0, &preproc_args, cmdline_arglist },
975 { "-nostartfiles", OPT_ARG_OPT, 1, 1, &nostartfiles, cmdline_set_int },
976 { "-nostdinc", OPT_ARG_OPT, 1, 1, &nostdinc, cmdline_set_int },
977 { "-nostdlib", OPT_ARG_OPT, 1, 1, &nostdlib, cmdline_set_int },
978 { "-O", OPT_ARG_OPT, 0, CMD_MISC_OPTIMIZE, NULL, cmdline_misc },
979 { "-o", OPT_ARG_SEP, 0, 2, &output_file, cmdline_set_stringifnull },
980 { "-S", OPT_ARG_OPT, 1, PHASE_ASSEMBLE, &stop_after, cmdline_set_intifzero },
981 { "-save-temps", OPT_ARG_OPT, 1, 1, &save_temps, cmdline_set_int },
982 { "-trigraphs", OPT_ARG_OPT, 1, 0, &preproc_args, cmdline_arglist },
983 { "-U", OPT_ARG_INC, 0, 0, &preproc_args, cmdline_arglist },
984 { "-v", OPT_ARG_OPT, 1, 1, &verbose_mode, cmdline_set_int },
985 { "-Wp,", OPT_ARG_INC, 0, 0, &preproc_args, cmdline_argsplit },
986 { "-Wa,", OPT_ARG_INC, 0, 0, &asm_args, cmdline_argsplit },
987 { "-Wl,", OPT_ARG_INC, 0, 0, &linker_args, cmdline_argsplit },
988 { "-W", OPT_ARG_INC, 0, 0, NULL, NULL }, /* warning options */
989 { "-x", OPT_ARG_SEP, 1, 0, NULL, NULL }, /* language options */
990 { NULL, 0, 0 }
991 };
992
993 static void parse_command_line(int argc, char **argv)
994 {
995 int i, j, olen, ilen;
996 char *optarg;
997
998 for (i = 1; i < argc; i++)
999 {
1000 if (argv[i][0] != '-' || argv[i][1] == '\0')
1001 {
1002 /* we have a non-option argument */
1003 lw_stringlist_addstring(input_files, argv[i]);
1004 continue;
1005 }
1006 olen = strlen(argv[i]);
1007 for (j = 0; optionlist[j].optbase; j++)
1008 {
1009 ilen = strlen(optionlist[j].optbase);
1010 /* if length of optbase is longer than argv[i], it can't match */
1011 if (ilen > olen)
1012 continue;
1013 /* does the base match? */
1014 if (strncmp(optionlist[j].optbase, argv[i], ilen) == 0)
1015 break;
1016 }
1017 if (optionlist[j].optbase == NULL)
1018 {
1019 do_error("Unsupported option %s", argv[i]);
1020 }
1021 /* is the option supposed to be exact? */
1022 if (optionlist[j].noextra && argv[i][ilen] != '\0')
1023 {
1024 do_error("Unsupported option %s", argv[i]);
1025 }
1026 /* is there an argument? */
1027 optarg = NULL;
1028 if (argv[i][ilen])
1029 optarg = argv[i] + ilen;
1030 if (!optarg && optionlist[j].needarg == 1)
1031 {
1032 if (i == argc)
1033 {
1034 do_error("Option %s requires an argument", argv[i]);
1035 }
1036 optarg = argv[++i];
1037 }
1038 if (!optarg && optionlist[j].needarg == 2)
1039 {
1040 do_error("Option %s requires an argument", argv[i]);
1041 }
1042 /* handle the option */
1043 if (optionlist[j].fn)
1044 {
1045 if ((*(optionlist[j].fn))(argv[i], optarg, optionlist[j].optcode, optionlist[j].optptr) != 0)
1046 do_error("Unsupported option %s %s", argv[i], optarg ? optarg : "");
1047 }
1048 }
1049 }