source: trunk/src/kDepPre/kDepPre.c@ 326

Last change on this file since 326 was 326, checked in by bird, 20 years ago

Corrected bug in line skipping where two lines would be skipped. Corrected slashes on win32.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.9 KB
Line 
1/* $Id: kDepPre.c 326 2005-10-15 00:43:02Z bird $ */
2/** @file
3 *
4 * kDepPre - Dependency Generator using Precompiler output.
5 *
6 * Copyright (c) 2005 knut st. osmundsen <bird@innotek.de>
7 *
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <ctype.h>
32#ifdef __WIN32__
33#include <windows.h>
34#endif
35
36#ifdef HAVE_FGETC_UNLOCKED
37# define FGETC(s) getc_unlocked(s)
38#else
39# define FGETC(s) fgetc(s)
40#endif
41
42#ifdef NEED_ISBLANK
43# define isblank(ch) ( (unsigned char)(ch) == ' ' || (unsigned char)(ch) == '\t' )
44#endif
45
46
47
48
49/*******************************************************************************
50* Structures and Typedefs *
51*******************************************************************************/
52/** A dependency. */
53typedef struct DEP
54{
55 /** Next dependency in the list. */
56 struct DEP *pNext;
57 /** The filename hash. */
58 unsigned uHash;
59 /** The length of the filename. */
60 size_t cchFilename;
61 /** The filename. */
62 char szFilename[4];
63} DEP, *PDEP;
64
65
66/*******************************************************************************
67* Global Variables *
68*******************************************************************************/
69/** List of dependencies. */
70static PDEP g_pDeps = NULL;
71
72
73#ifdef __WIN32__
74/**
75 * Corrects the case of a path.
76 * Expects a fullpath!
77 *
78 * @param pszPath Pointer to the path, both input and output.
79 * The buffer must be able to hold one more byte than the string length.
80 */
81void w32_fixcase(char *pszPath)
82{
83#define my_assert(expr) \
84 do { \
85 if (!(expr)) { \
86 printf("my_assert: %s, file %s, line %d\npszPath=%s\npsz=%s\n", \
87 #expr, __FILE__, __LINE__, pszPath, psz); \
88 __asm { __asm int 3 } \
89 exit(1); \
90 } \
91 } while (0)
92
93 char *psz = pszPath;
94 if (*psz == '/' || *psz == '\\')
95 {
96 if (psz[1] == '/' || psz[1] == '\\')
97 {
98 /* UNC */
99 my_assert(psz[1] == '/' || psz[1] == '\\');
100 my_assert(psz[2] != '/' && psz[2] != '\\');
101
102 /* skip server name */
103 psz += 2;
104 while (*psz != '\\' && *psz != '/')
105 {
106 if (!*psz)
107 return;
108 *psz++ = toupper(*psz);
109 }
110
111 /* skip the share name */
112 psz++;
113 my_assert(*psz != '/' && *psz != '\\');
114 while (*psz != '\\' && *psz != '/')
115 {
116 if (!*psz)
117 return;
118 *psz++ = toupper(*psz);
119 }
120 my_assert(*psz == '/' || *psz == '\\');
121 psz++;
122 }
123 else
124 {
125 /* Unix spec */
126 psz++;
127 }
128 }
129 else
130 {
131 /* Drive letter */
132 my_assert(psz[1] == ':');
133 *psz = toupper(*psz);
134 my_assert(psz[0] >= 'A' && psz[0] <= 'Z');
135 my_assert(psz[2] == '/' || psz[2] == '\\');
136 psz += 3;
137 }
138
139 /*
140 * Pointing to the first char after the unc or drive specifier.
141 */
142 while (*psz)
143 {
144 WIN32_FIND_DATA FindFileData;
145 HANDLE hDir;
146 char chSaved0;
147 char chSaved1;
148 char *pszEnd;
149
150
151 /* find the end of the component. */
152 pszEnd = psz;
153 while (*pszEnd && *pszEnd != '/' && *pszEnd != '\\')
154 pszEnd++;
155
156 /* replace the end with "?\0" */
157 chSaved0 = pszEnd[0];
158 chSaved1 = pszEnd[1];
159 pszEnd[0] = '?';
160 pszEnd[1] = '\0';
161
162 /* find the right filename. */
163 hDir = FindFirstFile(pszPath, &FindFileData);
164 pszEnd[1] = chSaved1;
165 if (!hDir)
166 {
167 pszEnd[0] = chSaved0;
168 return;
169 }
170 pszEnd[0] = '\0';
171 while (stricmp(FindFileData.cFileName, psz))
172 {
173 if (!FindNextFile(hDir, &FindFileData))
174 {
175 pszEnd[0] = chSaved0;
176 return;
177 }
178 }
179 strcpy(psz, FindFileData.cFileName);
180 pszEnd[0] = chSaved0;
181
182 /* advance to the next component */
183 if (!chSaved0)
184 return;
185 psz = pszEnd + 1;
186 my_assert(*psz != '/' && *psz != '\\');
187 }
188#undef my_assert
189}
190
191#endif
192
193
194/**
195 * Prints the dependency chain.
196 *
197 * @returns Pointer to the allocated dependency.
198 * @param pOutput Output stream.
199 */
200static void depPrint(FILE *pOutput)
201{
202 PDEP pDep = g_pDeps;
203 for (pDep = g_pDeps; pDep; pDep = pDep->pNext)
204 {
205 /*
206 * Skip some fictive names like <built-in> and <command line>.
207 */
208 if ( pDep->szFilename[0] == '<'
209 && pDep->szFilename[pDep->cchFilename - 1] == '>')
210 continue;
211#ifdef __WIN32__
212 {
213 char *psz;
214 char szFilename[_MAX_PATH + 1];
215 if (_fullpath(szFilename, pDep->szFilename, sizeof(szFilename)))
216 w32_fixcase(szFilename);
217 psz = szFilename;
218 while ((psz = strchr(psz, '\\')) != NULL)
219 *psz++ = '/';
220 fprintf(pOutput, " \\\n\t%s", szFilename);
221 }
222#else
223 fprintf(pOutput, " \\\n\t%s", pDep->szFilename);
224#endif
225 }
226 fprintf(pOutput, "\n\n");
227}
228
229
230/* sdbm:
231 This algorithm was created for sdbm (a public-domain reimplementation of
232 ndbm) database library. it was found to do well in scrambling bits,
233 causing better distribution of the keys and fewer splits. it also happens
234 to be a good general hashing function with good distribution. the actual
235 function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below
236 is the faster version used in gawk. [there is even a faster, duff-device
237 version] the magic constant 65599 was picked out of thin air while
238 experimenting with different constants, and turns out to be a prime.
239 this is one of the algorithms used in berkeley db (see sleepycat) and
240 elsewhere. */
241static unsigned sdbm(const char *str)
242{
243 unsigned hash = 0;
244 int c;
245
246 while ((c = *(unsigned const char *)str++))
247 hash = c + (hash << 6) + (hash << 16) - hash;
248
249 return hash;
250}
251
252
253/**
254 * Adds a dependency.
255 *
256 * @returns Pointer to the allocated dependency.
257 * @param pszFilename The filename.
258 * @param cchFilename The length of the filename.
259 */
260static PDEP depAdd(const char *pszFilename, size_t cchFilename)
261{
262 unsigned uHash = sdbm(pszFilename);
263 PDEP pDep;
264 PDEP pDepPrev;
265
266 /*
267 * Check if we've already got this one.
268 */
269 pDepPrev = NULL;
270 for (pDep = g_pDeps; pDep; pDepPrev = pDep, pDep = pDep->pNext)
271 if ( pDep->uHash == uHash
272 && pDep->cchFilename == cchFilename
273 && !memcmp(pDep->szFilename, pszFilename, cchFilename))
274 return pDep;
275
276 /*
277 * Add it.
278 */
279 pDep = malloc(sizeof(*pDep) + cchFilename);
280 if (!pDep)
281 {
282 fprintf(stderr, "\nOut of memory! (requested %#x bytes)\n\n", sizeof(*pDep) + cchFilename);
283 exit(1);
284 }
285
286 pDep->cchFilename = cchFilename;
287 memcpy(pDep->szFilename, pszFilename, cchFilename + 1);
288 pDep->uHash = uHash;
289
290 if (pDepPrev)
291 {
292 pDep->pNext = pDepPrev->pNext;
293 pDepPrev->pNext = pDep;
294 }
295 else
296 {
297 pDep->pNext = g_pDeps;
298 g_pDeps = pDep;
299 }
300 return pDep;
301}
302
303
304/**
305 * Parses the output from a preprocessor of a C-style language.
306 *
307 * @returns 0 on success.
308 * @returns 1 or other approriate exit code on failure.
309 * @param pInput Input stream. (probably not seekable)
310 */
311static int ParseCPrecompiler(FILE *pInput)
312{
313 enum
314 {
315 C_DISCOVER = 0,
316 C_SKIP_LINE,
317 C_PARSE_FILENAME,
318 C_EOF
319 } enmMode = C_DISCOVER;
320 PDEP pDep = NULL;
321 int ch;
322 char szBuf[8192];
323
324 for (;;)
325 {
326 switch (enmMode)
327 {
328 /*
329 * Start of line, need to look for '#[[:space]]*line <num> "file"' and '# <num> "file"'.
330 */
331 case C_DISCOVER:
332 /* first find '#' */
333 while ((ch = FGETC(pInput)) != EOF)
334 if (!isblank(ch))
335 break;
336 if (ch == '#')
337 {
338 /* skip spaces */
339 while ((ch = FGETC(pInput)) != EOF)
340 if (!isblank(ch))
341 break;
342
343 /* check for "line" */
344 if (ch == 'l')
345 {
346 if ( (ch = FGETC(pInput)) == 'i'
347 && (ch = FGETC(pInput)) == 'n'
348 && (ch = FGETC(pInput)) == 'e')
349 {
350 ch = FGETC(pInput);
351 if (isblank(ch))
352 {
353 /* skip spaces */
354 while ((ch = FGETC(pInput)) != EOF)
355 if (!isblank(ch))
356 break;
357 }
358 else
359 ch = 'x';
360 }
361 else
362 ch = 'x';
363 }
364
365 /* line number */
366 if (ch >= '0' && ch <= '9')
367 {
368 /* skip the number following spaces */
369 while ((ch = FGETC(pInput)) != EOF)
370 if (!isxdigit(ch))
371 break;
372 if (isblank(ch))
373 {
374 while ((ch = FGETC(pInput)) != EOF)
375 if (!isblank(ch))
376 break;
377 /* quoted filename */
378 if (ch == '"')
379 {
380 enmMode = C_PARSE_FILENAME;
381 break;
382 }
383 }
384 }
385 }
386 enmMode = C_SKIP_LINE;
387 break;
388
389 /*
390 * Skip past the end of the current line.
391 */
392 case C_SKIP_LINE:
393 do
394 {
395 if ( ch == '\r'
396 || ch == '\n')
397 break;
398 } while ((ch = FGETC(pInput)) != EOF);
399 enmMode = C_DISCOVER;
400 break;
401
402 /*
403 * Parse the filename.
404 */
405 case C_PARSE_FILENAME:
406 {
407 /* retreive and unescape the filename. */
408 char *psz = &szBuf[0];
409 while ( (ch = FGETC(pInput)) != EOF
410 && psz < &szBuf[sizeof(szBuf) - 1])
411 {
412 if (ch == '\\')
413 {
414 ch = FGETC(pInput);
415 switch (ch)
416 {
417 case '\\': ch = '/'; break;
418 case 't': ch = '\t'; break;
419 case 'r': ch = '\r'; break;
420 case 'n': ch = '\n'; break;
421 case 'b': ch = '\b'; break;
422 default:
423 fprintf(stderr, "warning: unknown escape char '%c'\n", ch);
424 continue;
425
426 }
427 *psz++ = ch == '\\' ? '/' : ch;
428 }
429 else if (ch != '"')
430 *psz++ = ch;
431 else
432 {
433 size_t cchFilename = psz - &szBuf[0];
434 *psz = '\0';
435 /* compare with current dep, add & switch on mismatch. */
436 if ( !pDep
437 || pDep->cchFilename != cchFilename
438 || memcmp(pDep->szFilename, szBuf, cchFilename))
439 pDep = depAdd(szBuf, cchFilename);
440 break;
441 }
442 }
443 enmMode = C_SKIP_LINE;
444 break;
445 }
446
447 /*
448 * Handle EOF.
449 */
450 case C_EOF:
451 if (feof(pInput))
452 return 0;
453 enmMode = C_DISCOVER;
454 break;
455 }
456 if (ch == EOF)
457 enmMode = C_EOF;
458 }
459
460 return 0;
461}
462
463
464static void usage(const char *argv0)
465{
466 printf("syntax: %s [-l=c] -o <output> -t <target> < - | <filename> | -e <cmdline> >\n", argv0);
467}
468
469int main(int argc, char *argv[])
470{
471 int i;
472
473 /* Arguments. */
474 int iExec = 0;
475 FILE *pOutput = NULL;
476 const char *pszOutput = NULL;
477 FILE *pInput = NULL;
478 const char *pszTarget = NULL;
479 /* Argument parsing. */
480 int fInput = 0; /* set when we've found input argument. */
481
482 /*
483 * Parse arguments.
484 */
485 if (argc <= 1)
486 {
487 usage(argv[0]);
488 return 1;
489 }
490 for (i = 1; i < argc; i++)
491 {
492 if (argv[i][0] == '-')
493 {
494 switch (argv[i][1])
495 {
496 /*
497 * Output file.
498 */
499 case 'o':
500 {
501 pszOutput = &argv[i][2];
502 if (pOutput)
503 {
504 fprintf(stderr, "%s: syntax error: only one output file!\n", argv[0]);
505 return 1;
506 }
507 if (!*pszOutput)
508 {
509 if (++i >= argc)
510 {
511 fprintf(stderr, "%s: syntax error: The '-o' argument is missing the filename.\n", argv[0]);
512 return 1;
513 }
514 pszOutput = argv[i];
515 }
516 pOutput = fopen(pszOutput, "w");
517 if (!pOutput)
518 {
519 fprintf(stderr, "%s: error: Failed to create output file '%s'.\n", argv[0], pszOutput);
520 return 1;
521 }
522 break;
523 }
524
525 /*
526 * Language spec.
527 */
528 case 'l':
529 {
530 const char *psz = &argv[i][2];
531 if (*psz == '=')
532 psz++;
533 if (!strcmp(psz, "c"))
534 ;
535 else
536 {
537 fprintf(stderr, "%s: error: The '%s' language is not supported.\n", argv[0], psz);
538 return 1;
539 }
540 break;
541 }
542
543 /*
544 * Target name.
545 */
546 case 't':
547 {
548 if (pszTarget)
549 {
550 fprintf(stderr, "%s: syntax error: only one target!\n", argv[0]);
551 return 1;
552 }
553 pszTarget = &argv[i][2];
554 if (!*pszTarget)
555 {
556 if (++i >= argc)
557 {
558 fprintf(stderr, "%s: syntax error: The '-t' argument is missing the target name.\n", argv[0]);
559 return 1;
560 }
561 pszTarget = argv[i];
562 }
563 break;
564 }
565
566 /*
567 * Exec.
568 */
569 case 'e':
570 {
571 if (++i >= argc)
572 {
573 fprintf(stderr, "%s: syntax error: The '-e' argument is missing the command.\n", argv[0]);
574 return 1;
575 }
576 iExec = i;
577 i = argc - 1;
578 break;
579 }
580
581
582 /*
583 * Pipe input.
584 */
585 case '\0':
586 {
587 pInput = stdin;
588 fInput = 1;
589 break;
590 }
591
592 /*
593 * Invalid argument.
594 */
595 default:
596 fprintf(stderr, "%s: syntax error: Invalid argument '%s'.\n", argv[0], argv[i]);
597 usage(argv[0]);
598 return 1;
599 }
600 }
601 else
602 {
603 pInput = fopen(argv[i], "r");
604 if (!pOutput)
605 {
606 fprintf(stderr, "%s: error: Failed to open optput file '%s'.\n", argv[0], argv[i]);
607 return 1;
608 }
609 fInput = 1;
610 }
611
612 /*
613 * End of the line?
614 */
615 if (fInput)
616 {
617 if (++i < argc)
618 {
619 fprintf(stderr, "%s: syntax error: No arguments shall follow the input spec.\n", argv[0]);
620 return 1;
621 }
622 break;
623 }
624 }
625
626 /*
627 * Got all we require?
628 */
629 if (!pInput && iExec <= 0)
630 {
631 fprintf(stderr, "%s: syntax error: No input!\n", argv[0]);
632 return 1;
633 }
634 if (!pOutput)
635 {
636 fprintf(stderr, "%s: syntax error: No output!\n", argv[0]);
637 return 1;
638 }
639 if (!pszTarget)
640 {
641 fprintf(stderr, "%s: syntax error: No target!\n", argv[0]);
642 return 1;
643 }
644
645 /*
646 * Spawn process?
647 */
648 if (iExec > 0)
649 {
650 fprintf(stderr, "%s: -e is not yet implemented!\n", argv[0]);
651 return 1;
652 }
653
654 /*
655 * Do the parsing.
656 */
657 i = ParseCPrecompiler(pInput);
658
659 /*
660 * Reap child.
661 */
662 if (iExec > 0)
663 {
664 // later
665 }
666
667 /*
668 * Write the dependecy file.
669 */
670 if (!i)
671 {
672 fprintf(pOutput, "%s:", pszTarget);
673 depPrint(pOutput);
674 }
675
676 /*
677 * Close the output, delete output on failure.
678 */
679 if (!i && ferror(pOutput))
680 {
681 i = 1;
682 fprintf(stderr, "%s: error: Error writing to '%s'.\n", argv[0], pszOutput);
683 }
684 fclose(pOutput);
685 if (i)
686 {
687 if (unlink(pszOutput))
688 fprintf(stderr, "%s: warning: failed to remove output file '%s' on failure.\n", argv[0], pszOutput);
689 }
690
691 return i;
692}
Note: See TracBrowser for help on using the repository browser.