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

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

debugging. kind of working now...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 39.9 KB
Line 
1/* $Id: kObjCache.c 1005 2007-06-02 13:54:25Z 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#ifndef PATH_MAX
41# define PATH_MAX _MAX_PATH /* windows */
42#endif
43#if defined(__OS2__) || defined(__WIN__)
44# include <process.h>
45# include <io.h>
46# ifdef __OS2__
47# include <unistd.h>
48# endif
49#else
50# include <unistd.h>
51# include <sys/wait.h>
52# ifndef O_BINARY
53# define O_BINARY 0
54# endif
55#endif
56#include "crc32.h"
57#include "md5.h"
58
59/*******************************************************************************
60* Defined Constants And Macros *
61*******************************************************************************/
62/** The max line length in a cache file. */
63#define KOBJCACHE_MAX_LINE_LEN 16384
64#if defined(__WIN__)
65# define PATH_SLASH '\\'
66#else
67# define PATH_SLASH '/'
68#endif
69#if defined(__OS2__) || defined(__WIN__)
70# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
71# define IS_SLASH_DRV(ch) ((ch) == '/' || (ch) == '\\' || (ch) == ':')
72#else
73# define IS_SLASH(ch) ((ch) == '/')
74# define IS_SLASH_DRV(ch) ((ch) == '/')
75#endif
76
77
78
79/*******************************************************************************
80* Structures and Typedefs *
81*******************************************************************************/
82/** A checksum list entry.
83 * We keep a list checksums (of precompiler output) that matches, The planned
84 * matching algorithm doesn't require the precompiler output to be indentical,
85 * only to produce the same object files.
86 */
87typedef struct KOCSUM
88{
89 /** The next checksum. */
90 struct KOCSUM *pNext;
91 /** The crc32 checksum. */
92 uint32_t crc32;
93 /** The MD5 digest. */
94 unsigned char md5[16];
95} KOCSUM, *PKOCSUM;
96/** Pointer to a const KOCSUM. */
97typedef const KOCSUM *PCKOCSUM;
98
99/**
100 * The object cache data.
101 */
102typedef struct KOBJCACHE
103{
104 /** The cache dir that all other names are relative to. */
105 char *pszDir;
106 /** The name of the cache file. */
107 const char *pszName;
108 /** Set if the object needs to be (re)compiled. */
109 unsigned fNeedCompiling;
110
111 /** The name of new precompiled output. */
112 const char *pszNewCppName;
113 /** Pointer to the 'mapping' of the new precompiled output. */
114 char *pszNewCppMapping;
115 /** The size of the new precompiled output 'mapping'. */
116 size_t cbNewCppMapping;
117 /** The new checksum. */
118 KOCSUM NewSum;
119 /** The new object filename (relative to the cache file). */
120 char *pszNewObjName;
121
122 /** The name of the precompiled output. (relative to the cache file) */
123 char *pszOldCppName;
124 /** Pointer to the 'mapping' of the old precompiled output. */
125 char *pszOldCppMapping;
126 /** The size of the old precompiled output 'mapping'. */
127 size_t cbOldCppMapping;
128
129 /** The head of the checksum list. */
130 KOCSUM SumHead;
131 /** The object filename (relative to the cache file). */
132 char *pszObjName;
133 /** The compile argument vector used to build the object. */
134 char **papszArgvCompile;
135 /** The size of the compile */
136 unsigned cArgvCompile;
137} KOBJCACHE, *PKOBJCACHE;
138/** Pointer to a const KOBJCACHE. */
139typedef const KOBJCACHE *PCKOBJCACHE;
140
141
142/*******************************************************************************
143* Global Variables *
144*******************************************************************************/
145/** Whether verbose output is enabled. */
146static int g_fVerbose = 0;
147
148
149/*******************************************************************************
150* Internal Functions *
151*******************************************************************************/
152static const char *FindFilenameInPath(const char *pszPath);
153static char *AbsPath(const char *pszPath);
154static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir);
155static char *CalcRelativeName(const char *pszPath, const char *pszDir);
156static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode);
157static int UnlinkFileInDir(const char *pszName, const char *pszDir);
158static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir);
159static int DoesFileInDirExist(const char *pszName, const char *pszDir);
160static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile);
161static void *xmalloc(size_t);
162static void *xrealloc(void *, size_t);
163static char *xstrdup(const char *);
164
165
166/**
167 * Compares two check sum entries.
168 *
169 * @returns 1 if equal, 0 if not equal.
170 *
171 * @param pSum1 The first checksum.
172 * @param pSum2 The second checksum.
173 */
174static int kObjCacheSumIsEqual(PCKOCSUM pSum1, PCKOCSUM pSum2)
175{
176 if (pSum1 == pSum2)
177 return 1;
178 if (!pSum1 || !pSum2)
179 return 0;
180 if (pSum1->crc32 != pSum2->crc32)
181 return 0;
182 if (memcmp(&pSum1->md5[0], &pSum2->md5[0], sizeof(pSum1->md5)))
183 return 0;
184 return 1;
185}
186
187
188/**
189 * Print a fatal error message and exit with rc=1.
190 *
191 * @param pEntry The cache entry.
192 * @param pszFormat The message to print.
193 * @param ... Format arguments.
194 */
195static void kObjCacheFatal(PCKOBJCACHE pEntry, const char *pszFormat, ...)
196{
197 va_list va;
198
199 fprintf(stderr, "kObjCache %s - fatal error: ", pEntry->pszName);
200 va_start(va, pszFormat);
201 vfprintf(stderr, pszFormat, va);
202 va_end(va);
203
204 exit(1);
205}
206
207
208/**
209 * Print a verbose message if verbosisty is enabled.
210 *
211 * @param pEntry The cache entry.
212 * @param pszFormat The message to print.
213 * @param ... Format arguments.
214 */
215static void kObjCacheVerbose(PCKOBJCACHE pEntry, const char *pszFormat, ...)
216{
217 if (g_fVerbose)
218 {
219 va_list va;
220
221 fprintf(stdout, "kObjCache %s - info: ", pEntry->pszName);
222 va_start(va, pszFormat);
223 vfprintf(stdout, pszFormat, va);
224 va_end(va);
225 }
226}
227
228
229/**
230 * Creates a cache entry for the given cache file name.
231 *
232 * @returns Pointer to a cache entry.
233 * @param pszFilename The cache file name.
234 */
235static PKOBJCACHE kObjCacheCreate(const char *pszFilename)
236{
237 PKOBJCACHE pEntry;
238
239 /*
240 * Allocate an empty entry.
241 */
242 pEntry = xmalloc(sizeof(*pEntry));
243 memset(pEntry, 0, sizeof(*pEntry));
244
245 /*
246 * Setup the directory and cache file name.
247 */
248 pEntry->pszDir = AbsPath(pszFilename);
249 pEntry->pszName = FindFilenameInPath(pEntry->pszDir);
250 if (pEntry->pszDir == pEntry->pszName)
251 kObjCacheFatal(pEntry, "Failed to find abs path for '%s'!\n", pszFilename);
252 ((char *)pEntry->pszName)[-1] = '\0';
253
254 return pEntry;
255}
256
257
258#if 0 /* don't bother. */
259/**
260 * Destroys the cache entry freeing up all it's resources.
261 *
262 * @param pEntry The entry to free.
263 */
264static void kObjCacheDestroy(PKOBJCACHE pEntry)
265{
266 free(pEntry->pszDir);
267 free(pEntry->pszName);
268 while (pEntry->SumHead.pNext)
269 {
270 void *pv = pEntry->SumHead.pNext;
271 pEntry->SumHead.pNext = pEntry->SumHead.pNext->pNext;
272 if (pv != &pEntry->NewSum)
273 free(pv);
274 }
275 free(pEntry);
276}
277#endif
278
279
280/**
281 * Reads and parses the cache file.
282 *
283 * @param pEntry The entry to read it into.
284 */
285static void kObjCacheRead(PKOBJCACHE pEntry)
286{
287 static char s_szLine[KOBJCACHE_MAX_LINE_LEN + 16];
288 FILE *pFile;
289 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "rb");
290 if (pFile)
291 {
292 kObjCacheVerbose(pEntry, "reading cache file...\n");
293
294 /*
295 * Check the magic.
296 */
297 if ( !fgets(s_szLine, sizeof(s_szLine), pFile)
298 || strcmp(s_szLine, "magic=kObjCache-1\n"))
299 {
300 kObjCacheVerbose(pEntry, "bad cache file (magic)\n");
301 pEntry->fNeedCompiling = 1;
302 }
303 else
304 {
305 /*
306 * Parse the rest of the file (relaxed order).
307 */
308 unsigned i;
309 int fBad = 0;
310 int fBadBeforeMissing;
311 int fFirstSum = 1;
312 while (fgets(s_szLine, sizeof(s_szLine), pFile))
313 {
314 /* Split the line and drop the trailing newline. */
315 char *pszNl = strchr(s_szLine, '\n');
316 char *pszVal = strchr(s_szLine, '=');
317 if ((fBad = pszVal == NULL))
318 break;
319 if (pszNl)
320 *pszNl = '\0';
321 *pszVal++ = '\0';
322
323 /* string case on variable name */
324 if (!strcmp(s_szLine, "obj"))
325 {
326 if ((fBad = pEntry->pszObjName != NULL))
327 break;
328 pEntry->pszObjName = xstrdup(pszVal);
329 }
330 else if (!strcmp(s_szLine, "cpp"))
331 {
332 if ((fBad = pEntry->pszOldCppName != NULL))
333 break;
334 pEntry->pszOldCppName = xstrdup(pszVal);
335 }
336 else if (!strcmp(s_szLine, "cc-argc"))
337 {
338 if ((fBad = pEntry->papszArgvCompile != NULL))
339 break;
340 pEntry->cArgvCompile = atoi(pszVal); /* if wrong, we'll fail below. */
341 pEntry->papszArgvCompile = xmalloc((pEntry->cArgvCompile + 1) * sizeof(pEntry->papszArgvCompile[0]));
342 memset(pEntry->papszArgvCompile, 0, (pEntry->cArgvCompile + 1) * sizeof(pEntry->papszArgvCompile[0]));
343 }
344 else if (!strncmp(s_szLine, "cc-argv-#", sizeof("cc-argv-#") - 1))
345 {
346 char *pszNext;
347 unsigned i = strtoul(&s_szLine[sizeof("cc-argv-#") - 1], &pszNext, 0);
348 if ((fBad = i >= pEntry->cArgvCompile || pEntry->papszArgvCompile[i] || (pszNext && *pszNext)))
349 break;
350 pEntry->papszArgvCompile[i] = xstrdup(pszVal);
351 }
352 else if (!strcmp(s_szLine, "sum"))
353 {
354 KOCSUM Sum;
355 unsigned i;
356 char *pszNext;
357 char *pszMD5 = strchr(pszVal, ':');
358 if ((fBad = pszMD5 == NULL))
359 break;
360 *pszMD5++ = '\0';
361
362 /* crc32 */
363 Sum.crc32 = (uint32_t)strtoul(pszVal, &pszNext, 16);
364 if ((fBad = (pszNext && *pszNext)))
365 break;
366
367 /* md5 */
368 for (i = 0; i < sizeof(Sum.md5) * 2; i++)
369 {
370 unsigned char ch = pszMD5[i];
371 int x;
372 if (ch - '0' <= 9)
373 x = ch - '0';
374 else if (ch - 'a' <= 5)
375 x = ch - 'a' + 10;
376 else if (ch - 'A' <= 5)
377 x = ch - 'A' + 10;
378 else
379 {
380 fBad = 1;
381 break;
382 }
383 if (!(i & 1))
384 Sum.md5[i >> 1] = x << 4;
385 else
386 Sum.md5[i >> 1] |= x;
387 }
388 if (fBad)
389 break;
390
391 if (fFirstSum)
392 {
393 pEntry->SumHead = Sum;
394 pEntry->SumHead.pNext = NULL;
395 fFirstSum = 0;
396 }
397 else
398 {
399 Sum.pNext = pEntry->SumHead.pNext;
400 pEntry->SumHead.pNext = xmalloc(sizeof(Sum));
401 *pEntry->SumHead.pNext = Sum;
402 }
403 }
404 else
405 {
406 fBad = 1;
407 break;
408 }
409 } /* parse loop */
410
411 /*
412 * Did we find everything?
413 */
414 fBadBeforeMissing = fBad;
415 if ( !fBad
416 && ( !pEntry->papszArgvCompile
417 || !pEntry->pszObjName
418 || !pEntry->pszOldCppName
419 || fFirstSum))
420 fBad = 1;
421 if (!fBad)
422 for (i = 0; i < pEntry->cArgvCompile; i++)
423 if ((fBad = !pEntry->papszArgvCompile[i]))
424 break;
425 if (fBad)
426 kObjCacheVerbose(pEntry, "bad cache file (%s)\n", fBadBeforeMissing ? s_szLine : "missing stuff");
427 else if (ferror(pFile))
428 kObjCacheVerbose(pEntry, "cache file read error\n");
429 pEntry->fNeedCompiling = fBad;
430 }
431 fclose(pFile);
432 }
433 else
434 {
435 kObjCacheVerbose(pEntry, "no cache file\n");
436 pEntry->fNeedCompiling = 1;
437 }
438}
439
440
441/**
442 * Writes the cache file.
443 *
444 * @param pEntry The entry to write.
445 */
446static void kObjCacheWrite(PKOBJCACHE pEntry)
447{
448 FILE *pFile;
449 PCKOCSUM pSum;
450 unsigned i;
451
452 kObjCacheVerbose(pEntry, "writing cache file...\n");
453 pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "wb");
454 if (!pFile)
455 kObjCacheFatal(pEntry, "Failed to open '%s' in '%s': %s\n",
456 pEntry->pszName, pEntry->pszDir, strerror(errno));
457
458#define CHECK_LEN(expr) \
459 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)
460
461 fprintf(pFile, "magic=kObjCache-1\n");
462 CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->pszNewObjName ? pEntry->pszNewObjName : pEntry->pszObjName));
463 CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->pszNewCppName ? pEntry->pszNewCppName : pEntry->pszOldCppName));
464 CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->cArgvCompile));
465 for (i = 0; i < pEntry->cArgvCompile; i++)
466 CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->papszArgvCompile[i]));
467 for (pSum = pEntry->fNeedCompiling ? &pEntry->NewSum : &pEntry->SumHead;
468 pSum;
469 pSum = pSum->pNext)
470 fprintf(pFile, "sum=%#x:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
471 pSum->crc32,
472 pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3],
473 pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7],
474 pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11],
475 pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]);
476
477 if ( fflush(pFile) < 0
478 || ferror(pFile))
479 {
480 int iErr = errno;
481 fclose(pFile);
482 UnlinkFileInDir(pEntry->pszName, pEntry->pszDir);
483 kObjCacheFatal(pEntry, "Stream error occured while writing '%s' in '%s': %d (?)\n",
484 pEntry->pszName, pEntry->pszDir, strerror(iErr));
485 }
486 fclose(pFile);
487}
488
489
490/**
491 * Spawns a child in a synchronous fashion.
492 * Terminating on failure.
493 *
494 * @param papszArgv Argument vector. The cArgv element is NULL.
495 * @param cArgv The number of arguments in the vector.
496 */
497static void kObjCacheSpawn(PCKOBJCACHE pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut)
498{
499#if defined(__OS2__) || defined(__WIN__)
500 intptr_t rc;
501 int fdStdOut = -1;
502 if (pszStdOut)
503 {
504 int fdReDir;
505 fdStdOut = dup(1); /* dup2(1,-1) doesn't work right on windows */
506 close(1);
507 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0777);
508 if (fdReDir < 0)
509 kObjCacheFatal(pEntry, "%s - failed to create stdout redirection file '%s': %s\n",
510 pszMsg, pszStdOut, strerror(errno));
511 if (fdReDir != 1)
512 {
513 if (dup2(fdReDir, 1) < 0)
514 kObjCacheFatal(pEntry, "%s - dup2 failed: %s\n", pszMsg, strerror(errno));
515 close(fdReDir);
516 }
517 }
518
519 errno = 0;
520 rc = _spawnvp(_P_WAIT, papszArgv[0], papszArgv);
521 if (rc < 0)
522 kObjCacheFatal(pEntry, "%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno));
523 if (rc > 0)
524 kObjCacheFatal(pEntry, "%s - failed rc=%d\n", pszMsg, (int)rc);
525 if (fdStdOut)
526 {
527 close(1);
528 fdStdOut = dup2(fdStdOut, 1);
529 close(fdStdOut);
530 }
531
532#else
533 int iStatus;
534 pid_t pidWait;
535 pid_t pid = fork();
536 if (!pid)
537 {
538 if (pszStdOut)
539 {
540 close(1);
541 fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0777);
542 if (fdReDir < 0)
543 kObjCacheFatal(pEntry, "%s - failed to create stdout redirection file '%s': %s\n",
544 pszMsg, pszStdOut, strerror(errno));
545 if (fdReDir != 1)
546 {
547 if (dup2(fdReDir, 1) < 0)
548 kObjCacheFatal(pEntry, "%s - dup2 failed: %s\n", pszMsg, strerror(errno));
549 close(fdReDir);
550 }
551 }
552
553 execvp(papszArgv[0], papszArgv);
554 kObjCacheFatal(pEntry, "%s - execvp failed rc=%d errno=%d %s\n",
555 pszMsg, rc, errno, strerror(errno));
556 }
557 pidWait = waitpid(pid, &iStatus);
558 while (pidWait < 0 && errno == EINTR)
559 pidWait = waitpid(pid, &iStatus);
560 if (pidWait != pid)
561 kObjCacheFatal(pEntry, "%s - waitpid failed rc=%d errno=%d %s\n",
562 pszMsg, rc, errno, strerror(errno));
563 if (!WIFEXITED(iStatus))
564 kObjCacheFatal(pEntry, "%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
565 if (WEXITSTATUS(iStatus))
566 kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
567#endif
568 (void)cArgv;
569}
570
571
572/**
573 * Worker for kObjCachePreCompile and calculates the checksum of
574 * the precompiler output.
575 *
576 * @param pEntry The cache entry. NewSum will be updated.
577 */
578static void kObjCacheCalcChecksum(PKOBJCACHE pEntry)
579{
580 struct MD5Context MD5Ctx;
581
582 /*
583 * Read/maps the entire file into a buffer and does the crc sums
584 * on the buffer. This assumes the precompiler output isn't
585 * gigantic, but that's a pretty safe assumption I hope...
586 */
587 pEntry->pszNewCppMapping = ReadFileInDir(pEntry->pszNewCppName, pEntry->pszDir, &pEntry->cbNewCppMapping);
588 if (!pEntry->pszNewCppMapping)
589 kObjCacheFatal(pEntry, "failed to open/read '%s' in '%s': %s\n",
590 pEntry->pszNewCppName, pEntry->pszDir, strerror(errno));
591 kObjCacheVerbose(pEntry, "precompiled file is %lu bytes long\n", (unsigned long)pEntry->cbNewCppMapping);
592
593 memset(&pEntry->NewSum, 0, sizeof(pEntry->NewSum));
594 pEntry->NewSum.crc32 = crc32(0, pEntry->pszNewCppMapping, pEntry->cbNewCppMapping);
595 MD5Init(&MD5Ctx);
596 MD5Update(&MD5Ctx, pEntry->pszNewCppMapping, pEntry->cbNewCppMapping);
597 MD5Final(&pEntry->NewSum.md5[0], &MD5Ctx);
598 kObjCacheVerbose(pEntry, "crc32=%#lx md5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
599 pEntry->NewSum.crc32,
600 pEntry->NewSum.md5[0], pEntry->NewSum.md5[1], pEntry->NewSum.md5[2], pEntry->NewSum.md5[3],
601 pEntry->NewSum.md5[4], pEntry->NewSum.md5[5], pEntry->NewSum.md5[6], pEntry->NewSum.md5[7],
602 pEntry->NewSum.md5[8], pEntry->NewSum.md5[9], pEntry->NewSum.md5[10], pEntry->NewSum.md5[11],
603 pEntry->NewSum.md5[12], pEntry->NewSum.md5[13], pEntry->NewSum.md5[14], pEntry->NewSum.md5[15]);
604
605}
606
607
608/**
609 * Run the precompiler and calculate the checksum of the output.
610 *
611 * @param pEntry The cache entry.
612 * @param papszArgvPreComp The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
613 * @param cArgvPreComp The number of arguments.
614 * @param pszPreCompName Precompile output name. (must kick around)
615 * @param fRedirStdOut Whether stdout needs to be redirected or not.
616 */
617static void kObjCachePreCompile(PKOBJCACHE pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp, const char *pszPreCompName, int fRedirStdOut)
618{
619 /*
620 * Rename the old precompiled output to '-old'.
621 * We'll discard the old output and keep the new output, but because
622 * we might with to do a quick matchup later we can't remove it just now.
623 */
624 if ( pEntry->pszOldCppName
625 && DoesFileInDirExist(pEntry->pszOldCppName, pEntry->pszDir))
626 {
627 size_t cch = strlen(pEntry->pszOldCppName);
628 char *psz = xmalloc(cch + sizeof("-old"));
629 memcpy(psz, pEntry->pszOldCppName, cch);
630 memcpy(psz + cch, "-old", sizeof("-old"));
631
632 kObjCacheVerbose(pEntry, "renaming '%s' to '%s' in '%s'\n", pEntry->pszOldCppName, psz, pEntry->pszDir);
633 UnlinkFileInDir(psz, pEntry->pszDir);
634 if (RenameFileInDir(pEntry->pszOldCppName, psz, pEntry->pszDir))
635 kObjCacheFatal(pEntry, "failed to rename '%s' -> '%s' in '%s': %s\n",
636 pEntry->pszOldCppName, psz, pEntry->pszDir, strerror(errno));
637 free(pEntry->pszOldCppName);
638 pEntry->pszOldCppName = psz;
639 }
640 pEntry->pszNewCppName = CalcRelativeName(pszPreCompName, pEntry->pszDir);
641
642 /*
643 * Precompile it and calculate the checksum on the output.
644 */
645 kObjCacheVerbose(pEntry, "precompiling -> '%s'...\n", pEntry->pszNewCppName);
646 if (fRedirStdOut)
647 kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", pszPreCompName);
648 else
649 kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
650 kObjCacheCalcChecksum(pEntry);
651}
652
653
654/**
655 * Worker for kObjCacheCompileIfNeeded that compares the
656 * precompiled output.
657 *
658 * @returns 1 if matching, 0 if not matching.
659 * @param pEntry The entry containing the names of the files to compare.
660 * The entry is not updated in any way.
661 */
662static int kObjCacheCompareOldAndNewOutput(PCKOBJCACHE pEntry)
663{
664 /** @todo do some quick but fancy comparing that determins whether code
665 * has actually changed or moved. We can ignore declarations and typedefs that
666 * has just been moved up/down a bit. The typical case is adding a new error
667 * #define that doesn't influence the current compile job. */
668 return 0;
669}
670
671
672/**
673 * Worker for kObjCacheCompileIfNeeded that does the actual (re)compilation.
674 *
675 * @returns 1 if matching, 0 if not matching.
676 * @param pEntry The cache entry.
677 * @param papszArgvCompile The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
678 * @param cArgvCompile The number of arguments in the vector.
679 * @param pszObjName The name of the object file.
680 */
681static void kObjCacheCompileIt(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
682{
683 /*
684 * Delete the old object file and precompiler output.
685 */
686 if (pEntry->pszObjName)
687 {
688 UnlinkFileInDir(pEntry->pszObjName, pEntry->pszDir);
689 pEntry->pszObjName = NULL;
690 }
691 pEntry->pszNewObjName = CalcRelativeName(pszObjName, pEntry->pszDir);
692
693 /*
694 * Release buffers we no longer need before starting the compile.
695 */
696 free(pEntry->pszNewCppMapping);
697 pEntry->pszNewCppMapping = NULL;
698 free(pEntry->pszOldCppMapping);
699 pEntry->pszOldCppMapping = NULL;
700
701 /*
702 * Do the recompilation.
703 */
704 kObjCacheVerbose(pEntry, "compiling -> '%s'...\n", pEntry->pszNewObjName);
705 pEntry->papszArgvCompile = (char **)papszArgvCompile; /* LEAK */
706 pEntry->cArgvCompile = cArgvCompile;
707 kObjCacheSpawn(pEntry, papszArgvCompile, cArgvCompile, "compile", NULL);
708}
709
710
711/**
712 * Check if (re-)compilation is required and do it.
713 *
714 * @returns 1 if matching, 0 if not matching.
715 * @param pEntry The cache entry.
716 * @param papszArgvCompile The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
717 * @param cArgvCompile The number of arguments in the vector.
718 * @param pszObjName The name of the object file.
719 */
720static void kObjCacheCompileIfNeeded(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
721{
722 /*
723 * Does the object name differ?
724 */
725 if (!pEntry->fNeedCompiling)
726 {
727 char *pszTmp = CalcRelativeName(pszObjName, pEntry->pszDir);
728 if (strcmp(pEntry->pszObjName, pszTmp))
729 {
730 pEntry->fNeedCompiling = 1;
731 kObjCacheVerbose(pEntry, "object name changed '%s' -> '%s'\n", pEntry->pszObjName, pszTmp);
732 }
733 free(pszTmp);
734 }
735
736 /*
737 * Does the compile command differ?
738 * TODO: Ignore irrelevant options here (like warning level).
739 */
740 if ( !pEntry->fNeedCompiling
741 && pEntry->cArgvCompile != cArgvCompile)
742 {
743 pEntry->fNeedCompiling = 1;
744 kObjCacheVerbose(pEntry, "compile argument count changed\n");
745 }
746 if (!pEntry->fNeedCompiling)
747 {
748 unsigned i;
749 for (i = 0; i < cArgvCompile; i++)
750 if (strcmp(papszArgvCompile[i], pEntry->papszArgvCompile[i]))
751 {
752 pEntry->fNeedCompiling = 1;
753 kObjCacheVerbose(pEntry, "compile argument differs (%#d)\n", i);
754 break;
755 }
756 }
757
758 /*
759 * Does the object file exist?
760 */
761 if ( !pEntry->fNeedCompiling
762 && !DoesFileInDirExist(pEntry->pszObjName, pEntry->pszDir))
763 {
764 pEntry->fNeedCompiling = 1;
765 kObjCacheVerbose(pEntry, "object file doesn't exist\n");
766 }
767
768 /*
769 * Does the precompiled output differ in any significant way?
770 */
771 if (!pEntry->fNeedCompiling)
772 {
773 int fFound = 0;
774 PCKOCSUM pSum;
775 for (pSum = &pEntry->SumHead; pSum; pSum = pSum->pNext)
776 if (kObjCacheSumIsEqual(pSum, &pEntry->NewSum))
777 {
778 fFound = 1;
779 break;
780 }
781 if (!fFound)
782 {
783 kObjCacheVerbose(pEntry, "no checksum match - comparing output\n");
784 if (!kObjCacheCompareOldAndNewOutput(pEntry))
785 pEntry->fNeedCompiling = 1;
786 else
787 {
788 /* insert the sum into the list. */
789 pEntry->NewSum.pNext = pEntry->SumHead.pNext;
790 pEntry->SumHead.pNext = &pEntry->NewSum;
791 }
792 }
793 }
794
795 /*
796 * Discard the old precompiled output it's no longer needed.s
797 */
798 if (pEntry->pszOldCppName)
799 {
800 UnlinkFileInDir(pEntry->pszOldCppName, pEntry->pszDir);
801 free(pEntry->pszOldCppName);
802 pEntry->pszOldCppName = NULL;
803 }
804
805 /*
806 * Do the compliation if found necessary.
807 */
808 if (pEntry->fNeedCompiling)
809 kObjCacheCompileIt(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
810}
811
812
813/**
814 * Gets the absolute path
815 *
816 * @returns A new heap buffer containing the absolute path.
817 * @param pszPath The path to make absolute. (Readonly)
818 */
819static char *AbsPath(const char *pszPath)
820{
821 char szTmp[PATH_MAX];
822#if defined(__OS2__) || defined(__WIN__)
823 if (!_fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp)))
824 return xstrdup(pszPath);
825#else
826 if (!realpath(pszPath, szTmp))
827 return xstrdup(pszPath);
828#endif
829 return xstrdup(szTmp);
830}
831
832
833/**
834 * Utility function that finds the filename part in a path.
835 *
836 * @returns Pointer to the file name part (this may be "").
837 * @param pszPath The path to parse.
838 */
839static const char *FindFilenameInPath(const char *pszPath)
840{
841 const char *pszFilename = strchr(pszPath, '\0') - 1;
842 while ( pszFilename > pszPath
843 && !IS_SLASH_DRV(pszFilename[-1]))
844 pszFilename--;
845 return pszFilename;
846}
847
848
849/**
850 * Utility function that combines a filename and a directory into a path.
851 *
852 * @returns malloced buffer containing the result.
853 * @param pszName The file name.
854 * @param pszDir The directory path.
855 */
856static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir)
857{
858 size_t cchName = strlen(pszName);
859 size_t cchDir = strlen(pszDir);
860 char *pszBuf = xmalloc(cchName + cchDir + 2);
861 memcpy(pszBuf, pszDir, cchDir);
862 if (cchDir > 0 && !IS_SLASH_DRV(pszDir[cchDir - 1]))
863 pszBuf[cchDir++] = PATH_SLASH;
864 memcpy(pszBuf + cchDir, pszName, cchName + 1);
865 return pszBuf;
866}
867
868
869/**
870 * Compares two path strings to see if they are identical.
871 *
872 * This doesn't do anything fancy, just the case ignoring and
873 * slash unification.
874 *
875 * @returns 1 if equal, 0 otherwise.
876 * @param pszPath1 The first path.
877 * @param pszPath2 The second path.
878 * @param cch The number of characters to compare.
879 */
880static int ArePathsIdentical(const char *pszPath1, const char *pszPath2, size_t cch)
881{
882#if defined(__OS2__) || defined(__WIN__)
883 if (strnicmp(pszPath1, pszPath2, cch))
884 {
885 /* Slashes may differ, compare char by char. */
886 const char *psz1 = pszPath1;
887 const char *psz2 = pszPath2;
888 for (;cch; psz1++, psz2++, cch--)
889 {
890 if (*psz1 != *psz2)
891 {
892 if ( tolower(*psz1) != tolower(*psz2)
893 && toupper(*psz1) != toupper(*psz2)
894 && *psz1 != '/'
895 && *psz1 != '\\'
896 && *psz2 != '/'
897 && *psz2 != '\\')
898 return 0;
899 }
900 }
901 }
902 return 1;
903#else
904 return !strncmp(pszPath1, pszPath2, cch);
905#endif
906}
907
908
909/**
910 * Calculate how to get to pszPath from pszDir.
911 *
912 * @returns The relative path from pszDir to path pszPath.
913 * @param pszPath The path to the object.
914 * @param pszDir The directory it shall be relative to.
915 */
916static char *CalcRelativeName(const char *pszPath, const char *pszDir)
917{
918 char *pszRet = NULL;
919 char *pszAbsPath = NULL;
920 size_t cchDir = strlen(pszDir);
921
922 /*
923 * This is indeed a bit tricky, so we'll try the easy way first...
924 */
925 if (ArePathsIdentical(pszPath, pszDir, cchDir))
926 {
927 if (pszPath[cchDir])
928 pszRet = (char *)pszPath + cchDir;
929 else
930 pszRet = "./";
931 }
932 else
933 {
934 pszAbsPath = AbsPath(pszPath);
935 if (ArePathsIdentical(pszAbsPath, pszDir, cchDir))
936 {
937 if (pszPath[cchDir])
938 pszRet = pszAbsPath + cchDir;
939 else
940 pszRet = "./";
941 }
942 }
943 if (pszRet)
944 {
945 while (IS_SLASH_DRV(*pszRet))
946 pszRet++;
947 pszRet = xstrdup(pszRet);
948 free(pszAbsPath);
949 return pszRet;
950 }
951
952 /*
953 * Damn, it's gonna be complicated. Deal with that later.
954 */
955 fprintf(stderr, "kObjCache: complicated relative path stuff isn't implemented yet. sorry.\n");
956 exit(1);
957 return NULL;
958}
959
960
961/**
962 * Utility function that combines a filename and directory and passes it onto fopen.
963 *
964 * @returns fopen return value.
965 * @param pszName The file name.
966 * @param pszDir The directory path.
967 * @param pszMode The fopen mode string.
968 */
969static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode)
970{
971 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
972 FILE *pFile = fopen(pszPath, pszMode);
973 free(pszPath);
974 return pFile;
975}
976
977
978/**
979 * Deletes a file in a directory.
980 *
981 * @returns whatever unlink returns.
982 * @param pszName The file name.
983 * @param pszDir The directory path.
984 */
985static int UnlinkFileInDir(const char *pszName, const char *pszDir)
986{
987 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
988 int rc = unlink(pszPath);
989 free(pszPath);
990 return rc;
991}
992
993
994/**
995 * Renames a file in a directory.
996 *
997 * @returns whatever unlink returns.
998 * @param pszOldName The new file name.
999 * @param pszNewName The old file name.
1000 * @param pszDir The directory path.
1001 */
1002static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir)
1003{
1004 char *pszOldPath = MakePathFromDirAndFile(pszOldName, pszDir);
1005 char *pszNewPath = MakePathFromDirAndFile(pszNewName, pszDir);
1006 int rc = rename(pszOldPath, pszNewPath);
1007 free(pszOldPath);
1008 free(pszNewPath);
1009 return rc;
1010}
1011
1012
1013/**
1014 * Check if a (regular) file exists in a directory.
1015 *
1016 * @returns 1 if it exists and is a regular file, 0 if not.
1017 * @param pszName The file name.
1018 * @param pszDir The directory path.
1019 */
1020static int DoesFileInDirExist(const char *pszName, const char *pszDir)
1021{
1022 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1023 struct stat st;
1024 int rc = stat(pszPath, &st);
1025 free(pszPath);
1026#ifdef S_ISREG
1027 return !rc && S_ISREG(st.st_mode);
1028#elif defined(_MSC_VER)
1029 return !rc && (st.st_mode & _S_IFMT) == _S_IFREG;
1030#else
1031#error "Port me"
1032#endif
1033}
1034
1035
1036/**
1037 * Reads into memory an entire file.
1038 *
1039 * @returns Pointer to the heap allocation containing the file.
1040 * On failure NULL and errno is returned.
1041 * @param pszName The file.
1042 * @param pszDir The directory the file resides in.
1043 * @param pcbFile Where to store the file size.
1044 */
1045static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile)
1046{
1047 int SavedErrno;
1048 char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
1049 int fd = open(pszPath, O_RDONLY | O_BINARY);
1050 if (fd >= 0)
1051 {
1052 off_t cbFile = lseek(fd, 0, SEEK_END);
1053 if ( cbFile >= 0
1054 && lseek(fd, 0, SEEK_SET) == 0)
1055 {
1056 char *pb = malloc(cbFile + 1);
1057 if (pb)
1058 {
1059 if (read(fd, pb, cbFile) == cbFile)
1060 {
1061 close(fd);
1062 pb[cbFile] = '\0';
1063 *pcbFile = (size_t)cbFile;
1064 return pb;
1065 }
1066 SavedErrno = errno;
1067 free(pb);
1068 }
1069 else
1070 SavedErrno = ENOMEM;
1071 }
1072 else
1073 SavedErrno = errno;
1074 close(fd);
1075 }
1076 else
1077 SavedErrno = errno;
1078 free(pszPath);
1079 errno = SavedErrno;
1080 return NULL;
1081}
1082
1083
1084static void *xmalloc(size_t cb)
1085{
1086 void *pv = malloc(cb);
1087 if (!pv)
1088 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
1089 return pv;
1090}
1091
1092
1093static void *xrealloc(void *pvOld, size_t cb)
1094{
1095 void *pv = realloc(pvOld, cb);
1096 if (!pv)
1097 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
1098 return pv;
1099}
1100
1101
1102static char *xstrdup(const char *pszIn)
1103{
1104 char *psz = strdup(pszIn);
1105 if (!psz)
1106 kObjCacheFatal(NULL, "out of memory (%d)\n", (int)strlen(pszIn));
1107 return psz;
1108}
1109
1110
1111/**
1112 * Prints a syntax error and returns the appropriate exit code
1113 *
1114 * @returns approriate exit code.
1115 * @param pszFormat The syntax error message.
1116 * @param ... Message args.
1117 */
1118static int SyntaxError(const char *pszFormat, ...)
1119{
1120 va_list va;
1121 fprintf(stderr, "kObjCache: syntax error: ");
1122 va_start(va, pszFormat);
1123 vfprintf(stderr, pszFormat, va);
1124 va_end(va);
1125 return 1;
1126}
1127
1128
1129/**
1130 * Prints the usage.
1131 * @returns 0.
1132 */
1133static int usage(void)
1134{
1135 printf("syntax: kObjCache [-v|--verbose] [-f|--file] <cache-file> [-V|--version] [-r|--redir-stdout]\n"
1136 " --kObjCache-cpp <filename> <precompiler + args> \n"
1137 " --kObjCache-cc <object> <compiler + args>\n"
1138 " [--kObjCache-both [args]]\n"
1139 " [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
1140 "\n");
1141 return 0;
1142}
1143
1144
1145int main(int argc, char **argv)
1146{
1147 PKOBJCACHE pEntry;
1148
1149 const char *pszCacheFile;
1150
1151 const char **papszArgvPreComp = NULL;
1152 unsigned cArgvPreComp = 0;
1153 const char *pszPreCompName = NULL;
1154 int fRedirStdOut = 0;
1155
1156 const char **papszArgvCompile = NULL;
1157 unsigned cArgvCompile = 0;
1158 const char *pszObjName = NULL;
1159
1160 enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
1161
1162 int i;
1163
1164 /*
1165 * Parse the arguments.
1166 */
1167 for (i = 1; i < argc; i++)
1168 {
1169 if (!strcmp(argv[i], "--kObjCache-cpp"))
1170 {
1171 enmMode = kOC_CppArgv;
1172 if (!pszPreCompName)
1173 {
1174 if (++i >= argc)
1175 return SyntaxError("--kObjCache-cpp requires an object filename!\n");
1176 pszPreCompName = argv[i];
1177 }
1178 }
1179 else if (!strcmp(argv[i], "--kObjCache-cc"))
1180 {
1181 enmMode = kOC_CcArgv;
1182 if (!pszObjName)
1183 {
1184 if (++i >= argc)
1185 return SyntaxError("--kObjCache-cc requires an precompiler output filename!\n");
1186 pszObjName = argv[i];
1187 }
1188 }
1189 else if (!strcmp(argv[i], "--kObjCache-both"))
1190 enmMode = kOC_BothArgv;
1191 else if (!strcmp(argv[i], "--help"))
1192 return usage();
1193 else if (enmMode != kOC_Options)
1194 {
1195 if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv)
1196 {
1197 if (!(cArgvPreComp % 16))
1198 papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0]));
1199 papszArgvPreComp[cArgvPreComp++] = argv[i];
1200 papszArgvPreComp[cArgvPreComp] = NULL;
1201 }
1202 if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv)
1203 {
1204 if (!(cArgvCompile % 16))
1205 papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0]));
1206 papszArgvCompile[cArgvCompile++] = argv[i];
1207 papszArgvCompile[cArgvCompile] = NULL;
1208 }
1209 }
1210 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file"))
1211 {
1212 if (i + 1 >= argc)
1213 return SyntaxError("%s requires a cache filename!\n", argv[i]);
1214 pszCacheFile = argv[++i];
1215 }
1216 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
1217 fRedirStdOut = 1;
1218 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
1219 g_fVerbose = 1;
1220 else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
1221 g_fVerbose = 0;
1222 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?"))
1223 return usage();
1224 else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version"))
1225 {
1226 printf("kObjCache v0.0.0 ($Revision: 1005 $)\n");
1227 return 0;
1228 }
1229 else
1230 return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
1231 }
1232 if (!pszCacheFile)
1233 return SyntaxError("No cache file name (-f)\n");
1234 if (!cArgvCompile)
1235 return SyntaxError("No compiler arguments (--kObjCache-cc)\n");
1236 if (!cArgvPreComp)
1237 return SyntaxError("No precompiler arguments (--kObjCache-cc)\n");
1238
1239 /*
1240 * Create a cache entry from the cache file (if found).
1241 */
1242 pEntry = kObjCacheCreate(pszCacheFile);
1243 kObjCacheRead(pEntry);
1244
1245 /*
1246 * Do the compiling.
1247 */
1248 kObjCachePreCompile(pEntry, papszArgvPreComp, cArgvPreComp, pszPreCompName, fRedirStdOut);
1249 kObjCacheCompileIfNeeded(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
1250
1251 /*
1252 * Write the cache file.
1253 */
1254 kObjCacheWrite(pEntry);
1255 //kObjCacheCleanup(pEntry);
1256 /* kObjCacheDestroy(pEntry); - don't bother */
1257 return 0;
1258}
1259
Note: See TracBrowser for help on using the repository browser.