VirtualBox

source: kBuild/trunk/src/kObjCache/kObjCache.c@ 1038

Last change on this file since 1038 was 1038, checked in by bird, 18 years ago

drop incorrect assertion.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 57.1 KB
Line 
1/* $Id: kObjCache.c 1038 2007-06-05 08:31:14Z bird $ */
2/** @file
3 *
4 * kObjCache - Object Cache.
5 *
6 * Copyright (c) 2007 knut st. osmundsen <[email protected]>
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/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include <string.h>
32#include <stdlib.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <errno.h>
36#include <assert.h>
37#include <sys/stat.h>
38#include <fcntl.h>
39#include <limits.h>
40#include <ctype.h>
41#ifndef PATH_MAX
42# define PATH_MAX _MAX_PATH /* windows */
43#endif
44#if defined(__OS2__) || defined(__WIN__)
45# include <process.h>
46# include <io.h>
47# ifdef __OS2__
48# include <unistd.h>
49# endif
50#else
51# include <unistd.h>
52# include <sys/wait.h>
53# ifndef O_BINARY
54# define O_BINARY 0
55# endif
56#endif
57#include "crc32.h"
58#include "md5.h"
59
60/*******************************************************************************
61* Defined Constants And Macros *
62*******************************************************************************/
63/** The max line length in a cache file. */
64#define KOBJCACHE_MAX_LINE_LEN 16384
65#if defined(__WIN__)
66# define PATH_SLASH '\\'
67#else
68# define PATH_SLASH '/'
69#endif
70#if defined(__OS2__) || defined(__WIN__)
71# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
72# define IS_SLASH_DRV(ch) ((ch) == '/' || (ch) == '\\' || (ch) == ':')
73#else
74# define IS_SLASH(ch) ((ch) == '/')
75# define IS_SLASH_DRV(ch) ((ch) == '/')
76#endif
77/** Use pipe instead of temp files when possible (speed). */
78#define USE_PIPE 1
79
80
81
82/*******************************************************************************
83* Structures and Typedefs *
84*******************************************************************************/
85/** A checksum list entry.
86 * We keep a list checksums (of precompiler output) that matches, The planned
87 * matching algorithm doesn't require the precompiler output to be indentical,
88 * only to produce the same object files.
89 */
90typedef struct KOCSUM
91{
92 /** The next checksum. */
93 struct KOCSUM *pNext;
94 /** The crc32 checksum. */
95 uint32_t crc32;
96 /** The MD5 digest. */
97 unsigned char md5[16];
98} KOCSUM, *PKOCSUM;
99/** Pointer to a const KOCSUM. */
100typedef const KOCSUM *PCKOCSUM;
101
102/**
103 * The object cache data.
104 */
105typedef struct KOBJCACHE
106{
107 /** The cache dir that all other names are relative to. */
108 char *pszDir;
109 /** The name of the cache file. */
110 const char *pszName;
111 /** Set if the object needs to be (re)compiled. */
112 unsigned fNeedCompiling;
113 /** Whether the precompiler runs in piped mode. If clear it's file
114 * mode (it could be redirected stdout, but that's essentially the
115 * same from our point of view). */
116 unsigned fPiped;
117
118 /** The name of new precompiled output. */
119 const char *pszNewCppName;
120 /** Pointer to the 'mapping' of the new precompiled output. */
121 char *pszNewCppMapping;
122 /** The size of the new precompiled output 'mapping'. */
123 size_t cbNewCpp;
124 /** The new checksum. */
125 KOCSUM NewSum;
126 /** The new object filename (relative to the cache file). */
127 char *pszNewObjName;
128
129 /** The name of the precompiled output. (relative to the cache file) */
130 char *pszOldCppName;
131 /** Pointer to the 'mapping' of the old precompiled output. */
132 char *pszOldCppMapping;
133 /** The size of the old precompiled output. */
134 size_t cbOldCpp;
135
136 /** The head of the checksum list. */
137 KOCSUM SumHead;
138 /** The object filename (relative to the cache file). */
139 char *pszObjName;
140 /** The compile argument vector used to build the object. */
141 char **papszArgvCompile;
142 /** The size of the compile */
143 unsigned cArgvCompile;
144} KOBJCACHE, *PKOBJCACHE;
145/** Pointer to a const KOBJCACHE. */
146typedef const KOBJCACHE *PCKOBJCACHE;
147
148
149/*******************************************************************************
150* Global Variables *
151*******************************************************************************/
152/** Whether verbose output is enabled. */
153static int g_fVerbose = 0;
154
155
156/*******************************************************************************
157* Internal Functions *
158*******************************************************************************/
159static const char *FindFilenameInPath(const char *pszPath);
160static char *AbsPath(const char *pszPath);
161static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir);
162static char *CalcRelativeName(const char *pszPath, const char *pszDir);
163static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode);
164static int UnlinkFileInDir(const char *pszName, const char *pszDir);
165static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir);
166static int DoesFileInDirExist(const char *pszName, const char *pszDir);
167static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile);
168static void *xmalloc(size_t);
169static void *xrealloc(void *, size_t);
170static char *xstrdup(const char *);
171
172
173/**
174 * Compares two check sum entries.
175 *
176 * @returns 1 if equal, 0 if not equal.
177 *
178 * @param pSum1 The first checksum.
179 * @param pSum2 The second checksum.
180 */
181static int kObjCacheSumIsEqual(PCKOCSUM pSum1, PCKOCSUM pSum2)
182{
183 if (pSum1 == pSum2)
184 return 1;
185 if (!pSum1 || !pSum2)
186 return 0;
187 if (pSum1->crc32 != pSum2->crc32)
188 return 0;
189 if (memcmp(&pSum1->md5[0], &pSum2->md5[0], sizeof(pSum1->md5)))
190 return 0;
191 return 1;
192}
193
194
195/**
196 * Print a fatal error message and exit with rc=1.
197 *
198 * @param pEntry The cache entry.
199 * @param pszFormat The message to print.
200 * @param ... Format arguments.
201 */
202static void kObjCacheFatal(PCKOBJCACHE pEntry, const char *pszFormat, ...)
203{
204 va_list va;
205
206 fprintf(stderr, "kObjCache %s - fatal error: ", pEntry->pszName);
207 va_start(va, pszFormat);
208 vfprintf(stderr, pszFormat, va);
209 va_end(va);
210
211 exit(1);
212}
213
214
215/**
216 * Print a verbose message if verbosisty is enabled.
217 *
218 * @param pEntry The cache entry.
219 * @param pszFormat The message to print.
220 * @param ... Format arguments.
221 */
222static void kObjCacheVerbose(PCKOBJCACHE pEntry, const char *pszFormat, ...)
223{
224 if (g_fVerbose)
225 {
226 va_list va;
227
228 fprintf(stdout, "kObjCache %s - info: ", pEntry->pszName);
229 va_start(va, pszFormat);
230 vfprintf(stdout, pszFormat, va);
231 va_end(va);
232 }
233}
234
235
236/**
237 * Creates a cache entry for the given cache file name.
238 *
239 * @returns Pointer to a cache entry.
240 * @param pszFilename The cache file name.
241 */
242static PKOBJCACHE kObjCacheCreate(const char *pszFilename)
243{
244 PKOBJCACHE pEntry;
245
246 /*
247 * Allocate an empty entry.
248 */
249 pEntry = xmalloc(sizeof(*pEntry));
250 memset(pEntry, 0, sizeof(*pEntry));
251
252 /*
253 * Setup the directory and cache file name.
254 */
255 pEntry->pszDir = AbsPath(pszFilename);
256 pEntry->pszName = FindFilenameInPath(pEntry->pszDir);
257 if (pEntry->pszDir == pEntry->pszName)
258 kObjCacheFatal(pEntry, "Failed to find abs path for '%s'!\n", pszFilename);
259 ((char *)pEntry->pszName)[-1] = '\0';
260
261 return pEntry;
262}
263
264
265#if 0 /* don't bother. */
266/**
267 * Destroys the cache entry freeing up all it's resources.
268 *
269 * @param pEntry The entry to free.
270 */
271static void kObjCacheDestroy(PKOBJCACHE pEntry)
272{
273 free(pEntry->pszDir);
274 free(pEntry->pszName);
275 while (pEntry->SumHead.pNext)
276 {
277 void *pv = pEntry->SumHead.pNext;
278 pEntry->SumHead.pNext = pEntry->SumHead.pNext->pNext;
279 if (pv != &pEntry->NewSum)
280 free(pv);
281 }
282 free(pEntry);
283}
284#endif
285
286
287/**
288 * Reads and parses the cache file.
289 *
290 * @param pEntry The entry to read it into.
291 */
292static void kObjCacheRead(PKOBJCACHE pEntry)
293{
294 static char s_szLine[KOBJCACHE_MAX_LINE_LEN + 16];
295 FILE *pFile;
296 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "rb");
297 if (pFile)
298 {
299 kObjCacheVerbose(pEntry, "reading cache file...\n");
300
301 /*
302 * Check the magic.
303 */
304 if ( !fgets(s_szLine, sizeof(s_szLine), pFile)
305 || strcmp(s_szLine, "magic=kObjCache-1\n"))
306 {
307 kObjCacheVerbose(pEntry, "bad cache file (magic)\n");
308 pEntry->fNeedCompiling = 1;
309 }
310 else
311 {
312 /*
313 * Parse the rest of the file (relaxed order).
314 */
315 unsigned i;
316 int fBad = 0;
317 int fBadBeforeMissing;
318 int fFirstSum = 1;
319 while (fgets(s_szLine, sizeof(s_szLine), pFile))
320 {
321 /* Split the line and drop the trailing newline. */
322 char *pszNl = strchr(s_szLine, '\n');
323 char *pszVal = strchr(s_szLine, '=');
324 if ((fBad = pszVal == NULL))
325 break;
326 if (pszNl)
327 *pszNl = '\0';
328 *pszVal++ = '\0';
329
330 /* string case on variable name */
331 if (!strcmp(s_szLine, "obj"))
332 {
333 if ((fBad = pEntry->pszObjName != NULL))
334 break;
335 pEntry->pszObjName = xstrdup(pszVal);
336 }
337 else if (!strcmp(s_szLine, "cpp"))
338 {
339 if ((fBad = pEntry->pszOldCppName != NULL))
340 break;
341 pEntry->pszOldCppName = xstrdup(pszVal);
342 }
343 else if (!strcmp(s_szLine, "cpp-size"))
344 {
345 char *pszNext;
346 if ((fBad = pEntry->cbOldCpp != 0))
347 break;
348 pEntry->cbOldCpp = strtoul(pszVal, &pszNext, 0);
349 if ((fBad = pszNext && *pszNext))
350 break;
351 }
352 else if (!strcmp(s_szLine, "cc-argc"))
353 {
354 if ((fBad = pEntry->papszArgvCompile != NULL))
355 break;
356 pEntry->cArgvCompile = atoi(pszVal); /* if wrong, we'll fail below. */
357 pEntry->papszArgvCompile = xmalloc((pEntry->cArgvCompile + 1) * sizeof(pEntry->papszArgvCompile[0]));
358 memset(pEntry->papszArgvCompile, 0, (pEntry->cArgvCompile + 1) * sizeof(pEntry->papszArgvCompile[0]));
359 }
360 else if (!strncmp(s_szLine, "cc-argv-#", sizeof("cc-argv-#") - 1))
361 {
362 char *pszNext;
363 unsigned i = strtoul(&s_szLine[sizeof("cc-argv-#") - 1], &pszNext, 0);
364 if ((fBad = i >= pEntry->cArgvCompile || pEntry->papszArgvCompile[i] || (pszNext && *pszNext)))
365 break;
366 pEntry->papszArgvCompile[i] = xstrdup(pszVal);
367 }
368 else if (!strcmp(s_szLine, "sum"))
369 {
370 KOCSUM Sum;
371 unsigned i;
372 char *pszNext;
373 char *pszMD5 = strchr(pszVal, ':');
374 if ((fBad = pszMD5 == NULL))
375 break;
376 *pszMD5++ = '\0';
377
378 /* crc32 */
379 Sum.crc32 = (uint32_t)strtoul(pszVal, &pszNext, 16);
380 if ((fBad = (pszNext && *pszNext)))
381 break;
382
383 /* md5 */
384 for (i = 0; i < sizeof(Sum.md5) * 2; i++)
385 {
386 unsigned char ch = pszMD5[i];
387 int x;
388 if ((unsigned char)(ch - '0') <= 9)
389 x = ch - '0';
390 else if ((unsigned char)(ch - 'a') <= 5)
391 x = ch - 'a' + 10;
392 else if ((unsigned char)(ch - 'A') <= 5)
393 x = ch - 'A' + 10;
394 else
395 {
396 fBad = 1;
397 break;
398 }
399 if (!(i & 1))
400 Sum.md5[i >> 1] = x << 4;
401 else
402 Sum.md5[i >> 1] |= x;
403 }
404 if (fBad)
405 break;
406
407 if (fFirstSum)
408 {
409 pEntry->SumHead = Sum;
410 pEntry->SumHead.pNext = NULL;
411 fFirstSum = 0;
412 }
413 else
414 {
415 Sum.pNext = pEntry->SumHead.pNext;
416 pEntry->SumHead.pNext = xmalloc(sizeof(Sum));
417 *pEntry->SumHead.pNext = Sum;
418 }
419 }
420 else
421 {
422 fBad = 1;
423 break;
424 }
425 } /* parse loop */
426
427 /*
428 * Did we find everything?
429 */
430 fBadBeforeMissing = fBad;
431 if ( !fBad
432 && ( !pEntry->papszArgvCompile
433 || !pEntry->pszObjName
434 || !pEntry->pszOldCppName
435 || fFirstSum))
436 fBad = 1;
437 if (!fBad)
438 for (i = 0; i < pEntry->cArgvCompile; i++)
439 if ((fBad = !pEntry->papszArgvCompile[i]))
440 break;
441 if (fBad)
442 kObjCacheVerbose(pEntry, "bad cache file (%s)\n", fBadBeforeMissing ? s_szLine : "missing stuff");
443 else if (ferror(pFile))
444 kObjCacheVerbose(pEntry, "cache file read error\n");
445 pEntry->fNeedCompiling = fBad;
446 }
447 fclose(pFile);
448 }
449 else
450 {
451 kObjCacheVerbose(pEntry, "no cache file\n");
452 pEntry->fNeedCompiling = 1;
453 }
454}
455
456
457/**
458 * Writes the cache file.
459 *
460 * @param pEntry The entry to write.
461 */
462static void kObjCacheWrite(PKOBJCACHE pEntry)
463{
464 FILE *pFile;
465 PCKOCSUM pSum;
466 unsigned i;
467
468 kObjCacheVerbose(pEntry, "writing cache file...\n");
469 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "wb");
470 if (!pFile)
471 kObjCacheFatal(pEntry, "Failed to open '%s' in '%s': %s\n",
472 pEntry->pszName, pEntry->pszDir, strerror(errno));
473
474#define CHECK_LEN(expr) \
475 do { int cch = expr; if (cch >= KOBJCACHE_MAX_LINE_LEN) kObjCacheFatal(pEntry, "Line too long: %d (max %d)\nexpr: %s\n", cch, KOBJCACHE_MAX_LINE_LEN, #expr); } while (0)
476
477 fprintf(pFile, "magic=kObjCache-1\n");
478 CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->pszNewObjName ? pEntry->pszNewObjName : pEntry->pszObjName));
479 CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->pszNewCppName ? pEntry->pszNewCppName : pEntry->pszOldCppName));
480 CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n", pEntry->pszNewCppName ? pEntry->cbNewCpp : pEntry->cbOldCpp));
481 CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->cArgvCompile));
482 for (i = 0; i < pEntry->cArgvCompile; i++)
483 CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->papszArgvCompile[i]));
484 for (pSum = pEntry->fNeedCompiling ? &pEntry->NewSum : &pEntry->SumHead;
485 pSum;
486 pSum = pSum->pNext)
487 fprintf(pFile, "sum=%#x:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
488 pSum->crc32,
489 pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3],
490 pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7],
491 pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11],
492 pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]);
493
494 if ( fflush(pFile) < 0
495 || ferror(pFile))
496 {
497 int iErr = errno;
498 fclose(pFile);
499 UnlinkFileInDir(pEntry->pszName, pEntry->pszDir);
500 kObjCacheFatal(pEntry, "Stream error occured while writing '%s' in '%s': %d (?)\n",
501 pEntry->pszName, pEntry->pszDir, strerror(iErr));
502 }
503 fclose(pFile);
504}
505
506
507/**
508 * Spawns a child in a synchronous fashion.
509 * Terminating on failure.
510 *
511 * @param papszArgv Argument vector. The cArgv element is NULL.
512 * @param cArgv The number of arguments in the vector.
513 */
514static void kObjCacheSpawn(PCKOBJCACHE pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut)
515{
516#if defined(__OS2__) || defined(__WIN__)
517 intptr_t rc;
518 int fdStdOut = -1;
519 if (pszStdOut)
520 {
521 int fdReDir;
522 fdStdOut = dup(1); /* dup2(1,-1) doesn't work right on windows */
523 close(1);
524 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0777);
525 if (fdReDir < 0)
526 kObjCacheFatal(pEntry, "%s - failed to create stdout redirection file '%s': %s\n",
527 pszMsg, pszStdOut, strerror(errno));
528
529 if (fdReDir != 1)
530 {
531 if (dup2(fdReDir, 1) < 0)
532 kObjCacheFatal(pEntry, "%s - dup2 failed: %s\n", pszMsg, strerror(errno));
533 close(fdReDir);
534 }
535 }
536
537 errno = 0;
538 rc = _spawnvp(_P_WAIT, papszArgv[0], papszArgv);
539 if (rc < 0)
540 kObjCacheFatal(pEntry, "%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno));
541 if (rc > 0)
542 kObjCacheFatal(pEntry, "%s - failed rc=%d\n", pszMsg, (int)rc);
543 if (fdStdOut)
544 {
545 close(1);
546 fdStdOut = dup2(fdStdOut, 1);
547 close(fdStdOut);
548 }
549
550#else
551 int iStatus;
552 pid_t pidWait;
553 pid_t pid = fork();
554 if (!pid)
555 {
556 if (pszStdOut)
557 {
558 int fdReDir;
559
560 close(1);
561 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0777);
562 if (fdReDir < 0)
563 kObjCacheFatal(pEntry, "%s - failed to create stdout redirection file '%s': %s\n",
564 pszMsg, pszStdOut, strerror(errno));
565 if (fdReDir != 1)
566 {
567 if (dup2(fdReDir, 1) < 0)
568 kObjCacheFatal(pEntry, "%s - dup2 failed: %s\n", pszMsg, strerror(errno));
569 close(fdReDir);
570 }
571 }
572
573 execvp(papszArgv[0], (char **)papszArgv);
574 kObjCacheFatal(pEntry, "%s - execvp failed: %s\n",
575 pszMsg, strerror(errno));
576 }
577 if (pid == -1)
578 kObjCacheFatal(pEntry, "%s - fork() failed: %s\n", pszMsg, strerror(errno));
579
580 pidWait = waitpid(pid, &iStatus, 0);
581 while (pidWait < 0 && errno == EINTR)
582 pidWait = waitpid(pid, &iStatus, 0);
583 if (pidWait != pid)
584 kObjCacheFatal(pEntry, "%s - waitpid failed rc=%d: %s\n",
585 pszMsg, pidWait, strerror(errno));
586 if (!WIFEXITED(iStatus))
587 kObjCacheFatal(pEntry, "%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
588 if (WEXITSTATUS(iStatus))
589 kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
590#endif
591 (void)cArgv;
592}
593
594
595#ifdef USE_PIPE
596/**
597 * Spawns a child in a synchronous fashion.
598 * Terminating on failure.
599 *
600 * @param papszArgv Argument vector. The cArgv element is NULL.
601 * @param cArgv The number of arguments in the vector.
602 */
603static void kObjCacheSpawnPipe(PCKOBJCACHE pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg, char **ppszOutput, size_t *pcchOutput)
604{
605 int fds[2];
606 int iStatus;
607#if defined(__WIN__)
608 intptr_t pid, pidWait;
609#else
610 pid_t pid, pidWait;
611#endif
612 int fdStdOut;
613 size_t cbAlloc;
614 size_t cbLeft;
615 char *psz;
616
617 /*
618 * Setup the pipe.
619 */
620#if defined(__WIN__)
621 if (_pipe(fds, 0, _O_NOINHERIT | _O_BINARY) < 0)
622#else
623 if (pipe(fds) < 0)
624#endif
625 kObjCacheFatal(pEntry, "pipe failed: %s\n", strerror(errno));
626 fdStdOut = dup(1);
627 if (dup2(fds[1 /* write */], 1) < 0)
628 kObjCacheFatal(pEntry, "dup2(,1) failed: %s\n", strerror(errno));
629 close(fds[1]);
630 fds[1] = -1;
631#ifndef __WIN__
632 fcntl(fds[0], F_SETFD, FD_CLOEXEC);
633 fcntl(fdStdOut, F_SETFD, FD_CLOEXEC);
634#endif
635
636 /*
637 * Create the child process.
638 */
639#if defined(__OS2__) || defined(__WIN__)
640 errno = 0;
641 pid = _spawnvp(_P_NOWAIT, papszArgv[0], papszArgv);
642 if (pid == -1)
643 kObjCacheFatal(pEntry, "%s - _spawnvp failed: %s\n", pszMsg, strerror(errno));
644
645#else
646 pid = fork();
647 if (!pid)
648 {
649 execvp(papszArgv[0], (char **)papszArgv);
650 kObjCacheFatal(pEntry, "%s - execvp failed: %s\n",
651 pszMsg, strerror(errno));
652 }
653 if (pid == -1)
654 kObjCacheFatal(pEntry, "%s - fork() failed: %s\n", pszMsg, strerror(errno));
655#endif
656
657 /*
658 * Restore stdout.
659 */
660 close(1);
661 fdStdOut = dup2(fdStdOut, 1);
662
663 /*
664 * Read data from the child.
665 */
666 cbAlloc = pEntry->cbOldCpp ? (pEntry->cbOldCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
667 cbLeft = cbAlloc;
668 *ppszOutput = psz = xmalloc(cbAlloc);
669 for (;;)
670 {
671 long cbRead = read(fds[0], psz, cbLeft - 1);
672 if (!cbRead)
673 break;
674 if (cbRead < 0 && errno != EINTR)
675 kObjCacheFatal(pEntry, "%s - read(%d,,%ld) failed: %s\n", pszMsg, fds[0], (long)cbLeft, strerror(errno));
676 psz += cbRead;
677 *psz = '\0';
678 cbLeft -= cbRead;
679
680 /* expand the buffer? */
681 if (cbLeft <= 1)
682 {
683 size_t off = psz - *ppszOutput;
684 cbLeft = 4*1024*1024;
685 cbAlloc += cbLeft;
686 *ppszOutput = xrealloc(*ppszOutput, cbAlloc);
687 psz = *ppszOutput + off;
688 }
689 }
690 close(fds[0]);
691 *pcchOutput = cbAlloc - cbLeft;
692
693 /*
694 * Reap the child.
695 */
696#ifdef __WIN__
697 pidWait = _cwait(&iStatus, pid, _WAIT_CHILD);
698 if (pidWait == -1)
699 kObjCacheFatal(pEntry, "%s - waitpid failed: %s\n",
700 pszMsg, strerror(errno));
701 if (iStatus)
702 kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, iStatus);
703#else
704 pidWait = waitpid(pid, &iStatus, 0);
705 while (pidWait < 0 && errno == EINTR)
706 pidWait = waitpid(pid, &iStatus, 0);
707 if (pidWait != pid)
708 kObjCacheFatal(pEntry, "%s - waitpid failed rc=%d: %s\n",
709 pszMsg, pidWait, strerror(errno));
710 if (!WIFEXITED(iStatus))
711 kObjCacheFatal(pEntry, "%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
712 if (WEXITSTATUS(iStatus))
713 kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
714#endif
715 (void)cArgv;
716}
717#endif /* USE_PIPE */
718
719
720/**
721 * Reads the (new) output of the precompiler.
722 *
723 * Not used when using pipes.
724 *
725 * @param pEntry The cache entry. cbNewCpp and pszNewCppMapping will be updated.
726 */
727static void kObjCacheReadPrecompileOutput(PKOBJCACHE pEntry)
728{
729 pEntry->pszNewCppMapping = ReadFileInDir(pEntry->pszNewCppName, pEntry->pszDir, &pEntry->cbNewCpp);
730 if (!pEntry->pszNewCppMapping)
731 kObjCacheFatal(pEntry, "failed to open/read '%s' in '%s': %s\n",
732 pEntry->pszNewCppName, pEntry->pszDir, strerror(errno));
733 kObjCacheVerbose(pEntry, "precompiled file is %lu bytes long\n", (unsigned long)pEntry->cbNewCpp);
734}
735
736
737/**
738 * Worker for kObjCachePreCompile and calculates the checksum of
739 * the precompiler output.
740 *
741 * @param pEntry The cache entry. NewSum will be updated.
742 */
743static void kObjCacheCalcChecksum(PKOBJCACHE pEntry)
744{
745 struct MD5Context MD5Ctx;
746
747 memset(&pEntry->NewSum, 0, sizeof(pEntry->NewSum));
748 pEntry->NewSum.crc32 = crc32(0, pEntry->pszNewCppMapping, pEntry->cbNewCpp);
749 MD5Init(&MD5Ctx);
750 MD5Update(&MD5Ctx, (unsigned char *)pEntry->pszNewCppMapping, pEntry->cbNewCpp);
751 MD5Final(&pEntry->NewSum.md5[0], &MD5Ctx);
752 kObjCacheVerbose(pEntry, "crc32=%#lx md5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
753 pEntry->NewSum.crc32,
754 pEntry->NewSum.md5[0], pEntry->NewSum.md5[1], pEntry->NewSum.md5[2], pEntry->NewSum.md5[3],
755 pEntry->NewSum.md5[4], pEntry->NewSum.md5[5], pEntry->NewSum.md5[6], pEntry->NewSum.md5[7],
756 pEntry->NewSum.md5[8], pEntry->NewSum.md5[9], pEntry->NewSum.md5[10], pEntry->NewSum.md5[11],
757 pEntry->NewSum.md5[12], pEntry->NewSum.md5[13], pEntry->NewSum.md5[14], pEntry->NewSum.md5[15]);
758
759}
760
761
762/**
763 * Run the precompiler and calculate the checksum of the output.
764 *
765 * @param pEntry The cache entry.
766 * @param papszArgvPreComp The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
767 * @param cArgvPreComp The number of arguments.
768 * @param pszPreCompName Precompile output name. (must kick around)
769 * @param fRedirStdOut Whether stdout needs to be redirected or not.
770 */
771static void kObjCachePreCompile(PKOBJCACHE pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp, const char *pszPreCompName, int fRedirStdOut)
772{
773#ifdef USE_PIPE
774 /*
775 * Flag it as piped or non-piped.
776 */
777 if (fRedirStdOut)
778 pEntry->fPiped = 1;
779 else
780#endif
781 pEntry->fPiped = 0;
782
783 /*
784 * Rename the old precompiled output to '-old'.
785 * We'll discard the old output and keep the new output, but because
786 * we might with to do a quick matchup later we can't remove it just now.
787 * If we're using the pipe strategy, we will not do any renaming.
788 */
789 if ( pEntry->pszOldCppName
790 && !pEntry->fPiped
791 && DoesFileInDirExist(pEntry->pszOldCppName, pEntry->pszDir))
792 {
793 size_t cch = strlen(pEntry->pszOldCppName);
794 char *psz = xmalloc(cch + sizeof("-old"));
795 memcpy(psz, pEntry->pszOldCppName, cch);
796 memcpy(psz + cch, "-old", sizeof("-old"));
797
798 kObjCacheVerbose(pEntry, "renaming '%s' to '%s' in '%s'\n", pEntry->pszOldCppName, psz, pEntry->pszDir);
799 UnlinkFileInDir(psz, pEntry->pszDir);
800 if (RenameFileInDir(pEntry->pszOldCppName, psz, pEntry->pszDir))
801 kObjCacheFatal(pEntry, "failed to rename '%s' -> '%s' in '%s': %s\n",
802 pEntry->pszOldCppName, psz, pEntry->pszDir, strerror(errno));
803 free(pEntry->pszOldCppName);
804 pEntry->pszOldCppName = psz;
805 }
806 pEntry->pszNewCppName = CalcRelativeName(pszPreCompName, pEntry->pszDir);
807
808 /*
809 * Precompile it and calculate the checksum on the output.
810 */
811 kObjCacheVerbose(pEntry, "precompiling -> '%s'...\n", pEntry->pszNewCppName);
812#ifdef USE_PIPE
813 if (pEntry->fPiped)
814 kObjCacheSpawnPipe(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", &pEntry->pszNewCppMapping, &pEntry->cbNewCpp);
815 else
816#endif
817 {
818 if (fRedirStdOut)
819 kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", pszPreCompName);
820 else
821 kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
822 kObjCacheReadPrecompileOutput(pEntry);
823 }
824 kObjCacheCalcChecksum(pEntry);
825}
826
827
828/**
829 * Check whether the string is a '#line' statement.
830 *
831 * @returns 1 if it is, 0 if it isn't.
832 * @param psz The line to examin.
833 * @parma piLine Where to store the line number.
834 * @parma ppszFile Where to store the start of the filename.
835 */
836static int IsLineStatement(const char *psz, unsigned *piLine, const char **ppszFile)
837{
838 unsigned iLine;
839
840 /* Expect a hash. */
841 if (*psz++ != '#')
842 return 0;
843
844 /* Skip blanks between '#' and the line / number */
845 while (*psz == ' ' || *psz == '\t')
846 psz++;
847
848 /* Skip the 'line' if present. */
849 if (!strncmp(psz, "line", sizeof("line") - 1))
850 psz += sizeof("line");
851
852 /* Expect a line number now. */
853 if ((unsigned char)(*psz - '0') > 9)
854 return 0;
855 iLine = 0;
856 do
857 {
858 iLine *= 10;
859 iLine += (*psz - '0');
860 psz++;
861 }
862 while ((unsigned char)(*psz - '0') <= 9);
863
864 /* Expect one or more space now. */
865 if (*psz != ' ' && *psz != '\t')
866 return 0;
867 do psz++;
868 while (*psz == ' ' || *psz == '\t');
869
870 /* that's good enough. */
871 *piLine = iLine;
872 *ppszFile = psz;
873 return 1;
874}
875
876
877/**
878 * Scan backwards for the previous #line statement.
879 *
880 * @returns The filename in the previous statement.
881 * @param pszStart Where to start.
882 * @param pszStop Where to stop. Less than pszStart.
883 * @param piLine The line number count to adjust.
884 */
885static const char *FindFileStatement(const char *pszStart, const char *pszStop, unsigned *piLine)
886{
887 unsigned iLine = *piLine;
888 assert(pszStart >= pszStop);
889 while (pszStart >= pszStop)
890 {
891 if (*pszStart == '\n')
892 iLine++;
893 else if (*pszStart == '#')
894 {
895 unsigned iLineTmp;
896 const char *pszFile;
897 const char *psz = pszStart - 1;
898 while (psz >= pszStop && (*psz == ' ' || *psz =='\t'))
899 psz--;
900 if ( (psz < pszStop || *psz == '\n')
901 && IsLineStatement(pszStart, &iLineTmp, &pszFile))
902 {
903 *piLine = iLine + iLineTmp - 1;
904 return pszFile;
905 }
906 }
907 pszStart--;
908 }
909 return NULL;
910}
911
912
913/**
914 * Worker for kObjCacheCompareOldAndNewOutput() that compares the
915 * precompiled output using a fast but not very good method.
916 *
917 * @returns 1 if matching, 0 if not matching.
918 * @param pEntry The entry containing the names of the files to compare.
919 * The entry is not updated in any way.
920 */
921static int kObjCacheCompareFast(PCKOBJCACHE pEntry)
922{
923 const char * psz1 = pEntry->pszNewCppMapping;
924 const char * const pszEnd1 = psz1 + pEntry->cbNewCpp;
925 const char * psz2 = pEntry->pszOldCppMapping;
926 const char * const pszEnd2 = psz2 + pEntry->cbOldCpp;
927
928 assert(*pszEnd1 == '\0');
929 assert(*pszEnd2 == '\0');
930
931 /*
932 * Iterate block by block and backtrack when we find a difference.
933 */
934 for (;;)
935 {
936 size_t cch = pszEnd1 - psz1;
937 if (cch > (size_t)(pszEnd2 - psz2))
938 cch = pszEnd2 - psz2;
939 if (cch > 4096)
940 cch = 4096;
941 if ( cch
942 && !memcmp(psz1, psz2, cch))
943 {
944 /* no differences */
945 psz1 += cch;
946 psz2 += cch;
947 }
948 else
949 {
950 /*
951 * Pinpoint the difference exactly and the try find the start
952 * of that line. Then skip forward until we find something to
953 * work on that isn't spaces, #line statements or closing curly
954 * braces.
955 *
956 * The closing curly braces are ignored because they are frequently
957 * found at the end of header files (__END_DECLS) and the worst
958 * thing that may happen if it isn't one of these braces we're
959 * ignoring is that the final line in a function block is a little
960 * bit off in the debug info.
961 *
962 * Since we might be skipping a few new empty headers, it is
963 * possible that we will omit this header from the dependencies
964 * when using VCC. This might not be a problem, since it seems
965 * we'll have to use the precompiler output to generate the deps
966 * anyway.
967 */
968 const char *psz;
969 const char *pszMismatch1;
970 const char *pszFile1 = NULL;
971 unsigned iLine1 = 0;
972 unsigned cCurlyBraces1 = 0;
973 const char *pszMismatch2;
974 const char *pszFile2 = NULL;
975 unsigned iLine2 = 0;
976 unsigned cCurlyBraces2 = 0;
977
978 /* locate the difference. */
979 while (cch >= 512 && !memcmp(psz1, psz2, 512))
980 psz1 += 512, psz2 += 512, cch -= 512;
981 while (cch >= 64 && !memcmp(psz1, psz2, 64))
982 psz1 += 64, psz2 += 64, cch -= 64;
983 while (*psz1 == *psz2 && cch > 0)
984 psz1++, psz2++, cch--;
985
986 /* locate the start of that line. */
987 psz = psz1;
988 while ( psz > pEntry->pszNewCppMapping
989 && psz[-1] != '\n')
990 psz--;
991 psz2 -= (psz1 - psz);
992 pszMismatch2 = psz2;
993 pszMismatch1 = psz1 = psz;
994
995 /* Parse the 1st file line by line. */
996 while (psz1 < pszEnd1)
997 {
998 if (*psz1 == '\n')
999 {
1000 psz1++;
1001 iLine1++;
1002 }
1003 else
1004 {
1005 psz = psz1;
1006 while (isspace(*psz) && *psz != '\n')
1007 psz++;
1008 if (*psz == '\n')
1009 {
1010 psz1 = psz + 1;
1011 iLine1++;
1012 }
1013 else if (*psz == '#' && IsLineStatement(psz, &iLine1, &pszFile1))
1014 {
1015 psz1 = memchr(psz, '\n', pszEnd1 - psz);
1016 if (!psz1++)
1017 psz1 = pszEnd1;
1018 }
1019 else if (*psz == '}')
1020 {
1021 do psz++;
1022 while (isspace(*psz) && *psz != '\n');
1023 if (*psz == '\n')
1024 iLine1++;
1025 else if (psz != pszEnd1)
1026 break;
1027 cCurlyBraces1++;
1028 psz1 = psz;
1029 }
1030 else if (psz == pszEnd1)
1031 psz1 = psz;
1032 else /* found something that can be compared. */
1033 break;
1034 }
1035 }
1036
1037 /* Ditto for the 2nd file. */
1038 while (psz2 < pszEnd2)
1039 {
1040 if (*psz2 == '\n')
1041 {
1042 psz2++;
1043 iLine2++;
1044 }
1045 else
1046 {
1047 psz = psz2;
1048 while (isspace(*psz) && *psz != '\n')
1049 psz++;
1050 if (*psz == '\n')
1051 {
1052 psz2 = psz + 1;
1053 iLine2++;
1054 }
1055 else if (*psz == '#' && IsLineStatement(psz, &iLine2, &pszFile2))
1056 {
1057 psz2 = memchr(psz, '\n', pszEnd2 - psz);
1058 if (!psz2++)
1059 psz2 = pszEnd2;
1060 }
1061 else if (*psz == '}')
1062 {
1063 do psz++;
1064 while (isspace(*psz) && *psz != '\n');
1065 if (*psz == '\n')
1066 iLine2++;
1067 else if (psz != pszEnd2)
1068 break;
1069 cCurlyBraces2++;
1070 psz2 = psz;
1071 }
1072 else if (psz == pszEnd2)
1073 psz2 = psz;
1074 else /* found something that can be compared. */
1075 break;
1076 }
1077 }
1078
1079 /* Match the number of ignored closing curly braces. */
1080 if (cCurlyBraces1 != cCurlyBraces2)
1081 return 0;
1082
1083 /* Reaching the end of any of them means the return statement can decide. */
1084 if ( psz1 == pszEnd1
1085 || psz2 == pszEnd2)
1086 break;
1087
1088 /* Match the current line. */
1089 psz = memchr(psz1, '\n', pszEnd1 - psz1);
1090 if (!psz)
1091 psz = pszEnd1;
1092 cch = psz - psz1;
1093 if (psz2 + cch > pszEnd2)
1094 break;
1095 if (memcmp(psz1, psz2, cch))
1096 break;
1097
1098 /* Check that we're at the same location now. */
1099 if (!pszFile1)
1100 pszFile1 = FindFileStatement(pszMismatch1, pEntry->pszNewCppMapping, &iLine1);
1101 if (!pszFile2)
1102 pszFile2 = FindFileStatement(pszMismatch2, pEntry->pszOldCppMapping, &iLine2);
1103 if (pszFile1 && pszFile2)
1104 {
1105 if (iLine1 != iLine2)
1106 break;
1107 while (*pszFile1 == *pszFile2 && *pszFile1 != '\n' && *pszFile1)
1108 pszFile1++, pszFile2++;
1109 if (*pszFile1 != *pszFile2)
1110 break;
1111 }
1112 else if (pszFile1 || pszFile2)
1113 {
1114 assert(0); /* this shouldn't happen. */
1115 break;
1116 }
1117
1118 /* Try align psz1 on 8 or 4 bytes so at least one of the buffers are aligned. */
1119 psz1 += cch;
1120 psz2 += cch;
1121 if (cch >= ((uintptr_t)psz1 & 7))
1122 {
1123 psz2 -= ((uintptr_t)psz1 & 7);
1124 psz1 -= ((uintptr_t)psz1 & 7);
1125 }
1126 else if (cch >= ((uintptr_t)psz1 & 3))
1127 {
1128 psz2 -= ((uintptr_t)psz1 & 3);
1129 psz1 -= ((uintptr_t)psz1 & 3);
1130 }
1131 }
1132 }
1133
1134 return psz1 == pszEnd1
1135 && psz2 == pszEnd2;
1136}
1137
1138
1139/**
1140 * Worker for kObjCacheCompileIfNeeded that compares the
1141 * precompiled output.
1142 *
1143 * @returns 1 if matching, 0 if not matching.
1144 * @param pEntry The entry containing the names of the files to compare.
1145 * This will load the old cpp output (changing pszOldCppName and cbOldCpp).
1146 */
1147static int kObjCacheCompareOldAndNewOutput(PKOBJCACHE pEntry)
1148{
1149 /** @todo do some quick but fancy comparing that determins whether code
1150 * has actually changed or moved. We can ignore declarations and typedefs that
1151 * has just been moved up/down a bit. The typical case is adding a new error
1152 * #define that doesn't influence the current compile job. */
1153
1154 /*
1155 * Load the old output.
1156 */
1157 pEntry->pszOldCppMapping = ReadFileInDir(pEntry->pszOldCppName, pEntry->pszDir, &pEntry->cbOldCpp);
1158 if (!pEntry->pszOldCppMapping)
1159 {
1160 kObjCacheVerbose(pEntry, "failed to read old cpp file ('%s' in '%s'): %s\n",
1161 pEntry->pszOldCppName, pEntry->pszDir, strerror(errno));
1162 return 0;
1163 }
1164
1165 /*
1166 * I may implement a more sophisticated alternative method later... maybe.
1167 */
1168 //if ()
1169 // return kObjCacheCompareBest(pEntry);
1170 return kObjCacheCompareFast(pEntry);
1171}
1172
1173
1174/**
1175 * Worker for kObjCacheCompileIfNeeded that does the actual (re)compilation.
1176 *
1177 * @returns 1 if matching, 0 if not matching.
1178 * @param pEntry The cache entry.
1179 * @param papszArgvCompile The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
1180 * @param cArgvCompile The number of arguments in the vector.
1181 * @param pszObjName The name of the object file.
1182 */
1183static void kObjCacheCompileIt(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
1184{
1185 /*
1186 * Delete the old object file and precompiler output.
1187 */
1188 if (pEntry->pszObjName)
1189 {
1190 UnlinkFileInDir(pEntry->pszObjName, pEntry->pszDir);
1191 pEntry->pszObjName = NULL;
1192 }
1193 pEntry->pszNewObjName = CalcRelativeName(pszObjName, pEntry->pszDir);
1194
1195 /*
1196 * If we executed the precompiled in piped mode we'll have to write the
1197 * precompiler output to disk so the compile has some thing to chew on.
1198 */
1199 if (pEntry->fPiped)
1200 {
1201 FILE *pFile = FOpenFileInDir(pEntry->pszNewCppName, pEntry->pszDir, "wb");
1202 if (!pFile)
1203 kObjCacheFatal(pEntry, "failed to create file '%s' in '%s': %s\n",
1204 pEntry->pszNewCppName, pEntry->pszDir, strerror(errno));
1205 if (fwrite(pEntry->pszNewCppMapping, pEntry->cbNewCpp, 1, pFile) != 1)
1206 kObjCacheFatal(pEntry, "fwrite failed: %s\n", strerror(errno));
1207 if (fclose(pFile))
1208 kObjCacheFatal(pEntry, "fclose failed: %s\n", strerror(errno));
1209 }
1210
1211 /*
1212 * Release buffers we no longer need before starting the compile.
1213 */
1214 free(pEntry->pszNewCppMapping);
1215 pEntry->pszNewCppMapping = NULL;
1216 free(pEntry->pszOldCppMapping);
1217 pEntry->pszOldCppMapping = NULL;
1218
1219 /*
1220 * Do the recompilation.
1221 */
1222 kObjCacheVerbose(pEntry, "compiling -> '%s'...\n", pEntry->pszNewObjName);
1223 pEntry->papszArgvCompile = (char **)papszArgvCompile; /* LEAK */
1224 pEntry->cArgvCompile = cArgvCompile;
1225 kObjCacheSpawn(pEntry, papszArgvCompile, cArgvCompile, "compile", NULL);
1226}
1227
1228
1229/**
1230 * Check if (re-)compilation is required and do it.
1231 *
1232 * @returns 1 if matching, 0 if not matching.
1233 * @param pEntry The cache entry.
1234 * @param papszArgvCompile The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
1235 * @param cArgvCompile The number of arguments in the vector.
1236 * @param pszObjName The name of the object file.
1237 */
1238static void kObjCacheCompileIfNeeded(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
1239{
1240 /*
1241 * Does the object name differ?
1242 */
1243 if (!pEntry->fNeedCompiling)
1244 {
1245 char *pszTmp = CalcRelativeName(pszObjName, pEntry->pszDir);
1246 if (strcmp(pEntry->pszObjName, pszTmp))
1247 {
1248 pEntry->fNeedCompiling = 1;
1249 kObjCacheVerbose(pEntry, "object name changed '%s' -> '%s'\n", pEntry->pszObjName, pszTmp);
1250 }
1251 free(pszTmp);
1252 }
1253
1254 /*
1255 * Does the compile command differ?
1256 * TODO: Ignore irrelevant options here (like warning level).
1257 */
1258 if ( !pEntry->fNeedCompiling
1259 && pEntry->cArgvCompile != cArgvCompile)
1260 {
1261 pEntry->fNeedCompiling = 1;
1262 kObjCacheVerbose(pEntry, "compile argument count changed\n");
1263 }
1264 if (!pEntry->fNeedCompiling)
1265 {
1266 unsigned i;
1267 for (i = 0; i < cArgvCompile; i++)
1268 if (strcmp(papszArgvCompile[i], pEntry->papszArgvCompile[i]))
1269 {
1270 pEntry->fNeedCompiling = 1;
1271 kObjCacheVerbose(pEntry, "compile argument differs (%#d)\n", i);
1272 break;
1273 }
1274 }
1275
1276 /*
1277 * Does the object file exist?
1278 */
1279 if ( !pEntry->fNeedCompiling
1280 && !DoesFileInDirExist(pEntry->pszObjName, pEntry->pszDir))
1281 {
1282 pEntry->fNeedCompiling = 1;
1283 kObjCacheVerbose(pEntry, "object file doesn't exist\n");
1284 }
1285
1286 /*
1287 * Does the precompiled output differ in any significant way?
1288 */
1289 if (!pEntry->fNeedCompiling)
1290 {
1291 int fFound = 0;
1292 PCKOCSUM pSum;
1293 for (pSum = &pEntry->SumHead; pSum; pSum = pSum->pNext)
1294 if (kObjCacheSumIsEqual(pSum, &pEntry->NewSum))
1295 {
1296 fFound = 1;
1297 break;
1298 }
1299 if (!fFound)
1300 {
1301 kObjCacheVerbose(pEntry, "no checksum match - comparing output\n");
1302 if (!kObjCacheCompareOldAndNewOutput(pEntry))
1303 pEntry->fNeedCompiling = 1;
1304 else
1305 {
1306 /* insert the sum into the list. */
1307 pEntry->NewSum.pNext = pEntry->SumHead.pNext;
1308 pEntry->SumHead.pNext = &pEntry->NewSum;
1309 }
1310 }
1311 }
1312
1313 /*
1314 * Discard the old precompiled output if it's no longer needed.
1315 */
1316 if ( pEntry->pszOldCppName
1317 && ( !pEntry->fPiped
1318 || pEntry->fNeedCompiling))
1319 {
1320 UnlinkFileInDir(pEntry->pszOldCppName, pEntry->pszDir);
1321 free(pEntry->pszOldCppName);
1322 pEntry->pszOldCppName = NULL;
1323 }
1324
1325 /*
1326 * Do the compliation if found necessary.
1327 */
1328 if (pEntry->fNeedCompiling)
1329 kObjCacheCompileIt(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
1330}
1331
1332
1333/**
1334 * Gets the absolute path
1335 *
1336 * @returns A new heap buffer containing the absolute path.
1337 * @param pszPath The path to make absolute. (Readonly)
1338 */
1339static char *AbsPath(const char *pszPath)
1340{
1341 char szTmp[PATH_MAX];
1342#if defined(__OS2__) || defined(__WIN__)
1343 if (!_fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp)))
1344 return xstrdup(pszPath);
1345#else
1346 if (!realpath(pszPath, szTmp))
1347 return xstrdup(pszPath);
1348#endif
1349 return xstrdup(szTmp);
1350}
1351
1352
1353/**
1354 * Utility function that finds the filename part in a path.
1355 *
1356 * @returns Pointer to the file name part (this may be "").
1357 * @param pszPath The path to parse.
1358 */
1359static const char *FindFilenameInPath(const char *pszPath)
1360{
1361 const char *pszFilename = strchr(pszPath, '\0') - 1;
1362 while ( pszFilename > pszPath
1363 && !IS_SLASH_DRV(pszFilename[-1]))
1364 pszFilename--;
1365 return pszFilename;
1366}
1367
1368
1369/**
1370 * Utility function that combines a filename and a directory into a path.
1371 *
1372 * @returns malloced buffer containing the result.
1373 * @param pszName The file name.
1374 * @param pszDir The directory path.
1375 */
1376static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir)
1377{
1378 size_t cchName = strlen(pszName);
1379 size_t cchDir = strlen(pszDir);
1380 char *pszBuf = xmalloc(cchName + cchDir + 2);
1381 memcpy(pszBuf, pszDir, cchDir);
1382 if (cchDir > 0 && !IS_SLASH_DRV(pszDir[cchDir - 1]))
1383 pszBuf[cchDir++] = PATH_SLASH;
1384 memcpy(pszBuf + cchDir, pszName, cchName + 1);
1385 return pszBuf;
1386}
1387
1388
1389/**
1390 * Compares two path strings to see if they are identical.
1391 *
1392 * This doesn't do anything fancy, just the case ignoring and
1393 * slash unification.
1394 *
1395 * @returns 1 if equal, 0 otherwise.
1396 * @param pszPath1 The first path.
1397 * @param pszPath2 The second path.
1398 * @param cch The number of characters to compare.
1399 */
1400static int ArePathsIdentical(const char *pszPath1, const char *pszPath2, size_t cch)
1401{
1402#if defined(__OS2__) || defined(__WIN__)
1403 if (strnicmp(pszPath1, pszPath2, cch))
1404 {
1405 /* Slashes may differ, compare char by char. */
1406 const char *psz1 = pszPath1;
1407 const char *psz2 = pszPath2;
1408 for (;cch; psz1++, psz2++, cch--)
1409 {
1410 if (*psz1 != *psz2)
1411 {
1412 if ( tolower(*psz1) != tolower(*psz2)
1413 && toupper(*psz1) != toupper(*psz2)
1414 && *psz1 != '/'
1415 && *psz1 != '\\'
1416 && *psz2 != '/'
1417 && *psz2 != '\\')
1418 return 0;
1419 }
1420 }
1421 }
1422 return 1;
1423#else
1424 return !strncmp(pszPath1, pszPath2, cch);
1425#endif
1426}
1427
1428
1429/**
1430 * Calculate how to get to pszPath from pszDir.
1431 *
1432 * @returns The relative path from pszDir to path pszPath.
1433 * @param pszPath The path to the object.
1434 * @param pszDir The directory it shall be relative to.
1435 */
1436static char *CalcRelativeName(const char *pszPath, const char *pszDir)
1437{
1438 char *pszRet = NULL;
1439 char *pszAbsPath = NULL;
1440 size_t cchDir = strlen(pszDir);
1441
1442 /*
1443 * This is indeed a bit tricky, so we'll try the easy way first...
1444 */
1445 if (ArePathsIdentical(pszPath, pszDir, cchDir))
1446 {
1447 if (pszPath[cchDir])
1448 pszRet = (char *)pszPath + cchDir;
1449 else
1450 pszRet = "./";
1451 }
1452 else
1453 {
1454 pszAbsPath = AbsPath(pszPath);
1455 if (ArePathsIdentical(pszAbsPath, pszDir, cchDir))
1456 {
1457 if (pszPath[cchDir])
1458 pszRet = pszAbsPath + cchDir;
1459 else
1460 pszRet = "./";
1461 }
1462 }
1463 if (pszRet)
1464 {
1465 while (IS_SLASH_DRV(*pszRet))
1466 pszRet++;
1467 pszRet = xstrdup(pszRet);
1468 free(pszAbsPath);
1469 return pszRet;
1470 }
1471
1472 /*
1473 * Damn, it's gonna be complicated. Deal with that later.
1474 */
1475 fprintf(stderr, "kObjCache: complicated relative path stuff isn't implemented yet. sorry.\n");
1476 exit(1);
1477 return NULL;
1478}
1479
1480
1481/**
1482 * Utility function that combines a filename and directory and passes it onto fopen.
1483 *
1484 * @returns fopen return value.
1485 * @param pszName The file name.
1486 * @param pszDir The directory path.
1487 * @param pszMode The fopen mode string.
1488 */
1489static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode)
1490{
1491 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1492 FILE *pFile = fopen(pszPath, pszMode);
1493 free(pszPath);
1494 return pFile;
1495}
1496
1497
1498/**
1499 * Deletes a file in a directory.
1500 *
1501 * @returns whatever unlink returns.
1502 * @param pszName The file name.
1503 * @param pszDir The directory path.
1504 */
1505static int UnlinkFileInDir(const char *pszName, const char *pszDir)
1506{
1507 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1508 int rc = unlink(pszPath);
1509 free(pszPath);
1510 return rc;
1511}
1512
1513
1514/**
1515 * Renames a file in a directory.
1516 *
1517 * @returns whatever unlink returns.
1518 * @param pszOldName The new file name.
1519 * @param pszNewName The old file name.
1520 * @param pszDir The directory path.
1521 */
1522static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir)
1523{
1524 char *pszOldPath = MakePathFromDirAndFile(pszOldName, pszDir);
1525 char *pszNewPath = MakePathFromDirAndFile(pszNewName, pszDir);
1526 int rc = rename(pszOldPath, pszNewPath);
1527 free(pszOldPath);
1528 free(pszNewPath);
1529 return rc;
1530}
1531
1532
1533/**
1534 * Check if a (regular) file exists in a directory.
1535 *
1536 * @returns 1 if it exists and is a regular file, 0 if not.
1537 * @param pszName The file name.
1538 * @param pszDir The directory path.
1539 */
1540static int DoesFileInDirExist(const char *pszName, const char *pszDir)
1541{
1542 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1543 struct stat st;
1544 int rc = stat(pszPath, &st);
1545 free(pszPath);
1546#ifdef S_ISREG
1547 return !rc && S_ISREG(st.st_mode);
1548#elif defined(_MSC_VER)
1549 return !rc && (st.st_mode & _S_IFMT) == _S_IFREG;
1550#else
1551#error "Port me"
1552#endif
1553}
1554
1555
1556/**
1557 * Reads into memory an entire file.
1558 *
1559 * @returns Pointer to the heap allocation containing the file.
1560 * On failure NULL and errno is returned.
1561 * @param pszName The file.
1562 * @param pszDir The directory the file resides in.
1563 * @param pcbFile Where to store the file size.
1564 */
1565static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile)
1566{
1567 int SavedErrno;
1568 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1569 int fd = open(pszPath, O_RDONLY | O_BINARY);
1570 if (fd >= 0)
1571 {
1572 off_t cbFile = lseek(fd, 0, SEEK_END);
1573 if ( cbFile >= 0
1574 && lseek(fd, 0, SEEK_SET) == 0)
1575 {
1576 char *pb = malloc(cbFile + 1);
1577 if (pb)
1578 {
1579 if (read(fd, pb, cbFile) == cbFile)
1580 {
1581 close(fd);
1582 pb[cbFile] = '\0';
1583 *pcbFile = (size_t)cbFile;
1584 return pb;
1585 }
1586 SavedErrno = errno;
1587 free(pb);
1588 }
1589 else
1590 SavedErrno = ENOMEM;
1591 }
1592 else
1593 SavedErrno = errno;
1594 close(fd);
1595 }
1596 else
1597 SavedErrno = errno;
1598 free(pszPath);
1599 errno = SavedErrno;
1600 return NULL;
1601}
1602
1603
1604static void *xmalloc(size_t cb)
1605{
1606 void *pv = malloc(cb);
1607 if (!pv)
1608 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
1609 return pv;
1610}
1611
1612
1613static void *xrealloc(void *pvOld, size_t cb)
1614{
1615 void *pv = realloc(pvOld, cb);
1616 if (!pv)
1617 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
1618 return pv;
1619}
1620
1621
1622static char *xstrdup(const char *pszIn)
1623{
1624 char *psz = strdup(pszIn);
1625 if (!psz)
1626 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)strlen(pszIn));
1627 return psz;
1628}
1629
1630
1631/**
1632 * Prints a syntax error and returns the appropriate exit code
1633 *
1634 * @returns approriate exit code.
1635 * @param pszFormat The syntax error message.
1636 * @param ... Message args.
1637 */
1638static int SyntaxError(const char *pszFormat, ...)
1639{
1640 va_list va;
1641 fprintf(stderr, "kObjCache: syntax error: ");
1642 va_start(va, pszFormat);
1643 vfprintf(stderr, pszFormat, va);
1644 va_end(va);
1645 return 1;
1646}
1647
1648
1649/**
1650 * Prints the usage.
1651 * @returns 0.
1652 */
1653static int usage(void)
1654{
1655 printf("syntax: kObjCache [-v|--verbose] [-f|--file] <cache-file> [-V|--version] [-r|--redir-stdout]\n"
1656 " --kObjCache-cpp <filename> <precompiler + args> \n"
1657 " --kObjCache-cc <object> <compiler + args>\n"
1658 " [--kObjCache-both [args]]\n"
1659 " [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
1660 "\n");
1661 return 0;
1662}
1663
1664
1665int main(int argc, char **argv)
1666{
1667 PKOBJCACHE pEntry;
1668
1669 const char *pszCacheFile;
1670
1671 const char **papszArgvPreComp = NULL;
1672 unsigned cArgvPreComp = 0;
1673 const char *pszPreCompName = NULL;
1674 int fRedirStdOut = 0;
1675
1676 const char **papszArgvCompile = NULL;
1677 unsigned cArgvCompile = 0;
1678 const char *pszObjName = NULL;
1679
1680 enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
1681
1682 int i;
1683
1684 /*
1685 * Parse the arguments.
1686 */
1687 for (i = 1; i < argc; i++)
1688 {
1689 if (!strcmp(argv[i], "--kObjCache-cpp"))
1690 {
1691 enmMode = kOC_CppArgv;
1692 if (!pszPreCompName)
1693 {
1694 if (++i >= argc)
1695 return SyntaxError("--kObjCache-cpp requires an object filename!\n");
1696 pszPreCompName = argv[i];
1697 }
1698 }
1699 else if (!strcmp(argv[i], "--kObjCache-cc"))
1700 {
1701 enmMode = kOC_CcArgv;
1702 if (!pszObjName)
1703 {
1704 if (++i >= argc)
1705 return SyntaxError("--kObjCache-cc requires an precompiler output filename!\n");
1706 pszObjName = argv[i];
1707 }
1708 }
1709 else if (!strcmp(argv[i], "--kObjCache-both"))
1710 enmMode = kOC_BothArgv;
1711 else if (!strcmp(argv[i], "--help"))
1712 return usage();
1713 else if (enmMode != kOC_Options)
1714 {
1715 if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv)
1716 {
1717 if (!(cArgvPreComp % 16))
1718 papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0]));
1719 papszArgvPreComp[cArgvPreComp++] = argv[i];
1720 papszArgvPreComp[cArgvPreComp] = NULL;
1721 }
1722 if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv)
1723 {
1724 if (!(cArgvCompile % 16))
1725 papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0]));
1726 papszArgvCompile[cArgvCompile++] = argv[i];
1727 papszArgvCompile[cArgvCompile] = NULL;
1728 }
1729 }
1730 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file"))
1731 {
1732 if (i + 1 >= argc)
1733 return SyntaxError("%s requires a cache filename!\n", argv[i]);
1734 pszCacheFile = argv[++i];
1735 }
1736 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
1737 fRedirStdOut = 1;
1738 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
1739 g_fVerbose = 1;
1740 else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
1741 g_fVerbose = 0;
1742 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?"))
1743 return usage();
1744 else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version"))
1745 {
1746 printf("kObjCache v0.0.0 ($Revision: 1038 $)\n");
1747 return 0;
1748 }
1749 else
1750 return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
1751 }
1752 if (!pszCacheFile)
1753 return SyntaxError("No cache file name (-f)\n");
1754 if (!cArgvCompile)
1755 return SyntaxError("No compiler arguments (--kObjCache-cc)\n");
1756 if (!cArgvPreComp)
1757 return SyntaxError("No precompiler arguments (--kObjCache-cc)\n");
1758
1759 /*
1760 * Create a cache entry from the cache file (if found).
1761 */
1762 pEntry = kObjCacheCreate(pszCacheFile);
1763 kObjCacheRead(pEntry);
1764
1765 /*
1766 * Do the compiling.
1767 */
1768 kObjCachePreCompile(pEntry, papszArgvPreComp, cArgvPreComp, pszPreCompName, fRedirStdOut);
1769 kObjCacheCompileIfNeeded(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
1770
1771 /*
1772 * Write the cache file.
1773 */
1774 kObjCacheWrite(pEntry);
1775 /* kObjCacheDestroy(pEntry); - don't bother */
1776 return 0;
1777}
1778
1779
1780/** @page kObjCache Benchmarks.
1781 *
1782 * 2007-06-02 - 21-23:00:
1783 * Mac OS X debug -j 3 clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
1784 * real 10m26.077s
1785 * user 13m13.291s
1786 * sys 2m58.193s
1787 *
1788 * Mac OS X debug -j 3 depend build (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
1789 * real 3m55.275s
1790 * user 4m11.852s
1791 * sys 0m54.931s
1792 *
1793 * Mac OS X debug -j 3 cached clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
1794 * real 11m42.513s
1795 * user 14m27.736s
1796 * sys 3m39.512s
1797 *
1798 * Mac OS X debug -j 3 cached depend build (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
1799 * real 1m17.445s
1800 * user 1m13.410s
1801 * sys 0m22.789s
1802 *
1803 * Mac OS X debug -j3 cached depend build (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
1804 * real 1m29.315s
1805 * user 1m31.391s
1806 * sys 0m32.748s
1807 *
1808 */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette