/* $Id: kObjCache.c 3315 2020-03-31 01:12:19Z bird $ */ /** @file * kObjCache - Object Cache. */ /* * Copyright (c) 2007-2012 knut st. osmundsen * * This file is part of kBuild. * * kBuild is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * kBuild is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with kBuild. If not, see * */ /******************************************************************************* * Header Files * *******************************************************************************/ #if 0 # define ELECTRIC_HEAP # include "../kmk/electric.h" # include "../kmk/electric.c" #endif #include #include #include #include #include #include #include #include #include #include #ifndef PATH_MAX # ifdef _MAX_PATH # define PATH_MAX _MAX_PATH /* windows */ # else # define PATH_MAX 4096 /* gnu hurd */ # endif #endif #if defined(__OS2__) || defined(__WIN__) # include # include # ifdef __OS2__ # include # include # include # endif # if defined(_MSC_VER) # include typedef intptr_t pid_t; # endif # ifndef _P_WAIT # define _P_WAIT P_WAIT # endif # ifndef _P_NOWAIT # define _P_NOWAIT P_NOWAIT # endif #else # include # include # include # ifndef O_BINARY # define O_BINARY 0 # endif # ifndef __sun__ # include /* flock */ # endif #endif #if defined(__WIN__) # include # include "quoted_spawn.h" #endif #if defined(__HAIKU__) # include #endif #include "crc32.h" #include "md5.h" #include "kDep.h" /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ /** The max line length in a cache file. */ #define KOBJCACHE_MAX_LINE_LEN 16384 #if defined(__WIN__) # define PATH_SLASH '\\' #else # define PATH_SLASH '/' #endif #if defined(__OS2__) || defined(__WIN__) # define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\') # define IS_SLASH_DRV(ch) ((ch) == '/' || (ch) == '\\' || (ch) == ':') #else # define IS_SLASH(ch) ((ch) == '/') # define IS_SLASH_DRV(ch) ((ch) == '/') #endif #ifndef STDIN_FILENO # define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO # define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO # define STDERR_FILENO 2 #endif #define MY_IS_BLANK(a_ch) ((a_ch) == ' ' || (a_ch) == '\t') #define KOC_BUF_MIN KOC_BUF_ALIGNMENT #define KOC_BUF_INCR KOC_BUF_ALIGNMENT #define KOC_BUF_ALIGNMENT (4U*1024U*1024U) /******************************************************************************* * Global Variables * *******************************************************************************/ /** Whether verbose output is enabled. */ static unsigned g_cVerbosityLevel = 0; /** What to prefix the errors with. */ static char g_szErrorPrefix[128]; /** Read buffer shared by the cache components. */ static char g_szLine[KOBJCACHE_MAX_LINE_LEN + 16]; /** How many times we've moved memory around. */ static size_t g_cMemMoves = 0; /** How much memory we've moved. */ static size_t g_cbMemMoved = 0; /******************************************************************************* * Internal Functions * *******************************************************************************/ static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir); static char *CalcRelativeName(const char *pszPath, const char *pszDir); static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode); static int UnlinkFileInDir(const char *pszName, const char *pszDir); static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir); static int DoesFileInDirExist(const char *pszName, const char *pszDir); static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile); void FatalMsg(const char *pszFormat, ...) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix); else fprintf(stderr, "fatal error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); } void FatalDie(const char *pszFormat, ...) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix); else fprintf(stderr, "fatal error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); exit(1); } #if 0 /* unused */ static void ErrorMsg(const char *pszFormat, ...) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - error: ", g_szErrorPrefix); else fprintf(stderr, "error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); } #endif /* unused */ static void InfoMsg(unsigned uLevel, const char *pszFormat, ...) { if (uLevel <= g_cVerbosityLevel) { va_list va; if (g_szErrorPrefix[0]) fprintf(stderr, "%s - info: ", g_szErrorPrefix); else fprintf(stderr, "info: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); } } static void SetErrorPrefix(const char *pszPrefix, ...) { int cch; va_list va; va_start(va, pszPrefix); #if defined(_MSC_VER) || defined(__sun__) cch = vsprintf(g_szErrorPrefix, pszPrefix, va); if (cch >= sizeof(g_szErrorPrefix)) FatalDie("Buffer overflow setting error prefix!\n"); #else vsnprintf(g_szErrorPrefix, sizeof(g_szErrorPrefix), pszPrefix, va); #endif va_end(va); (void)cch; } #ifndef ELECTRIC_HEAP void *xmalloc(size_t cb) { void *pv = malloc(cb); if (!pv) FatalDie("out of memory (%d)\n", (int)cb); return pv; } void *xrealloc(void *pvOld, size_t cb) { void *pv = realloc(pvOld, cb); if (!pv) FatalDie("out of memory (%d)\n", (int)cb); return pv; } char *xstrdup(const char *pszIn) { char *psz; if (pszIn) { psz = strdup(pszIn); if (!psz) FatalDie("out of memory (%d)\n", (int)strlen(pszIn)); } else psz = NULL; return psz; } #endif void *xmallocz(size_t cb) { void *pv = xmalloc(cb); memset(pv, 0, cb); return pv; } /** * Returns a millisecond timestamp. * * @returns Millisecond timestamp. */ static uint32_t NowMs(void) { #if defined(__WIN__) return GetTickCount(); #else int iSavedErrno = errno; struct timeval tv = {0, 0}; gettimeofday(&tv, NULL); errno = iSavedErrno; return tv.tv_sec * 1000 + tv.tv_usec / 1000; #endif } /** * Gets the absolute path * * @returns A new heap buffer containing the absolute path. * @param pszPath The path to make absolute. (Readonly) */ static char *AbsPath(const char *pszPath) { /** @todo this isn't really working as it should... */ char szTmp[PATH_MAX]; #if defined(__OS2__) if ( _fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp)) && !realpath(pszPath, szTmp)) return xstrdup(pszPath); #elif defined(__WIN__) if (!_fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp))) return xstrdup(pszPath); #else if (!realpath(pszPath, szTmp)) return xstrdup(pszPath); #endif return xstrdup(szTmp); } /** * Utility function that finds the filename part in a path. * * @returns Pointer to the file name part (this may be ""). * @param pszPath The path to parse. */ static const char *FindFilenameInPath(const char *pszPath) { const char *pszFilename = strchr(pszPath, '\0') - 1; if (pszFilename < pszPath) return pszPath; while ( pszFilename > pszPath && !IS_SLASH_DRV(pszFilename[-1])) pszFilename--; return pszFilename; } /** * Utility function that combines a filename and a directory into a path. * * @returns malloced buffer containing the result. * @param pszName The file name. * @param pszDir The directory path. */ static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir) { size_t cchName = strlen(pszName); size_t cchDir = strlen(pszDir); char *pszBuf = xmalloc(cchName + cchDir + 2); memcpy(pszBuf, pszDir, cchDir); if (cchDir > 0 && !IS_SLASH_DRV(pszDir[cchDir - 1])) pszBuf[cchDir++] = PATH_SLASH; memcpy(pszBuf + cchDir, pszName, cchName + 1); return pszBuf; } /** * Compares two path strings to see if they are identical. * * This doesn't do anything fancy, just the case ignoring and * slash unification. * * @returns 1 if equal, 0 otherwise. * @param pszPath1 The first path. * @param pszPath2 The second path. */ static int ArePathsIdentical(const char *pszPath1, const char *pszPath2) { #if defined(__OS2__) || defined(__WIN__) if (stricmp(pszPath1, pszPath2)) { /* Slashes may differ, compare char by char. */ const char *psz1 = pszPath1; const char *psz2 = pszPath2; for (;;) { if (*psz1 != *psz2) { if ( tolower(*psz1) != tolower(*psz2) && toupper(*psz1) != toupper(*psz2) && *psz1 != '/' && *psz1 != '\\' && *psz2 != '/' && *psz2 != '\\') return 0; } if (!*psz1) break; psz1++; psz2++; } } return 1; #else return !strcmp(pszPath1, pszPath2); #endif } /** * Compares two path strings to see if they are identical. * * This doesn't do anything fancy, just the case ignoring and * slash unification. * * @returns 1 if equal, 0 otherwise. * @param pszPath1 The first path. * @param pszPath2 The second path. * @param cch The number of characters to compare. */ static int ArePathsIdenticalN(const char *pszPath1, const char *pszPath2, size_t cch) { #if defined(__OS2__) || defined(__WIN__) if (strnicmp(pszPath1, pszPath2, cch)) { /* Slashes may differ, compare char by char. */ const char *psz1 = pszPath1; const char *psz2 = pszPath2; for ( ; cch; psz1++, psz2++, cch--) { if (*psz1 != *psz2) { if ( tolower(*psz1) != tolower(*psz2) && toupper(*psz1) != toupper(*psz2) && *psz1 != '/' && *psz1 != '\\' && *psz2 != '/' && *psz2 != '\\') return 0; } } } return 1; #else return !strncmp(pszPath1, pszPath2, cch); #endif } /** * Calculate how to get to pszPath from pszDir. * * @returns The relative path from pszDir to path pszPath. * @param pszPath The path to the object. * @param pszDir The directory it shall be relative to. */ static char *CalcRelativeName(const char *pszPath, const char *pszDir) { char *pszRet = NULL; char *pszAbsPath = NULL; size_t cchDir = strlen(pszDir); /* * This is indeed a bit tricky, so we'll try the easy way first... */ if (ArePathsIdenticalN(pszPath, pszDir, cchDir)) { if (pszPath[cchDir]) pszRet = (char *)pszPath + cchDir; else pszRet = "./"; } else { pszAbsPath = AbsPath(pszPath); if (ArePathsIdenticalN(pszAbsPath, pszDir, cchDir)) { if (pszPath[cchDir]) pszRet = pszAbsPath + cchDir; else pszRet = "./"; } } if (pszRet) { while (IS_SLASH_DRV(*pszRet)) pszRet++; pszRet = xstrdup(pszRet); free(pszAbsPath); return pszRet; } /* * Damn, it's gonna be complicated. Deal with that later. */ FatalDie("complicated relative path stuff isn't implemented yet. sorry.\n"); return NULL; } /** * Utility function that combines a filename and directory and passes it onto fopen. * * @returns fopen return value. * @param pszName The file name. * @param pszDir The directory path. * @param pszMode The fopen mode string. */ static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode) { char *pszPath = MakePathFromDirAndFile(pszName, pszDir); FILE *pFile = fopen(pszPath, pszMode); free(pszPath); return pFile; } /** * Utility function that combines a filename and directory and passes it onto open. * * @returns open return value. * @param pszName The file name. * @param pszDir The directory path. * @param fFlags The open flags. * @param fCreateMode The file creation mode. */ static int OpenFileInDir(const char *pszName, const char *pszDir, int fFlags, int fCreateMode) { char *pszPath = MakePathFromDirAndFile(pszName, pszDir); int fd = open(pszPath, fFlags, fCreateMode); free(pszPath); return fd; } /** * Deletes a file in a directory. * * @returns whatever unlink returns. * @param pszName The file name. * @param pszDir The directory path. */ static int UnlinkFileInDir(const char *pszName, const char *pszDir) { char *pszPath = MakePathFromDirAndFile(pszName, pszDir); int rc = unlink(pszPath); free(pszPath); return rc; } /** * Renames a file in a directory. * * @returns whatever rename returns. * @param pszOldName The new file name. * @param pszNewName The old file name. * @param pszDir The directory path. */ static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir) { char *pszOldPath = MakePathFromDirAndFile(pszOldName, pszDir); char *pszNewPath = MakePathFromDirAndFile(pszNewName, pszDir); int rc = rename(pszOldPath, pszNewPath); free(pszOldPath); free(pszNewPath); return rc; } /** * Check if a (regular) file exists in a directory. * * @returns 1 if it exists and is a regular file, 0 if not. * @param pszName The file name. * @param pszDir The directory path. */ static int DoesFileInDirExist(const char *pszName, const char *pszDir) { char *pszPath = MakePathFromDirAndFile(pszName, pszDir); struct stat st; int rc = stat(pszPath, &st); free(pszPath); #ifdef S_ISREG return !rc && S_ISREG(st.st_mode); #elif defined(_MSC_VER) return !rc && (st.st_mode & _S_IFMT) == _S_IFREG; #else #error "Port me" #endif } /** * Reads into memory an entire file. * * @returns Pointer to the heap allocation containing the file. * On failure NULL and errno is returned. * @param pszName The file. * @param pszDir The directory the file resides in. * @param pcbFile Where to store the file size. */ static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile) { int SavedErrno; char *pszPath = MakePathFromDirAndFile(pszName, pszDir); int fd = open(pszPath, O_RDONLY | O_BINARY); if (fd >= 0) { off_t cbFile = lseek(fd, 0, SEEK_END); if ( cbFile >= 0 && lseek(fd, 0, SEEK_SET) == 0) { char *pb = malloc(cbFile + 1); if (pb) { if (read(fd, pb, cbFile) == cbFile) { close(fd); pb[cbFile] = '\0'; *pcbFile = (size_t)cbFile; return pb; } SavedErrno = errno; free(pb); } else SavedErrno = ENOMEM; } else SavedErrno = errno; close(fd); } else SavedErrno = errno; free(pszPath); errno = SavedErrno; return NULL; } /** * Creates a directory including all necessary parent directories. * * @returns 0 on success, -1 + errno on failure. * @param pszDir The directory. */ static int MakePath(const char *pszPath) { int iErr = 0; char *pszAbsPath = AbsPath(pszPath); char *psz = pszAbsPath; /* Skip to the root slash (PC). */ while (!IS_SLASH(*psz) && *psz) psz++; /** @todo UNC */ for (;;) { char chSaved; /* skip slashes */ while (IS_SLASH(*psz)) psz++; if (!*psz) break; /* find the next slash or end and terminate the string. */ while (!IS_SLASH(*psz) && *psz) psz++; chSaved = *psz; *psz = '\0'; /* try create the directory, ignore failure because the directory already exists. */ errno = 0; #ifdef _MSC_VER if ( _mkdir(pszAbsPath) && errno != EEXIST) #else if ( mkdir(pszAbsPath, 0777) && errno != EEXIST && errno != ENOSYS /* Solaris nonsensical mkdir crap. */ && errno != EACCES /* Solaris nonsensical mkdir crap. */ ) #endif { iErr = errno; break; } /* restore the slash/terminator */ *psz = chSaved; } free(pszAbsPath); return iErr ? -1 : 0; } /** * Adds the arguments found in the pszCmdLine string to argument vector. * * The parsing of the pszCmdLine string isn't very sophisticated, no * escaping or quotes. * * @param pcArgs Pointer to the argument counter. * @param ppapszArgs Pointer to the argument vector pointer. * @param pszCmdLine The command line to parse and append. * @param pszWedgeArg Argument to put infront of anything found in pszCmdLine. */ static void AppendArgs(int *pcArgs, char ***ppapszArgs, const char *pszCmdLine, const char *pszWedgeArg) { int i; int cExtraArgs; const char *psz; char **papszArgs; /* * Count the new arguments. */ cExtraArgs = 0; psz = pszCmdLine; while (*psz) { while (isspace(*psz)) psz++; if (!psz) break; cExtraArgs++; while (!isspace(*psz) && *psz) psz++; } if (!cExtraArgs) return; /* * Allocate a new vector that can hold the arguments. * (Reallocating might not work since the argv might not be allocated * from the heap but off the stack or somewhere... ) */ i = *pcArgs; *pcArgs = i + cExtraArgs + !!pszWedgeArg; papszArgs = xmalloc((*pcArgs + 1) * sizeof(char *)); *ppapszArgs = memcpy(papszArgs, *ppapszArgs, i * sizeof(char *)); if (pszWedgeArg) papszArgs[i++] = xstrdup(pszWedgeArg); psz = pszCmdLine; while (*psz) { size_t cch; const char *pszEnd; while (isspace(*psz)) psz++; if (!psz) break; pszEnd = psz; while (!isspace(*pszEnd) && *pszEnd) pszEnd++; cch = pszEnd - psz; papszArgs[i] = xmalloc(cch + 1); memcpy(papszArgs[i], psz, cch); papszArgs[i][cch] = '\0'; i++; psz = pszEnd; } papszArgs[i] = NULL; } /** * Dependency collector state. */ typedef struct KOCDEP { /** The statemachine for processing the preprocessed code stream. */ enum KOCDEPSTATE { kOCDepState_Invalid = 0, kOCDepState_NeedNewLine, kOCDepState_NeedHash, kOCDepState_NeedLine_l, kOCDepState_NeedLine_l_HaveSpace, kOCDepState_NeedLine_i, kOCDepState_NeedLine_n, kOCDepState_NeedLine_e, kOCDepState_NeedSpaceBeforeDigit, kOCDepState_NeedFirstDigit, kOCDepState_NeedMoreDigits, kOCDepState_NeedQuote, kOCDepState_NeedEndQuote } enmState; /** Current offset into the filename buffer. */ uint32_t offFilename; /** The amount of space currently allocated for the filename buffer. */ uint32_t cbFilenameAlloced; /** Pointer to the filename buffer. */ char *pszFilename; /** The current dependency file. */ PDEP pCurDep; /** The core dependency collector state. */ DEPGLOBALS Core; } KOCDEP; /** Pointer to a KOCDEP. */ typedef KOCDEP *PKOCDEP; /** * Initializes the dependency collector state. * * @param pDepState The dependency collector state. */ static void kOCDepInit(PKOCDEP pDepState) { pDepState->enmState = kOCDepState_NeedHash; pDepState->offFilename = 0; pDepState->cbFilenameAlloced = 0; pDepState->pszFilename = NULL; pDepState->pCurDep = NULL; depInit(&pDepState->Core); } /** * Deletes the dependency collector state, releasing all resources. * * @param pDepState The dependency collector state. */ static void kOCDepDelete(PKOCDEP pDepState) { pDepState->enmState = kOCDepState_Invalid; free(pDepState->pszFilename); pDepState->pszFilename = NULL; depCleanup(&pDepState->Core); } /** * Unescapes a string in place. * * @returns The new string length. * @param psz The string to unescape (input and output). */ static size_t kOCDepUnescape(char *psz) { char *pszSrc = psz; char *pszDst = psz; char ch; while ((ch = *pszSrc++) != '\0') { if (ch == '\\') { char ch2 = *pszSrc; if (ch2) { pszSrc++; ch = ch2; } /* else: cannot happen / just ignore */ } *pszDst++ = ch; } *pszDst = '\0'; return pszDst - psz; } /** * Checks if the character at @a offChar is escaped or not. * * @returns 1 if escaped, 0 if not. * @param pach The string (not terminated). * @param offChar The offset of the character in question. */ static int kOCDepIsEscaped(char *pach, size_t offChar) { while (offChar > 0 && pach[offChar - 1] == '\\') { if ( offChar == 1 || pach[offChar - 2] != '\\') return 1; offChar -= 2; } return 0; } static void kOCDepEnter(PKOCDEP pDepState, const char *pszUnescFilename, size_t cchFilename) { if (cchFilename + 1 >= pDepState->cbFilenameAlloced) { pDepState->cbFilenameAlloced = (cchFilename + 1 + 15) & ~15; pDepState->pszFilename = (char *)xrealloc(pDepState->pszFilename, pDepState->cbFilenameAlloced); } memcpy(pDepState->pszFilename, pszUnescFilename, cchFilename); pDepState->pszFilename[cchFilename] = '\0'; cchFilename = kOCDepUnescape(pDepState->pszFilename); if ( !pDepState->pCurDep || cchFilename != pDepState->pCurDep->cchFilename || strcmp(pDepState->pszFilename, pDepState->pCurDep->szFilename)) pDepState->pCurDep = depAdd(&pDepState->Core, pDepState->pszFilename, cchFilename); } /** * This consumes the preprocessor output and generate dependencies from it. * * The trick is to look at the line directives and which files get listed there. * * @returns The new state. This is a convenience for saving code space and it * isn't really meant to be of any use to the caller. * @param pDepState The dependency collector state. * @param pszInput The input. * @param cchInput The input length. */ static enum KOCDEPSTATE kOCDepConsumer(PKOCDEP pDepState, const char *pszInput, size_t cchInput) { enum KOCDEPSTATE enmState = pDepState->enmState; const char *psz; while (cchInput > 0) { switch (enmState) { case kOCDepState_NeedNewLine: psz = (const char *)memchr(pszInput, '\n', cchInput); if (!psz) return enmState; psz++; cchInput -= psz - pszInput; pszInput = psz; /* fall thru */ case kOCDepState_NeedHash: while (cchInput > 0 && MY_IS_BLANK(*pszInput)) cchInput--, pszInput++; if (!cchInput) return pDepState->enmState = kOCDepState_NeedHash; if (*pszInput != '#') break; pszInput++; cchInput--; enmState = kOCDepState_NeedLine_l; /* fall thru */ case kOCDepState_NeedLine_l: case kOCDepState_NeedLine_l_HaveSpace: while (cchInput > 0 && MY_IS_BLANK(*pszInput)) { enmState = kOCDepState_NeedLine_l_HaveSpace; cchInput--, pszInput++; } if (!cchInput) return pDepState->enmState = enmState; if (*pszInput != 'l') { /* # "" */ if (enmState != kOCDepState_NeedLine_l_HaveSpace || !isdigit(*pszInput)) break; pszInput++; cchInput--; enmState = kOCDepState_NeedMoreDigits; continue; } pszInput++; if (!--cchInput) return pDepState->enmState = kOCDepState_NeedLine_i; /* fall thru */ case kOCDepState_NeedLine_i: if (*pszInput != 'i') break; pszInput++; if (!--cchInput) return pDepState->enmState = kOCDepState_NeedLine_n; /* fall thru */ case kOCDepState_NeedLine_n: if (*pszInput != 'n') break; pszInput++; if (!--cchInput) return pDepState->enmState = kOCDepState_NeedLine_e; /* fall thru */ case kOCDepState_NeedLine_e: if (*pszInput != 'e') break; pszInput++; if (!--cchInput) return pDepState->enmState = kOCDepState_NeedSpaceBeforeDigit; /* fall thru */ case kOCDepState_NeedSpaceBeforeDigit: if (!MY_IS_BLANK(*pszInput)) break; pszInput++; cchInput--; /* fall thru */ case kOCDepState_NeedFirstDigit: while (cchInput > 0 && MY_IS_BLANK(*pszInput)) cchInput--, pszInput++; if (!cchInput) return pDepState->enmState = kOCDepState_NeedFirstDigit; if (!isdigit(*pszInput)) break; pszInput++; cchInput--; /* fall thru */ case kOCDepState_NeedMoreDigits: while (cchInput > 0 && isdigit(*pszInput)) cchInput--, pszInput++; if (!cchInput) return pDepState->enmState = kOCDepState_NeedMoreDigits; /* fall thru */ case kOCDepState_NeedQuote: while (cchInput > 0 && MY_IS_BLANK(*pszInput)) cchInput--, pszInput++; if (!cchInput) return pDepState->enmState = kOCDepState_NeedQuote; if (*pszInput != '"') break; pszInput++; cchInput--; /* fall thru */ case kOCDepState_NeedEndQuote: { uint32_t off = pDepState->offFilename; for (;;) { char ch; if (!cchInput) { pDepState->offFilename = off; return pDepState->enmState = kOCDepState_NeedEndQuote; } if (off + 1 >= pDepState->cbFilenameAlloced) { if (!pDepState->cbFilenameAlloced) pDepState->cbFilenameAlloced = 32; else pDepState->cbFilenameAlloced *= 2; pDepState->pszFilename = (char *)xrealloc(pDepState->pszFilename, pDepState->cbFilenameAlloced); } pDepState->pszFilename[off] = ch = *pszInput++; cchInput--; if ( ch == '"' && ( off == 0 || pDepState->pszFilename[off - 1] != '\\' || !kOCDepIsEscaped(pDepState->pszFilename, off))) { /* Done, unescape and add the file. */ size_t cchFilename; pDepState->pszFilename[off] = '\0'; cchFilename = kOCDepUnescape(pDepState->pszFilename); if ( !pDepState->pCurDep || cchFilename != pDepState->pCurDep->cchFilename || strcmp(pDepState->pszFilename, pDepState->pCurDep->szFilename)) pDepState->pCurDep = depAdd(&pDepState->Core, pDepState->pszFilename, cchFilename); pDepState->offFilename = 0; break; } off++; } } /* fall thru */ case kOCDepState_Invalid: assert(0); break; } /* next newline */ enmState = kOCDepState_NeedNewLine; } return pDepState->enmState = enmState; } /** * Writes the dependencies to the specified file. * * @param pDepState The dependency collector state. * @param pszFilename The name of the dependency file. * @param pszObjFile The object file name, relative to @a pszObjDir. * @param pszObjDir The object file directory. * @param fFixCase Whether to fix the case of dependency files. * @param fQuiet Whether to be quiet about the dependencies. * @param fGenStubs Whether to generate stubs. */ static void kOCDepWriteToFile(PKOCDEP pDepState, const char *pszFilename, const char *pszObjFile, const char *pszObjDir, int fFixCase, int fQuiet, int fGenStubs) { char *pszObjFileAbs; char *psz; FILE *pFile = fopen(pszFilename, "w"); if (!pFile) FatalMsg("Failed to open dependency file '%s': %s\n", pszFilename, strerror(errno)); depOptimize(&pDepState->Core, fFixCase, fQuiet, NULL /*pszIgnoredExt*/); /* Make object file name with unix slashes. */ pszObjFileAbs = MakePathFromDirAndFile(pszObjFile, pszObjDir); psz = pszObjFileAbs; while ((psz = strchr(psz, '\\')) != NULL) *psz++ = '/'; depPrintTargetWithDeps(&pDepState->Core, pFile, pszObjFileAbs, 1 /*fEscapeTarget*/); free(pszObjFileAbs); if (fGenStubs) depPrintStubs(&pDepState->Core, pFile); if (fclose(pFile) != 0) FatalMsg("Failed to write dependency file '%s': %s\n", pszFilename, strerror(errno)); } /** * Preprocessor output reader state. */ typedef struct KOCCPPRD { /** Pointer to the preprocessor output. */ char *pszBuf; /** Allocated buffer size. */ size_t cbBufAlloc; /** Amount preprocessor output that we've completed optimizations for. */ size_t cchDstOptimized; /** Offset to the start of the unoptimized source. */ size_t offSrcUnoptimized; /** The offset of the next bits to process. */ size_t offSrcCur; /** The offset where to put more raw preprocessor output. */ size_t offSrcRead; /** The line number corresponding to offOptimized. */ uint32_t uOptLineNo; /** The current line number. */ uint32_t uCurLineNo; /** Set if we're done, clear if we're expecting more preprocessor output. */ int fDone; /** The saved character at cchOptimized. */ char chSaved; /** Whether the optimizations are enabled. */ int fOptimize; /** Buffer holding the current file name (unescaped). */ char *pszFileNmBuf; /** The size of the file name buffer. */ size_t cbFileNmBuf; /** The length of the current file string. */ size_t cchCurFileNm; /** Line directive / new line sequence buffer. */ char *pszLineBuf; /** The size of the buffer pointed to by pszLineBuf. */ size_t cbLineBuf; /** Set if we should work the dependency generator as well. */ PKOCDEP pDepState; } KOCCPPRD; /** Pointer to a preprocessor reader state. */ typedef KOCCPPRD *PKOCCPPRD; /** * Allocate the initial C preprocessor output buffer. * * @param pCppRd The C preprocessor reader instance. * @param cbOldCpp The size of the output the last time. This is 0 if * there was not previous run. * @param fOptimize Whether optimizations are enabled. * @param pDepState Pointer to the dependency generator. Must only be set * if @a fOptimize is also set. */ static void kOCCppRdInit(PKOCCPPRD pCppRd, size_t cbOldCpp, int fOptimize, PKOCDEP pDepState) { assert(!pDepState || fOptimize); pCppRd->cbBufAlloc = cbOldCpp ? (cbOldCpp + KOC_BUF_INCR) & ~(KOC_BUF_ALIGNMENT - 1) : KOC_BUF_MIN; pCppRd->pszBuf = xmalloc(pCppRd->cbBufAlloc); pCppRd->cchCurFileNm = 0; pCppRd->cchDstOptimized = 0; pCppRd->offSrcUnoptimized = 0; pCppRd->offSrcCur = 0; pCppRd->offSrcRead = 0; pCppRd->uOptLineNo = 1; pCppRd->uCurLineNo = 1; pCppRd->fDone = 0; pCppRd->chSaved = 0; pCppRd->fOptimize = fOptimize; pCppRd->pszFileNmBuf = NULL; pCppRd->cbFileNmBuf = 0; pCppRd->cchCurFileNm = 0; pCppRd->pszLineBuf = NULL; pCppRd->cbLineBuf = 0; pCppRd->pDepState = pDepState; } static void kOCCppRdDelete(PKOCCPPRD pCppRd) { free(pCppRd->pszBuf); pCppRd->pszBuf = NULL; free(pCppRd->pszFileNmBuf); pCppRd->pszFileNmBuf = NULL; free(pCppRd->pszLineBuf); pCppRd->pszLineBuf = NULL; } /** * Allocate more buffer space for the C preprocessor output. * * @param pCppRd The C preprocessor reader instance. */ static size_t kOCCppRdGrowBuffer(PKOCCPPRD pCppRd) { pCppRd->cbBufAlloc += KOC_BUF_INCR; pCppRd->pszBuf = xrealloc(pCppRd->pszBuf, pCppRd->cbBufAlloc); return pCppRd->cbBufAlloc - pCppRd->offSrcRead; } static size_t kOCCppRdOptInsert(PKOCCPPRD pCppRd, size_t cchSrcReplaced, const char *pchInsert, size_t cchInsert) { size_t offDelta = 0; size_t cchAvail; pCppRd->offSrcUnoptimized += cchSrcReplaced; assert(pCppRd->offSrcUnoptimized <= pCppRd->offSrcCur); cchAvail = pCppRd->offSrcUnoptimized - pCppRd->cchDstOptimized; if (cchAvail < cchInsert) { size_t const cbToMove = pCppRd->offSrcRead - pCppRd->offSrcUnoptimized; assert(cbToMove <= pCppRd->offSrcRead); offDelta = cchInsert - cchAvail; while (pCppRd->offSrcRead + offDelta >= pCppRd->cbBufAlloc) kOCCppRdGrowBuffer(pCppRd); g_cMemMoves++; g_cbMemMoved += cbToMove + 1; memmove(pCppRd->pszBuf + pCppRd->offSrcUnoptimized + offDelta, pCppRd->pszBuf + pCppRd->offSrcUnoptimized, cbToMove + 1); pCppRd->offSrcRead += offDelta; pCppRd->offSrcUnoptimized += offDelta; pCppRd->offSrcCur += offDelta; assert(pCppRd->offSrcRead < 1 || pCppRd->pszBuf[pCppRd->offSrcRead - 1] != '\0'); } memcpy(pCppRd->pszBuf + pCppRd->cchDstOptimized, pchInsert, cchInsert); pCppRd->cchDstOptimized += cchInsert; return offDelta; } static void kOCCppRdOptCommit(PKOCCPPRD pCppRd) { size_t cchToCommit = pCppRd->offSrcCur - pCppRd->offSrcUnoptimized; assert(pCppRd->offSrcUnoptimized <= pCppRd->offSrcCur); if (cchToCommit) { memmove(pCppRd->pszBuf + pCppRd->cchDstOptimized, pCppRd->pszBuf + pCppRd->offSrcUnoptimized, cchToCommit); pCppRd->cchDstOptimized += cchToCommit; pCppRd->offSrcUnoptimized = pCppRd->offSrcCur; } pCppRd->uOptLineNo = pCppRd->uCurLineNo; } static char *kOCCppRdOptGetEol(PKOCCPPRD pCppRd, char *pszCur, size_t cbLeft) { char *pszEol = memchr(pszCur, '\n', cbLeft); if (pszEol) { if (pszCur != pszEol && pszEol[-1] == '\r') pszEol--; } else if (pCppRd->fDone && cbLeft) pszEol = pszCur + cbLeft; return pszEol; } static void kOCCppRdOptSetFile(PKOCCPPRD pCppRd, const char *pchFile, size_t cchFile) { if (cchFile >= pCppRd->cbFileNmBuf) { pCppRd->cbFileNmBuf = (cchFile + 15 + 1) & ~(size_t)15; pCppRd->pszFileNmBuf = xrealloc(pCppRd->pszFileNmBuf, pCppRd->cbFileNmBuf); } memcpy(pCppRd->pszFileNmBuf, pchFile, cchFile); pCppRd->pszFileNmBuf[cchFile] = '\0'; pCppRd->cchCurFileNm = cchFile; } static size_t kOCCppRdOptFmtLine(PKOCCPPRD pCppRd, uint32_t uLine, const char *pchFile, size_t cchFile) { size_t cchUsed; size_t cbNeeded; /* Make sure we've got enough buffer space. */ cbNeeded = sizeof("#line 4888222111 \"\"\n") + cchFile; if (cbNeeded > pCppRd->cbLineBuf) { pCppRd->cbLineBuf = (cbNeeded + 32 + 15) & ~(size_t)15; pCppRd->pszLineBuf = xrealloc(pCppRd->pszLineBuf, pCppRd->cbLineBuf); } /* Do the formatting. */ cchUsed = sprintf(pCppRd->pszLineBuf, "#line %lu", (unsigned long)uLine); if (cchFile) { pCppRd->pszLineBuf[cchUsed++] = ' '; pCppRd->pszLineBuf[cchUsed++] = '"'; memcpy(&pCppRd->pszLineBuf[cchUsed], pchFile, cchFile); cchUsed += cchFile; pCppRd->pszLineBuf[cchUsed++] = '"'; } pCppRd->pszLineBuf[cchUsed++] = '\n'; pCppRd->pszLineBuf[cchUsed] = '\0'; return cchUsed; } static size_t kOCCppRdOptFmtNewLines(PKOCCPPRD pCppRd, uint32_t cNewLines) { if (cNewLines + 1 > pCppRd->cbLineBuf) { pCppRd->cbLineBuf = (cNewLines + 1 + 32 + 15) & ~(size_t)15; pCppRd->pszLineBuf = xrealloc(pCppRd->pszLineBuf, pCppRd->cbLineBuf); } memset(pCppRd->pszLineBuf, '\n', cNewLines); pCppRd->pszLineBuf[cNewLines] = '\0'; return cNewLines; } static size_t kOCCppRdOptFlush(PKOCCPPRD pCppRd, size_t offSrcCur, int fLineDirNext) { size_t offDelta = 0; size_t const offSrcUnoptimized = pCppRd->offSrcUnoptimized; assert(offSrcUnoptimized <= offSrcCur); if (offSrcCur > offSrcUnoptimized) { /* * We've got unflushed whitelines. */ size_t const cchSrcInQuestion = offSrcCur - offSrcUnoptimized; uint32_t const cLinesInQuestion = pCppRd->uCurLineNo - pCppRd->uOptLineNo; size_t cchLineDir; if ( cLinesInQuestion <= 7 || (cchLineDir = kOCCppRdOptFmtLine(pCppRd, pCppRd->uCurLineNo, NULL, 0)) >= cLinesInQuestion) cchLineDir = kOCCppRdOptFmtNewLines(pCppRd, cLinesInQuestion); offDelta = kOCCppRdOptInsert(pCppRd, cchSrcInQuestion, pCppRd->pszLineBuf, cchLineDir); } (void)fLineDirNext; /* Use later if required. */ return offDelta; } static int kOCCppRdOptParseLine(PKOCCPPRD pCppRd, char *pszCur, char *pszEol, uint32_t *puNewLineNo, char **ppszNewFile, size_t *pcchNewFile) { char *psz = pszCur; uint32_t uNewLineNo; int fIsShort; /* * Check if it's a #line directive of some kind and parse it. */ if (*psz != '#') return 0; psz++; fIsShort = MY_IS_BLANK(*psz); while (MY_IS_BLANK(*psz)) psz++; if ( psz[0] == 'l' && psz[1] == 'i' && psz[2] == 'n' && psz[3] == 'e' && MY_IS_BLANK(psz[4]) ) { fIsShort = 0; psz += 5; while (MY_IS_BLANK(*psz)) psz++; } else if (fIsShort && isdigit(*psz)) fIsShort = 1; else return 0; /* Parse the line number. */ if (!isdigit(*psz)) return 0; uNewLineNo = *psz++ - '0'; while (isdigit(*psz)) { uNewLineNo *= 10; uNewLineNo += *psz++ - '0'; } if ( psz != pszEol && !MY_IS_BLANK(*psz)) return 0; /* * The file name part is optional. */ while (MY_IS_BLANK(*psz)) psz++; if ( psz != pszEol && *psz == '"') { *ppszNewFile = ++psz; while ( psz != pszEol && ( *psz != '"' || ( psz[-1] == '\\' && kOCDepIsEscaped(psz, psz - *ppszNewFile)) ) ) psz++; if (psz == pszEol) { /** @todo complain? */ return 0; } *pcchNewFile = psz - *ppszNewFile; do psz++; while (psz != pszEol && MY_IS_BLANK(*psz)); } else { /* No file given => Same as the current. */ *ppszNewFile = pCppRd->cchCurFileNm ? pCppRd->pszFileNmBuf : NULL; *pcchNewFile = pCppRd->cchCurFileNm; } if (psz != pszEol) { /** @todo complain? */ return 0; } *puNewLineNo = uNewLineNo; return 1; } static char *kOCCppRdOptHandleLine(PKOCCPPRD pCppRd, char *pszCur, size_t *pcbLeft, int *pfEmptyLine, char *pszEol) { size_t const offSrcLine = pCppRd->offSrcCur; size_t const cchSrcLine = pszEol - pCppRd->pszBuf - (pCppRd->fOptimize & 2 ? pCppRd->offSrcUnoptimized : pCppRd->offSrcCur); size_t const cbLeftAssert = *pcbLeft; char *pszNewFile; size_t cchNewFile; uint32_t uNewLineNo; assert(*pszEol == '\r' || *pszEol == '\n' || *pszEol == '\0'); /* Advance to the end of the line before we do anything. This can be a little confusing but it saves effort and avoid trouble in the end. */ pCppRd->offSrcCur = pszEol - pCppRd->pszBuf; *pcbLeft -= pszEol - pszCur; assert(*pcbLeft <= cbLeftAssert); (void)cbLeftAssert; /* * Try parse the directive a '#line' one.... */ if (!kOCCppRdOptParseLine(pCppRd, pszCur, pszEol, &uNewLineNo, &pszNewFile, &cchNewFile)) { /* * No line directive. Flush pending optimizations and indicate that * the line isn't empty and needs to be commited at EOL. */ kOCCppRdOptFlush(pCppRd, offSrcLine, 0); *pfEmptyLine = 0; } else { char *pszCurFile = pCppRd->cchCurFileNm ? pCppRd->pszFileNmBuf : NULL; if ( pszNewFile == pszCurFile || ( cchNewFile == pCppRd->cchCurFileNm && !memcmp(pszNewFile, pszCurFile, cchNewFile)) ) { /* * A #line directive specifying the same file. */ if (uNewLineNo >= pCppRd->uCurLineNo) *pfEmptyLine = 1; else { /* * It went backwards, so we need to flush the old section of * the file and emit another directive for starting the new one. */ size_t cchLineDir; if (!(pCppRd->fOptimize & 2)) kOCCppRdOptFlush(pCppRd, offSrcLine, 1); cchLineDir = kOCCppRdOptFmtLine(pCppRd, uNewLineNo, NULL, 0) - 1; /* sans \n */ kOCCppRdOptInsert(pCppRd, cchSrcLine, pCppRd->pszLineBuf, cchLineDir); *pfEmptyLine = 0; } } else { /* * The #line directive changed the file. */ size_t cchLineDir; kOCCppRdOptSetFile(pCppRd, pszNewFile, cchNewFile); /* save to do this early */ if (!(pCppRd->fOptimize & 2)) kOCCppRdOptFlush(pCppRd, offSrcLine, 1); cchLineDir = kOCCppRdOptFmtLine(pCppRd, uNewLineNo, pCppRd->pszFileNmBuf, cchNewFile) - 1; /* sans \n */ kOCCppRdOptInsert(pCppRd, cchSrcLine, pCppRd->pszLineBuf, cchLineDir); if (pCppRd->pDepState) kOCDepEnter(pCppRd->pDepState, pCppRd->pszFileNmBuf, cchNewFile); *pfEmptyLine = 0; } pCppRd->uCurLineNo = uNewLineNo - 1; } return pCppRd->pszBuf + pCppRd->offSrcCur; } static void kOCCppRdOpt(PKOCCPPRD pCppRd) { size_t cch; char *pszEol; char *pszCur = pCppRd->pszBuf + pCppRd->offSrcCur; size_t cbTodo = pCppRd->offSrcRead - pCppRd->offSrcCur; int fEmptyLine = 1; while (cbTodo > 0) { switch (*pszCur) { case ' ': case '\t': break; case '\n': pCppRd->offSrcCur = pszCur - pCppRd->pszBuf + 1; pCppRd->uCurLineNo++; if (!fEmptyLine) kOCCppRdOptCommit(pCppRd); fEmptyLine = 1; break; case '\r': /* "\r\n" -> "\n" */ if (cbTodo <= 1 && !pCppRd->fDone) return; if (pszCur[1] == '\n' && !fEmptyLine) { /* Commit the part up to the '\r' first, replace '\r\n' with '\n'. */ pCppRd->offSrcCur = pszCur - pCppRd->pszBuf; kOCCppRdOptCommit(pCppRd); pCppRd->offSrcCur += 2; kOCCppRdOptInsert(pCppRd, 2, "\n", 1); assert(cbTodo >= 2); cbTodo -= 2; pszCur += 2; fEmptyLine = 1; continue; } break; case '#': pszEol = kOCCppRdOptGetEol(pCppRd, pszCur + 1, cbTodo - 1); if (!pszEol) return; pszCur = kOCCppRdOptHandleLine(pCppRd, pszCur, &cbTodo, &fEmptyLine, pszEol); continue; default: /* * Some non-white stuff encountered, flush pending white * line optimizations and skip to the end of the line. */ fEmptyLine = 0; pszEol = kOCCppRdOptGetEol(pCppRd, pszCur + 1, cbTodo - 1); if (!pszEol) return; cch = pszEol - pszCur; pszCur += kOCCppRdOptFlush(pCppRd, pCppRd->offSrcCur, 0); assert(cch <= cbTodo); cbTodo -= cch; pszCur += cch; continue; } cbTodo--; pszCur++; } } static void kOCCppRdOptFinalize(PKOCCPPRD pCppRd) { pCppRd->fDone = 1; assert(pCppRd->offSrcRead < 1 || pCppRd->pszBuf[pCppRd->offSrcRead - 1] != '\0'); pCppRd->pszBuf[pCppRd->offSrcRead] = '\0'; kOCCppRdOpt(pCppRd); assert(pCppRd->offSrcCur == pCppRd->offSrcRead); kOCCppRdOptFlush(pCppRd, pCppRd->offSrcCur, 0); } /** * Read C preprocessor output from the given file descriptor, optionally * optimzing it. * * @returns Number of bytes read. 0 indicates end of file. * * @param pCppRd The C preprocessor reader instance. * @param fdIn The file descriptor to read the raw preprocessor output * from. * @param ppszRet Where to return the pointer to the output. * * @remarks Won't return on error, calls FatalDie on those occasions. */ static long kOCCppRdRead(PKOCCPPRD pCppRd, int fdIn, const char **ppszRet) { size_t cbLeft; long cbRead; if (pCppRd->fOptimize) { /* * Optimize the C preprocessor output on the way thru. */ size_t const cchOldOptimized = pCppRd->cchDstOptimized; if (pCppRd->chSaved) pCppRd->pszBuf[pCppRd->cchDstOptimized] = pCppRd->chSaved; do { /* Read more raw C preprocessor output. */ cbLeft = pCppRd->cbBufAlloc - pCppRd->offSrcRead; if (cbLeft <= 1) cbLeft = kOCCppRdGrowBuffer(pCppRd); do cbRead = read(fdIn, pCppRd->pszBuf + pCppRd->offSrcRead, (long)(cbLeft - 1)); while (cbRead < 0 && errno == EINTR); if (cbRead < 0) FatalDie("kOCCppRdRead - read(%d,,%ld) failed: %s\n", fdIn, (long)(cbLeft - 1), strerror(errno)); pCppRd->offSrcRead += cbRead; /* Optimize it. */ if (!cbRead) { kOCCppRdOptFinalize(pCppRd); break; } kOCCppRdOpt(pCppRd); } while (pCppRd->cchDstOptimized == cchOldOptimized); *ppszRet = &pCppRd->pszBuf[cchOldOptimized]; pCppRd->chSaved = pCppRd->pszBuf[pCppRd->cchDstOptimized]; pCppRd->pszBuf[pCppRd->cchDstOptimized] = '\0'; cbRead = (long)(pCppRd->cchDstOptimized - cchOldOptimized); } else { /* * Pass thru. */ char *pszBuf; cbLeft = pCppRd->cbBufAlloc - pCppRd->offSrcRead; if (cbLeft <= 1) cbLeft = kOCCppRdGrowBuffer(pCppRd); pszBuf = pCppRd->pszBuf + pCppRd->offSrcRead; do cbRead = read(fdIn, pszBuf, (long)(cbLeft - 1)); while (cbRead < 0 && errno == EINTR); if (cbRead < 0) FatalDie("kOCCppRdRead - read(%d,,%ld) failed: %s\n", fdIn, (long)(cbLeft - 1), strerror(errno)); *ppszRet = pszBuf; pCppRd->offSrcRead += cbRead; pszBuf[cbRead] = '\0'; } return cbRead; } /** * Grabs the output buffer from the C preprocessor reader. * * @param pCppRd The C preprocessor reader instance. * @param ppszRet Where to return the pointer to the output. * @param pcbRet Where to return the size of the output. */ static void kOCCppRdGrabOutput(PKOCCPPRD pCppRd, char **ppszRet, size_t *pcbRet) { assert(pCppRd->offSrcRead < 1 || pCppRd->pszBuf[pCppRd->offSrcRead - 1] != '\0'); *ppszRet = pCppRd->pszBuf; *pcbRet = pCppRd->fOptimize ? pCppRd->cchDstOptimized : pCppRd->offSrcRead; pCppRd->pszBuf = NULL; pCppRd->offSrcRead = 0; } /** A checksum list entry. * We keep a list checksums (of preprocessor output) that matches. * * The matching algorithm doesn't require the preprocessor output to be * indentical, only to produce the same object files. */ typedef struct KOCSUM { /** The next checksum. */ struct KOCSUM *pNext; /** The crc32 checksum. */ uint32_t crc32; /** The MD5 digest. */ unsigned char md5[16]; /** Valid or not. */ unsigned fUsed; } KOCSUM; /** Pointer to a KOCSUM. */ typedef KOCSUM *PKOCSUM; /** Pointer to a const KOCSUM. */ typedef const KOCSUM *PCKOCSUM; /** * Temporary context record used when calculating the checksum of some data. */ typedef struct KOCSUMCTX { /** The MD5 context. */ struct MD5Context MD5Ctx; } KOCSUMCTX; /** Pointer to a check context record. */ typedef KOCSUMCTX *PKOCSUMCTX; /** * Initializes a checksum object with an associated context. * * @param pSum The checksum object. * @param pCtx The checksum context. */ static void kOCSumInitWithCtx(PKOCSUM pSum, PKOCSUMCTX pCtx) { memset(pSum, 0, sizeof(*pSum)); MD5Init(&pCtx->MD5Ctx); } /** * Updates the checksum calculation. * * @param pSum The checksum. * @param pCtx The checksum calcuation context. * @param pvBuf The input data to checksum. * @param cbBuf The size of the input data. */ static void kOCSumUpdate(PKOCSUM pSum, PKOCSUMCTX pCtx, const void *pvBuf, size_t cbBuf) { /* * Take in relativly small chunks to try keep it in the cache. */ const unsigned char *pb = (const unsigned char *)pvBuf; while (cbBuf > 0) { size_t cb = cbBuf >= 128*1024 ? 128*1024 : cbBuf; pSum->crc32 = crc32(pSum->crc32, pb, cb); MD5Update(&pCtx->MD5Ctx, pb, (unsigned)cb); cbBuf -= cb; } } /** * Finalizes a checksum calculation. * * @param pSum The checksum. * @param pCtx The checksum calcuation context. */ static void kOCSumFinalize(PKOCSUM pSum, PKOCSUMCTX pCtx) { MD5Final(&pSum->md5[0], &pCtx->MD5Ctx); pSum->fUsed = 1; } /** * Init a check sum chain head. * * @param pSumHead The checksum head to init. */ static void kOCSumInit(PKOCSUM pSumHead) { memset(pSumHead, 0, sizeof(*pSumHead)); } /** * Parses the given string into a checksum head object. * * @returns 0 on success, -1 on format error. * @param pSumHead The checksum head to init. * @param pszVal The string to initialized it from. */ static int kOCSumInitFromString(PKOCSUM pSumHead, const char *pszVal) { unsigned i; char *pszNext; char *pszMD5; memset(pSumHead, 0, sizeof(*pSumHead)); pszMD5 = strchr(pszVal, ':'); if (pszMD5 == NULL) return -1; *pszMD5++ = '\0'; /* crc32 */ pSumHead->crc32 = (uint32_t)strtoul(pszVal, &pszNext, 16); if (pszNext && *pszNext) return -1; /* md5 */ for (i = 0; i < sizeof(pSumHead->md5) * 2; i++) { unsigned char ch = pszMD5[i]; int x; if ((unsigned char)(ch - '0') <= 9) x = ch - '0'; else if ((unsigned char)(ch - 'a') <= 5) x = ch - 'a' + 10; else if ((unsigned char)(ch - 'A') <= 5) x = ch - 'A' + 10; else return -1; if (!(i & 1)) pSumHead->md5[i >> 1] = x << 4; else pSumHead->md5[i >> 1] |= x; } pSumHead->fUsed = 1; return 0; } /** * Delete a check sum chain. * * @param pSumHead The head of the checksum chain. */ static void kOCSumDeleteChain(PKOCSUM pSumHead) { PKOCSUM pSum = pSumHead->pNext; while (pSum) { void *pvFree = pSum; pSum = pSum->pNext; free(pvFree); } memset(pSumHead, 0, sizeof(*pSumHead)); } /** * Insert a check sum into the chain. * * @param pSumHead The head of the checksum list. * @param pSumAdd The checksum to add (duplicate). */ static void kOCSumAdd(PKOCSUM pSumHead, PCKOCSUM pSumAdd) { if (pSumHead->fUsed) { PKOCSUM pNew = xmalloc(sizeof(*pNew)); *pNew = *pSumAdd; pNew->pNext = pSumHead->pNext; pNew->fUsed = 1; pSumHead->pNext = pNew; } else { *pSumHead = *pSumAdd; pSumHead->pNext = NULL; pSumHead->fUsed = 1; } } /** * Inserts an entrie chain into the given check sum chain. * * @param pSumHead The head of the checksum list. * @param pSumHeadAdd The head of the checksum list to be added. */ static void kOCSumAddChain(PKOCSUM pSumHead, PCKOCSUM pSumHeadAdd) { while (pSumHeadAdd) { kOCSumAdd(pSumHead, pSumHeadAdd); pSumHeadAdd = pSumHeadAdd->pNext; } } /** * Prints the checksum to the specified stream. * * @param pSum The checksum. * @param pFile The output file stream */ static void kOCSumFPrintf(PCKOCSUM pSum, FILE *pFile) { fprintf(pFile, "%#x:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", pSum->crc32, pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3], pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7], pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11], pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]); } /** * Displays the checksum (not chain!) using the InfoMsg() method. * * @param pSum The checksum. * @param uLevel The info message level. * @param pszMsg Message to prefix the info message with. */ static void kOCSumInfo(PCKOCSUM pSum, unsigned uLevel, const char *pszMsg) { InfoMsg(uLevel, "%s: crc32=%#010x md5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", pszMsg, pSum->crc32, pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3], pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7], pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11], pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]); } /** * Compares two check sum entries. * * @returns 1 if equal, 0 if not equal. * * @param pSum1 The first checksum. * @param pSum2 The second checksum. */ static int kOCSumIsEqual(PCKOCSUM pSum1, PCKOCSUM pSum2) { if (pSum1 == pSum2) return 1; if (!pSum1 || !pSum2) return 0; if (pSum1->crc32 != pSum2->crc32) return 0; if (memcmp(&pSum1->md5[0], &pSum2->md5[0], sizeof(pSum1->md5))) return 0; return 1; } /** * Checks if the specified checksum equals one of the * checksums in the chain. * * @returns 1 if equals one of them, 0 if not. * * @param pSumHead The checksum chain too look in. * @param pSum The checksum to look for. * @todo ugly name. fix. */ static int kOCSumHasEqualInChain(PCKOCSUM pSumHead, PCKOCSUM pSum) { for (; pSumHead; pSumHead = pSumHead->pNext) { if (pSumHead == pSum) return 1; if (pSumHead->crc32 != pSum->crc32) continue; if (memcmp(&pSumHead->md5[0], &pSum->md5[0], sizeof(pSumHead->md5))) continue; return 1; } return 0; } /** * Checks if the checksum (chain) empty. * * @returns 1 if empty, 0 if it there is one or more checksums. * @param pSum The checksum to test. */ static int kOCSumIsEmpty(PCKOCSUM pSum) { return !pSum->fUsed; } /** * The representation of a cache entry. */ typedef struct KOCENTRY { /** The name of the cache entry. */ const char *pszName; /** The dir that all other names are relative to. */ char *pszDir; /** The absolute path. */ char *pszAbsPath; /** Set if the object needs to be (re)compiled. */ unsigned fNeedCompiling; /** Whether the preprocessor runs in piped mode. If clear it's file * mode (it could be redirected stdout, but that's essentially the * same from our point of view). */ unsigned fPipedPreComp; /** Whether the compiler runs in piped mode (preprocessor output on stdin). */ unsigned fPipedCompile; /** The name of the pipe that we're feeding the preprocessed output to the * compiler via. This is a Windows thing. */ char *pszNmPipeCompile; /** Name of the dependency file (generated from #line statements in the * preprocessor output). */ char *pszMakeDepFilename; /** Whether to fix the case of the make depedencies. */ int fMakeDepFixCase; /** Whether to do the make dependencies quietly. */ int fMakeDepQuiet; /** Whether to generate stubs for headers files. */ int fMakeDepGenStubs; /** The dependency collector state. */ KOCDEP DepState; /** Whether the optimizations are enabled. */ int fOptimizeCpp; /** Cache entry key that's used for some quick digest validation. */ uint32_t uKey; /** The file data. */ struct KOCENTRYDATA { /** The name of file containing the preprocessor output. */ char *pszCppName; /** Pointer to the preprocessor output. */ char *pszCppMapping; /** The size of the preprocessor output. 0 if not determined. */ size_t cbCpp; /** The preprocessor output checksums that will produce the cached object. */ KOCSUM SumHead; /** The number of milliseconds spent precompiling. */ uint32_t cMsCpp; /** The object filename (relative to the cache file). */ char *pszObjName; /** The compile argument vector used to build the object. */ char **papszArgvCompile; /** The size of the compile */ unsigned cArgvCompile; /** The checksum of the compiler argument vector. */ KOCSUM SumCompArgv; /** The number of milliseconds spent compiling. */ uint32_t cMsCompile; /** @todo need a list of additional output files for MSC. */ /** @todo need compiler output (warnings). */ /** The target os/arch identifier. */ char *pszTarget; } /** The old data.*/ Old, /** The new data. */ New; } KOCENTRY; /** Pointer to a KOCENTRY. */ typedef KOCENTRY *PKOCENTRY; /** Pointer to a const KOCENTRY. */ typedef const KOCENTRY *PCKOCENTRY; /** * Creates a cache entry for the given cache file name. * * @returns Pointer to a cache entry. * @param pszFilename The cache file name. */ static PKOCENTRY kOCEntryCreate(const char *pszFilename) { PKOCENTRY pEntry; size_t off; /* * Allocate an empty entry. */ pEntry = xmallocz(sizeof(*pEntry)); kOCDepInit(&pEntry->DepState); kOCSumInit(&pEntry->New.SumHead); kOCSumInit(&pEntry->Old.SumHead); kOCSumInit(&pEntry->New.SumCompArgv); kOCSumInit(&pEntry->Old.SumCompArgv); /* * Setup the directory and cache file name. */ pEntry->pszAbsPath = AbsPath(pszFilename); pEntry->pszName = FindFilenameInPath(pEntry->pszAbsPath); off = pEntry->pszName - pEntry->pszAbsPath; if (!off) FatalDie("Failed to find abs path for '%s'!\n", pszFilename); pEntry->pszDir = xmalloc(off); memcpy(pEntry->pszDir, pEntry->pszAbsPath, off - 1); pEntry->pszDir[off - 1] = '\0'; return pEntry; } /** * Destroys the cache entry freeing up all it's resources. * * @param pEntry The entry to free. */ static void kOCEntryDestroy(PKOCENTRY pEntry) { /** @todo free pEntry->pszName? */ free(pEntry->pszDir); free(pEntry->pszAbsPath); free(pEntry->pszNmPipeCompile); free(pEntry->pszMakeDepFilename); kOCDepDelete(&pEntry->DepState); kOCSumDeleteChain(&pEntry->New.SumHead); kOCSumDeleteChain(&pEntry->Old.SumHead); kOCSumDeleteChain(&pEntry->New.SumCompArgv); kOCSumDeleteChain(&pEntry->Old.SumCompArgv); free(pEntry->New.pszCppName); free(pEntry->Old.pszCppName); free(pEntry->New.pszCppMapping); free(pEntry->Old.pszCppMapping); free(pEntry->New.pszObjName); free(pEntry->Old.pszObjName); free(pEntry->New.pszTarget); free(pEntry->Old.pszTarget); while (pEntry->New.cArgvCompile > 0) free(pEntry->New.papszArgvCompile[--pEntry->New.cArgvCompile]); while (pEntry->Old.cArgvCompile > 0) free(pEntry->Old.papszArgvCompile[--pEntry->Old.cArgvCompile]); free(pEntry->New.papszArgvCompile); free(pEntry->Old.papszArgvCompile); free(pEntry); } /** * Calculates the checksum of an compiler argument vector. * * @param pEntry The cache entry. * @param papszArgv The argument vector. * @param cArgc The number of entries in the vector. * @param pszIgnorePath1 Path to ignore when encountered at the end of * arguments. (Not quite safe for simple file names, * but what the heck.) * @param pszIgnorePath2 Path to ignore when encountered at the end of * arguments. (Not quite safe for simple file names, * but what the heck.) * @param pSum Where to store the check sum. */ static void kOCEntryCalcArgvSum(PKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgc, const char *pszIgnorePath1, const char *pszIgnorePath2, PKOCSUM pSum) { size_t cchIgnorePath1 = strlen(pszIgnorePath1); size_t cchIgnorePath2 = pszIgnorePath2 ? strlen(pszIgnorePath2) : ~(size_t)0; KOCSUMCTX Ctx; unsigned i; kOCSumInitWithCtx(pSum, &Ctx); for (i = 0; i < cArgc; i++) { size_t cch = strlen(papszArgv[i]); if ( ( cch < cchIgnorePath1 || !ArePathsIdenticalN(papszArgv[i] + cch - cchIgnorePath1, pszIgnorePath1, cch)) && ( cch < cchIgnorePath2 || !ArePathsIdenticalN(papszArgv[i] + cch - cchIgnorePath2, pszIgnorePath2, cch)) ) kOCSumUpdate(pSum, &Ctx, papszArgv[i], cch + 1); } kOCSumFinalize(pSum, &Ctx); (void)pEntry; } /** * Reads and parses the cache file. * * @param pEntry The entry to read it into. */ static void kOCEntryRead(PKOCENTRY pEntry) { FILE *pFile; pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "rb"); if (pFile) { InfoMsg(4, "reading cache entry...\n"); /* * Check the magic. */ if ( !fgets(g_szLine, sizeof(g_szLine), pFile) || ( strcmp(g_szLine, "magic=kObjCacheEntry-v0.1.0\n") && strcmp(g_szLine, "magic=kObjCacheEntry-v0.1.1\n")) ) { InfoMsg(2, "bad cache file (magic)\n"); pEntry->fNeedCompiling = 1; } else { /* * Parse the rest of the file (relaxed order). */ unsigned i; int fBad = 0; int fBadBeforeMissing = 1; while (fgets(g_szLine, sizeof(g_szLine), pFile)) { char *pszNl; char *pszVal; /* Split the line and drop the trailing newline. */ pszVal = strchr(g_szLine, '='); if ((fBad = pszVal == NULL)) break; *pszVal++ = '\0'; pszNl = strchr(pszVal, '\n'); if (pszNl) *pszNl = '\0'; /* string case on variable name */ if (!strcmp(g_szLine, "obj")) { if ((fBad = pEntry->Old.pszObjName != NULL)) break; pEntry->Old.pszObjName = xstrdup(pszVal); } else if (!strcmp(g_szLine, "cpp")) { if ((fBad = pEntry->Old.pszCppName != NULL)) break; pEntry->Old.pszCppName = xstrdup(pszVal); } else if (!strcmp(g_szLine, "cpp-size")) { char *pszNext; if ((fBad = pEntry->Old.cbCpp != 0)) break; pEntry->Old.cbCpp = strtoul(pszVal, &pszNext, 0); if ((fBad = pszNext && *pszNext)) break; } else if (!strcmp(g_szLine, "cpp-sum")) { KOCSUM Sum; if ((fBad = kOCSumInitFromString(&Sum, pszVal))) break; kOCSumAdd(&pEntry->Old.SumHead, &Sum); } else if (!strcmp(g_szLine, "cpp-ms")) { char *pszNext; if ((fBad = pEntry->Old.cMsCpp != 0)) break; pEntry->Old.cMsCpp = strtoul(pszVal, &pszNext, 0); if ((fBad = pszNext && *pszNext)) break; } else if (!strcmp(g_szLine, "cc-argc")) { if ((fBad = pEntry->Old.papszArgvCompile != NULL)) break; pEntry->Old.cArgvCompile = atoi(pszVal); /* if wrong, we'll fail below. */ pEntry->Old.papszArgvCompile = xmallocz((pEntry->Old.cArgvCompile + 1) * sizeof(pEntry->Old.papszArgvCompile[0])); } else if (!strncmp(g_szLine, "cc-argv-#", sizeof("cc-argv-#") - 1)) { char *pszNext; unsigned iArg = strtoul(&g_szLine[sizeof("cc-argv-#") - 1], &pszNext, 0); if ((fBad = iArg >= pEntry->Old.cArgvCompile || pEntry->Old.papszArgvCompile[iArg] || (pszNext && *pszNext))) break; pEntry->Old.papszArgvCompile[iArg] = xstrdup(pszVal); } else if (!strcmp(g_szLine, "cc-argv-sum")) { if ((fBad = !kOCSumIsEmpty(&pEntry->Old.SumCompArgv))) break; if ((fBad = kOCSumInitFromString(&pEntry->Old.SumCompArgv, pszVal))) break; } else if (!strcmp(g_szLine, "cc-ms")) { char *pszNext; if ((fBad = pEntry->Old.cMsCompile != 0)) break; pEntry->Old.cMsCompile = strtoul(pszVal, &pszNext, 0); if ((fBad = pszNext && *pszNext)) break; } else if (!strcmp(g_szLine, "target")) { if ((fBad = pEntry->Old.pszTarget != NULL)) break; pEntry->Old.pszTarget = xstrdup(pszVal); } else if (!strcmp(g_szLine, "key")) { char *pszNext; if ((fBad = pEntry->uKey != 0)) break; pEntry->uKey = strtoul(pszVal, &pszNext, 0); if ((fBad = pszNext && *pszNext)) break; } else if (!strcmp(g_szLine, "the-end")) { fBadBeforeMissing = fBad = strcmp(pszVal, "fine"); break; } else { fBad = 1; break; } } /* parse loop */ /* * Did we find everything and does it add up correctly? */ if (!fBad && fBadBeforeMissing) { InfoMsg(2, "bad cache file (no end)\n"); fBad = 1; } else { fBadBeforeMissing = fBad; if ( !fBad && ( !pEntry->Old.papszArgvCompile || !pEntry->Old.pszObjName || !pEntry->Old.pszCppName || kOCSumIsEmpty(&pEntry->Old.SumHead))) fBad = 1; if (!fBad) for (i = 0; i < pEntry->Old.cArgvCompile; i++) if ((fBad = !pEntry->Old.papszArgvCompile[i])) break; if (!fBad) { KOCSUM Sum; kOCEntryCalcArgvSum(pEntry, (const char * const *)pEntry->Old.papszArgvCompile, pEntry->Old.cArgvCompile, pEntry->Old.pszObjName, pEntry->Old.pszCppName, &Sum); fBad = !kOCSumIsEqual(&pEntry->Old.SumCompArgv, &Sum); } if (fBad) InfoMsg(2, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff"); else if (ferror(pFile)) { InfoMsg(2, "cache file read error\n"); fBad = 1; } /* * Verify the existance of the object file. */ if (!fBad) { struct stat st; char *pszPath = MakePathFromDirAndFile(pEntry->Old.pszObjName, pEntry->pszDir); if (stat(pszPath, &st) != 0) { InfoMsg(2, "failed to stat object file: %s\n", strerror(errno)); fBad = 1; } else { /** @todo verify size and the timestamp. */ } } } pEntry->fNeedCompiling = fBad; } fclose(pFile); } else { InfoMsg(2, "no cache file\n"); pEntry->fNeedCompiling = 1; } } /** * Writes the cache file. * * @param pEntry The entry to write. */ static void kOCEntryWrite(PKOCENTRY pEntry) { FILE *pFile; PCKOCSUM pSum; unsigned i; InfoMsg(4, "writing cache entry '%s'...\n", pEntry->pszName); pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "wb"); if (!pFile) FatalDie("Failed to open '%s' in '%s': %s\n", pEntry->pszName, pEntry->pszDir, strerror(errno)); #define CHECK_LEN(expr) \ do { int cch = expr; if (cch >= KOBJCACHE_MAX_LINE_LEN) FatalDie("Line too long: %d (max %d)\nexpr: %s\n", cch, KOBJCACHE_MAX_LINE_LEN, #expr); } while (0) fprintf(pFile, "magic=kObjCacheEntry-v0.1.1\n"); CHECK_LEN(fprintf(pFile, "target=%s\n", pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget)); CHECK_LEN(fprintf(pFile, "key=%lu\n", (unsigned long)pEntry->uKey)); CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->New.pszObjName ? pEntry->New.pszObjName : pEntry->Old.pszObjName)); CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->New.pszCppName ? pEntry->New.pszCppName : pEntry->Old.pszCppName)); CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n", (unsigned long)(pEntry->New.pszCppName ? pEntry->New.cbCpp : pEntry->Old.cbCpp))); CHECK_LEN(fprintf(pFile, "cpp-ms=%lu\n", (unsigned long)(pEntry->New.pszCppName ? pEntry->New.cMsCpp : pEntry->Old.cMsCpp))); CHECK_LEN(fprintf(pFile, "cc-ms=%lu\n", (unsigned long)(pEntry->New.pszCppName ? pEntry->New.cMsCompile : pEntry->Old.cMsCompile))); if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv)) { CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->New.cArgvCompile)); for (i = 0; i < pEntry->New.cArgvCompile; i++) CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->New.papszArgvCompile[i])); fprintf(pFile, "cc-argv-sum="); kOCSumFPrintf(&pEntry->New.SumCompArgv, pFile); } else { CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->Old.cArgvCompile)); for (i = 0; i < pEntry->Old.cArgvCompile; i++) CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->Old.papszArgvCompile[i])); fprintf(pFile, "cc-argv-sum="); kOCSumFPrintf(&pEntry->Old.SumCompArgv, pFile); } for (pSum = !kOCSumIsEmpty(&pEntry->New.SumHead) ? &pEntry->New.SumHead : &pEntry->Old.SumHead; pSum; pSum = pSum->pNext) { fprintf(pFile, "cpp-sum="); kOCSumFPrintf(pSum, pFile); } fprintf(pFile, "the-end=fine\n"); #undef CHECK_LEN /* * Flush the file and check for errors. * On failure delete the file so we won't be seeing any invalid * files the next time or upset make with new timestamps. */ errno = 0; if ( fflush(pFile) < 0 || ferror(pFile)) { int iErr = errno; fclose(pFile); UnlinkFileInDir(pEntry->pszName, pEntry->pszDir); FatalDie("Stream error occured while writing '%s' in '%s': %s\n", pEntry->pszName, pEntry->pszDir, strerror(iErr)); } fclose(pFile); } /** * Checks that the read cache entry is valid. * It sets fNeedCompiling if it isn't. * * @returns 1 valid, 0 invalid. * @param pEntry The cache entry. */ static int kOCEntryCheck(PKOCENTRY pEntry) { return !pEntry->fNeedCompiling; } /** * Sets the object name and compares it with the old name if present. * * @param pEntry The cache entry. * @param pszObjName The new object name. */ static void kOCEntrySetCompileObjName(PKOCENTRY pEntry, const char *pszObjName) { assert(!pEntry->New.pszObjName); pEntry->New.pszObjName = CalcRelativeName(pszObjName, pEntry->pszDir); if ( !pEntry->fNeedCompiling && ( !pEntry->Old.pszObjName || strcmp(pEntry->New.pszObjName, pEntry->Old.pszObjName))) { InfoMsg(2, "object file name differs\n"); pEntry->fNeedCompiling = 1; } if ( !pEntry->fNeedCompiling && !DoesFileInDirExist(pEntry->New.pszObjName, pEntry->pszDir)) { InfoMsg(2, "object file doesn't exist\n"); pEntry->fNeedCompiling = 1; } } /** * Set the new compiler args, calc their checksum, and comparing them with any old ones. * * @param pEntry The cache entry. * @param papszArgvCompile The new argument vector for compilation. * @param cArgvCompile The number of arguments in the vector. * * @remark Must call kOCEntrySetCompileObjName before this function! */ static void kOCEntrySetCompileArgv(PKOCENTRY pEntry, const char * const *papszArgvCompile, unsigned cArgvCompile) { unsigned i; /* call me only once! */ assert(!pEntry->New.cArgvCompile); /* call kOCEntrySetCompilerObjName first! */ assert(pEntry->New.pszObjName); /* * Copy the argument vector and calculate the checksum. */ pEntry->New.cArgvCompile = cArgvCompile; pEntry->New.papszArgvCompile = xmalloc((cArgvCompile + 1) * sizeof(pEntry->New.papszArgvCompile[0])); for (i = 0; i < cArgvCompile; i++) pEntry->New.papszArgvCompile[i] = xstrdup(papszArgvCompile[i]); pEntry->New.papszArgvCompile[i] = NULL; /* for exev/spawnv */ kOCEntryCalcArgvSum(pEntry, papszArgvCompile, cArgvCompile, pEntry->New.pszObjName, pEntry->New.pszCppName, &pEntry->New.SumCompArgv); kOCSumInfo(&pEntry->New.SumCompArgv, 4, "comp-argv"); /* * Compare with the old argument vector. */ if ( !pEntry->fNeedCompiling && !kOCSumIsEqual(&pEntry->New.SumCompArgv, &pEntry->Old.SumCompArgv)) { InfoMsg(2, "compiler args differs\n"); pEntry->fNeedCompiling = 1; } } /** * Sets the arch/os target and compares it with the old name if present. * * @param pEntry The cache entry. * @param pszObjName The new object name. */ static void kOCEntrySetTarget(PKOCENTRY pEntry, const char *pszTarget) { assert(!pEntry->New.pszTarget); pEntry->New.pszTarget = xstrdup(pszTarget); if ( !pEntry->fNeedCompiling && ( !pEntry->Old.pszTarget || strcmp(pEntry->New.pszTarget, pEntry->Old.pszTarget))) { InfoMsg(2, "target differs\n"); pEntry->fNeedCompiling = 1; } } /** * Sets the preprocessor output filename. We don't generally care if this * matches the old name or not. * * @param pEntry The cache entry. * @param pszCppName The preprocessor output filename. */ static void kOCEntrySetCppName(PKOCENTRY pEntry, const char *pszCppName) { assert(!pEntry->New.pszCppName); pEntry->New.pszCppName = CalcRelativeName(pszCppName, pEntry->pszDir); } /** * Sets the piped mode of the preprocessor and compiler. * * @param pEntry The cache entry. * @param fRedirPreCompStdOut Whether the preprocessor is in piped mode. * @param fRedirCompileStdIn Whether the compiler is in piped mode. * @param pszNmPipeCompile The name of the named pipe to use to feed * the microsoft compiler. */ static void kOCEntrySetPipedMode(PKOCENTRY pEntry, int fRedirPreCompStdOut, int fRedirCompileStdIn, const char *pszNmPipeCompile) { pEntry->fPipedPreComp = fRedirPreCompStdOut; pEntry->fPipedCompile = fRedirCompileStdIn || pszNmPipeCompile; pEntry->pszNmPipeCompile = xstrdup(pszNmPipeCompile); } /** * Sets the dependency file. * * @param pEntry The cache entry. * @param pszMakeDepFilename The dependency filename. * @param fMakeDepFixCase Whether to fix the case of dependency files. * @param fMakeDepQuiet Whether to be quiet about the dependencies. * @param fMakeDepGenStubs Whether to generate stubs. */ static void kOCEntrySetDepFilename(PKOCENTRY pEntry, const char *pszMakeDepFilename, int fMakeDepFixCase, int fMakeDepQuiet, int fMakeDepGenStubs) { pEntry->pszMakeDepFilename = xstrdup(pszMakeDepFilename); pEntry->fMakeDepFixCase = fMakeDepFixCase; pEntry->fMakeDepQuiet = fMakeDepQuiet; pEntry->fMakeDepGenStubs = fMakeDepGenStubs; } /** * Configures the preprocessor output optimizations. * * @param pEntry The cache entry. * @param fOptimizeCpp The one and only flag, so far. */ static void kOCEntrySetOptimizations(PKOCENTRY pEntry, int fOptimizeCpp) { pEntry->fOptimizeCpp = fOptimizeCpp; } /** * Spawns a child in a synchronous fashion. * Terminating on failure. * * @param papszArgv Argument vector. The cArgv element is NULL. * @param pcMs The cache entry member use for time keeping. This * will be set to the current timestamp. * @param cArgv The number of arguments in the vector. * @param pszMsg Which operation this is, for use in messages. * @param pszStdOut Where to redirect standard out. */ static void kOCEntrySpawn(PCKOCENTRY pEntry, uint32_t *pcMs, const char * const *papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut) { #if defined(__OS2__) || defined(__WIN__) intptr_t rc; int fdStdOut = -1; if (pszStdOut) { int fdReDir; fdStdOut = dup(STDOUT_FILENO); close(STDOUT_FILENO); fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666); if (fdReDir < 0) FatalDie("%s - failed to create stdout redirection file '%s': %s\n", pszMsg, pszStdOut, strerror(errno)); if (fdReDir != STDOUT_FILENO) { if (dup2(fdReDir, STDOUT_FILENO) < 0) FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno)); close(fdReDir); } } *pcMs = NowMs(); errno = 0; # ifdef __WIN__ rc = quoted_spawnvp(_P_WAIT, papszArgv[0], papszArgv); # else rc = _spawnvp(_P_WAIT, papszArgv[0], papszArgv); # endif *pcMs = NowMs() - *pcMs; if (rc < 0) FatalDie("%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno)); if (rc > 0) FatalDie("%s - failed rc=%d\n", pszMsg, (int)rc); if (fdStdOut != -1) { close(STDOUT_FILENO); fdStdOut = dup2(fdStdOut, STDOUT_FILENO); close(fdStdOut); } #else int iStatus; pid_t pidWait; pid_t pid; *pcMs = NowMs(); pid = fork(); if (!pid) { if (pszStdOut) { int fdReDir; close(STDOUT_FILENO); fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666); if (fdReDir < 0) FatalDie("%s - failed to create stdout redirection file '%s': %s\n", pszMsg, pszStdOut, strerror(errno)); if (fdReDir != STDOUT_FILENO) { if (dup2(fdReDir, STDOUT_FILENO) < 0) FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno)); close(fdReDir); } } execvp(papszArgv[0], (char **)papszArgv); FatalDie("%s - execvp failed: %s\n", pszMsg, strerror(errno)); } if (pid == -1) FatalDie("%s - fork() failed: %s\n", pszMsg, strerror(errno)); pidWait = waitpid(pid, &iStatus, 0); while (pidWait < 0 && errno == EINTR) pidWait = waitpid(pid, &iStatus, 0); *pcMs = NowMs() - *pcMs; if (pidWait != pid) FatalDie("%s - waitpid failed rc=%d: %s\n", pszMsg, pidWait, strerror(errno)); if (!WIFEXITED(iStatus)) FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus); if (WEXITSTATUS(iStatus)) FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus)); #endif (void)pEntry; (void)cArgv; } /** * Spawns child with optional redirection of stdin and stdout. * * @param pEntry The cache entry. * @param pcMs The cache entry member use for time keeping. This * will be set to the current timestamp. * @param papszArgv Argument vector. The cArgv element is NULL. * @param cArgv The number of arguments in the vector. * @param fdStdIn Child stdin, -1 if it should inherit our stdin. Will be closed. * @param fdStdOut Child stdout, -1 if it should inherit our stdout. Will be closed. * @param pszMsg Message to start the info/error messages with. */ static pid_t kOCEntrySpawnChild(PCKOCENTRY pEntry, uint32_t *pcMs, const char * const *papszArgv, unsigned cArgv, int fdStdIn, int fdStdOut, const char *pszMsg) { pid_t pid; int fdSavedStdOut = -1; int fdSavedStdIn = -1; /* * Setup redirection. */ if (fdStdOut != -1 && fdStdOut != STDOUT_FILENO) { fdSavedStdOut = dup(STDOUT_FILENO); if (dup2(fdStdOut, STDOUT_FILENO) < 0) FatalDie("%s - dup2(,1) failed: %s\n", pszMsg, strerror(errno)); close(fdStdOut); #ifndef __WIN__ fcntl(fdSavedStdOut, F_SETFD, FD_CLOEXEC); #endif } if (fdStdIn != -1 && fdStdIn != STDIN_FILENO) { fdSavedStdIn = dup(STDIN_FILENO); if (dup2(fdStdIn, STDIN_FILENO) < 0) FatalDie("%s - dup2(,0) failed: %s\n", pszMsg, strerror(errno)); close(fdStdIn); #ifndef __WIN__ fcntl(fdSavedStdIn, F_SETFD, FD_CLOEXEC); #endif } /* * Create the child process. */ *pcMs = NowMs(); #if defined(__OS2__) || defined(__WIN__) errno = 0; # ifdef __WIN__ pid = quoted_spawnvp(_P_NOWAIT, papszArgv[0], papszArgv); # else pid = _spawnvp(_P_NOWAIT, papszArgv[0], papszArgv); # endif if (pid == -1) FatalDie("preprocess - _spawnvp failed: %s\n", strerror(errno)); #else pid = fork(); if (!pid) { execvp(papszArgv[0], (char **)papszArgv); FatalDie("preprocess - execvp failed: %s\n", strerror(errno)); } if (pid == -1) FatalDie("preprocess - fork() failed: %s\n", strerror(errno)); #endif /* * Restore stdout & stdin. */ if (fdSavedStdIn != -1) { close(STDIN_FILENO); dup2(fdStdOut, STDIN_FILENO); close(fdSavedStdIn); } if (fdSavedStdOut != -1) { close(STDOUT_FILENO); dup2(fdSavedStdOut, STDOUT_FILENO); close(fdSavedStdOut); } InfoMsg(3, "%s - spawned %ld\n", pszMsg, (long)pid); (void)cArgv; (void)pEntry; return pid; } /** * Waits for a child and exits fatally if the child failed in any way. * * @param pEntry The cache entry. * @param pcMs The millisecond timestamp that should be convert to * elapsed time. * @param pid The child to wait for. * @param pszMsg Message to start the info/error messages with. */ static void kOCEntryWaitChild(PCKOCENTRY pEntry, uint32_t *pcMs, pid_t pid, const char *pszMsg) { int iStatus = -1; pid_t pidWait; InfoMsg(3, "%s - wait-child %ld\n", pszMsg, (long)pid); #ifdef __WIN__ pidWait = _cwait(&iStatus, pid, _WAIT_CHILD); *pcMs = NowMs() - *pcMs; if (pidWait == -1) FatalDie("%s - waitpid failed: %s\n", pszMsg, strerror(errno)); if (iStatus) FatalDie("%s - failed with rc %d\n", pszMsg, iStatus); #else pidWait = waitpid(pid, &iStatus, 0); while (pidWait < 0 && errno == EINTR) pidWait = waitpid(pid, &iStatus, 0); *pcMs = NowMs() - *pcMs; if (pidWait != pid) FatalDie("%s - waitpid failed rc=%d: %s\n", pidWait, strerror(errno)); if (!WIFEXITED(iStatus)) FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus); if (WEXITSTATUS(iStatus)) FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus)); #endif (void)pEntry; } /** * Creates a pipe for setting up redirected stdin/stdout. * * @param pEntry The cache entry. * @param paFDs Where to store the two file descriptors. * @param pszMsg The operation message for info/error messages. * @param pszPipeName The pipe name if it is supposed to be named. (Windows only.) * @param fText Whether to read text mode or binary mode. */ static void kOCEntryCreatePipe(PKOCENTRY pEntry, int *paFDs, const char *pszPipeName, const char *pszMsg, int fText) { paFDs[0] = paFDs[1] = -1; #if defined(__WIN__) if (pszPipeName) { HANDLE hPipe = CreateNamedPipeA(pszPipeName, /*PIPE_ACCESS_OUTBOUND*/ PIPE_ACCESS_DUPLEX, PIPE_READMODE_BYTE | PIPE_WAIT, 10 /* nMaxInstances */, 0x10000 /* nOutBuffer */, 0x10000 /* nInBuffer */, NMPWAIT_WAIT_FOREVER, NULL /* pSecurityAttributes */); if (hPipe == INVALID_HANDLE_VALUE) FatalDie("%s - CreateNamedPipe(%s) failed: %d\n", pszMsg, pszPipeName, GetLastError()); paFDs[1 /* write */] = _open_osfhandle((intptr_t)hPipe, _O_WRONLY | _O_TEXT | _O_NOINHERIT); if (paFDs[1 /* write */] == -1) FatalDie("%s - _open_osfhandle failed: %d\n", pszMsg, strerror(errno)); } else if ( _pipe(paFDs, 256*1024, _O_NOINHERIT | (fText ? _O_TEXT : _O_BINARY)) < 0 && _pipe(paFDs, 0, _O_NOINHERIT | (fText ? _O_TEXT : _O_BINARY)) < 0) #else if (pipe(paFDs) < 0) #endif FatalDie("%s - pipe failed: %s\n", pszMsg, strerror(errno)); #if !defined(__WIN__) fcntl(paFDs[0], F_SETFD, FD_CLOEXEC); fcntl(paFDs[1], F_SETFD, FD_CLOEXEC); #endif (void)pEntry; } /** * Spawns a child that produces output to stdout. * * @param papszArgv Argument vector. The cArgv element is NULL. * @param cArgv The number of arguments in the vector. * @param pszMsg The operation message for info/error messages. * @param pfnConsumer Pointer to a consumer callback function that is responsible * for servicing the child output and closing the pipe. */ static void kOCEntrySpawnProducer(PKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, const char *pszMsg, void (*pfnConsumer)(PKOCENTRY, int)) { int fds[2]; pid_t pid; kOCEntryCreatePipe(pEntry, fds, NULL, pszMsg, pEntry->fOptimizeCpp); pid = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCpp, papszArgv, cArgv, -1, fds[1 /* write */], pszMsg); pfnConsumer(pEntry, fds[0 /* read */]); kOCEntryWaitChild(pEntry, &pEntry->New.cMsCpp, pid, pszMsg); } /** * Spawns a child that consumes input on stdin or via a named pipe. * * @param papszArgv Argument vector. The cArgv element is NULL. * @param cArgv The number of arguments in the vector. * @param pszMsg The operation message for info/error messages. * @param pfnProducer Pointer to a producer callback function that is responsible * for serving the child input and closing the pipe. */ static void kOCEntrySpawnConsumer(PKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, const char *pszMsg, void (*pfnProducer)(PKOCENTRY, int)) { int fds[2]; pid_t pid; kOCEntryCreatePipe(pEntry, fds, pEntry->pszNmPipeCompile, pszMsg, 0 /*fText*/); pid = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCompile, papszArgv, cArgv, fds[0 /* read */], -1, pszMsg); #ifdef __WIN__ if (pEntry->pszNmPipeCompile && !ConnectNamedPipe((HANDLE)_get_osfhandle(fds[1 /* write */]), NULL)) FatalDie("compile - ConnectNamedPipe failed: %d\n", GetLastError()); #endif pfnProducer(pEntry, fds[1 /* write */]); kOCEntryWaitChild(pEntry, &pEntry->New.cMsCompile, pid, pszMsg); } /** * Spawns two child processes, one producing output and one consuming. * Terminating on failure. * * @param papszArgv Argument vector. The cArgv element is NULL. * @param cArgv The number of arguments in the vector. * @param pszMsg The operation message for info/error messages. * @param pfnConsumer Pointer to a consumer callback function that is responsible * for servicing the child output and closing the pipe. */ static void kOCEntrySpawnTee(PKOCENTRY pEntry, const char * const *papszProdArgv, unsigned cProdArgv, const char * const *papszConsArgv, unsigned cConsArgv, const char *pszMsg, void (*pfnTeeConsumer)(PKOCENTRY, int, int)) { int fds[2]; int fdIn, fdOut; pid_t pidProducer, pidConsumer; /* * The producer. */ kOCEntryCreatePipe(pEntry, fds, NULL, pszMsg, pEntry->fOptimizeCpp); pidConsumer = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCpp, papszProdArgv, cProdArgv, -1, fds[1 /* write */], pszMsg); fdIn = fds[0 /* read */]; /* * The consumer. */ kOCEntryCreatePipe(pEntry, fds, pEntry->pszNmPipeCompile, pszMsg, 0 /*fText*/); pidProducer = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCompile, papszConsArgv, cConsArgv, fds[0 /* read */], -1, pszMsg); fdOut = fds[1 /* write */]; /* * Hand it on to the tee consumer. */ pfnTeeConsumer(pEntry, fdIn, fdOut); /* * Reap the children. */ kOCEntryWaitChild(pEntry, &pEntry->New.cMsCpp, pidProducer, pszMsg); kOCEntryWaitChild(pEntry, &pEntry->New.cMsCompile, pidConsumer, pszMsg); } /** * Reads the output from the preprocessor. * * @param pEntry The cache entry. New.cbCpp and New.pszCppMapping will be updated. * @param pWhich Specifies what to read (old/new). * @param fNonFatal Whether failure is fatal or not. */ static int kOCEntryReadCppOutput(PKOCENTRY pEntry, struct KOCENTRYDATA *pWhich, int fNonFatal) { pWhich->pszCppMapping = ReadFileInDir(pWhich->pszCppName, pEntry->pszDir, &pWhich->cbCpp); if (!pWhich->pszCppMapping) { if (!fNonFatal) FatalDie("failed to open/read '%s' in '%s': %s\n", pWhich->pszCppName, pEntry->pszDir, strerror(errno)); InfoMsg(2, "failed to open/read '%s' in '%s': %s\n", pWhich->pszCppName, pEntry->pszDir, strerror(errno)); return -1; } InfoMsg(3, "preprocessed file is %lu bytes long\n", (unsigned long)pWhich->cbCpp); return 0; } /** * Worker for kOCEntryPreProcess and calculates the checksum of * the preprocessor output. * * @param pEntry The cache entry. NewSum will be updated. */ static void kOCEntryCalcChecksum(PKOCENTRY pEntry) { KOCSUMCTX Ctx; kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx); kOCSumUpdate(&pEntry->New.SumHead, &Ctx, pEntry->New.pszCppMapping, pEntry->New.cbCpp); kOCSumFinalize(&pEntry->New.SumHead, &Ctx); kOCSumInfo(&pEntry->New.SumHead, 4, "cpp (file)"); } /** * This consumes the preprocessor output and checksums it. * * @param pEntry The cache entry. * @param fdIn The preprocessor output pipe. * @param fdOut The compiler input pipe, -1 if no compiler. */ static void kOCEntryPreProcessConsumer(PKOCENTRY pEntry, int fdIn) { KOCSUMCTX Ctx; KOCCPPRD CppRd; kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx); kOCCppRdInit(&CppRd, pEntry->Old.cbCpp, pEntry->fOptimizeCpp, pEntry->pszMakeDepFilename ? &pEntry->DepState : NULL); for (;;) { /* * Read data from the pipe. */ const char *psz; long cbRead = kOCCppRdRead(&CppRd, fdIn, &psz); if (!cbRead) break; /* * Process the data. */ kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead); if (pEntry->pszMakeDepFilename && !pEntry->fOptimizeCpp) kOCDepConsumer(&pEntry->DepState, psz, cbRead); } close(fdIn); kOCCppRdGrabOutput(&CppRd, &pEntry->New.pszCppMapping, &pEntry->New.cbCpp); kOCCppRdDelete(&CppRd); kOCSumFinalize(&pEntry->New.SumHead, &Ctx); kOCSumInfo(&pEntry->New.SumHead, 4, "cpp (pipe)"); } /** * Run the preprocessor and calculate the checksum of the output. * * @param pEntry The cache entry. * @param papszArgvPreComp The argument vector for executing preprocessor. * The cArgvPreComp'th argument must be NULL. * @param cArgvPreComp The number of arguments. */ static void kOCEntryPreProcess(PKOCENTRY pEntry, const char * const *papszArgvPreComp, unsigned cArgvPreComp) { /* * If we're executing the preprocessor in piped mode, it's relatively simple. */ if (pEntry->fPipedPreComp) kOCEntrySpawnProducer(pEntry, papszArgvPreComp, cArgvPreComp, "preprocess", kOCEntryPreProcessConsumer); else { /* * Rename the old preprocessed output to '-old' so the preprocessor won't * overwrite it when we execute it. */ if ( pEntry->Old.pszCppName && DoesFileInDirExist(pEntry->Old.pszCppName, pEntry->pszDir)) { size_t cch = strlen(pEntry->Old.pszCppName); char *psz = xmalloc(cch + sizeof("-old")); memcpy(psz, pEntry->Old.pszCppName, cch); memcpy(psz + cch, "-old", sizeof("-old")); InfoMsg(3, "renaming '%s' to '%s' in '%s'\n", pEntry->Old.pszCppName, psz, pEntry->pszDir); UnlinkFileInDir(psz, pEntry->pszDir); if (RenameFileInDir(pEntry->Old.pszCppName, psz, pEntry->pszDir)) FatalDie("failed to rename '%s' -> '%s' in '%s': %s\n", pEntry->Old.pszCppName, psz, pEntry->pszDir, strerror(errno)); free(pEntry->Old.pszCppName); pEntry->Old.pszCppName = psz; } /* * Preprocess it and calculate the checksum on the output. */ InfoMsg(3, "precompiling -> '%s'...\n", pEntry->New.pszCppName); kOCEntrySpawn(pEntry, &pEntry->New.cMsCpp, papszArgvPreComp, cArgvPreComp, "preprocess", NULL); kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */); kOCEntryCalcChecksum(pEntry); if (pEntry->pszMakeDepFilename) kOCDepConsumer(&pEntry->DepState, pEntry->New.pszCppMapping, pEntry->New.cbCpp); } if (pEntry->pszMakeDepFilename) kOCDepWriteToFile(&pEntry->DepState, pEntry->pszMakeDepFilename, pEntry->New.pszObjName, pEntry->pszDir, pEntry->fMakeDepFixCase, pEntry->fMakeDepQuiet, pEntry->fMakeDepGenStubs); } /** * Worker function for kOCEntryTeeConsumer and kOCEntryCompileIt that * writes the preprocessor output to disk. * * @param pEntry The cache entry. * @param fFreeIt Whether we can free it after writing it or not. */ static void kOCEntryWriteCppOutput(PKOCENTRY pEntry, int fFreeIt) { /* * Remove old files. */ if (pEntry->Old.pszCppName) UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir); if (pEntry->New.pszCppName) UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir); /* * Write it to disk if we've got a file name. */ if (pEntry->New.pszCppName) { long cbLeft; char *psz; int fd = OpenFileInDir(pEntry->New.pszCppName, pEntry->pszDir, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (fd == -1) FatalDie("Failed to create '%s' in '%s': %s\n", pEntry->New.pszCppName, pEntry->pszDir, strerror(errno)); psz = pEntry->New.pszCppMapping; cbLeft = (long)pEntry->New.cbCpp; while (cbLeft > 0) { long cbWritten = write(fd, psz, cbLeft); if (cbWritten < 0) { int iErr = errno; if (iErr == EINTR) continue; close(fd); UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir); FatalDie("error writing '%s' in '%s': %s\n", pEntry->New.pszCppName, pEntry->pszDir, strerror(iErr)); } psz += cbWritten; cbLeft -= cbWritten; } close(fd); } /* * Free it. */ if (fFreeIt) { free(pEntry->New.pszCppMapping); pEntry->New.pszCppMapping = NULL; } } /** * kOCEntrySpawnConsumer callback that passes the preprocessor output to the * compiler and writes it to the disk (latter only when necesary). * * @param pEntry The cache entry. * @param fdOut The pipe handle connected to the childs stdin. */ static void kOCEntryCompileProducer(PKOCENTRY pEntry, int fdOut) { const char *psz = pEntry->New.pszCppMapping; long cbLeft = (long)pEntry->New.cbCpp; while (cbLeft > 0) { long cbWritten = write(fdOut, psz, cbLeft); if (cbWritten < 0) { if (errno == EINTR) continue; #ifdef __WIN__ /* HACK */ if ( errno == EINVAL && pEntry->pszNmPipeCompile && DisconnectNamedPipe((HANDLE)_get_osfhandle(fdOut)) && ConnectNamedPipe((HANDLE)_get_osfhandle(fdOut), NULL)) { psz = pEntry->New.pszCppMapping; cbLeft = (long)pEntry->New.cbCpp; } FatalDie("compile - write(%d,,%ld) failed: %s - _doserrno=%d\n", fdOut, cbLeft, strerror(errno), _doserrno); #else FatalDie("compile - write(%d,,%ld) failed: %s\n", fdOut, cbLeft, strerror(errno)); #endif } psz += cbWritten; cbLeft -= cbWritten; } close(fdOut); if (pEntry->fPipedPreComp) kOCEntryWriteCppOutput(pEntry, 1 /* free it */); } /** * Does the actual compiling. * * @param pEntry The cache entry. */ static void kOCEntryCompileIt(PKOCENTRY pEntry) { /* * Delete the object files and free old cpp output that's no longer needed. */ if (pEntry->Old.pszObjName) UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir); UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir); free(pEntry->Old.pszCppMapping); pEntry->Old.pszCppMapping = NULL; if (!pEntry->fPipedPreComp && !pEntry->fPipedCompile) { free(pEntry->New.pszCppMapping); pEntry->New.pszCppMapping = NULL; } /* * Do the (re-)compile job. */ if (pEntry->fPipedCompile) { if ( !pEntry->fPipedPreComp && !pEntry->New.pszCppMapping) kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */); InfoMsg(3, "compiling -> '%s'...\n", pEntry->New.pszObjName); kOCEntrySpawnConsumer(pEntry, (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile, "compile", kOCEntryCompileProducer); } else { if (pEntry->fPipedPreComp) kOCEntryWriteCppOutput(pEntry, 1 /* free it */); InfoMsg(3, "compiling -> '%s'...\n", pEntry->New.pszObjName); kOCEntrySpawn(pEntry, &pEntry->New.cMsCompile, (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile, "compile", NULL); } } /** * kOCEntrySpawnTee callback that works sort of like 'tee'. * * It will calculate the preprocessed output checksum and * write it to disk while the compiler is busy compiling it. * * @param pEntry The cache entry. * @param fdIn The input handle (connected to the preprocessor). * @param fdOut The output handle (connected to the compiler). */ static void kOCEntryTeeConsumer(PKOCENTRY pEntry, int fdIn, int fdOut) { #ifdef __WIN__ unsigned fConnectedToCompiler = fdOut == -1 || pEntry->pszNmPipeCompile == NULL; #endif KOCSUMCTX Ctx; KOCCPPRD CppRd; kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx); kOCCppRdInit(&CppRd, pEntry->Old.cbCpp, pEntry->fOptimizeCpp, pEntry->pszMakeDepFilename ? &pEntry->DepState : NULL); InfoMsg(3, "preprocessor|compile - starting passhtru...\n"); for (;;) { /* * Read data from the pipe. */ const char *psz; long cbRead = kOCCppRdRead(&CppRd, fdIn, &psz); if (!cbRead) break; InfoMsg(3, "preprocessor|compile - read %d\n", cbRead); /* * Process the data. */ kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead); if (pEntry->pszMakeDepFilename && !pEntry->fOptimizeCpp) kOCDepConsumer(&pEntry->DepState, psz, cbRead); #ifdef __WIN__ if ( !fConnectedToCompiler && !(fConnectedToCompiler = ConnectNamedPipe((HANDLE)_get_osfhandle(fdOut), NULL))) FatalDie("preprocess|compile - ConnectNamedPipe failed: %d\n", GetLastError()); #endif do { long cbWritten = write(fdOut, psz, cbRead); if (cbWritten < 0) { if (errno == EINTR) continue; FatalDie("preprocess|compile - write(%d,,%ld) failed: %s\n", fdOut, cbRead, strerror(errno)); } psz += cbWritten; cbRead -= cbWritten; } while (cbRead > 0); } InfoMsg(3, "preprocessor|compile - done passhtru\n"); close(fdIn); close(fdOut); kOCCppRdGrabOutput(&CppRd, &pEntry->New.pszCppMapping, &pEntry->New.cbCpp); kOCCppRdDelete(&CppRd); kOCSumFinalize(&pEntry->New.SumHead, &Ctx); kOCSumInfo(&pEntry->New.SumHead, 4, "cpp (tee)"); /* * Write the preprocessor output to disk and free the memory it * occupies while the compiler is busy compiling. */ kOCEntryWriteCppOutput(pEntry, 1 /* free it */); } /** * Performs pre-compile and compile in one go (typical clean build scenario). * * @param pEntry The cache entry. * @param papszArgvPreComp The argument vector for executing preprocessor. * The cArgvPreComp'th argument must be NULL. * @param cArgvPreComp The number of arguments. */ static void kOCEntryPreProcessAndCompile(PKOCENTRY pEntry, const char * const *papszArgvPreComp, unsigned cArgvPreComp) { if ( pEntry->fPipedCompile && pEntry->fPipedPreComp) { /* * Clean up old stuff first. */ if (pEntry->Old.pszObjName) UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir); if (pEntry->New.pszObjName) UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir); if (pEntry->Old.pszCppName) UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir); if (pEntry->New.pszCppName) UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir); /* * Do the actual compile and write the preprocessor output to disk. */ kOCEntrySpawnTee(pEntry, papszArgvPreComp, cArgvPreComp, (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile, "preprocess|compile", kOCEntryTeeConsumer); if (pEntry->pszMakeDepFilename) kOCDepWriteToFile(&pEntry->DepState, pEntry->pszMakeDepFilename, pEntry->New.pszObjName, pEntry->pszDir, pEntry->fMakeDepFixCase, pEntry->fMakeDepQuiet, pEntry->fMakeDepGenStubs); } else { kOCEntryPreProcess(pEntry, papszArgvPreComp, cArgvPreComp); kOCEntryCompileIt(pEntry); } } /** * Check whether the string is a '#line' statement. * * @returns 1 if it is, 0 if it isn't. * @param psz The line to examin. * @parma piLine Where to store the line number. * @parma ppszFile Where to store the start of the filename. */ static int kOCEntryIsLineStatement(const char *psz, unsigned *piLine, const char **ppszFile) { unsigned iLine; /* Expect a hash. */ if (*psz++ != '#') return 0; /* Skip blanks between '#' and the line / number */ while (*psz == ' ' || *psz == '\t') psz++; /* Skip the 'line' if present. */ if (!strncmp(psz, "line", sizeof("line") - 1)) psz += sizeof("line"); /* Expect a line number now. */ if ((unsigned char)(*psz - '0') > 9) return 0; iLine = 0; do { iLine *= 10; iLine += (*psz - '0'); psz++; } while ((unsigned char)(*psz - '0') <= 9); /* Expect one or more space now. */ if (*psz != ' ' && *psz != '\t') return 0; do psz++; while (*psz == ' ' || *psz == '\t'); /* that's good enough. */ *piLine = iLine; *ppszFile = psz; return 1; } /** * Scan backwards for the previous #line statement. * * @returns The filename in the previous statement. * @param pszStart Where to start. * @param pszStop Where to stop. Less than pszStart. * @param piLine The line number count to adjust. */ static const char *kOCEntryFindFileStatement(const char *pszStart, const char *pszStop, unsigned *piLine) { unsigned iLine = *piLine; assert(pszStart >= pszStop); while (pszStart >= pszStop) { if (*pszStart == '\n') iLine++; else if (*pszStart == '#') { unsigned iLineTmp; const char *pszFile; const char *psz = pszStart - 1; while (psz >= pszStop && (*psz == ' ' || *psz =='\t')) psz--; if ( (psz < pszStop || *psz == '\n') && kOCEntryIsLineStatement(pszStart, &iLineTmp, &pszFile)) { *piLine = iLine + iLineTmp - 1; return pszFile; } } pszStart--; } return NULL; } /** * Worker for kOCEntryCompareOldAndNewOutput() that compares the * preprocessed output using a fast but not very good method. * * @returns 1 if matching, 0 if not matching. * @param pEntry The entry containing the names of the files to compare. * The entry is not updated in any way. */ static int kOCEntryCompareFast(PCKOCENTRY pEntry) { const char * psz1 = pEntry->New.pszCppMapping; const char * const pszEnd1 = psz1 + pEntry->New.cbCpp; const char * psz2 = pEntry->Old.pszCppMapping; const char * const pszEnd2 = psz2 + pEntry->Old.cbCpp; assert(*pszEnd1 == '\0'); assert(*pszEnd2 == '\0'); /* * Iterate block by block and backtrack when we find a difference. */ for (;;) { size_t cch = pszEnd1 - psz1; if (cch > (size_t)(pszEnd2 - psz2)) cch = pszEnd2 - psz2; if (cch > 4096) cch = 4096; if ( cch && !memcmp(psz1, psz2, cch)) { /* no differences */ psz1 += cch; psz2 += cch; } else { /* * Pinpoint the difference exactly and the try find the start * of that line. Then skip forward until we find something to * work on that isn't spaces, #line statements or closing curly * braces. * * The closing curly braces are ignored because they are frequently * found at the end of header files (__END_DECLS) and the worst * thing that may happen if it isn't one of these braces we're * ignoring is that the final line in a function block is a little * bit off in the debug info. * * Since we might be skipping a few new empty headers, it is * possible that we will omit this header from the dependencies * when using VCC. This might not be a problem, since it seems * we'll have to use the preprocessor output to generate the deps * anyway. */ const char *psz; const char *pszMismatch1; const char *pszFile1 = NULL; unsigned iLine1 = 0; unsigned cCurlyBraces1 = 0; const char *pszMismatch2; const char *pszFile2 = NULL; unsigned iLine2 = 0; unsigned cCurlyBraces2 = 0; /* locate the difference. */ while (cch >= 512 && !memcmp(psz1, psz2, 512)) psz1 += 512, psz2 += 512, cch -= 512; while (cch >= 64 && !memcmp(psz1, psz2, 64)) psz1 += 64, psz2 += 64, cch -= 64; while (*psz1 == *psz2 && cch > 0) psz1++, psz2++, cch--; /* locate the start of that line. */ psz = psz1; while ( psz > pEntry->New.pszCppMapping && psz[-1] != '\n') psz--; psz2 -= (psz1 - psz); pszMismatch2 = psz2; pszMismatch1 = psz1 = psz; /* Parse the 1st file line by line. */ while (psz1 < pszEnd1) { if (*psz1 == '\n') { psz1++; iLine1++; } else { psz = psz1; while (isspace(*psz) && *psz != '\n') psz++; if (*psz == '\n') { psz1 = psz + 1; iLine1++; } else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine1, &pszFile1)) { psz1 = memchr(psz, '\n', pszEnd1 - psz); if (!psz1++) psz1 = pszEnd1; } else if (*psz == '}') { do psz++; while (isspace(*psz) && *psz != '\n'); if (*psz == '\n') iLine1++; else if (psz != pszEnd1) break; cCurlyBraces1++; psz1 = psz; } else if (psz == pszEnd1) psz1 = psz; else /* found something that can be compared. */ break; } } /* Ditto for the 2nd file. */ while (psz2 < pszEnd2) { if (*psz2 == '\n') { psz2++; iLine2++; } else { psz = psz2; while (isspace(*psz) && *psz != '\n') psz++; if (*psz == '\n') { psz2 = psz + 1; iLine2++; } else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine2, &pszFile2)) { psz2 = memchr(psz, '\n', pszEnd2 - psz); if (!psz2++) psz2 = pszEnd2; } else if (*psz == '}') { do psz++; while (isspace(*psz) && *psz != '\n'); if (*psz == '\n') iLine2++; else if (psz != pszEnd2) break; cCurlyBraces2++; psz2 = psz; } else if (psz == pszEnd2) psz2 = psz; else /* found something that can be compared. */ break; } } /* Match the number of ignored closing curly braces. */ if (cCurlyBraces1 != cCurlyBraces2) return 0; /* Reaching the end of any of them means the return statement can decide. */ if ( psz1 == pszEnd1 || psz2 == pszEnd2) break; /* Match the current line. */ psz = memchr(psz1, '\n', pszEnd1 - psz1); if (!psz++) psz = pszEnd1; cch = psz - psz1; if (psz2 + cch > pszEnd2) break; if (memcmp(psz1, psz2, cch)) break; /* Check that we're at the same location now. */ if (!pszFile1) pszFile1 = kOCEntryFindFileStatement(pszMismatch1, pEntry->New.pszCppMapping, &iLine1); if (!pszFile2) pszFile2 = kOCEntryFindFileStatement(pszMismatch2, pEntry->Old.pszCppMapping, &iLine2); if (pszFile1 && pszFile2) { if (iLine1 != iLine2) break; while (*pszFile1 == *pszFile2 && *pszFile1 != '\n' && *pszFile1) pszFile1++, pszFile2++; if (*pszFile1 != *pszFile2) break; } else if (pszFile1 || pszFile2) { assert(0); /* this shouldn't happen. */ break; } /* Advance. We might now have a misaligned buffer, but that's memcmps problem... */ psz1 += cch; psz2 += cch; } } return psz1 == pszEnd1 && psz2 == pszEnd2; } /** * Worker for kOCEntryCompileIfNeeded that compares the * preprocessed output. * * @returns 1 if matching, 0 if not matching. * @param pEntry The entry containing the names of the files to compare. * This will load the old cpp output (changing pszOldCppName and Old.cbCpp). */ static int kOCEntryCompareOldAndNewOutput(PKOCENTRY pEntry) { /* * I may implement a more sophisticated alternative method later... maybe. */ if (kOCEntryReadCppOutput(pEntry, &pEntry->Old, 1 /* nonfatal */) == -1) return 0; /*if () return kOCEntryCompareBest(pEntry);*/ return kOCEntryCompareFast(pEntry); } /** * Check if re-compilation is required. * This sets the fNeedCompile flag. * * @param pEntry The cache entry. */ static void kOCEntryCalcRecompile(PKOCENTRY pEntry) { if (pEntry->fNeedCompiling) return; /* * Check if the preprocessor output differ in any significant way? */ if (!kOCSumHasEqualInChain(&pEntry->Old.SumHead, &pEntry->New.SumHead)) { if (pEntry->fOptimizeCpp & 2) { InfoMsg(2, "no checksum match - no need to compare output, -O2.\n"); pEntry->fNeedCompiling = 1; } else { InfoMsg(2, "no checksum match - comparing output\n"); if (!kOCEntryCompareOldAndNewOutput(pEntry)) pEntry->fNeedCompiling = 1; else kOCSumAddChain(&pEntry->New.SumHead, &pEntry->Old.SumHead); } } } /** * Does this cache entry need compiling or what? * * @returns 1 if it does, 0 if it doesn't. * @param pEntry The cache entry in question. */ static int kOCEntryNeedsCompiling(PCKOCENTRY pEntry) { return pEntry->fNeedCompiling; } /** * Tries to hardlink a file. * * @returns 1 if it succeeded, 0 if it didn't. * @param pszLink The name of the hardlink. * @param pszLinkTo The file to hardlinkg @a pszDst to. */ static int kOCEntryTryHardlink(const char *pszLink, const char *pszLinkTo) { #ifdef __WIN__ typedef BOOL (WINAPI *PFNCREATEHARDLINKA)(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES); static PFNCREATEHARDLINKA s_pfnCreateHardLinkA = NULL; static int s_fTried = FALSE; /* The API was introduced in Windows 2000, so resolve it dynamically. */ if (!s_pfnCreateHardLinkA) { if (!s_fTried) { HMODULE hmod = LoadLibrary("KERNEL32.DLL"); if (hmod) *(FARPROC *)&s_pfnCreateHardLinkA = GetProcAddress(hmod, "CreateHardLinkA"); s_fTried = TRUE; } if (!s_pfnCreateHardLinkA) return 0; } if (!s_pfnCreateHardLinkA(pszLink, pszLinkTo, NULL)) return 0; #else if (link(pszLinkTo, pszLink) != 0) return 0; #endif return 1; } /** * Worker function for kOCEntryCopy. * * @param pEntry The entry we're coping to, which pszTo is relative to. * @param pszTo The destination. * @param pszFrom The source. This path will be freed. */ static void kOCEntryCopyFile(PCKOCENTRY pEntry, const char *pszTo, char *pszSrc) { char *pszDst = MakePathFromDirAndFile(pszTo, pEntry->pszDir); unlink(pszDst); if (!kOCEntryTryHardlink(pszDst, pszSrc)) { char *pszBuf = xmalloc(256 * 1024); char *psz; int fdSrc; int fdDst; /* * Open the files. */ fdSrc = open(pszSrc, O_RDONLY | O_BINARY); if (fdSrc == -1) FatalDie("failed to open '%s': %s\n", pszSrc, strerror(errno)); fdDst = open(pszDst, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (fdDst == -1) FatalDie("failed to create '%s': %s\n", pszDst, strerror(errno)); /* * Copy them. */ for (;;) { /* read a chunk. */ long cbRead = read(fdSrc, pszBuf, 256*1024); if (cbRead < 0) { if (errno == EINTR) continue; FatalDie("read '%s' failed: %s\n", pszSrc, strerror(errno)); } if (!cbRead) break; /* eof */ /* write the chunk. */ psz = pszBuf; do { long cbWritten = write(fdDst, psz, cbRead); if (cbWritten < 0) { if (errno == EINTR) continue; FatalDie("write '%s' failed: %s\n", pszSrc, strerror(errno)); } psz += cbWritten; cbRead -= cbWritten; } while (cbRead > 0); } /* cleanup */ if (close(fdDst) != 0) FatalDie("closing '%s' failed: %s\n", pszDst, strerror(errno)); close(fdSrc); free(pszBuf); } free(pszDst); free(pszSrc); } /** * Copies the object (and whatever else) from one cache entry to another. * * This is called when a matching cache entry has been found and we don't * need to recompile anything. * * @param pEntry The entry to copy to. * @param pFrom The entry to copy from. */ static void kOCEntryCopy(PKOCENTRY pEntry, PCKOCENTRY pFrom) { kOCEntryCopyFile(pEntry, pEntry->New.pszObjName, MakePathFromDirAndFile(pFrom->New.pszObjName ? pFrom->New.pszObjName : pFrom->Old.pszObjName, pFrom->pszDir)); } /** * Gets the absolute path to the cache entry. * * @returns absolute path to the cache entry. * @param pEntry The cache entry in question. */ static const char *kOCEntryAbsPath(PCKOCENTRY pEntry) { return pEntry->pszAbsPath; } /** * Digest of one cache entry. * * This contains all the information required to find a matching * cache entry without having to open each of the files. */ typedef struct KOCDIGEST { /** The relative path to the entry. Optional if pszAbsPath is set. */ char *pszRelPath; /** The absolute path to the entry. Optional if pszRelPath is set. */ char *pszAbsPath; /** The target os/arch identifier. */ char *pszTarget; /** A unique number assigned to the entry when it's (re)-inserted * into the cache. This is used for simple consitency checking. */ uint32_t uKey; /** The checksum of the compile argument vector. */ KOCSUM SumCompArgv; /** The list of preprocessor output checksums that's . */ KOCSUM SumHead; } KOCDIGEST; /** Pointer to a file digest. */ typedef KOCDIGEST *PKOCDIGEST; /** Pointer to a const file digest. */ typedef KOCDIGEST *PCKOCDIGEST; /** * Initializes the specified digest. * * @param pDigest The digest. */ static void kOCDigestInit(PKOCDIGEST pDigest) { memset(pDigest, 0, sizeof(*pDigest)); kOCSumInit(&pDigest->SumHead); } /** * Initializes the digest for the specified entry. * * @param pDigest The (uninitialized) digest. * @param pEntry The entry. */ static void kOCDigestInitFromEntry(PKOCDIGEST pDigest, PCKOCENTRY pEntry) { kOCDigestInit(pDigest); pDigest->uKey = pEntry->uKey; pDigest->pszTarget = xstrdup(pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget); kOCSumInit(&pDigest->SumCompArgv); if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv)) kOCSumAdd(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv); else kOCSumAdd(&pDigest->SumCompArgv, &pEntry->Old.SumCompArgv); kOCSumInit(&pDigest->SumHead); if (!kOCSumIsEmpty(&pEntry->New.SumHead)) kOCSumAddChain(&pDigest->SumHead, &pEntry->New.SumHead); else kOCSumAddChain(&pDigest->SumHead, &pEntry->Old.SumHead); /** @todo implement selective relative path support. */ pDigest->pszRelPath = NULL; pDigest->pszAbsPath = xstrdup(kOCEntryAbsPath(pEntry)); } /** * Purges a digest, freeing all resources and returning * it to the initial state. * * @param pDigest The digest. */ static void kOCDigestPurge(PKOCDIGEST pDigest) { free(pDigest->pszRelPath); free(pDigest->pszAbsPath); free(pDigest->pszTarget); pDigest->pszTarget = pDigest->pszAbsPath = pDigest->pszRelPath = NULL; pDigest->uKey = 0; kOCSumDeleteChain(&pDigest->SumCompArgv); kOCSumDeleteChain(&pDigest->SumHead); } /** * Returns the absolute path to the entry, calculating * the path if necessary. * * @returns absolute path. * @param pDigest The digest. * @param pszDir The cache directory that it might be relative to. */ static const char *kOCDigestAbsPath(PCKOCDIGEST pDigest, const char *pszDir) { if (!pDigest->pszAbsPath) { char *pszPath = MakePathFromDirAndFile(pDigest->pszRelPath, pszDir); ((PKOCDIGEST)pDigest)->pszAbsPath = AbsPath(pszPath); free(pszPath); } return pDigest->pszAbsPath; } /** * Checks that the digest matches the * * @returns 1 if valid, 0 if invalid in some way. * * @param pDigest The digest to validate. * @param pEntry What to validate it against. */ static int kOCDigestIsValid(PCKOCDIGEST pDigest, PCKOCENTRY pEntry) { PCKOCSUM pSum; PCKOCSUM pSumEntry; if (pDigest->uKey != pEntry->uKey) return 0; if (!kOCSumIsEqual(&pDigest->SumCompArgv, kOCSumIsEmpty(&pEntry->New.SumCompArgv) ? &pEntry->Old.SumCompArgv : &pEntry->New.SumCompArgv)) return 0; if (strcmp(pDigest->pszTarget, pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget)) return 0; /* match the checksums */ pSumEntry = kOCSumIsEmpty(&pEntry->New.SumHead) ? &pEntry->Old.SumHead : &pEntry->New.SumHead; for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext) if (!kOCSumHasEqualInChain(pSumEntry, pSum)) return 0; return 1; } /** * The structure for the central cache entry. */ typedef struct KOBJCACHE { /** The entry name. */ const char *pszName; /** The dir that relative names in the digest are relative to. */ char *pszDir; /** The absolute path. */ char *pszAbsPath; /** The cache file descriptor. */ int fd; /** The stream associated with fd. */ FILE *pFile; /** Whether it's currently locked or not. */ unsigned fLocked; /** Whether the cache file is dirty and needs writing back. */ unsigned fDirty; /** Whether this is a new cache or not. */ unsigned fNewCache; /** The cache file generation. */ uint32_t uGeneration; /** The next valid key. (Determin at load time.) */ uint32_t uNextKey; /** Number of digests in paDigests. */ unsigned cDigests; /** Array of digests for the KOCENTRY objects in the cache. */ PKOCDIGEST paDigests; } KOBJCACHE; /** Pointer to a cache. */ typedef KOBJCACHE *PKOBJCACHE; /** Pointer to a const cache. */ typedef KOBJCACHE const *PCKOBJCACHE; /** * Creates an empty cache. * * This doesn't touch the file system, it just create the data structure. * * @returns Pointer to a cache. * @param pszCacheFile The cache file. */ static PKOBJCACHE kObjCacheCreate(const char *pszCacheFile) { PKOBJCACHE pCache; size_t off; /* * Allocate an empty entry. */ pCache = xmallocz(sizeof(*pCache)); pCache->fd = -1; /* * Setup the directory and cache file name. */ pCache->pszAbsPath = AbsPath(pszCacheFile); pCache->pszName = FindFilenameInPath(pCache->pszAbsPath); off = pCache->pszName - pCache->pszAbsPath; if (!off) FatalDie("Failed to find abs path for '%s'!\n", pszCacheFile); pCache->pszDir = xmalloc(off); memcpy(pCache->pszDir, pCache->pszAbsPath, off - 1); pCache->pszDir[off - 1] = '\0'; return pCache; } /** * Destroys the cache - closing any open files, freeing up heap memory and such. * * @param pCache The cache. */ static void kObjCacheDestroy(PKOBJCACHE pCache) { if (pCache->pFile) { errno = 0; if (fclose(pCache->pFile) != 0) FatalMsg("fclose failed: %s\n", strerror(errno)); pCache->pFile = NULL; pCache->fd = -1; } free(pCache->paDigests); free(pCache->pszAbsPath); free(pCache->pszDir); free(pCache); } /** * Purges the data in the cache object. * * @param pCache The cache object. */ static void kObjCachePurge(PKOBJCACHE pCache) { while (pCache->cDigests > 0) kOCDigestPurge(&pCache->paDigests[--pCache->cDigests]); free(pCache->paDigests); pCache->paDigests = NULL; pCache->uGeneration = 0; pCache->uNextKey = 0; } /** * (Re-)reads the file. * * @param pCache The cache to (re)-read. */ static void kObjCacheRead(PKOBJCACHE pCache) { unsigned i; char szBuf[8192]; int fBad = 0; InfoMsg(4, "reading cache file...\n"); /* * Rewind the file & stream, and associate a temporary buffer * with the stream to speed up reading. */ if (lseek(pCache->fd, 0, SEEK_SET) == -1) FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno)); rewind(pCache->pFile); if (setvbuf(pCache->pFile, szBuf, _IOFBF, sizeof(szBuf)) != 0) FatalDie("fdopen(cache-fd,rb) failed: %s\n", strerror(errno)); /* * Read magic and generation. */ if ( !fgets(g_szLine, sizeof(g_szLine), pCache->pFile) || strcmp(g_szLine, "magic=kObjCache-v0.1.0\n")) { InfoMsg(2, "bad cache file (magic)\n"); fBad = 1; } else if ( !fgets(g_szLine, sizeof(g_szLine), pCache->pFile) || strncmp(g_szLine, "generation=", sizeof("generation=") - 1)) { InfoMsg(2, "bad cache file (generation)\n"); fBad = 1; } else if ( pCache->uGeneration && (long)pCache->uGeneration == atol(&g_szLine[sizeof("generation=") - 1])) { InfoMsg(3, "drop re-read unmodified cache file\n"); fBad = 0; } else { int fBadBeforeMissing; /* * Read everything (anew). */ kObjCachePurge(pCache); do { PKOCDIGEST pDigest; char *pszNl; char *pszVal; char *psz; /* Split the line and drop the trailing newline. */ pszVal = strchr(g_szLine, '='); if ((fBad = pszVal == NULL)) break; *pszVal++ = '\0'; pszNl = strchr(pszVal, '\n'); if (pszNl) *pszNl = '\0'; /* digest '#'? */ psz = strchr(g_szLine, '#'); if (psz) { char *pszNext; i = strtoul(++psz, &pszNext, 0); if ((fBad = pszNext && *pszNext)) break; if ((fBad = i >= pCache->cDigests)) break; pDigest = &pCache->paDigests[i]; *psz = '\0'; } else pDigest = NULL; /* string case on value name. */ if (!strcmp(g_szLine, "sum-#")) { KOCSUM Sum; if ((fBad = kOCSumInitFromString(&Sum, pszVal) != 0)) break; kOCSumAdd(&pDigest->SumHead, &Sum); } else if (!strcmp(g_szLine, "digest-abs-#")) { if ((fBad = pDigest->pszAbsPath != NULL)) break; pDigest->pszAbsPath = xstrdup(pszVal); } else if (!strcmp(g_szLine, "digest-rel-#")) { if ((fBad = pDigest->pszRelPath != NULL)) break; pDigest->pszRelPath = xstrdup(pszVal); } else if (!strcmp(g_szLine, "key-#")) { if ((fBad = pDigest->uKey != 0)) break; pDigest->uKey = strtoul(pszVal, &psz, 0); if ((fBad = psz && *psz)) break; if (pDigest->uKey >= pCache->uNextKey) pCache->uNextKey = pDigest->uKey + 1; } else if (!strcmp(g_szLine, "comp-argv-sum-#")) { if ((fBad = !kOCSumIsEmpty(&pDigest->SumCompArgv))) break; if ((fBad = kOCSumInitFromString(&pDigest->SumCompArgv, pszVal) != 0)) break; } else if (!strcmp(g_szLine, "target-#")) { if ((fBad = pDigest->pszTarget != NULL)) break; pDigest->pszTarget = xstrdup(pszVal); } else if (!strcmp(g_szLine, "digests")) { if ((fBad = pCache->paDigests != NULL)) break; pCache->cDigests = strtoul(pszVal, &psz, 0); if ((fBad = psz && *psz)) break; i = (pCache->cDigests + 4) & ~3; pCache->paDigests = xmalloc(i * sizeof(pCache->paDigests[0])); for (i = 0; i < pCache->cDigests; i++) kOCDigestInit(&pCache->paDigests[i]); } else if (!strcmp(g_szLine, "generation")) { if ((fBad = pCache->uGeneration != 0)) break; pCache->uGeneration = strtoul(pszVal, &psz, 0); if ((fBad = psz && *psz)) break; } else if (!strcmp(g_szLine, "the-end")) { fBad = strcmp(pszVal, "fine"); break; } else { fBad = 1; break; } } while (fgets(g_szLine, sizeof(g_szLine), pCache->pFile)); /* * Did we find everything? */ fBadBeforeMissing = fBad; if ( !fBad && !pCache->uGeneration) fBad = 1; if (!fBad) for (i = 0; i < pCache->cDigests; i++) { if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumCompArgv))) break; if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumHead))) break; if ((fBad = pCache->paDigests[i].uKey == 0)) break; if ((fBad = pCache->paDigests[i].pszAbsPath == NULL && pCache->paDigests[i].pszRelPath == NULL)) break; if ((fBad = pCache->paDigests[i].pszTarget == NULL)) break; InfoMsg(4, "digest-%u: %s\n", i, pCache->paDigests[i].pszAbsPath ? pCache->paDigests[i].pszAbsPath : pCache->paDigests[i].pszRelPath); } if (fBad) InfoMsg(2, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff"); else if (ferror(pCache->pFile)) { InfoMsg(2, "cache file read error\n"); fBad = 1; } } if (fBad) { kObjCachePurge(pCache); pCache->fNewCache = 1; } /* * Disassociate the buffer from the stream changing * it to non-buffered mode. */ if (setvbuf(pCache->pFile, NULL, _IONBF, 0) != 0) FatalDie("setvbuf(,0,,0) failed: %s\n", strerror(errno)); } /** * Re-writes the cache file. * * @param pCache The cache to commit and unlock. */ static void kObjCacheWrite(PKOBJCACHE pCache) { unsigned i; off_t cb; char szBuf[8192]; assert(pCache->fLocked); assert(pCache->fDirty); /* * Rewind the file & stream, and associate a temporary buffer * with the stream to speed up the writing. */ if (lseek(pCache->fd, 0, SEEK_SET) == -1) FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno)); rewind(pCache->pFile); if (setvbuf(pCache->pFile, szBuf, _IOFBF, sizeof(szBuf)) != 0) FatalDie("setvbuf failed: %s\n", strerror(errno)); /* * Write the header. */ pCache->uGeneration++; fprintf(pCache->pFile, "magic=kObjCache-v0.1.0\n" "generation=%d\n" "digests=%d\n", pCache->uGeneration, pCache->cDigests); /* * Write the digests. */ for (i = 0; i < pCache->cDigests; i++) { PCKOCDIGEST pDigest = &pCache->paDigests[i]; PKOCSUM pSum; if (pDigest->pszAbsPath) fprintf(pCache->pFile, "digest-abs-#%u=%s\n", i, pDigest->pszAbsPath); if (pDigest->pszRelPath) fprintf(pCache->pFile, "digest-rel-#%u=%s\n", i, pDigest->pszRelPath); fprintf(pCache->pFile, "key-#%u=%u\n", i, pDigest->uKey); fprintf(pCache->pFile, "target-#%u=%s\n", i, pDigest->pszTarget); fprintf(pCache->pFile, "comp-argv-sum-#%u=", i); kOCSumFPrintf(&pDigest->SumCompArgv, pCache->pFile); for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext) { fprintf(pCache->pFile, "sum-#%u=", i); kOCSumFPrintf(pSum, pCache->pFile); } } /* * Close the stream and unlock fhe file. * (Closing the stream shouldn't close the file handle IIRC...) */ fprintf(pCache->pFile, "the-end=fine\n"); errno = 0; if ( fflush(pCache->pFile) < 0 || ferror(pCache->pFile)) { int iErr = errno; fclose(pCache->pFile); UnlinkFileInDir(pCache->pszName, pCache->pszDir); FatalDie("Stream error occured while writing '%s' in '%s': %s\n", pCache->pszName, pCache->pszDir, strerror(iErr)); } if (setvbuf(pCache->pFile, NULL, _IONBF, 0) != 0) FatalDie("setvbuf(,0,,0) failed: %s\n", strerror(errno)); cb = lseek(pCache->fd, 0, SEEK_CUR); if (cb == -1) FatalDie("lseek(cache-file,0,CUR) failed: %s\n", strerror(errno)); #if defined(__WIN__) if (_chsize(pCache->fd, cb) == -1) #else if (ftruncate(pCache->fd, cb) == -1) #endif FatalDie("file truncation failed: %s\n", strerror(errno)); InfoMsg(4, "wrote '%s' in '%s', %d bytes\n", pCache->pszName, pCache->pszDir, cb); } /** * Cleans out all invalid digests.s * * This is done periodically from the unlock routine to make * sure we don't accidentally accumulate stale digests. * * @param pCache The cache to chek. */ static void kObjCacheClean(PKOBJCACHE pCache) { unsigned i = pCache->cDigests; while (i-- > 0) { /* * Try open it and purge it if it's bad. * (We don't kill the entry file because that's kmk clean's job.) */ PCKOCDIGEST pDigest = &pCache->paDigests[i]; PKOCENTRY pEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir)); kOCEntryRead(pEntry); if ( !kOCEntryCheck(pEntry) || !kOCDigestIsValid(pDigest, pEntry)) { unsigned cLeft; kOCDigestPurge(pDigest); pCache->cDigests--; cLeft = pCache->cDigests - i; if (cLeft) memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest)); pCache->fDirty = 1; } kOCEntryDestroy(pEntry); } } /** * Locks the cache for exclusive access. * * This will open the file if necessary and lock the entire file * using the best suitable platform API (tricky). * * @param pCache The cache to lock. */ static void kObjCacheLock(PKOBJCACHE pCache) { struct stat st; #if defined(__WIN__) OVERLAPPED OverLapped; #endif assert(!pCache->fLocked); /* * Open it? */ if (pCache->fd < 0) { pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666); if (pCache->fd == -1) { MakePath(pCache->pszDir); pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666); if (pCache->fd == -1) FatalDie("Failed to create '%s' in '%s': %s\n", pCache->pszName, pCache->pszDir, strerror(errno)); } pCache->pFile = fdopen(pCache->fd, "r+b"); if (!pCache->pFile) FatalDie("fdopen failed: %s\n", strerror(errno)); if (setvbuf(pCache->pFile, NULL, _IONBF, 0) != 0) FatalDie("setvbuf(,0,,0) failed: %s\n", strerror(errno)); } /* * Lock it. */ #if defined(__WIN__) memset(&OverLapped, 0, sizeof(OverLapped)); if (!LockFileEx((HANDLE)_get_osfhandle(pCache->fd), LOCKFILE_EXCLUSIVE_LOCK, 0, ~0, 0, &OverLapped)) FatalDie("Failed to lock the cache file: Windows Error %d\n", GetLastError()); #elif defined(__sun__) { struct flock fl; fl.l_whence = 0; fl.l_start = 0; fl.l_len = 0; fl.l_type = F_WRLCK; if (fcntl(pCache->fd, F_SETLKW, &fl) != 0) FatalDie("Failed to lock the cache file: %s\n", strerror(errno)); } #else if (flock(pCache->fd, LOCK_EX) != 0) FatalDie("Failed to lock the cache file: %s\n", strerror(errno)); #endif pCache->fLocked = 1; /* * Check for new cache and read it it's an existing cache. * * There is no point in initializing a new cache until we've finished * compiling and has something to put into it, so we'll leave it as a * 0 byte file. */ if (fstat(pCache->fd, &st) == -1) FatalDie("fstat(cache-fd) failed: %s\n", strerror(errno)); if (st.st_size) kObjCacheRead(pCache); else { pCache->fNewCache = 1; InfoMsg(2, "the cache file is empty\n"); } } /** * Unlocks the cache (without writing anything back). * * @param pCache The cache to unlock. */ static void kObjCacheUnlock(PKOBJCACHE pCache) { #if defined(__WIN__) OVERLAPPED OverLapped; #endif assert(pCache->fLocked); /* * Write it back if it's dirty. */ if (pCache->fDirty) { if ( pCache->cDigests >= 16 && (pCache->uGeneration % 19) == 19) kObjCacheClean(pCache); kObjCacheWrite(pCache); pCache->fDirty = 0; } /* * Lock it. */ #if defined(__WIN__) memset(&OverLapped, 0, sizeof(OverLapped)); if (!UnlockFileEx((HANDLE)_get_osfhandle(pCache->fd), 0, ~0U, 0, &OverLapped)) FatalDie("Failed to unlock the cache file: Windows Error %d\n", GetLastError()); #elif defined(__sun__) { struct flock fl; fl.l_whence = 0; fl.l_start = 0; fl.l_len = 0; fl.l_type = F_UNLCK; if (fcntl(pCache->fd, F_SETLKW, &fl) != 0) FatalDie("Failed to lock the cache file: %s\n", strerror(errno)); } #else if (flock(pCache->fd, LOCK_UN) != 0) FatalDie("Failed to unlock the cache file: %s\n", strerror(errno)); #endif pCache->fLocked = 0; } /** * Removes the entry from the cache. * * The entry doesn't need to be in the cache. * The cache entry (file) itself is not touched. * * @param pCache The cache. * @param pEntry The entry. */ static void kObjCacheRemoveEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry) { unsigned i = pCache->cDigests; while (i-- > 0) { PKOCDIGEST pDigest = &pCache->paDigests[i]; if (ArePathsIdentical(kOCDigestAbsPath(pDigest, pCache->pszDir), kOCEntryAbsPath(pEntry))) { unsigned cLeft; kOCDigestPurge(pDigest); pCache->cDigests--; cLeft = pCache->cDigests - i; if (cLeft) memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest)); pCache->fDirty = 1; InfoMsg(3, "removing entry '%s'; %d left.\n", kOCEntryAbsPath(pEntry), pCache->cDigests); } } } /** * Inserts the entry into the cache. * * The cache entry (file) itself is not touched by this operation, * the pEntry object otoh is. * * @param pCache The cache. * @param pEntry The entry. */ static void kObjCacheInsertEntry(PKOBJCACHE pCache, PKOCENTRY pEntry) { unsigned i; /* * Find a new key. */ pEntry->uKey = pCache->uNextKey++; if (!pEntry->uKey) pEntry->uKey = pCache->uNextKey++; i = pCache->cDigests; while (i-- > 0) if (pCache->paDigests[i].uKey == pEntry->uKey) { pEntry->uKey = pCache->uNextKey++; if (!pEntry->uKey) pEntry->uKey = pCache->uNextKey++; i = pCache->cDigests; } /* * Reallocate the digest array? */ if ( !(pCache->cDigests & 3) && (pCache->cDigests || !pCache->paDigests)) pCache->paDigests = xrealloc(pCache->paDigests, sizeof(pCache->paDigests[0]) * (pCache->cDigests + 4)); /* * Create a new digest. */ kOCDigestInitFromEntry(&pCache->paDigests[pCache->cDigests], pEntry); pCache->cDigests++; InfoMsg(4, "Inserted digest #%u: %s\n", pCache->cDigests - 1, kOCEntryAbsPath(pEntry)); pCache->fDirty = 1; } /** * Find a matching cache entry. */ static PKOCENTRY kObjCacheFindMatchingEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry) { unsigned i = pCache->cDigests; assert(pEntry->fNeedCompiling); assert(!kOCSumIsEmpty(&pEntry->New.SumCompArgv)); assert(!kOCSumIsEmpty(&pEntry->New.SumHead)); while (i-- > 0) { /* * Matching? */ PCKOCDIGEST pDigest = &pCache->paDigests[i]; if ( kOCSumIsEqual(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv) && kOCSumHasEqualInChain(&pDigest->SumHead, &pEntry->New.SumHead)) { /* * Try open it. */ unsigned cLeft; PKOCENTRY pRetEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir)); kOCEntryRead(pRetEntry); if ( kOCEntryCheck(pRetEntry) && kOCDigestIsValid(pDigest, pRetEntry)) return pRetEntry; kOCEntryDestroy(pRetEntry); /* bad entry, purge it. */ InfoMsg(3, "removing bad digest '%s'\n", kOCDigestAbsPath(pDigest, pCache->pszDir)); kOCDigestPurge(pDigest); pCache->cDigests--; cLeft = pCache->cDigests - i; if (cLeft) memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest)); pCache->fDirty = 1; } } return NULL; } /** * Is this a new cache? * * @returns 1 if new, 0 if not new. * @param pEntry The entry. */ static int kObjCacheIsNew(PKOBJCACHE pCache) { return pCache->fNewCache; } /** * Prints a syntax error and returns the appropriate exit code * * @returns approriate exit code. * @param pszFormat The syntax error message. * @param ... Message args. */ static int SyntaxError(const char *pszFormat, ...) { va_list va; fprintf(stderr, "kObjCache: syntax error: "); va_start(va, pszFormat); vfprintf(stderr, pszFormat, va); va_end(va); return 1; } /** * Prints the usage. * @returns 0. */ static int usage(FILE *pOut) { fprintf(pOut, "syntax: kObjCache [--kObjCache-options] [-v|--verbose]\n" " < [-c|--cache-file ]\n" " | [-n|--name ] [[-d|--cache-dir ]] >\n" " <-f|--file >\n" " <-t|--target >\n" " [-r|--redir-stdout] [-p|--passthru] [--named-pipe-compile ]\n" " --kObjCache-cpp \n" " --kObjCache-cc \n" " [--kObjCache-both [args]]\n" ); fprintf(pOut, " [--kObjCache-cpp|--kObjCache-cc [more args]]\n" " kObjCache <-V|--version>\n" " kObjCache [-?|/?|-h|/h|--help|/help]\n" "\n" "The env.var. KOBJCACHE_DIR sets the default cache diretory (-d).\n" "The env.var. KOBJCACHE_OPTS allow you to specifie additional options\n" "without having to mess with the makefiles. These are appended with " "a --kObjCache-options between them and the command args.\n" "\n"); return 0; } int main(int argc, char **argv) { PKOBJCACHE pCache; PKOCENTRY pEntry; const char *pszCacheDir = getenv("KOBJCACHE_DIR"); const char *pszCacheName = NULL; const char *pszCacheFile = NULL; const char *pszEntryFile = NULL; const char **papszArgvPreComp = NULL; unsigned cArgvPreComp = 0; const char *pszPreCompName = NULL; int fRedirPreCompStdOut = 0; const char **papszArgvCompile = NULL; unsigned cArgvCompile = 0; const char *pszObjName = NULL; int fRedirCompileStdIn = 0; const char *pszNmPipeCompile = NULL; const char *pszMakeDepFilename = NULL; int fMakeDepFixCase = 0; int fMakeDepGenStubs = 0; int fMakeDepQuiet = 0; int fOptimizePreprocessorOutput = 0; const char *pszTarget = NULL; enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options; size_t cch; char *psz; int i; SetErrorPrefix("kObjCache"); /* * Arguments passed in the environmnet? */ psz = getenv("KOBJCACHE_OPTS"); if (psz) AppendArgs(&argc, &argv, psz, "--kObjCache-options"); /* * Parse the arguments. */ if (argc <= 1) return usage(stderr); for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--kObjCache-cpp")) { enmMode = kOC_CppArgv; if (!pszPreCompName) { if (++i >= argc) return SyntaxError("--kObjCache-cpp requires an object filename!\n"); pszPreCompName = argv[i]; } } else if (!strcmp(argv[i], "--kObjCache-cc")) { enmMode = kOC_CcArgv; if (!pszObjName) { if (++i >= argc) return SyntaxError("--kObjCache-cc requires an preprocessor output filename!\n"); pszObjName = argv[i]; } } else if (!strcmp(argv[i], "--kObjCache-both")) enmMode = kOC_BothArgv; else if (!strcmp(argv[i], "--kObjCache-options")) enmMode = kOC_Options; else if (!strcmp(argv[i], "--help")) return usage(stderr); else if (enmMode != kOC_Options) { if (enmMode == kOC_CppArgv || enmMode == kOC_BothArgv) { if (!(cArgvPreComp % 16)) papszArgvPreComp = xrealloc((void *)papszArgvPreComp, (cArgvPreComp + 17) * sizeof(papszArgvPreComp[0])); papszArgvPreComp[cArgvPreComp++] = argv[i]; papszArgvPreComp[cArgvPreComp] = NULL; } if (enmMode == kOC_CcArgv || enmMode == kOC_BothArgv) { if (!(cArgvCompile % 16)) papszArgvCompile = xrealloc((void *)papszArgvCompile, (cArgvCompile + 17) * sizeof(papszArgvCompile[0])); papszArgvCompile[cArgvCompile++] = argv[i]; papszArgvCompile[cArgvCompile] = NULL; } } else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--entry-file")) { if (i + 1 >= argc) return SyntaxError("%s requires a cache entry filename!\n", argv[i]); pszEntryFile = argv[++i]; } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache-file")) { if (i + 1 >= argc) return SyntaxError("%s requires a cache filename!\n", argv[i]); pszCacheFile = argv[++i]; } else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--name")) { if (i + 1 >= argc) return SyntaxError("%s requires a cache name!\n", argv[i]); pszCacheName = argv[++i]; } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--cache-dir")) { if (i + 1 >= argc) return SyntaxError("%s requires a cache directory!\n", argv[i]); pszCacheDir = argv[++i]; } else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--target")) { if (i + 1 >= argc) return SyntaxError("%s requires a target platform/arch name!\n", argv[i]); pszTarget = argv[++i]; } else if (!strcmp(argv[i], "--named-pipe-compile")) { if (i + 1 >= argc) return SyntaxError("%s requires a pipe name!\n", argv[i]); pszNmPipeCompile = argv[++i]; fRedirCompileStdIn = 0; } else if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--make-dep-file")) { if (i + 1 >= argc) return SyntaxError("%s requires a filename!\n", argv[i]); pszMakeDepFilename = argv[++i]; } else if (!strcmp(argv[i], "--make-dep-fix-case")) fMakeDepFixCase = 1; else if (!strcmp(argv[i], "--make-dep-gen-stubs")) fMakeDepGenStubs = 1; else if (!strcmp(argv[i], "--make-dep-quiet")) fMakeDepQuiet = 1; else if (!strcmp(argv[i], "-O1") || !strcmp(argv[i], "--optimize-1")) fOptimizePreprocessorOutput = 1; else if (!strcmp(argv[i], "-O2") || !strcmp(argv[i], "--optimize-2")) fOptimizePreprocessorOutput = 1 | 2; else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--passthru")) fRedirPreCompStdOut = fRedirCompileStdIn = 1; else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout")) fRedirPreCompStdOut = 1; else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) g_cVerbosityLevel++; else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet")) g_cVerbosityLevel = 0; else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?") || !strcmp(argv[i], "/h") || !strcmp(argv[i], "/?") || !strcmp(argv[i], "/help")) { usage(stdout); return 0; } else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) { printf("kObjCache - kBuild version %d.%d.%d ($Revision: 3315 $)\n" "Copyright (c) 2007-2012 knut st. osmundsen\n", KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH); return 0; } else return SyntaxError("Doesn't grok '%s'!\n", argv[i]); } if (!pszEntryFile) return SyntaxError("No cache entry filename (-f)!\n"); if (!pszTarget) return SyntaxError("No target name (-t)!\n"); if (!cArgvCompile) return SyntaxError("No compiler arguments (--kObjCache-cc)!\n"); if (!cArgvPreComp) return SyntaxError("No preprocessor arguments (--kObjCache-cc)!\n"); /* * Calc the cache file name. * It's a bit messy since the extension has to be replaced. */ if (!pszCacheFile) { if (!pszCacheDir) return SyntaxError("No cache dir (-d / KOBJCACHE_DIR) and no cache filename!\n"); if (!pszCacheName) { psz = (char *)FindFilenameInPath(pszEntryFile); if (!*psz) return SyntaxError("The cache file (-f) specifies a directory / nothing!\n"); cch = psz - pszEntryFile; pszCacheName = memcpy(xmalloc(cch + 5), psz, cch + 1); psz = strrchr(pszCacheName, '.'); if (!psz || psz <= pszCacheName) psz = (char *)pszCacheName + cch; memcpy(psz, ".koc", sizeof(".koc")); } pszCacheFile = MakePathFromDirAndFile(pszCacheName, pszCacheDir); } /* * Create and initialize the two objects we'll be working on. * * We're supposed to be the only ones actually writing to the local file, * so it's perfectly fine to read it here before we lock it. This simplifies * the detection of object name and compiler argument changes. */ SetErrorPrefix("kObjCache - %s", FindFilenameInPath(pszCacheFile)); pCache = kObjCacheCreate(pszCacheFile); pEntry = kOCEntryCreate(pszEntryFile); kOCEntryRead(pEntry); kOCEntrySetCppName(pEntry, pszPreCompName); kOCEntrySetCompileObjName(pEntry, pszObjName); kOCEntrySetCompileArgv(pEntry, papszArgvCompile, cArgvCompile); kOCEntrySetTarget(pEntry, pszTarget); kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn, pszNmPipeCompile); kOCEntrySetDepFilename(pEntry, pszMakeDepFilename, fMakeDepFixCase, fMakeDepQuiet, fMakeDepGenStubs); kOCEntrySetOptimizations(pEntry, fOptimizePreprocessorOutput); /* * Open (& lock) the two files and do validity checks and such. */ kObjCacheLock(pCache); if ( kObjCacheIsNew(pCache) && kOCEntryNeedsCompiling(pEntry)) { /* * Both files are missing/invalid. * Optimize this path as it is frequently used when making a clean build. */ kObjCacheUnlock(pCache); InfoMsg(1, "doing full compile\n"); kOCEntryPreProcessAndCompile(pEntry, papszArgvPreComp, cArgvPreComp); kObjCacheLock(pCache); } else { /* * Do the preprocess (don't need to lock the cache file for this). */ kObjCacheUnlock(pCache); kOCEntryPreProcess(pEntry, papszArgvPreComp, cArgvPreComp); /* * Check if we need to recompile. If we do, try see if the is a cache entry first. */ kOCEntryCalcRecompile(pEntry); if (kOCEntryNeedsCompiling(pEntry)) { PKOCENTRY pUseEntry; kObjCacheLock(pCache); kObjCacheRemoveEntry(pCache, pEntry); pUseEntry = kObjCacheFindMatchingEntry(pCache, pEntry); if (pUseEntry) { InfoMsg(1, "using cache entry '%s'\n", kOCEntryAbsPath(pUseEntry)); kOCEntryCopy(pEntry, pUseEntry); kOCEntryDestroy(pUseEntry); } else { kObjCacheUnlock(pCache); InfoMsg(1, "recompiling\n"); kOCEntryCompileIt(pEntry); kObjCacheLock(pCache); } } else { InfoMsg(1, "no need to recompile\n"); kObjCacheLock(pCache); } } /* * Update the cache files. */ kObjCacheRemoveEntry(pCache, pEntry); kObjCacheInsertEntry(pCache, pEntry); kOCEntryWrite(pEntry); kObjCacheUnlock(pCache); kObjCacheDestroy(pCache); if (fOptimizePreprocessorOutput) { InfoMsg(3, "g_cbMemMoved=%#x (%d)\n", g_cbMemMoved, g_cbMemMoved); InfoMsg(3, "g_cMemMoves=%#x (%d)\n", g_cMemMoves, g_cMemMoves); } return 0; } /** @page kObjCache Benchmarks. * * (2007-06-10) * * Mac OS X debug -j 3 cached clobber build (rm -Rf out ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1): * real 11m28.811s * user 13m59.291s * sys 3m24.590s * * Mac OS X debug -j 3 cached depend build [cdefs.h] (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1): * real 1m26.895s * user 1m26.971s * sys 0m32.532s * * Mac OS X debug -j 3 cached depend build [err.h] (touch include/iprt/err.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1): * real 1m18.049s * user 1m20.462s * sys 0m27.887s * * Mac OS X release -j 3 cached clobber build (rm -Rf out/darwin.x86/release ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1 BUILD_TYPE=release): * real 13m27.751s * user 18m12.654s * sys 3m25.170s * * Mac OS X profile -j 3 cached clobber build (rm -Rf out/darwin.x86/profile ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 USE_KOBJCACHE=1 BUILD_TYPE=profile): * real 9m9.720s * user 8m53.005s * sys 2m13.110s * * Mac OS X debug -j 3 clobber build (rm -Rf out/darwin.x86/debug ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=debug): * real 10m18.129s * user 12m52.687s * sys 2m51.277s * * Mac OS X debug -j 3 debug build [cdefs.h] (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=debug): * real 4m46.147s * user 5m27.087s * sys 1m11.775s * * Mac OS X debug -j 3 debug build [err.h] (touch include/iprt/cdefs.h ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=debug): * real 4m17.572s * user 5m7.450s * sys 1m3.450s * * Mac OS X release -j 3 clobber build (rm -Rf out/darwin.x86/release ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=release): * real 12m14.742s * user 17m11.794s * sys 2m51.454s * * Mac OS X profile -j 3 clobber build (rm -Rf out/darwin.x86/profile ; sync ; svn diff ; sync ; sleep 1 ; time kmk -j 3 BUILD_TYPE=profile): * real 12m33.821s * user 17m35.086s * sys 2m53.312s * * Note. The profile build can pick object files from the release build. * (all with KOBJCACHE_OPTS=-v; which means a bit more output and perhaps a second or two slower.) */