diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib/kDep.c | 726 |
1 files changed, 726 insertions, 0 deletions
diff --git a/src/lib/kDep.c b/src/lib/kDep.c new file mode 100644 index 0000000..5ee053e --- /dev/null +++ b/src/lib/kDep.c @@ -0,0 +1,726 @@ +/* $Id: kDep.c 3315 2020-03-31 01:12:19Z bird $ */ +/** @file + * kDep - Common Dependency Managemnt Code. + */ + +/* + * Copyright (c) 2004-2013 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Alternatively, the content of this file may be used under the terms of the + * GPL version 2 or later, or LGPL version 2.1 or later. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#ifdef KMK /* For when it gets compiled and linked into kmk. */ +# include "makeint.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <limits.h> +#include <sys/stat.h> +#include "k/kDefs.h" +#include "k/kTypes.h" +#if K_OS == K_OS_WINDOWS +# define USE_WIN_MMAP +# include <io.h> +# include <Windows.h> +# include "nt_fullpath.h" +# include "nt/ntstat.h" +#else +# include <dirent.h> +# include <unistd.h> +# include <stdint.h> +#endif + +#include "kDep.h" + +#ifdef KWORKER +extern int kwFsPathExists(const char *pszPath); +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* For the GNU/hurd weirdo. */ +#if !defined(PATH_MAX) && !defined(_MAX_PATH) +# define PATH_MAX 4096 +#endif + + +/** + * Initializes the dep instance. + * + * @param pThis The dep instance to init. + */ +void depInit(PDEPGLOBALS pThis) +{ + pThis->pDeps = NULL; +} + + +/** + * Cleans up the dep instance (frees resources). + * + * @param pThis The dep instance to cleanup. + */ +void depCleanup(PDEPGLOBALS pThis) +{ + PDEP pDep = pThis->pDeps; + pThis->pDeps = NULL; + while (pDep) + { + PDEP pFree = pDep; + pDep = pDep->pNext; + free(pFree); + } +} + + +/** + * Corrects all slashes to unix slashes. + * + * @returns pszFilename. + * @param pszFilename The filename to correct. + */ +static char *fixslash(char *pszFilename) +{ + char *psz = pszFilename; + while ((psz = strchr(psz, '\\')) != NULL) + *psz++ = '/'; + return pszFilename; +} + + +#if K_OS == K_OS_OS2 + +/** + * Corrects the case of a path. + * + * @param pszPath Pointer to the path, both input and output. + * The buffer must be able to hold one more byte than the string length. + */ +static void fixcase(char *pszFilename) +{ + return; +} + +#elif K_OS != K_OS_WINDOWS + +/** + * Corrects the case of a path. + * + * @param pszPath Pointer to the path, both input and output. + */ +static void fixcase(char *pszFilename) +{ + char *psz; + + /* + * Skip the root. + */ + psz = pszFilename; + while (*psz == '/') + psz++; + + /* + * Iterate all the components. + */ + while (*psz) + { + char chSlash; + struct stat s; + char *pszStart = psz; + + /* + * Find the next slash (or end of string) and terminate the string there. + */ + while (*psz != '/' && *psz) + psz++; + chSlash = *psz; + *psz = '\0'; + + /* + * Does this part exist? + * If not we'll enumerate the directory and search for an case-insensitive match. + */ + if (stat(pszFilename, &s)) + { + struct dirent *pEntry; + DIR *pDir; + if (pszStart == pszFilename) + pDir = opendir(*pszFilename ? pszFilename : "."); + else + { + pszStart[-1] = '\0'; + pDir = opendir(pszFilename); + pszStart[-1] = '/'; + } + if (!pDir) + { + *psz = chSlash; + break; /* giving up, if we fail to open the directory. */ + } + + while ((pEntry = readdir(pDir)) != NULL) + { + if (!strcasecmp(pEntry->d_name, pszStart)) + { + strcpy(pszStart, pEntry->d_name); + break; + } + } + closedir(pDir); + if (!pEntry) + { + *psz = chSlash; + break; /* giving up if not found. */ + } + } + + /* restore the slash and press on. */ + *psz = chSlash; + while (*psz == '/') + psz++; + } + + return; +} + +#endif /* !OS/2 && !Windows */ + + +/** + * 'Optimizes' and corrects the dependencies. + */ +void depOptimize(PDEPGLOBALS pThis, int fFixCase, int fQuiet, const char *pszIgnoredExt) +{ + /* + * Walk the list correct the names and re-insert them. + */ + size_t cchIgnoredExt = pszIgnoredExt ? strlen(pszIgnoredExt) : 0; + PDEP pDepOrg = pThis->pDeps; + PDEP pDep = pThis->pDeps; + pThis->pDeps = NULL; + for (; pDep; pDep = pDep->pNext) + { +#ifndef PATH_MAX + char szFilename[_MAX_PATH + 1]; +#else + char szFilename[PATH_MAX + 1]; +#endif + char *pszFilename; +#if !defined(KWORKER) && !defined(KMK) + struct stat s; +#endif + + /* + * Skip some fictive names like <built-in> and <command line>. + */ + if ( pDep->szFilename[0] == '<' + && pDep->szFilename[pDep->cchFilename - 1] == '>') + continue; + pszFilename = pDep->szFilename; + + /* + * Skip pszIgnoredExt if given. + */ + if ( pszIgnoredExt + && pDep->cchFilename > cchIgnoredExt + && memcmp(&pDep->szFilename[pDep->cchFilename - cchIgnoredExt], pszIgnoredExt, cchIgnoredExt) == 0) + continue; + +#if K_OS != K_OS_OS2 && K_OS != K_OS_WINDOWS + /* + * Skip any drive letters from compilers running in wine. + */ + if (pszFilename[1] == ':') + pszFilename += 2; +#endif + + /* + * The microsoft compilers are notoriously screwing up the casing. + * This will screw up kmk (/ GNU Make). + */ + if (fFixCase) + { +#if K_OS == K_OS_WINDOWS + nt_fullpath_cached(pszFilename, szFilename, sizeof(szFilename)); + fixslash(szFilename); +#else + strcpy(szFilename, pszFilename); + fixslash(szFilename); + fixcase(szFilename); +#endif + pszFilename = szFilename; + } + + /* + * Check that the file exists before we start depending on it. + */ + errno = 0; +#ifdef KWORKER + if (!kwFsPathExists(pszFilename)) +#elif defined(KMK) + if (!file_exists_p(pszFilename)) +#elif K_OS == K_OS_WINDOWS + if (birdStatModTimeOnly(pszFilename, &s.st_mtim, 1 /*fFollowLink*/) != 0) +#else + if (stat(pszFilename, &s) != 0) +#endif + { + if ( !fQuiet + || errno != ENOENT + || ( pszFilename[0] != '/' + && pszFilename[0] != '\\' + && ( !isalpha(pszFilename[0]) + || pszFilename[1] != ':' + || ( pszFilename[2] != '/' + && pszFilename[2] != '\\'))) + ) + fprintf(stderr, "kDep: Skipping '%s' - %s!\n", pszFilename, strerror(errno)); + continue; + } + + /* + * Insert the corrected dependency. + */ + depAdd(pThis, pszFilename, strlen(pszFilename)); + } + + /* + * Free the old ones. + */ + while (pDepOrg) + { + pDep = pDepOrg; + pDepOrg = pDepOrg->pNext; + free(pDep); + } +} + + +/** + * Write a filename that contains characters that needs escaping. + * + * @param pOutput The output stream. + * @param pszFile The filename. + * @param cchFile The length of the filename. + * @param fDep Whether this is for a dependency file or a target file. + */ +int depNeedsEscaping(const char *pszFile, size_t cchFile, int fDependency) +{ + return memchr(pszFile, ' ', cchFile) != NULL + || memchr(pszFile, '\t', cchFile) != NULL + || memchr(pszFile, '#', cchFile) != NULL + || memchr(pszFile, '=', cchFile) != NULL + || memchr(pszFile, ';', cchFile) != NULL + || memchr(pszFile, '$', cchFile) != NULL + || memchr(pszFile, fDependency ? '|' : '%', cchFile) != NULL; +} + + +/** + * Write a filename that contains characters that needs escaping. + * + * @param pOutput The output stream. + * @param pszFile The filename. + * @param cchFile The length of the filename. + * @param fDep Whether this is for a dependency file or a target file. + */ +void depEscapedWrite(FILE *pOutput, const char *pszFile, size_t cchFile, int fDepenency) +{ + size_t cchWritten = 0; + size_t off = 0; + while (off < cchFile) + { + char const ch = pszFile[off]; + switch (ch) + { + default: + off++; + break; + + /* + * Escaped by slash, but any preceeding slashes must be escaped too. + * A couple of characters are only escaped on one side of the ':'. + */ + case '%': /* target side only */ + case '|': /* dependency side only */ + if (ch != (fDepenency ? '|' : '%')) + { + off++; + break; + } + /* fall thru */ + case ' ': + case '\t': + case '#': + case '=': /** @todo buggy GNU make handling */ + case ';': /** @todo buggy GNU make handling */ + if (cchWritten < off) + fwrite(&pszFile[cchWritten], off - cchWritten, 1, pOutput); + if (off == 0 || pszFile[off - 1] != '\\') + { + fputc('\\', pOutput); + cchWritten = off; /* We write the escaped character with the next bunch. */ + } + else + { + size_t cchSlashes = 1; + while (cchSlashes < off && pszFile[off - cchSlashes - 1] == '\\') + cchSlashes++; + fwrite(&pszFile[off - cchSlashes], cchSlashes, 1, pOutput); + cchWritten = off - 1; /* Write a preceeding slash and the escaped character with the next bunch. */ + } + off += 1; + break; + + /* + * Escaped by doubling it. + * Implemented by including in the pending writeout job as well as in the next one. + */ + case '$': + fwrite(&pszFile[cchWritten], off - cchWritten + 1, 1, pOutput); + cchWritten = off++; /* write it again the next time */ + break; + } + } + + /* Remainder: */ + if (cchWritten < cchFile) + fwrite(&pszFile[cchWritten], cchFile - cchWritten, 1, pOutput); +} + + +/** + * Escapes all trailing trailing slashes in a filename that ends with such. + */ +static void depPrintTrailngSlashEscape(FILE *pOutput, const char *pszFilename, size_t cchFilename) +{ + size_t cchSlashes = 1; + while (cchSlashes < cchFilename && pszFilename[cchFilename - cchSlashes - 1] == '\\') + cchSlashes++; + fwrite(&pszFilename[cchFilename - cchSlashes], cchSlashes, 1, pOutput); +} + + +/** + * Prints the dependency chain. + * + * @param pThis The 'dep' instance. + * @param pOutput Output stream. + */ +void depPrintChain(PDEPGLOBALS pThis, FILE *pOutput) +{ + static char const g_szEntryText[] = " \\\n\t"; + static char const g_szTailText[] = "\n\n"; + static char const g_szTailSlashText[] = " \\\n\n"; + PDEP pDep; + for (pDep = pThis->pDeps; pDep; pDep = pDep->pNext) + { + fwrite(g_szEntryText, sizeof(g_szEntryText) - 1, 1, pOutput); + if (!pDep->fNeedsEscaping) + fwrite(pDep->szFilename, pDep->cchFilename, 1, pOutput); + else + depEscapedWrite(pOutput, pDep->szFilename, pDep->cchFilename, 1 /*fDependency*/); + if (pDep->fTrailingSlash) + { /* Escape only if more dependencies. If last, we must add a line continuation or it won't work. */ + if (pDep->pNext) + depPrintTrailngSlashEscape(pOutput, pDep->szFilename, pDep->cchFilename); + else + { + fwrite(g_szTailSlashText, sizeof(g_szTailSlashText), 1, pOutput); + return; + } + } + } + + fwrite(g_szTailText, sizeof(g_szTailText) - 1, 1, pOutput); +} + + +/** + * Prints the dependency chain with a preceeding target. + * + * @param pThis The 'dep' instance. + * @param pOutput Output stream. + * @param pszTarget The target filename. + * @param fEscapeTarget Whether to consider escaping the target. + */ +void depPrintTargetWithDeps(PDEPGLOBALS pThis, FILE *pOutput, const char *pszTarget, int fEscapeTarget) +{ + static char const g_szSeparator[] = ":"; + size_t const cchTarget = strlen(pszTarget); + if (!fEscapeTarget || !depNeedsEscaping(pszTarget, cchTarget, 0 /*fDependency*/)) + fwrite(pszTarget, cchTarget, 1, pOutput); + else + depEscapedWrite(pOutput, pszTarget, cchTarget, 0 /*fDependency*/); + + if (cchTarget == 0 || pszTarget[cchTarget - 1] != '\\') + { /* likely */ } + else + depPrintTrailngSlashEscape(pOutput, pszTarget, cchTarget); + fwrite(g_szSeparator, sizeof(g_szSeparator) - 1, 1, pOutput); + + depPrintChain(pThis, pOutput); +} + + +/** + * Prints empty dependency stubs for all dependencies. + * + * @param pThis The 'dep' instance. + * @param pOutput Output stream. + */ +void depPrintStubs(PDEPGLOBALS pThis, FILE *pOutput) +{ + static char g_szTailText[] = ":\n\n"; + PDEP pDep; + for (pDep = pThis->pDeps; pDep; pDep = pDep->pNext) + { + if (!pDep->fNeedsEscaping && memchr(pDep->szFilename, '%', pDep->cchFilename) == 0) + fwrite(pDep->szFilename, pDep->cchFilename, 1, pOutput); + else + depEscapedWrite(pOutput, pDep->szFilename, pDep->cchFilename, 0 /*fDependency*/); + + if (pDep->cchFilename == 0 || !pDep->fTrailingSlash) + { /* likely */ } + else + depPrintTrailngSlashEscape(pOutput, pDep->szFilename, pDep->cchFilename); + fwrite(g_szTailText, sizeof(g_szTailText) - 1, 1, pOutput); + } +} + + +/* sdbm: + This algorithm was created for sdbm (a public-domain reimplementation of + ndbm) database library. it was found to do well in scrambling bits, + causing better distribution of the keys and fewer splits. it also happens + to be a good general hashing function with good distribution. the actual + function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below + is the faster version used in gawk. [there is even a faster, duff-device + version] the magic constant 65599 was picked out of thin air while + experimenting with different constants, and turns out to be a prime. + this is one of the algorithms used in berkeley db (see sleepycat) and + elsewhere. */ +static unsigned sdbm(const char *str, size_t size) +{ + unsigned hash = 0; + int c; + + while (size-- > 0 && (c = *(unsigned const char *)str++)) + hash = c + (hash << 6) + (hash << 16) - hash; + + return hash; +} + + +/** + * Adds a dependency. + * + * @returns Pointer to the allocated dependency. + * @param pThis The 'dep' instance. + * @param pszFilename The filename. Does not need to be terminated. + * @param cchFilename The length of the filename. + */ +PDEP depAdd(PDEPGLOBALS pThis, const char *pszFilename, size_t cchFilename) +{ + unsigned uHash = sdbm(pszFilename, cchFilename); + PDEP pDep; + PDEP pDepPrev; + + /* + * Check if we've already got this one. + */ + pDepPrev = NULL; + for (pDep = pThis->pDeps; pDep; pDepPrev = pDep, pDep = pDep->pNext) + if ( pDep->uHash == uHash + && pDep->cchFilename == cchFilename + && !memcmp(pDep->szFilename, pszFilename, cchFilename)) + return pDep; + + /* + * Add it. + */ + pDep = (PDEP)malloc(sizeof(*pDep) + cchFilename); + if (!pDep) + { + fprintf(stderr, "\nOut of memory! (requested %lx bytes)\n\n", + (unsigned long)(sizeof(*pDep) + cchFilename)); + exit(1); + } + + pDep->cchFilename = cchFilename; + memcpy(pDep->szFilename, pszFilename, cchFilename); + pDep->szFilename[cchFilename] = '\0'; + pDep->fNeedsEscaping = depNeedsEscaping(pszFilename, cchFilename, 1 /*fDependency*/); + pDep->fTrailingSlash = cchFilename > 0 && pszFilename[cchFilename - 1] == '\\'; + pDep->uHash = uHash; + + if (pDepPrev) + { + pDep->pNext = pDepPrev->pNext; + pDepPrev->pNext = pDep; + } + else + { + pDep->pNext = pThis->pDeps; + pThis->pDeps = pDep; + } + return pDep; +} + + +/** + * Performs a hexdump. + */ +void depHexDump(const KU8 *pb, size_t cb, size_t offBase) +{ + const unsigned cchWidth = 16; + size_t off = 0; + while (off < cb) + { + unsigned i; + printf("%s%0*lx %04lx:", off ? "\n" : "", (int)sizeof(pb) * 2, + (unsigned long)offBase + (unsigned long)off, (unsigned long)off); + for (i = 0; i < cchWidth && off + i < cb ; i++) + printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]); + + while (i++ < cchWidth) + printf(" "); + printf(" "); + + for (i = 0; i < cchWidth && off + i < cb; i++) + { + const KU8 u8 = pb[i]; + printf("%c", u8 < 127 && u8 >= 32 ? u8 : '.'); + } + off += cchWidth; + pb += cchWidth; + } + printf("\n"); +} + + +/** + * Reads the file specified by the pInput file stream into memory. + * + * @returns The address of the memory mapping on success. This must be + * freed by calling depFreeFileMemory. + * + * @param pInput The file stream to load or map into memory. + * @param pcbFile Where to return the mapping (file) size. + * @param ppvOpaque Opaque data when mapping, otherwise NULL. + */ +void *depReadFileIntoMemory(FILE *pInput, size_t *pcbFile, void **ppvOpaque) +{ + void *pvFile; + long cbFile; + + /* + * Figure out file size. + */ +#if defined(_MSC_VER) + cbFile = _filelength(fileno(pInput)); + if (cbFile < 0) +#else + if ( fseek(pInput, 0, SEEK_END) < 0 + || (cbFile = ftell(pInput)) < 0 + || fseek(pInput, 0, SEEK_SET)) +#endif + { + fprintf(stderr, "kDep: error: Failed to determin file size.\n"); + return NULL; + } + if (pcbFile) + *pcbFile = cbFile; + + /* + * Try mmap first. + */ +#ifdef USE_WIN_MMAP + { + HANDLE hMapObj = CreateFileMapping((HANDLE)_get_osfhandle(fileno(pInput)), + NULL, PAGE_READONLY, 0, cbFile, NULL); + if (hMapObj != NULL) + { + pvFile = MapViewOfFile(hMapObj, FILE_MAP_READ, 0, 0, cbFile); + if (pvFile) + { + *ppvOpaque = hMapObj; + return pvFile; + } + fprintf(stderr, "kDep: warning: MapViewOfFile failed, %d.\n", GetLastError()); + CloseHandle(hMapObj); + } + else + fprintf(stderr, "kDep: warning: CreateFileMapping failed, %d.\n", GetLastError()); + } + +#endif + + /* + * Allocate memory and read the file. + */ + pvFile = malloc(cbFile + 1); + if (pvFile) + { + if (fread(pvFile, cbFile, 1, pInput)) + { + ((KU8 *)pvFile)[cbFile] = '\0'; + *ppvOpaque = NULL; + return pvFile; + } + fprintf(stderr, "kDep: error: Failed to read %ld bytes.\n", cbFile); + free(pvFile); + } + else + fprintf(stderr, "kDep: error: Failed to allocate %ld bytes (file mapping).\n", cbFile); + return NULL; +} + + +/** + * Free resources allocated by depReadFileIntoMemory. + * + * @param pvFile The address of the memory mapping. + * @param pvOpaque The opaque value returned together with the mapping. + */ +void depFreeFileMemory(void *pvFile, void *pvOpaque) +{ +#if defined(USE_WIN_MMAP) + if (pvOpaque) + { + UnmapViewOfFile(pvFile); + CloseHandle(pvOpaque); + return; + } +#endif + free(pvFile); +} + |