Mercurial > hg > index.cgi
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 } |