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

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

kind of works.

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