Mercurial > hg > index.cgi
comparison lwcc/driver-main.c @ 295:4b17780f2777 ccdev
Checkpoint lwcc development
Changed tactics with the preprocessor. Instead of getting clever and trying
to do things the "fast" way, instead, just tokenize the whole input and
process it that way. Also, set up so the preprocessor and compiler can be
integrated instead of having to have a specifically correct output for the
preprocessed file.
Also removed the subdirectories in the lwcc directory. It made things more
complicated than they needed to be.
author | William Astle <lost@l-w.ca> |
---|---|
date | Thu, 12 Sep 2013 22:06:26 -0600 |
parents | lwcc/driver/main.c@83f682ed4d65 |
children | a38542cf4cc6 |
comparison
equal
deleted
inserted
replaced
294:048adfee2933 | 295:4b17780f2777 |
---|---|
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 } |