comparison lwcc/driver-main.c @ 495:5b8871fd7503

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