source: trunk/src/kObjCache/kObjCache.c@ 1017

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

Implemented an usable method for comparing files and avoiding changes in blank spaces (#defines and stuff). It has a tiny deficiency on VCC dependencies, but we can live with that.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 55.7 KB
Line 
1/* $Id: kObjCache.c 1017 2007-06-03 02:49:52Z bird $ */
2/** @file
3 *
4 * kObjCache - Object Cache.
5 *
6 * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net>
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 assert(off == cbAlloc);
685 cbLeft = 4*1024*1024;
686 cbAlloc += cbLeft;
687 *ppszOutput = xrealloc(*ppszOutput, cbAlloc);
688 psz = *ppszOutput + off;
689 }
690 }
691 close(fds[0]);
692 *pcchOutput = cbAlloc - cbLeft;
693
694 /*
695 * Reap the child.
696 */
697#ifdef __WIN__
698 pidWait = _cwait(&iStatus, pid, _WAIT_CHILD);
699 if (pidWait == -1)
700 kObjCacheFatal(pEntry, "%s - waitpid failed: %s\n",
701 pszMsg, strerror(errno));
702 if (iStatus)
703 kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, iStatus);
704#else
705 pidWait = waitpid(pid, &iStatus, 0);
706 while (pidWait < 0 && errno == EINTR)
707 pidWait = waitpid(pid, &iStatus, 0);
708 if (pidWait != pid)
709 kObjCacheFatal(pEntry, "%s - waitpid failed rc=%d: %s\n",
710 pszMsg, pidWait, strerror(errno));
711 if (!WIFEXITED(iStatus))
712 kObjCacheFatal(pEntry, "%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
713 if (WEXITSTATUS(iStatus))
714 kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
715#endif
716 (void)cArgv;
717}
718#endif /* USE_PIPE */
719
720
721/**
722 * Reads the (new) output of the precompiler.
723 *
724 * Not used when using pipes.
725 *
726 * @param pEntry The cache entry. cbNewCpp and pszNewCppMapping will be updated.
727 */
728static void kObjCacheReadPrecompileOutput(PKOBJCACHE pEntry)
729{
730 pEntry->pszNewCppMapping = ReadFileInDir(pEntry->pszNewCppName, pEntry->pszDir, &pEntry->cbNewCpp);
731 if (!pEntry->pszNewCppMapping)
732 kObjCacheFatal(pEntry, "failed to open/read '%s' in '%s': %s\n",
733 pEntry->pszNewCppName, pEntry->pszDir, strerror(errno));
734 kObjCacheVerbose(pEntry, "precompiled file is %lu bytes long\n", (unsigned long)pEntry->cbNewCpp);
735}
736
737
738/**
739 * Worker for kObjCachePreCompile and calculates the checksum of
740 * the precompiler output.
741 *
742 * @param pEntry The cache entry. NewSum will be updated.
743 */
744static void kObjCacheCalcChecksum(PKOBJCACHE pEntry)
745{
746 struct MD5Context MD5Ctx;
747
748 memset(&pEntry->NewSum, 0, sizeof(pEntry->NewSum));
749 pEntry->NewSum.crc32 = crc32(0, pEntry->pszNewCppMapping, pEntry->cbNewCpp);
750 MD5Init(&MD5Ctx);
751 MD5Update(&MD5Ctx, (unsigned char *)pEntry->pszNewCppMapping, pEntry->cbNewCpp);
752 MD5Final(&pEntry->NewSum.md5[0], &MD5Ctx);
753 kObjCacheVerbose(pEntry, "crc32=%#lx md5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
754 pEntry->NewSum.crc32,
755 pEntry->NewSum.md5[0], pEntry->NewSum.md5[1], pEntry->NewSum.md5[2], pEntry->NewSum.md5[3],
756 pEntry->NewSum.md5[4], pEntry->NewSum.md5[5], pEntry->NewSum.md5[6], pEntry->NewSum.md5[7],
757 pEntry->NewSum.md5[8], pEntry->NewSum.md5[9], pEntry->NewSum.md5[10], pEntry->NewSum.md5[11],
758 pEntry->NewSum.md5[12], pEntry->NewSum.md5[13], pEntry->NewSum.md5[14], pEntry->NewSum.md5[15]);
759
760}
761
762
763/**
764 * Run the precompiler and calculate the checksum of the output.
765 *
766 * @param pEntry The cache entry.
767 * @param papszArgvPreComp The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
768 * @param cArgvPreComp The number of arguments.
769 * @param pszPreCompName Precompile output name. (must kick around)
770 * @param fRedirStdOut Whether stdout needs to be redirected or not.
771 */
772static void kObjCachePreCompile(PKOBJCACHE pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp, const char *pszPreCompName, int fRedirStdOut)
773{
774#ifdef USE_PIPE
775 /*
776 * Flag it as piped or non-piped.
777 */
778 if (fRedirStdOut)
779 pEntry->fPiped = 1;
780 else
781#endif
782 pEntry->fPiped = 0;
783
784 /*
785 * Rename the old precompiled output to '-old'.
786 * We'll discard the old output and keep the new output, but because
787 * we might with to do a quick matchup later we can't remove it just now.
788 * If we're using the pipe strategy, we will not do any renaming.
789 */
790 if ( pEntry->pszOldCppName
791 && !pEntry->fPiped
792 && DoesFileInDirExist(pEntry->pszOldCppName, pEntry->pszDir))
793 {
794 size_t cch = strlen(pEntry->pszOldCppName);
795 char *psz = xmalloc(cch + sizeof("-old"));
796 memcpy(psz, pEntry->pszOldCppName, cch);
797 memcpy(psz + cch, "-old", sizeof("-old"));
798
799 kObjCacheVerbose(pEntry, "renaming '%s' to '%s' in '%s'\n", pEntry->pszOldCppName, psz, pEntry->pszDir);
800 UnlinkFileInDir(psz, pEntry->pszDir);
801 if (RenameFileInDir(pEntry->pszOldCppName, psz, pEntry->pszDir))
802 kObjCacheFatal(pEntry, "failed to rename '%s' -> '%s' in '%s': %s\n",
803 pEntry->pszOldCppName, psz, pEntry->pszDir, strerror(errno));
804 free(pEntry->pszOldCppName);
805 pEntry->pszOldCppName = psz;
806 }
807 pEntry->pszNewCppName = CalcRelativeName(pszPreCompName, pEntry->pszDir);
808
809 /*
810 * Precompile it and calculate the checksum on the output.
811 */
812 kObjCacheVerbose(pEntry, "precompiling -> '%s'...\n", pEntry->pszNewCppName);
813#ifdef USE_PIPE
814 if (pEntry->fPiped)
815 kObjCacheSpawnPipe(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", &pEntry->pszNewCppMapping, &pEntry->cbNewCpp);
816 else
817#endif
818 {
819 if (fRedirStdOut)
820 kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", pszPreCompName);
821 else
822 kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
823 kObjCacheReadPrecompileOutput(pEntry);
824 }
825 kObjCacheCalcChecksum(pEntry);
826}
827
828
829/**
830 * Check whether the string is a '#line' statement.
831 *
832 * @returns 1 if it is, 0 if it isn't.
833 * @param psz The line to examin.
834 * @parma piLine Where to store the line number.
835 * @parma ppszFile Where to store the start of the filename.
836 */
837static int IsLineStatement(const char *psz, unsigned *piLine, const char **ppszFile)
838{
839 unsigned iLine;
840
841 /* Expect a hash. */
842 if (*psz++ != '#')
843 return 0;
844
845 /* Skip blanks between '#' and the line / number */
846 while (*psz == ' ' || *psz == '\t')
847 psz++;
848
849 /* Skip the 'line' if present. */
850 if (!strncmp(psz, "line", sizeof("line") - 1))
851 psz += sizeof("line");
852
853 /* Expect a line number now. */
854 if ((unsigned char)(*psz - '0') > 9)
855 return 0;
856 iLine = 0;
857 do
858 {
859 iLine *= 10;
860 iLine += (*psz - '0');
861 psz++;
862 }
863 while ((unsigned char)(*psz - '0') <= 9);
864
865 /* Expect one or more space now. */
866 if (*psz != ' ' && *psz != '\t')
867 return 0;
868 do psz++;
869 while (*psz == ' ' || *psz == '\t');
870
871 /* that's good enough. */
872 *piLine = iLine;
873 *ppszFile = psz;
874 return 1;
875}
876
877
878/**
879 * Scan backwards for the previous #line statement.
880 *
881 * @returns The filename in the previous statement.
882 * @param pszStart Where to start.
883 * @param pszStop Where to stop. Less than pszStart.
884 * @param piLine The line number count to adjust.
885 */
886static const char *FindFileStatement(const char *pszStart, const char *pszStop, unsigned *piLine)
887{
888 unsigned iLine = *piLine;
889 assert(pszStart >= pszStop);
890 while (pszStart >= pszStop)
891 {
892 if (*pszStart == '\n')
893 iLine++;
894 else if (*pszStart == '#')
895 {
896 unsigned iLineTmp;
897 const char *pszFile;
898 const char *psz = pszStart - 1;
899 while (psz >= pszStop && (*psz == ' ' || *psz =='\t'))
900 psz--;
901 if ( (psz < pszStop || *psz == '\n')
902 && IsLineStatement(pszStart, &iLineTmp, &pszFile))
903 {
904 *piLine = iLine + iLineTmp - 1;
905 return pszFile;
906 }
907 }
908 pszStart--;
909 }
910 return NULL;
911}
912
913
914/**
915 * Worker for kObjCacheCompareOldAndNewOutput() that compares the
916 * precompiled output using a fast but not very good method.
917 *
918 * @returns 1 if matching, 0 if not matching.
919 * @param pEntry The entry containing the names of the files to compare.
920 * The entry is not updated in any way.
921 */
922static int kObjCacheCompareFast(PCKOBJCACHE pEntry)
923{
924 const char * psz1 = pEntry->pszNewCppMapping;
925 const char * const pszEnd1 = psz1 + pEntry->cbNewCpp;
926 const char * psz2 = pEntry->pszOldCppMapping;
927 const char * const pszEnd2 = psz2 + pEntry->cbOldCpp;
928
929 assert(*pszEnd1 == '\0');
930 assert(*pszEnd2 == '\0');
931
932 /*
933 * Iterate block by block and backtrack when we find a difference.
934 */
935 for (;;)
936 {
937 size_t cch = pszEnd1 - psz1;
938 if (cch > (size_t)(pszEnd2 - psz2))
939 cch = pszEnd2 - psz2;
940 if (cch > 4096)
941 cch = 4096;
942 if ( cch
943 && !memcmp(psz1, psz2, cch))
944 {
945 /* no differences */
946 psz1 += cch;
947 psz2 += cch;
948 }
949 else
950 {
951 /*
952 * Pinpoint the difference exactly and the try find the start
953 * of that line. Then skip forward until we find something to
954 * work on that isn't spaces or #line statements. Since we
955 * might be skipping a few new empty headers, it is possible
956 * that we will omit this header from the dependencies when
957 * using VCC. But I think that's a reasonable trade off for
958 * a simple algorithm.
959 */
960 const char *psz;
961 const char *pszMismatch1;
962 const char *pszFile1 = NULL;
963 unsigned iLine1 = 0;
964 const char *pszMismatch2;
965 const char *pszFile2 = NULL;
966 unsigned iLine2 = 0;
967
968 /* locate the difference. */
969 while (cch >= 512 && !memcmp(psz1, psz2, 512))
970 psz1 += 512, psz2 += 512, cch -= 512;
971 while (cch >= 64 && !memcmp(psz1, psz2, 64))
972 psz1 += 64, psz2 += 64, cch -= 64;
973 while (*psz1 == *psz2 && cch > 0)
974 psz1++, psz2++, cch--;
975
976 /* locate the start of that line. */
977 psz = psz1;
978 while ( psz > pEntry->pszNewCppMapping
979 && psz[-1] != '\n')
980 psz--;
981 psz2 -= (psz1 - psz);
982 pszMismatch2 = psz2;
983 pszMismatch1 = psz1 = psz;
984
985 /* Parse the 1st file line by line. */
986 while (psz1 < pszEnd1)
987 {
988 if (*psz1 == '\n')
989 {
990 psz1++;
991 iLine1++;
992 }
993 else
994 {
995 psz = psz1;
996 while (isspace(*psz) && *psz != '\n')
997 psz++;
998 if (*psz == '\n')
999 {
1000 psz1 = psz + 1;
1001 iLine1++;
1002 }
1003 else if (*psz == '#' && IsLineStatement(psz, &iLine1, &pszFile1))
1004 {
1005 psz1 = memchr(psz, '\n', pszEnd1 - psz);
1006 if (!psz1++)
1007 psz1 = pszEnd1;
1008 }
1009 else if (psz == pszEnd1)
1010 psz1 = psz;
1011 else /* found something that can be compared. */
1012 break;
1013 }
1014 }
1015
1016 /* Ditto for the 2nd file. */
1017 while (psz2 < pszEnd2)
1018 {
1019 if (*psz2 == '\n')
1020 {
1021 psz2++;
1022 iLine2++;
1023 }
1024 else
1025 {
1026 psz = psz2;
1027 while (isspace(*psz) && *psz != '\n')
1028 psz++;
1029 if (*psz == '\n')
1030 {
1031 psz2 = psz + 1;
1032 iLine2++;
1033 }
1034 else if (*psz == '#' && IsLineStatement(psz, &iLine2, &pszFile2))
1035 {
1036 psz2 = memchr(psz, '\n', pszEnd2 - psz);
1037 if (!psz2++)
1038 psz2 = pszEnd2;
1039 }
1040 else if (psz == pszEnd2)
1041 psz2 = psz;
1042 else /* found something that can be compared. */
1043 break;
1044 }
1045 }
1046
1047 /* Reaching the end of any of them means the return statement can decide. */
1048 if ( psz1 == pszEnd1
1049 || psz2 == pszEnd2)
1050 break;
1051
1052 /* Match the current line. */
1053 psz = memchr(psz1, '\n', pszEnd1 - psz1);
1054 if (!psz)
1055 psz = pszEnd1;
1056 cch = psz - psz1;
1057 if (psz2 + cch > pszEnd2)
1058 break;
1059 if (memcmp(psz1, psz2, cch))
1060 break;
1061
1062 /* Check that we're at the same location now. */
1063 if (!pszFile1)
1064 pszFile1 = FindFileStatement(pszMismatch1, pEntry->pszNewCppMapping, &iLine1);
1065 if (!pszFile2)
1066 pszFile2 = FindFileStatement(pszMismatch2, pEntry->pszOldCppMapping, &iLine2);
1067 if (pszFile1 && pszFile2)
1068 {
1069 if (iLine1 != iLine2)
1070 break;
1071 while (*pszFile1 == *pszFile2 && *pszFile1 != '\n' && *pszFile1)
1072 pszFile1++, pszFile2++;
1073 if (*pszFile1 != *pszFile2)
1074 break;
1075 }
1076 else if (pszFile1 || pszFile2)
1077 {
1078 assert(0); /* this shouldn't happen. */
1079 break;
1080 }
1081
1082 /* Try align psz1 on 8 or 4 bytes so at least one of the buffers are aligned. */
1083 psz1 += cch;
1084 psz2 += cch;
1085 if (cch >= ((uintptr_t)psz1 & 7))
1086 {
1087 psz2 -= ((uintptr_t)psz1 & 7);
1088 psz1 -= ((uintptr_t)psz1 & 7);
1089 }
1090 else if (cch >= ((uintptr_t)psz1 & 3))
1091 {
1092 psz2 -= ((uintptr_t)psz1 & 3);
1093 psz1 -= ((uintptr_t)psz1 & 3);
1094 }
1095 }
1096 }
1097
1098 return psz1 == pszEnd1
1099 && psz2 == pszEnd2;
1100}
1101
1102
1103/**
1104 * Worker for kObjCacheCompileIfNeeded that compares the
1105 * precompiled output.
1106 *
1107 * @returns 1 if matching, 0 if not matching.
1108 * @param pEntry The entry containing the names of the files to compare.
1109 * This will load the old cpp output (changing pszOldCppName and cbOldCpp).
1110 */
1111static int kObjCacheCompareOldAndNewOutput(PKOBJCACHE pEntry)
1112{
1113 /** @todo do some quick but fancy comparing that determins whether code
1114 * has actually changed or moved. We can ignore declarations and typedefs that
1115 * has just been moved up/down a bit. The typical case is adding a new error
1116 * #define that doesn't influence the current compile job. */
1117
1118 /*
1119 * Load the old output.
1120 */
1121 pEntry->pszOldCppMapping = ReadFileInDir(pEntry->pszOldCppName, pEntry->pszDir, &pEntry->cbOldCpp);
1122 if (!pEntry->pszOldCppMapping)
1123 {
1124 kObjCacheVerbose(pEntry, "failed to read old cpp file ('%s' in '%s'): %s\n",
1125 pEntry->pszOldCppName, pEntry->pszDir, strerror(errno));
1126 return 0;
1127 }
1128
1129 /*
1130 * I may implement a more sophisticated alternative method later... maybe.
1131 */
1132 //if ()
1133 // return kObjCacheCompareBest(pEntry);
1134 return kObjCacheCompareFast(pEntry);
1135}
1136
1137
1138/**
1139 * Worker for kObjCacheCompileIfNeeded that does the actual (re)compilation.
1140 *
1141 * @returns 1 if matching, 0 if not matching.
1142 * @param pEntry The cache entry.
1143 * @param papszArgvCompile The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
1144 * @param cArgvCompile The number of arguments in the vector.
1145 * @param pszObjName The name of the object file.
1146 */
1147static void kObjCacheCompileIt(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
1148{
1149 /*
1150 * Delete the old object file and precompiler output.
1151 */
1152 if (pEntry->pszObjName)
1153 {
1154 UnlinkFileInDir(pEntry->pszObjName, pEntry->pszDir);
1155 pEntry->pszObjName = NULL;
1156 }
1157 pEntry->pszNewObjName = CalcRelativeName(pszObjName, pEntry->pszDir);
1158
1159 /*
1160 * If we executed the precompiled in piped mode we'll have to write the
1161 * precompiler output to disk so the compile has some thing to chew on.
1162 */
1163 if (pEntry->fPiped)
1164 {
1165 FILE *pFile = FOpenFileInDir(pEntry->pszNewCppName, pEntry->pszDir, "wb");
1166 if (!pFile)
1167 kObjCacheFatal(pEntry, "failed to create file '%s' in '%s': %s\n",
1168 pEntry->pszNewCppName, pEntry->pszDir, strerror(errno));
1169 if (fwrite(pEntry->pszNewCppMapping, pEntry->cbNewCpp, 1, pFile) != 1)
1170 kObjCacheFatal(pEntry, "fwrite failed: %s\n", strerror(errno));
1171 if (fclose(pFile))
1172 kObjCacheFatal(pEntry, "fclose failed: %s\n", strerror(errno));
1173 }
1174
1175 /*
1176 * Release buffers we no longer need before starting the compile.
1177 */
1178 free(pEntry->pszNewCppMapping);
1179 pEntry->pszNewCppMapping = NULL;
1180 free(pEntry->pszOldCppMapping);
1181 pEntry->pszOldCppMapping = NULL;
1182
1183 /*
1184 * Do the recompilation.
1185 */
1186 kObjCacheVerbose(pEntry, "compiling -> '%s'...\n", pEntry->pszNewObjName);
1187 pEntry->papszArgvCompile = (char **)papszArgvCompile; /* LEAK */
1188 pEntry->cArgvCompile = cArgvCompile;
1189 kObjCacheSpawn(pEntry, papszArgvCompile, cArgvCompile, "compile", NULL);
1190}
1191
1192
1193/**
1194 * Check if (re-)compilation is required and do it.
1195 *
1196 * @returns 1 if matching, 0 if not matching.
1197 * @param pEntry The cache entry.
1198 * @param papszArgvCompile The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
1199 * @param cArgvCompile The number of arguments in the vector.
1200 * @param pszObjName The name of the object file.
1201 */
1202static void kObjCacheCompileIfNeeded(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
1203{
1204 /*
1205 * Does the object name differ?
1206 */
1207 if (!pEntry->fNeedCompiling)
1208 {
1209 char *pszTmp = CalcRelativeName(pszObjName, pEntry->pszDir);
1210 if (strcmp(pEntry->pszObjName, pszTmp))
1211 {
1212 pEntry->fNeedCompiling = 1;
1213 kObjCacheVerbose(pEntry, "object name changed '%s' -> '%s'\n", pEntry->pszObjName, pszTmp);
1214 }
1215 free(pszTmp);
1216 }
1217
1218 /*
1219 * Does the compile command differ?
1220 * TODO: Ignore irrelevant options here (like warning level).
1221 */
1222 if ( !pEntry->fNeedCompiling
1223 && pEntry->cArgvCompile != cArgvCompile)
1224 {
1225 pEntry->fNeedCompiling = 1;
1226 kObjCacheVerbose(pEntry, "compile argument count changed\n");
1227 }
1228 if (!pEntry->fNeedCompiling)
1229 {
1230 unsigned i;
1231 for (i = 0; i < cArgvCompile; i++)
1232 if (strcmp(papszArgvCompile[i], pEntry->papszArgvCompile[i]))
1233 {
1234 pEntry->fNeedCompiling = 1;
1235 kObjCacheVerbose(pEntry, "compile argument differs (%#d)\n", i);
1236 break;
1237 }
1238 }
1239
1240 /*
1241 * Does the object file exist?
1242 */
1243 if ( !pEntry->fNeedCompiling
1244 && !DoesFileInDirExist(pEntry->pszObjName, pEntry->pszDir))
1245 {
1246 pEntry->fNeedCompiling = 1;
1247 kObjCacheVerbose(pEntry, "object file doesn't exist\n");
1248 }
1249
1250 /*
1251 * Does the precompiled output differ in any significant way?
1252 */
1253 if (!pEntry->fNeedCompiling)
1254 {
1255 int fFound = 0;
1256 PCKOCSUM pSum;
1257 for (pSum = &pEntry->SumHead; pSum; pSum = pSum->pNext)
1258 if (kObjCacheSumIsEqual(pSum, &pEntry->NewSum))
1259 {
1260 fFound = 1;
1261 break;
1262 }
1263 if (!fFound)
1264 {
1265 kObjCacheVerbose(pEntry, "no checksum match - comparing output\n");
1266 if (!kObjCacheCompareOldAndNewOutput(pEntry))
1267 pEntry->fNeedCompiling = 1;
1268 else
1269 {
1270 /* insert the sum into the list. */
1271 pEntry->NewSum.pNext = pEntry->SumHead.pNext;
1272 pEntry->SumHead.pNext = &pEntry->NewSum;
1273 }
1274 }
1275 }
1276
1277 /*
1278 * Discard the old precompiled output if it's no longer needed.
1279 */
1280 if ( pEntry->pszOldCppName
1281 && ( !pEntry->fPiped
1282 || pEntry->fNeedCompiling))
1283 {
1284 UnlinkFileInDir(pEntry->pszOldCppName, pEntry->pszDir);
1285 free(pEntry->pszOldCppName);
1286 pEntry->pszOldCppName = NULL;
1287 }
1288
1289 /*
1290 * Do the compliation if found necessary.
1291 */
1292 if (pEntry->fNeedCompiling)
1293 kObjCacheCompileIt(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
1294}
1295
1296
1297/**
1298 * Gets the absolute path
1299 *
1300 * @returns A new heap buffer containing the absolute path.
1301 * @param pszPath The path to make absolute. (Readonly)
1302 */
1303static char *AbsPath(const char *pszPath)
1304{
1305 char szTmp[PATH_MAX];
1306#if defined(__OS2__) || defined(__WIN__)
1307 if (!_fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp)))
1308 return xstrdup(pszPath);
1309#else
1310 if (!realpath(pszPath, szTmp))
1311 return xstrdup(pszPath);
1312#endif
1313 return xstrdup(szTmp);
1314}
1315
1316
1317/**
1318 * Utility function that finds the filename part in a path.
1319 *
1320 * @returns Pointer to the file name part (this may be "").
1321 * @param pszPath The path to parse.
1322 */
1323static const char *FindFilenameInPath(const char *pszPath)
1324{
1325 const char *pszFilename = strchr(pszPath, '\0') - 1;
1326 while ( pszFilename > pszPath
1327 && !IS_SLASH_DRV(pszFilename[-1]))
1328 pszFilename--;
1329 return pszFilename;
1330}
1331
1332
1333/**
1334 * Utility function that combines a filename and a directory into a path.
1335 *
1336 * @returns malloced buffer containing the result.
1337 * @param pszName The file name.
1338 * @param pszDir The directory path.
1339 */
1340static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir)
1341{
1342 size_t cchName = strlen(pszName);
1343 size_t cchDir = strlen(pszDir);
1344 char *pszBuf = xmalloc(cchName + cchDir + 2);
1345 memcpy(pszBuf, pszDir, cchDir);
1346 if (cchDir > 0 && !IS_SLASH_DRV(pszDir[cchDir - 1]))
1347 pszBuf[cchDir++] = PATH_SLASH;
1348 memcpy(pszBuf + cchDir, pszName, cchName + 1);
1349 return pszBuf;
1350}
1351
1352
1353/**
1354 * Compares two path strings to see if they are identical.
1355 *
1356 * This doesn't do anything fancy, just the case ignoring and
1357 * slash unification.
1358 *
1359 * @returns 1 if equal, 0 otherwise.
1360 * @param pszPath1 The first path.
1361 * @param pszPath2 The second path.
1362 * @param cch The number of characters to compare.
1363 */
1364static int ArePathsIdentical(const char *pszPath1, const char *pszPath2, size_t cch)
1365{
1366#if defined(__OS2__) || defined(__WIN__)
1367 if (strnicmp(pszPath1, pszPath2, cch))
1368 {
1369 /* Slashes may differ, compare char by char. */
1370 const char *psz1 = pszPath1;
1371 const char *psz2 = pszPath2;
1372 for (;cch; psz1++, psz2++, cch--)
1373 {
1374 if (*psz1 != *psz2)
1375 {
1376 if ( tolower(*psz1) != tolower(*psz2)
1377 && toupper(*psz1) != toupper(*psz2)
1378 && *psz1 != '/'
1379 && *psz1 != '\\'
1380 && *psz2 != '/'
1381 && *psz2 != '\\')
1382 return 0;
1383 }
1384 }
1385 }
1386 return 1;
1387#else
1388 return !strncmp(pszPath1, pszPath2, cch);
1389#endif
1390}
1391
1392
1393/**
1394 * Calculate how to get to pszPath from pszDir.
1395 *
1396 * @returns The relative path from pszDir to path pszPath.
1397 * @param pszPath The path to the object.
1398 * @param pszDir The directory it shall be relative to.
1399 */
1400static char *CalcRelativeName(const char *pszPath, const char *pszDir)
1401{
1402 char *pszRet = NULL;
1403 char *pszAbsPath = NULL;
1404 size_t cchDir = strlen(pszDir);
1405
1406 /*
1407 * This is indeed a bit tricky, so we'll try the easy way first...
1408 */
1409 if (ArePathsIdentical(pszPath, pszDir, cchDir))
1410 {
1411 if (pszPath[cchDir])
1412 pszRet = (char *)pszPath + cchDir;
1413 else
1414 pszRet = "./";
1415 }
1416 else
1417 {
1418 pszAbsPath = AbsPath(pszPath);
1419 if (ArePathsIdentical(pszAbsPath, pszDir, cchDir))
1420 {
1421 if (pszPath[cchDir])
1422 pszRet = pszAbsPath + cchDir;
1423 else
1424 pszRet = "./";
1425 }
1426 }
1427 if (pszRet)
1428 {
1429 while (IS_SLASH_DRV(*pszRet))
1430 pszRet++;
1431 pszRet = xstrdup(pszRet);
1432 free(pszAbsPath);
1433 return pszRet;
1434 }
1435
1436 /*
1437 * Damn, it's gonna be complicated. Deal with that later.
1438 */
1439 fprintf(stderr, "kObjCache: complicated relative path stuff isn't implemented yet. sorry.\n");
1440 exit(1);
1441 return NULL;
1442}
1443
1444
1445/**
1446 * Utility function that combines a filename and directory and passes it onto fopen.
1447 *
1448 * @returns fopen return value.
1449 * @param pszName The file name.
1450 * @param pszDir The directory path.
1451 * @param pszMode The fopen mode string.
1452 */
1453static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode)
1454{
1455 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1456 FILE *pFile = fopen(pszPath, pszMode);
1457 free(pszPath);
1458 return pFile;
1459}
1460
1461
1462/**
1463 * Deletes a file in a directory.
1464 *
1465 * @returns whatever unlink returns.
1466 * @param pszName The file name.
1467 * @param pszDir The directory path.
1468 */
1469static int UnlinkFileInDir(const char *pszName, const char *pszDir)
1470{
1471 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1472 int rc = unlink(pszPath);
1473 free(pszPath);
1474 return rc;
1475}
1476
1477
1478/**
1479 * Renames a file in a directory.
1480 *
1481 * @returns whatever unlink returns.
1482 * @param pszOldName The new file name.
1483 * @param pszNewName The old file name.
1484 * @param pszDir The directory path.
1485 */
1486static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir)
1487{
1488 char *pszOldPath = MakePathFromDirAndFile(pszOldName, pszDir);
1489 char *pszNewPath = MakePathFromDirAndFile(pszNewName, pszDir);
1490 int rc = rename(pszOldPath, pszNewPath);
1491 free(pszOldPath);
1492 free(pszNewPath);
1493 return rc;
1494}
1495
1496
1497/**
1498 * Check if a (regular) file exists in a directory.
1499 *
1500 * @returns 1 if it exists and is a regular file, 0 if not.
1501 * @param pszName The file name.
1502 * @param pszDir The directory path.
1503 */
1504static int DoesFileInDirExist(const char *pszName, const char *pszDir)
1505{
1506 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1507 struct stat st;
1508 int rc = stat(pszPath, &st);
1509 free(pszPath);
1510#ifdef S_ISREG
1511 return !rc && S_ISREG(st.st_mode);
1512#elif defined(_MSC_VER)
1513 return !rc && (st.st_mode & _S_IFMT) == _S_IFREG;
1514#else
1515#error "Port me"
1516#endif
1517}
1518
1519
1520/**
1521 * Reads into memory an entire file.
1522 *
1523 * @returns Pointer to the heap allocation containing the file.
1524 * On failure NULL and errno is returned.
1525 * @param pszName The file.
1526 * @param pszDir The directory the file resides in.
1527 * @param pcbFile Where to store the file size.
1528 */
1529static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile)
1530{
1531 int SavedErrno;
1532 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1533 int fd = open(pszPath, O_RDONLY | O_BINARY);
1534 if (fd >= 0)
1535 {
1536 off_t cbFile = lseek(fd, 0, SEEK_END);
1537 if ( cbFile >= 0
1538 && lseek(fd, 0, SEEK_SET) == 0)
1539 {
1540 char *pb = malloc(cbFile + 1);
1541 if (pb)
1542 {
1543 if (read(fd, pb, cbFile) == cbFile)
1544 {
1545 close(fd);
1546 pb[cbFile] = '\0';
1547 *pcbFile = (size_t)cbFile;
1548 return pb;
1549 }
1550 SavedErrno = errno;
1551 free(pb);
1552 }
1553 else
1554 SavedErrno = ENOMEM;
1555 }
1556 else
1557 SavedErrno = errno;
1558 close(fd);
1559 }
1560 else
1561 SavedErrno = errno;
1562 free(pszPath);
1563 errno = SavedErrno;
1564 return NULL;
1565}
1566
1567
1568static void *xmalloc(size_t cb)
1569{
1570 void *pv = malloc(cb);
1571 if (!pv)
1572 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
1573 return pv;
1574}
1575
1576
1577static void *xrealloc(void *pvOld, size_t cb)
1578{
1579 void *pv = realloc(pvOld, cb);
1580 if (!pv)
1581 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
1582 return pv;
1583}
1584
1585
1586static char *xstrdup(const char *pszIn)
1587{
1588 char *psz = strdup(pszIn);
1589 if (!psz)
1590 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)strlen(pszIn));
1591 return psz;
1592}
1593
1594
1595/**
1596 * Prints a syntax error and returns the appropriate exit code
1597 *
1598 * @returns approriate exit code.
1599 * @param pszFormat The syntax error message.
1600 * @param ... Message args.
1601 */
1602static int SyntaxError(const char *pszFormat, ...)
1603{
1604 va_list va;
1605 fprintf(stderr, "kObjCache: syntax error: ");
1606 va_start(va, pszFormat);
1607 vfprintf(stderr, pszFormat, va);
1608 va_end(va);
1609 return 1;
1610}
1611
1612
1613/**
1614 * Prints the usage.
1615 * @returns 0.
1616 */
1617static int usage(void)
1618{
1619 printf("syntax: kObjCache [-v|--verbose] [-f|--file] <cache-file> [-V|--version] [-r|--redir-stdout]\n"
1620 " --kObjCache-cpp <filename> <precompiler + args> \n"
1621 " --kObjCache-cc <object> <compiler + args>\n"
1622 " [--kObjCache-both [args]]\n"
1623 " [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
1624 "\n");
1625 return 0;
1626}
1627
1628
1629int main(int argc, char **argv)
1630{
1631 PKOBJCACHE pEntry;
1632
1633 const char *pszCacheFile;
1634
1635 const char **papszArgvPreComp = NULL;
1636 unsigned cArgvPreComp = 0;
1637 const char *pszPreCompName = NULL;
1638 int fRedirStdOut = 0;
1639
1640 const char **papszArgvCompile = NULL;
1641 unsigned cArgvCompile = 0;
1642 const char *pszObjName = NULL;
1643
1644 enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
1645
1646 int i;
1647
1648 /*
1649 * Parse the arguments.
1650 */
1651 for (i = 1; i < argc; i++)
1652 {
1653 if (!strcmp(argv[i], "--kObjCache-cpp"))
1654 {
1655 enmMode = kOC_CppArgv;
1656 if (!pszPreCompName)
1657 {
1658 if (++i >= argc)
1659 return SyntaxError("--kObjCache-cpp requires an object filename!\n");
1660 pszPreCompName = argv[i];
1661 }
1662 }
1663 else if (!strcmp(argv[i], "--kObjCache-cc"))
1664 {
1665 enmMode = kOC_CcArgv;
1666 if (!pszObjName)
1667 {
1668 if (++i >= argc)
1669 return SyntaxError("--kObjCache-cc requires an precompiler output filename!\n");
1670 pszObjName = argv[i];
1671 }
1672 }
1673 else if (!strcmp(argv[i], "--kObjCache-both"))
1674 enmMode = kOC_BothArgv;
1675 else if (!strcmp(argv[i], "--help"))
1676 return usage();
1677 else if (enmMode != kOC_Options)
1678 {
1679 if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv)
1680 {
1681 if (!(cArgvPreComp % 16))
1682 papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0]));
1683 papszArgvPreComp[cArgvPreComp++] = argv[i];
1684 papszArgvPreComp[cArgvPreComp] = NULL;
1685 }
1686 if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv)
1687 {
1688 if (!(cArgvCompile % 16))
1689 papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0]));
1690 papszArgvCompile[cArgvCompile++] = argv[i];
1691 papszArgvCompile[cArgvCompile] = NULL;
1692 }
1693 }
1694 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file"))
1695 {
1696 if (i + 1 >= argc)
1697 return SyntaxError("%s requires a cache filename!\n", argv[i]);
1698 pszCacheFile = argv[++i];
1699 }
1700 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
1701 fRedirStdOut = 1;
1702 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
1703 g_fVerbose = 1;
1704 else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
1705 g_fVerbose = 0;
1706 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?"))
1707 return usage();
1708 else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version"))
1709 {
1710 printf("kObjCache v0.0.0 ($Revision: 1017 $)\n");
1711 return 0;
1712 }
1713 else
1714 return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
1715 }
1716 if (!pszCacheFile)
1717 return SyntaxError("No cache file name (-f)\n");
1718 if (!cArgvCompile)
1719 return SyntaxError("No compiler arguments (--kObjCache-cc)\n");
1720 if (!cArgvPreComp)
1721 return SyntaxError("No precompiler arguments (--kObjCache-cc)\n");
1722
1723 /*
1724 * Create a cache entry from the cache file (if found).
1725 */
1726 pEntry = kObjCacheCreate(pszCacheFile);
1727 kObjCacheRead(pEntry);
1728
1729 /*
1730 * Do the compiling.
1731 */
1732 kObjCachePreCompile(pEntry, papszArgvPreComp, cArgvPreComp, pszPreCompName, fRedirStdOut);
1733 kObjCacheCompileIfNeeded(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
1734
1735 /*
1736 * Write the cache file.
1737 */
1738 kObjCacheWrite(pEntry);
1739 /* kObjCacheDestroy(pEntry); - don't bother */
1740 return 0;
1741}
1742
1743
1744/** @page kObjCache Benchmarks.
1745 *
1746 * 2007-06-02 - 21-23:00:
1747 * Mac OS X debug -j 3 clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
1748 * real 10m26.077s
1749 * user 13m13.291s
1750 * sys 2m58.193s
1751 *
1752 * Mac OS X debug -j 3 depend build (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3):
1753 * real 3m55.275s
1754 * user 4m11.852s
1755 * sys 0m54.931s
1756 *
1757 * 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):
1758 * real 11m42.513s
1759 * user 14m27.736s
1760 * sys 3m39.512s
1761 *
1762 * Mac OS X debug -j 3 cached clobber build (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1):
1763 * real 2m24.712s real 2m10.909s
1764 * user 2m15.339s user 2m15.146s
1765 * sys 0m56.278s sys 0m55.591s
1766 * !Stuff is built three times here because of + instead of +| in footer.kmk!
1767 *
1768 */
Note: See TracBrowser for help on using the repository browser.