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

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

Added new option -s for generating stub dependencies just like -Wp,-MP.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 24.1 KB
Line 
1/* $Id: kDepPre.c 338 2005-11-07 19:16:08Z 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 <errno.h>
32#include <ctype.h>
33#include <sys/stat.h>
34#ifdef __WIN32__
35#include <windows.h>
36#endif
37#if !defined(__WIN32__) && !defined(__OS2__)
38# include <dirent.h>
39#endif
40
41#ifdef HAVE_FGETC_UNLOCKED
42# define FGETC(s) getc_unlocked(s)
43#else
44# define FGETC(s) fgetc(s)
45#endif
46
47#ifdef NEED_ISBLANK
48# define isblank(ch) ( (unsigned char)(ch) == ' ' || (unsigned char)(ch) == '\t' )
49#endif
50
51
52
53
54/*******************************************************************************
55* Structures and Typedefs *
56*******************************************************************************/
57/** A dependency. */
58typedef struct DEP
59{
60 /** Next dependency in the list. */
61 struct DEP *pNext;
62 /** The filename hash. */
63 unsigned uHash;
64 /** The length of the filename. */
65 size_t cchFilename;
66 /** The filename. */
67 char szFilename[4];
68} DEP, *PDEP;
69
70
71/*******************************************************************************
72* Global Variables *
73*******************************************************************************/
74/** List of dependencies. */
75static PDEP g_pDeps = NULL;
76
77
78/*******************************************************************************
79* Internal Functions *
80*******************************************************************************/
81static PDEP depAdd(const char *pszFilename, size_t cchFilename);
82static void depOptimize(int fFixCase);
83static void depPrint(FILE *pOutput);
84static void depPrintStubs(FILE *pOutput);
85
86
87#ifdef __WIN32__
88/**
89 * Corrects the case of a path.
90 * Expects a fullpath!
91 *
92 * @param pszPath Pointer to the path, both input and output.
93 * The buffer must be able to hold one more byte than the string length.
94 */
95void fixcase(char *pszPath)
96{
97#define my_assert(expr) \
98 do { \
99 if (!(expr)) { \
100 printf("my_assert: %s, file %s, line %d\npszPath=%s\npsz=%s\n", \
101 #expr, __FILE__, __LINE__, pszPath, psz); \
102 __asm { __asm int 3 } \
103 exit(1); \
104 } \
105 } while (0)
106
107 char *psz = pszPath;
108 if (*psz == '/' || *psz == '\\')
109 {
110 if (psz[1] == '/' || psz[1] == '\\')
111 {
112 /* UNC */
113 my_assert(psz[1] == '/' || psz[1] == '\\');
114 my_assert(psz[2] != '/' && psz[2] != '\\');
115
116 /* skip server name */
117 psz += 2;
118 while (*psz != '\\' && *psz != '/')
119 {
120 if (!*psz)
121 return;
122 *psz++ = toupper(*psz);
123 }
124
125 /* skip the share name */
126 psz++;
127 my_assert(*psz != '/' && *psz != '\\');
128 while (*psz != '\\' && *psz != '/')
129 {
130 if (!*psz)
131 return;
132 *psz++ = toupper(*psz);
133 }
134 my_assert(*psz == '/' || *psz == '\\');
135 psz++;
136 }
137 else
138 {
139 /* Unix spec */
140 psz++;
141 }
142 }
143 else
144 {
145 /* Drive letter */
146 my_assert(psz[1] == ':');
147 *psz = toupper(*psz);
148 my_assert(psz[0] >= 'A' && psz[0] <= 'Z');
149 my_assert(psz[2] == '/' || psz[2] == '\\');
150 psz += 3;
151 }
152
153 /*
154 * Pointing to the first char after the unc or drive specifier.
155 */
156 while (*psz)
157 {
158 WIN32_FIND_DATA FindFileData;
159 HANDLE hDir;
160 char chSaved0;
161 char chSaved1;
162 char *pszEnd;
163
164
165 /* find the end of the component. */
166 pszEnd = psz;
167 while (*pszEnd && *pszEnd != '/' && *pszEnd != '\\')
168 pszEnd++;
169
170 /* replace the end with "?\0" */
171 chSaved0 = pszEnd[0];
172 chSaved1 = pszEnd[1];
173 pszEnd[0] = '?';
174 pszEnd[1] = '\0';
175
176 /* find the right filename. */
177 hDir = FindFirstFile(pszPath, &FindFileData);
178 pszEnd[1] = chSaved1;
179 if (!hDir)
180 {
181 pszEnd[0] = chSaved0;
182 return;
183 }
184 pszEnd[0] = '\0';
185 while (stricmp(FindFileData.cFileName, psz))
186 {
187 if (!FindNextFile(hDir, &FindFileData))
188 {
189 pszEnd[0] = chSaved0;
190 return;
191 }
192 }
193 strcpy(psz, FindFileData.cFileName);
194 pszEnd[0] = chSaved0;
195
196 /* advance to the next component */
197 if (!chSaved0)
198 return;
199 psz = pszEnd + 1;
200 my_assert(*psz != '/' && *psz != '\\');
201 }
202#undef my_assert
203}
204
205/**
206 * Corrects all slashes to unix slashes.
207 *
208 * @returns pszFilename.
209 * @param pszFilename The filename to correct.
210 */
211char *fixslash(char *pszFilename)
212{
213 char *psz = pszFilename;
214 while ((psz = strchr(psz, '\\')) != NULL)
215 *psz++ = '/';
216 return pszFilename;
217}
218
219#elif defined(__OS2__)
220
221/**
222 * Corrects the case of a path.
223 *
224 * @param pszPath Pointer to the path, both input and output.
225 * The buffer must be able to hold one more byte than the string length.
226 */
227void fixcase(char *pszFilename)
228{
229 return;
230}
231
232#else
233
234/**
235 * Corrects the case of a path.
236 *
237 * @param pszPath Pointer to the path, both input and output.
238 */
239void fixcase(char *pszFilename)
240{
241 char *psz;
242
243 /*
244 * Skip the root.
245 */
246 psz = pszFilename;
247 while (*psz == '/')
248 psz++;
249
250 /*
251 * Iterate all the components.
252 */
253 while (*psz)
254 {
255 char chSlash;
256 struct stat s;
257 char *pszStart = psz;
258
259 /*
260 * Find the next slash (or end of string) and terminate the string there.
261 */
262 while (*psz != '/' && *psz)
263 *psz++;
264 chSlash = *psz;
265 *psz = '\0';
266
267 /*
268 * Does this part exist?
269 * If not we'll enumerate the directory and search for an case-insensitive match.
270 */
271 if (stat(pszFilename, &s))
272 {
273 struct dirent *pEntry;
274 DIR *pDir;
275 if (pszStart == pszFilename)
276 pDir = opendir(*pszFilename ? pszFilename : ".");
277 else
278 {
279 pszStart[-1] = '\0';
280 pDir = opendir(pszFilename);
281 pszStart[-1] = '/';
282 }
283 if (!pDir)
284 {
285 *psz = chSlash;
286 break; /* giving up, if we fail to open the directory. */
287 }
288
289 while ((pEntry = readdir(pDir)) != NULL)
290 {
291 if (!strcasecmp(pEntry->d_name, pszStart))
292 {
293 strcpy(pszStart, pEntry->d_name);
294 break;
295 }
296 }
297 closedir(pDir);
298 if (!pEntry)
299 {
300 *psz = chSlash;
301 break; /* giving up if not found. */
302 }
303 }
304
305 /* restore the slash and press on. */
306 *psz = chSlash;
307 while (*psz == '/')
308 psz++;
309 }
310
311 return;
312}
313
314
315#endif
316
317
318/**
319 * 'Optimizes' and corrects the dependencies.
320 */
321static void depOptimize(int fFixCase)
322{
323 /*
324 * Walk the list correct the names and re-insert them.
325 */
326 PDEP pDepOrg = g_pDeps;
327 PDEP pDep = g_pDeps;
328 g_pDeps = NULL;
329 for (; pDep; pDep = pDep->pNext)
330 {
331#ifdef __WIN32__
332 char szFilename[_MAX_PATH + 1];
333#else
334 char szFilename[PATH_MAX + 1];
335#endif
336 char *pszFilename;
337 struct stat s;
338
339 /*
340 * Skip some fictive names like <built-in> and <command line>.
341 */
342 if ( pDep->szFilename[0] == '<'
343 && pDep->szFilename[pDep->cchFilename - 1] == '>')
344 continue;
345 pszFilename = pDep->szFilename;
346
347#if !defined(__OS2__) && !defined(__WIN32__)
348 /*
349 * Skip any drive letters from compilers running in wine.
350 */
351 if (pszFilename[1] == ':')
352 pszFilename += 2;
353#endif
354
355 /*
356 * The microsoft compilers are notoriously screwing up the casing.
357 * This will screw up kmk (/ GNU Make).
358 */
359 if (fFixCase)
360 {
361#ifdef __WIN32__
362 if (_fullpath(szFilename, pszFilename, sizeof(szFilename)))
363 fixslash(szFilename);
364 else
365#endif
366 strcpy(szFilename, pszFilename);
367 fixcase(szFilename);
368 pszFilename = szFilename;
369 }
370
371 /*
372 * Check that the file exists before we start depending on it.
373 */
374 if (stat(pszFilename, &s))
375 {
376 fprintf(stderr, "kDepPre: Skipping '%s' - %s!\n", szFilename, strerror(errno));
377 continue;
378 }
379
380 /*
381 * Insert the corrected dependency.
382 */
383 depAdd(pszFilename, strlen(pszFilename));
384 }
385
386#if 0 /* waste of time */
387 /*
388 * Free the old ones.
389 */
390 while (pDepOrg)
391 {
392 pDep = pDepOrg;
393 pDepOrg = pDepOrg->pNext;
394 free(pDep);
395 }
396#endif
397}
398
399
400/**
401 * Prints the dependency chain.
402 *
403 * @returns Pointer to the allocated dependency.
404 * @param pOutput Output stream.
405 */
406static void depPrint(FILE *pOutput)
407{
408 PDEP pDep;
409 for (pDep = g_pDeps; pDep; pDep = pDep->pNext)
410 fprintf(pOutput, " \\\n\t%s", pDep->szFilename);
411 fprintf(pOutput, "\n\n");
412}
413
414
415/**
416 * Prints empty dependency stubs for all dependencies.
417 */
418static void depPrintStubs(FILE *pOutput)
419{
420 PDEP pDep;
421 for (pDep = g_pDeps; pDep; pDep = pDep->pNext)
422 fprintf(pOutput, "%s:\n\n", pDep->szFilename);
423}
424
425
426/* sdbm:
427 This algorithm was created for sdbm (a public-domain reimplementation of
428 ndbm) database library. it was found to do well in scrambling bits,
429 causing better distribution of the keys and fewer splits. it also happens
430 to be a good general hashing function with good distribution. the actual
431 function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below
432 is the faster version used in gawk. [there is even a faster, duff-device
433 version] the magic constant 65599 was picked out of thin air while
434 experimenting with different constants, and turns out to be a prime.
435 this is one of the algorithms used in berkeley db (see sleepycat) and
436 elsewhere. */
437static unsigned sdbm(const char *str)
438{
439 unsigned hash = 0;
440 int c;
441
442 while ((c = *(unsigned const char *)str++))
443 hash = c + (hash << 6) + (hash << 16) - hash;
444
445 return hash;
446}
447
448
449/**
450 * Adds a dependency.
451 *
452 * @returns Pointer to the allocated dependency.
453 * @param pszFilename The filename.
454 * @param cchFilename The length of the filename.
455 */
456static PDEP depAdd(const char *pszFilename, size_t cchFilename)
457{
458 unsigned uHash = sdbm(pszFilename);
459 PDEP pDep;
460 PDEP pDepPrev;
461
462 /*
463 * Check if we've already got this one.
464 */
465 pDepPrev = NULL;
466 for (pDep = g_pDeps; pDep; pDepPrev = pDep, pDep = pDep->pNext)
467 if ( pDep->uHash == uHash
468 && pDep->cchFilename == cchFilename
469 && !memcmp(pDep->szFilename, pszFilename, cchFilename))
470 return pDep;
471
472 /*
473 * Add it.
474 */
475 pDep = malloc(sizeof(*pDep) + cchFilename);
476 if (!pDep)
477 {
478 fprintf(stderr, "\nOut of memory! (requested %#x bytes)\n\n", sizeof(*pDep) + cchFilename);
479 exit(1);
480 }
481
482 pDep->cchFilename = cchFilename;
483 memcpy(pDep->szFilename, pszFilename, cchFilename + 1);
484 pDep->uHash = uHash;
485
486 if (pDepPrev)
487 {
488 pDep->pNext = pDepPrev->pNext;
489 pDepPrev->pNext = pDep;
490 }
491 else
492 {
493 pDep->pNext = g_pDeps;
494 g_pDeps = pDep;
495 }
496 return pDep;
497}
498
499
500/**
501 * Parses the output from a preprocessor of a C-style language.
502 *
503 * @returns 0 on success.
504 * @returns 1 or other approriate exit code on failure.
505 * @param pInput Input stream. (probably not seekable)
506 */
507static int ParseCPrecompiler(FILE *pInput)
508{
509 enum
510 {
511 C_DISCOVER = 0,
512 C_SKIP_LINE,
513 C_PARSE_FILENAME,
514 C_EOF
515 } enmMode = C_DISCOVER;
516 PDEP pDep = NULL;
517 int ch;
518 char szBuf[8192];
519
520 for (;;)
521 {
522 switch (enmMode)
523 {
524 /*
525 * Start of line, need to look for '#[[:space]]*line <num> "file"' and '# <num> "file"'.
526 */
527 case C_DISCOVER:
528 /* first find '#' */
529 while ((ch = FGETC(pInput)) != EOF)
530 if (!isblank(ch))
531 break;
532 if (ch == '#')
533 {
534 /* skip spaces */
535 while ((ch = FGETC(pInput)) != EOF)
536 if (!isblank(ch))
537 break;
538
539 /* check for "line" */
540 if (ch == 'l')
541 {
542 if ( (ch = FGETC(pInput)) == 'i'
543 && (ch = FGETC(pInput)) == 'n'
544 && (ch = FGETC(pInput)) == 'e')
545 {
546 ch = FGETC(pInput);
547 if (isblank(ch))
548 {
549 /* skip spaces */
550 while ((ch = FGETC(pInput)) != EOF)
551 if (!isblank(ch))
552 break;
553 }
554 else
555 ch = 'x';
556 }
557 else
558 ch = 'x';
559 }
560
561 /* line number */
562 if (ch >= '0' && ch <= '9')
563 {
564 /* skip the number following spaces */
565 while ((ch = FGETC(pInput)) != EOF)
566 if (!isxdigit(ch))
567 break;
568 if (isblank(ch))
569 {
570 while ((ch = FGETC(pInput)) != EOF)
571 if (!isblank(ch))
572 break;
573 /* quoted filename */
574 if (ch == '"')
575 {
576 enmMode = C_PARSE_FILENAME;
577 break;
578 }
579 }
580 }
581 }
582 enmMode = C_SKIP_LINE;
583 break;
584
585 /*
586 * Skip past the end of the current line.
587 */
588 case C_SKIP_LINE:
589 do
590 {
591 if ( ch == '\r'
592 || ch == '\n')
593 break;
594 } while ((ch = FGETC(pInput)) != EOF);
595 enmMode = C_DISCOVER;
596 break;
597
598 /*
599 * Parse the filename.
600 */
601 case C_PARSE_FILENAME:
602 {
603 /* retreive and unescape the filename. */
604 char *psz = &szBuf[0];
605 while ( (ch = FGETC(pInput)) != EOF
606 && psz < &szBuf[sizeof(szBuf) - 1])
607 {
608 if (ch == '\\')
609 {
610 ch = FGETC(pInput);
611 switch (ch)
612 {
613 case '\\': ch = '/'; break;
614 case 't': ch = '\t'; break;
615 case 'r': ch = '\r'; break;
616 case 'n': ch = '\n'; break;
617 case 'b': ch = '\b'; break;
618 default:
619 fprintf(stderr, "warning: unknown escape char '%c'\n", ch);
620 continue;
621
622 }
623 *psz++ = ch == '\\' ? '/' : ch;
624 }
625 else if (ch != '"')
626 *psz++ = ch;
627 else
628 {
629 size_t cchFilename = psz - &szBuf[0];
630 *psz = '\0';
631 /* compare with current dep, add & switch on mismatch. */
632 if ( !pDep
633 || pDep->cchFilename != cchFilename
634 || memcmp(pDep->szFilename, szBuf, cchFilename))
635 pDep = depAdd(szBuf, cchFilename);
636 break;
637 }
638 }
639 enmMode = C_SKIP_LINE;
640 break;
641 }
642
643 /*
644 * Handle EOF.
645 */
646 case C_EOF:
647 if (feof(pInput))
648 return 0;
649 enmMode = C_DISCOVER;
650 break;
651 }
652 if (ch == EOF)
653 enmMode = C_EOF;
654 }
655
656 return 0;
657}
658
659
660static void usage(const char *argv0)
661{
662 printf("syntax: %s [-l=c] -o <output> -t <target> [-f] [-s] < - | <filename> | -e <cmdline> >\n", argv0);
663}
664
665int main(int argc, char *argv[])
666{
667 int i;
668
669 /* Arguments. */
670 int iExec = 0;
671 FILE *pOutput = NULL;
672 const char *pszOutput = NULL;
673 FILE *pInput = NULL;
674 const char *pszTarget = NULL;
675 int fStubs = 0;
676 int fFixCase = 0;
677 /* Argument parsing. */
678 int fInput = 0; /* set when we've found input argument. */
679
680 /*
681 * Parse arguments.
682 */
683 if (argc <= 1)
684 {
685 usage(argv[0]);
686 return 1;
687 }
688 for (i = 1; i < argc; i++)
689 {
690 if (argv[i][0] == '-')
691 {
692 switch (argv[i][1])
693 {
694 /*
695 * Output file.
696 */
697 case 'o':
698 {
699 pszOutput = &argv[i][2];
700 if (pOutput)
701 {
702 fprintf(stderr, "%s: syntax error: only one output file!\n", argv[0]);
703 return 1;
704 }
705 if (!*pszOutput)
706 {
707 if (++i >= argc)
708 {
709 fprintf(stderr, "%s: syntax error: The '-o' argument is missing the filename.\n", argv[0]);
710 return 1;
711 }
712 pszOutput = argv[i];
713 }
714 if (pszOutput[0] == '-' && !pszOutput[1])
715 pOutput = stdout;
716 else
717 pOutput = fopen(pszOutput, "w");
718 if (!pOutput)
719 {
720 fprintf(stderr, "%s: error: Failed to create output file '%s'.\n", argv[0], pszOutput);
721 return 1;
722 }
723 break;
724 }
725
726 /*
727 * Language spec.
728 */
729 case 'l':
730 {
731 const char *psz = &argv[i][2];
732 if (*psz == '=')
733 psz++;
734 if (!strcmp(psz, "c"))
735 ;
736 else
737 {
738 fprintf(stderr, "%s: error: The '%s' language is not supported.\n", argv[0], psz);
739 return 1;
740 }
741 break;
742 }
743
744 /*
745 * Target name.
746 */
747 case 't':
748 {
749 if (pszTarget)
750 {
751 fprintf(stderr, "%s: syntax error: only one target!\n", argv[0]);
752 return 1;
753 }
754 pszTarget = &argv[i][2];
755 if (!*pszTarget)
756 {
757 if (++i >= argc)
758 {
759 fprintf(stderr, "%s: syntax error: The '-t' argument is missing the target name.\n", argv[0]);
760 return 1;
761 }
762 pszTarget = argv[i];
763 }
764 break;
765 }
766
767 /*
768 * Exec.
769 */
770 case 'e':
771 {
772 if (++i >= argc)
773 {
774 fprintf(stderr, "%s: syntax error: The '-e' argument is missing the command.\n", argv[0]);
775 return 1;
776 }
777 iExec = i;
778 i = argc - 1;
779 break;
780 }
781
782 /*
783 * Pipe input.
784 */
785 case '\0':
786 {
787 pInput = stdin;
788 fInput = 1;
789 break;
790 }
791
792 /*
793 * Fix case.
794 */
795 case 'f':
796 {
797 fFixCase = 1;
798 break;
799 }
800
801 /*
802 * Generate stubs.
803 */
804 case 's':
805 {
806 fStubs = 1;
807 break;
808 }
809
810 /*
811 * Invalid argument.
812 */
813 default:
814 fprintf(stderr, "%s: syntax error: Invalid argument '%s'.\n", argv[0], argv[i]);
815 usage(argv[0]);
816 return 1;
817 }
818 }
819 else
820 {
821 pInput = fopen(argv[i], "r");
822 if (!pInput)
823 {
824 fprintf(stderr, "%s: error: Failed to open input file '%s'.\n", argv[0], argv[i]);
825 return 1;
826 }
827 fInput = 1;
828 }
829
830 /*
831 * End of the line?
832 */
833 if (fInput)
834 {
835 if (++i < argc)
836 {
837 fprintf(stderr, "%s: syntax error: No arguments shall follow the input spec.\n", argv[0]);
838 return 1;
839 }
840 break;
841 }
842 }
843
844 /*
845 * Got all we require?
846 */
847 if (!pInput && iExec <= 0)
848 {
849 fprintf(stderr, "%s: syntax error: No input!\n", argv[0]);
850 return 1;
851 }
852 if (!pOutput)
853 {
854 fprintf(stderr, "%s: syntax error: No output!\n", argv[0]);
855 return 1;
856 }
857 if (!pszTarget)
858 {
859 fprintf(stderr, "%s: syntax error: No target!\n", argv[0]);
860 return 1;
861 }
862
863 /*
864 * Spawn process?
865 */
866 if (iExec > 0)
867 {
868 fprintf(stderr, "%s: -e is not yet implemented!\n", argv[0]);
869 return 1;
870 }
871
872 /*
873 * Do the parsing.
874 */
875 i = ParseCPrecompiler(pInput);
876
877 /*
878 * Reap child.
879 */
880 if (iExec > 0)
881 {
882 // later
883 }
884
885 /*
886 * Write the dependecy file.
887 */
888 if (!i)
889 {
890 depOptimize(fFixCase);
891 fprintf(pOutput, "%s:", pszTarget);
892 depPrint(pOutput);
893 if (fStubs)
894 depPrintStubs(pOutput);
895 }
896
897 /*
898 * Close the output, delete output on failure.
899 */
900 if (!i && ferror(pOutput))
901 {
902 i = 1;
903 fprintf(stderr, "%s: error: Error writing to '%s'.\n", argv[0], pszOutput);
904 }
905 fclose(pOutput);
906 if (i)
907 {
908 if (unlink(pszOutput))
909 fprintf(stderr, "%s: warning: failed to remove output file '%s' on failure.\n", argv[0], pszOutput);
910 }
911
912 return i;
913}
Note: See TracBrowser for help on using the repository browser.