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

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

another backup commit.

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