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