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

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

Fix case option - need it on unix for cl.exe with wine.

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