diff options
Diffstat (limited to 'src/kObjCache/kObjCache.c')
-rw-r--r-- | src/kObjCache/kObjCache.c | 5304 |
1 files changed, 5304 insertions, 0 deletions
diff --git a/src/kObjCache/kObjCache.c b/src/kObjCache/kObjCache.c new file mode 100644 index 0000000..856be15 --- /dev/null +++ b/src/kObjCache/kObjCache.c @@ -0,0 +1,5304 @@ +/* $Id: kObjCache.c 3315 2020-03-31 01:12:19Z bird $ */ +/** @file + * kObjCache - Object Cache. + */ + +/* + * Copyright (c) 2007-2012 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#if 0 +# define ELECTRIC_HEAP +# include "../kmk/electric.h" +# include "../kmk/electric.c" +#endif +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.h> +#include <ctype.h> +#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 <process.h> +# include <io.h> +# ifdef __OS2__ +# include <unistd.h> +# include <sys/wait.h> +# include <sys/time.h> +# endif +# if defined(_MSC_VER) +# include <direct.h> + 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 <unistd.h> +# include <sys/wait.h> +# include <sys/time.h> +# ifndef O_BINARY +# define O_BINARY 0 +# endif +# ifndef __sun__ +# include <sys/file.h> /* flock */ +# endif +#endif +#if defined(__WIN__) +# include <Windows.h> +# include "quoted_spawn.h" +#endif +#if defined(__HAIKU__) +# include <posix/sys/file.h> +#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') + { + /* # <digit> "<file>" */ + 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 <cache-file>]\n" + " | [-n|--name <name-in-cache>] [[-d|--cache-dir <cache-dir>]] >\n" + " <-f|--file <local-cache-file>>\n" + " <-t|--target <target-name>>\n" + " [-r|--redir-stdout] [-p|--passthru] [--named-pipe-compile <pipename>]\n" + " --kObjCache-cpp <filename> <preprocessor + args>\n" + " --kObjCache-cc <object> <compiler + args>\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.) + */ + |