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

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

Make sure we don't get driveletters on non-PC OSes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.3 KB
Line 
1/* $Id: kDepPre.c 330 2005-10-29 20:38:04Z 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
212#if defined(__WIN32__)
213 {
214 char *psz;
215 char szFilename[_MAX_PATH + 1];
216 if (_fullpath(szFilename, pDep->szFilename, sizeof(szFilename)))
217 w32_fixcase(szFilename);
218 psz = szFilename;
219 while ((psz = strchr(psz, '\\')) != NULL)
220 *psz++ = '/';
221 fprintf(pOutput, " \\\n\t%s", szFilename);
222 }
223
224#elif !defined(__OS2__)
225 {
226 const char *psz = strchr(pDep->szFilename, ':');
227 if (psz)
228 fprintf(pOutput, " \\\n\t%s", psz + 1);
229 else
230 fprintf(pOutput, " \\\n\t%s", pDep->szFilename);
231 }
232
233#else /* __OS2__ */
234 fprintf(pOutput, " \\\n\t%s", pDep->szFilename);
235
236#endif /* __OS2__ */
237 }
238 fprintf(pOutput, "\n\n");
239}
240
241
242/* sdbm:
243 This algorithm was created for sdbm (a public-domain reimplementation of
244 ndbm) database library. it was found to do well in scrambling bits,
245 causing better distribution of the keys and fewer splits. it also happens
246 to be a good general hashing function with good distribution. the actual
247 function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below
248 is the faster version used in gawk. [there is even a faster, duff-device
249 version] the magic constant 65599 was picked out of thin air while
250 experimenting with different constants, and turns out to be a prime.
251 this is one of the algorithms used in berkeley db (see sleepycat) and
252 elsewhere. */
253static unsigned sdbm(const char *str)
254{
255 unsigned hash = 0;
256 int c;
257
258 while ((c = *(unsigned const char *)str++))
259 hash = c + (hash << 6) + (hash << 16) - hash;
260
261 return hash;
262}
263
264
265/**
266 * Adds a dependency.
267 *
268 * @returns Pointer to the allocated dependency.
269 * @param pszFilename The filename.
270 * @param cchFilename The length of the filename.
271 */
272static PDEP depAdd(const char *pszFilename, size_t cchFilename)
273{
274 unsigned uHash = sdbm(pszFilename);
275 PDEP pDep;
276 PDEP pDepPrev;
277
278 /*
279 * Check if we've already got this one.
280 */
281 pDepPrev = NULL;
282 for (pDep = g_pDeps; pDep; pDepPrev = pDep, pDep = pDep->pNext)
283 if ( pDep->uHash == uHash
284 && pDep->cchFilename == cchFilename
285 && !memcmp(pDep->szFilename, pszFilename, cchFilename))
286 return pDep;
287
288 /*
289 * Add it.
290 */
291 pDep = malloc(sizeof(*pDep) + cchFilename);
292 if (!pDep)
293 {
294 fprintf(stderr, "\nOut of memory! (requested %#x bytes)\n\n", sizeof(*pDep) + cchFilename);
295 exit(1);
296 }
297
298 pDep->cchFilename = cchFilename;
299 memcpy(pDep->szFilename, pszFilename, cchFilename + 1);
300 pDep->uHash = uHash;
301
302 if (pDepPrev)
303 {
304 pDep->pNext = pDepPrev->pNext;
305 pDepPrev->pNext = pDep;
306 }
307 else
308 {
309 pDep->pNext = g_pDeps;
310 g_pDeps = pDep;
311 }
312 return pDep;
313}
314
315
316/**
317 * Parses the output from a preprocessor of a C-style language.
318 *
319 * @returns 0 on success.
320 * @returns 1 or other approriate exit code on failure.
321 * @param pInput Input stream. (probably not seekable)
322 */
323static int ParseCPrecompiler(FILE *pInput)
324{
325 enum
326 {
327 C_DISCOVER = 0,
328 C_SKIP_LINE,
329 C_PARSE_FILENAME,
330 C_EOF
331 } enmMode = C_DISCOVER;
332 PDEP pDep = NULL;
333 int ch;
334 char szBuf[8192];
335
336 for (;;)
337 {
338 switch (enmMode)
339 {
340 /*
341 * Start of line, need to look for '#[[:space]]*line <num> "file"' and '# <num> "file"'.
342 */
343 case C_DISCOVER:
344 /* first find '#' */
345 while ((ch = FGETC(pInput)) != EOF)
346 if (!isblank(ch))
347 break;
348 if (ch == '#')
349 {
350 /* skip spaces */
351 while ((ch = FGETC(pInput)) != EOF)
352 if (!isblank(ch))
353 break;
354
355 /* check for "line" */
356 if (ch == 'l')
357 {
358 if ( (ch = FGETC(pInput)) == 'i'
359 && (ch = FGETC(pInput)) == 'n'
360 && (ch = FGETC(pInput)) == 'e')
361 {
362 ch = FGETC(pInput);
363 if (isblank(ch))
364 {
365 /* skip spaces */
366 while ((ch = FGETC(pInput)) != EOF)
367 if (!isblank(ch))
368 break;
369 }
370 else
371 ch = 'x';
372 }
373 else
374 ch = 'x';
375 }
376
377 /* line number */
378 if (ch >= '0' && ch <= '9')
379 {
380 /* skip the number following spaces */
381 while ((ch = FGETC(pInput)) != EOF)
382 if (!isxdigit(ch))
383 break;
384 if (isblank(ch))
385 {
386 while ((ch = FGETC(pInput)) != EOF)
387 if (!isblank(ch))
388 break;
389 /* quoted filename */
390 if (ch == '"')
391 {
392 enmMode = C_PARSE_FILENAME;
393 break;
394 }
395 }
396 }
397 }
398 enmMode = C_SKIP_LINE;
399 break;
400
401 /*
402 * Skip past the end of the current line.
403 */
404 case C_SKIP_LINE:
405 do
406 {
407 if ( ch == '\r'
408 || ch == '\n')
409 break;
410 } while ((ch = FGETC(pInput)) != EOF);
411 enmMode = C_DISCOVER;
412 break;
413
414 /*
415 * Parse the filename.
416 */
417 case C_PARSE_FILENAME:
418 {
419 /* retreive and unescape the filename. */
420 char *psz = &szBuf[0];
421 while ( (ch = FGETC(pInput)) != EOF
422 && psz < &szBuf[sizeof(szBuf) - 1])
423 {
424 if (ch == '\\')
425 {
426 ch = FGETC(pInput);
427 switch (ch)
428 {
429 case '\\': ch = '/'; break;
430 case 't': ch = '\t'; break;
431 case 'r': ch = '\r'; break;
432 case 'n': ch = '\n'; break;
433 case 'b': ch = '\b'; break;
434 default:
435 fprintf(stderr, "warning: unknown escape char '%c'\n", ch);
436 continue;
437
438 }
439 *psz++ = ch == '\\' ? '/' : ch;
440 }
441 else if (ch != '"')
442 *psz++ = ch;
443 else
444 {
445 size_t cchFilename = psz - &szBuf[0];
446 *psz = '\0';
447 /* compare with current dep, add & switch on mismatch. */
448 if ( !pDep
449 || pDep->cchFilename != cchFilename
450 || memcmp(pDep->szFilename, szBuf, cchFilename))
451 pDep = depAdd(szBuf, cchFilename);
452 break;
453 }
454 }
455 enmMode = C_SKIP_LINE;
456 break;
457 }
458
459 /*
460 * Handle EOF.
461 */
462 case C_EOF:
463 if (feof(pInput))
464 return 0;
465 enmMode = C_DISCOVER;
466 break;
467 }
468 if (ch == EOF)
469 enmMode = C_EOF;
470 }
471
472 return 0;
473}
474
475
476static void usage(const char *argv0)
477{
478 printf("syntax: %s [-l=c] -o <output> -t <target> < - | <filename> | -e <cmdline> >\n", argv0);
479}
480
481int main(int argc, char *argv[])
482{
483 int i;
484
485 /* Arguments. */
486 int iExec = 0;
487 FILE *pOutput = NULL;
488 const char *pszOutput = NULL;
489 FILE *pInput = NULL;
490 const char *pszTarget = NULL;
491 /* Argument parsing. */
492 int fInput = 0; /* set when we've found input argument. */
493
494 /*
495 * Parse arguments.
496 */
497 if (argc <= 1)
498 {
499 usage(argv[0]);
500 return 1;
501 }
502 for (i = 1; i < argc; i++)
503 {
504 if (argv[i][0] == '-')
505 {
506 switch (argv[i][1])
507 {
508 /*
509 * Output file.
510 */
511 case 'o':
512 {
513 pszOutput = &argv[i][2];
514 if (pOutput)
515 {
516 fprintf(stderr, "%s: syntax error: only one output file!\n", argv[0]);
517 return 1;
518 }
519 if (!*pszOutput)
520 {
521 if (++i >= argc)
522 {
523 fprintf(stderr, "%s: syntax error: The '-o' argument is missing the filename.\n", argv[0]);
524 return 1;
525 }
526 pszOutput = argv[i];
527 }
528 pOutput = fopen(pszOutput, "w");
529 if (!pOutput)
530 {
531 fprintf(stderr, "%s: error: Failed to create output file '%s'.\n", argv[0], pszOutput);
532 return 1;
533 }
534 break;
535 }
536
537 /*
538 * Language spec.
539 */
540 case 'l':
541 {
542 const char *psz = &argv[i][2];
543 if (*psz == '=')
544 psz++;
545 if (!strcmp(psz, "c"))
546 ;
547 else
548 {
549 fprintf(stderr, "%s: error: The '%s' language is not supported.\n", argv[0], psz);
550 return 1;
551 }
552 break;
553 }
554
555 /*
556 * Target name.
557 */
558 case 't':
559 {
560 if (pszTarget)
561 {
562 fprintf(stderr, "%s: syntax error: only one target!\n", argv[0]);
563 return 1;
564 }
565 pszTarget = &argv[i][2];
566 if (!*pszTarget)
567 {
568 if (++i >= argc)
569 {
570 fprintf(stderr, "%s: syntax error: The '-t' argument is missing the target name.\n", argv[0]);
571 return 1;
572 }
573 pszTarget = argv[i];
574 }
575 break;
576 }
577
578 /*
579 * Exec.
580 */
581 case 'e':
582 {
583 if (++i >= argc)
584 {
585 fprintf(stderr, "%s: syntax error: The '-e' argument is missing the command.\n", argv[0]);
586 return 1;
587 }
588 iExec = i;
589 i = argc - 1;
590 break;
591 }
592
593
594 /*
595 * Pipe input.
596 */
597 case '\0':
598 {
599 pInput = stdin;
600 fInput = 1;
601 break;
602 }
603
604 /*
605 * Invalid argument.
606 */
607 default:
608 fprintf(stderr, "%s: syntax error: Invalid argument '%s'.\n", argv[0], argv[i]);
609 usage(argv[0]);
610 return 1;
611 }
612 }
613 else
614 {
615 pInput = fopen(argv[i], "r");
616 if (!pInput)
617 {
618 fprintf(stderr, "%s: error: Failed to open input file '%s'.\n", argv[0], argv[i]);
619 return 1;
620 }
621 fInput = 1;
622 }
623
624 /*
625 * End of the line?
626 */
627 if (fInput)
628 {
629 if (++i < argc)
630 {
631 fprintf(stderr, "%s: syntax error: No arguments shall follow the input spec.\n", argv[0]);
632 return 1;
633 }
634 break;
635 }
636 }
637
638 /*
639 * Got all we require?
640 */
641 if (!pInput && iExec <= 0)
642 {
643 fprintf(stderr, "%s: syntax error: No input!\n", argv[0]);
644 return 1;
645 }
646 if (!pOutput)
647 {
648 fprintf(stderr, "%s: syntax error: No output!\n", argv[0]);
649 return 1;
650 }
651 if (!pszTarget)
652 {
653 fprintf(stderr, "%s: syntax error: No target!\n", argv[0]);
654 return 1;
655 }
656
657 /*
658 * Spawn process?
659 */
660 if (iExec > 0)
661 {
662 fprintf(stderr, "%s: -e is not yet implemented!\n", argv[0]);
663 return 1;
664 }
665
666 /*
667 * Do the parsing.
668 */
669 i = ParseCPrecompiler(pInput);
670
671 /*
672 * Reap child.
673 */
674 if (iExec > 0)
675 {
676 // later
677 }
678
679 /*
680 * Write the dependecy file.
681 */
682 if (!i)
683 {
684 fprintf(pOutput, "%s:", pszTarget);
685 depPrint(pOutput);
686 }
687
688 /*
689 * Close the output, delete output on failure.
690 */
691 if (!i && ferror(pOutput))
692 {
693 i = 1;
694 fprintf(stderr, "%s: error: Error writing to '%s'.\n", argv[0], pszOutput);
695 }
696 fclose(pOutput);
697 if (i)
698 {
699 if (unlink(pszOutput))
700 fprintf(stderr, "%s: warning: failed to remove output file '%s' on failure.\n", argv[0], pszOutput);
701 }
702
703 return i;
704}
Note: See TracBrowser for help on using the repository browser.