// BUILD.C -- build routines // // Copyright (c) 1988-1990, Microsoft Corporation. All rights reserved. // // Purpose: // Module contains routines to build targets // // Revision History: // 04-Feb-2000 BTF Ported to Win64 // 18-Jul-1996 GP Support "batch" inference rules // 15-Nov-1993 JR Major speed improvements // 15-Oct-1993 HV Use tchar.h instead of mbstring.h directly, change STR*() to _ftcs*() // 04-Aug-1993 HV Fixed Ikura bug #178. This is a separate bug but ArunJ // just reopen 178 anyway. // 07-Jul-1993 HV Fixed Ikura bug #178: Option K does not give non zero // return code when it should. // 10-May-1993 HV Add include file mbstring.h // Change the str* functions to STR* // 08-Jun-1992 SS Port to DOSX32 // 16-May-1991 SB Truncated History ... rest is now on SLM // 16-May-1991 SB Separated parts that should be in other modules #include "precomp.h" #pragma hdrstop // In order to make comparing dates easier, we cast the FILEINFO buffer to // be of type BOGUS, which has one long where the two unsigneds (for date // and time) are in the original buffer. That way only need a single compare. #ifdef CHECK_RECURSION_LEVEL #define MAXRECLEVEL 10000 // Maximum recursion level #endif // function prototypes for the module // I make as many things static as possible, just to be extra cautious int build(MAKEOBJECT*, UCHAR, time_t *, BOOL, char *, BATCHLIST**); MAKEOBJECT * makeTempObject(char*, UCHAR); void insertSort(DEPLIST **pDepList, DEPLIST *pElement); BOOL nextToken(char**, char**); DEPLIST * createDepList(BUILDBLOCK *pBlock, char *objectName); void addBatch(BATCHLIST **pBatchList, RULELIST *pRule, MAKEOBJECT *pObject, char *dollarLt); int doBatchCommand (BATCHLIST *pBatch); int RecLevel = 0; // Static recursion level. Changed from function // parameter because of treatment of recursive makes. int execBatchList(BATCHLIST *); void freeBatchList(BATCHLIST **); int invokeBuildEx(char *, UCHAR, time_t *, char *, BATCHLIST **); // we have to check for expansion on targets -- firstTarget had to be // expanded earlier to tell whether or not we were dealing w/ a rule, etc., // but targets from commandline might have macros, wildcards in them int processTree() { STRINGLIST *p; char *v; NMHANDLE searchHandle; int status; time_t dateTime; for (p = makeTargets; p; p = makeTargets) { if (_tcspbrk(makeTargets->text, "*?")) { // expand wildcards struct _finddata_t finddata; char *szFilename; if (szFilename = findFirst(makeTargets->text, &finddata, &searchHandle)) { do { v = prependPath(makeTargets->text, szFilename); dateTime = getDateTime(&finddata); status = invokeBuild(v, flags, &dateTime, NULL); FREE(v); if ((status < 0) && (ON(gFlags, F1_QUESTION_STATUS))) { freeStringList(p); // Was not being freed return(-1); } } while (szFilename = findNext(&finddata, searchHandle)); } else { makeError(0, NO_WILDCARD_MATCH, makeTargets->text); } } else { dateTime = 0L; status = invokeBuild(makeTargets->text, flags, &dateTime, NULL); if ((status < 0) && (ON(gFlags, F1_QUESTION_STATUS))) { freeStringList(p); // Was not being freed return(255); // Haituanv: change -1 to 255 to follow the manual } } makeTargets = p->next; FREE_STRINGLIST(p); } return(0); } int invokeBuild( char *target, UCHAR pFlags, time_t *timeVal, char *pFirstDep) { int status = 0; BATCHLIST *pLocalBatchList = NULL; status += invokeBuildEx(target, pFlags, timeVal, pFirstDep, &pLocalBatchList); if (pLocalBatchList) { status += execBatchList (pLocalBatchList); freeBatchList (&pLocalBatchList); } return status; } int invokeBuildEx( char *target, UCHAR pFlags, time_t *timeVal, char *pFirstDep, BATCHLIST **ppBatchList) { MAKEOBJECT *object; BOOL fInmakefile = TRUE; int rc; ++RecLevel; #ifdef CHECK_RECURSION_LEVEL if (RecLevel > MAXRECLEVEL) makeError(0, TOO_MANY_BUILDS_INTERNAL); #endif if (!(object = findTarget(target))) { object = makeTempObject(target, pFlags); fInmakefile = FALSE; } rc = build(object, pFlags, timeVal, fInmakefile, pFirstDep, ppBatchList); --RecLevel; return(rc); } int build( MAKEOBJECT *object, UCHAR parentFlags, time_t *targetTime, BOOL fInmakefile, char *pFirstDep, BATCHLIST **ppBatchList) { STRINGLIST *questionList, *starList, *temp, *implComList; struct _finddata_t finddata; // buffer for getting file times NMHANDLE tHandle; BUILDLIST *b; RULELIST *rule; // pointer to rule found to build target BUILDBLOCK *L_block, *explComBlock; DEPLIST *deps, *deplist; char L_name[MAXNAME]; int rc, status = 0; time_t targTime, // target's time in file system newTargTime, // target's time after being rebuilt tempTime, depTime, // time of dependency just built maxDepTime; // time of most recent dependency built BOOL built; // flag: target built with doublecolon commands time_t *blockTime; // points to dateTime of cmd. block extern char *makeStr; extern UCHAR okToDelete; UCHAR okDel; BATCHLIST *pLocalBatchList; #ifdef DEBUG_ALL printf("Build '%s'\n", object->name); #endif // The first dependent or inbuilt rule dependent is reqd for extmake syntax // handling. If it has a value then it is the dependent corr to the inf rule // otherwise it should be the first dependent specified if (!object) { *targetTime = 0L; return(0); } if (ON(object->flags3, F3_BUILDING_THIS_ONE)) // detect cycles makeError(0, CYCLE_IN_TREE, object->name); if (object->ppBatch) { // we need to build an object that is already placed in a batch list // Go ahead and build the whole batch list BATCHLIST **ppBatch = object->ppBatch; status += execBatchList (*ppBatch); freeBatchList(ppBatch); *targetTime = object->dateTime; return status; } if (ON(object->flags3, F3_ALREADY_BUILT)) { if (ON(parentFlags, F2_DISPLAY_FILE_DATES)) printDate(RecLevel*2, object->name, object->dateTime); *targetTime = object->dateTime; if ( OFF(gFlags, F1_QUESTION_STATUS) && RecLevel == 1 && OFF(object->flags3, F3_OUT_OF_DATE) && findFirst(object->name, &finddata, &tHandle)) { // Display 'up-to-date' msg for built level-1 targets // that exist as files. [VS98 1930] makeMessage(TARGET_UP_TO_DATE, object->name); } return(ON(object->flags3, F3_OUT_OF_DATE)? 1 : 0); } questionList = NULL; starList = NULL; implComList = NULL; explComBlock = NULL; L_block = NULL; targTime = 0L; newTargTime = 0L; tempTime = 0L; depTime = 0L; maxDepTime = 0L; blockTime = NULL; pLocalBatchList = NULL; SET(object->flags3, F3_BUILDING_THIS_ONE); dollarStar = dollarAt = object->name; // For Double Colon case we need the date of target before it's target's are // built. For all other cases the date matters only if dependents are up // to date. NOT TRUE: WE ALSO NEED THE TARGET'S TIME for @? b = object->buildList; if (b && ON(b->buildBlock->flags, F2_DOUBLECOLON) && findFirst(object->name, &finddata, &tHandle)) { targTime = getDateTime(&finddata); } for (; b; b = b->next) { depTime = 0L; L_block = b->buildBlock; if (L_block->dateTime != 0) { // cmd. block already executed targTime = __max(targTime, L_block->dateTime); built = TRUE; continue; // so set targTime and skip this block } blockTime = &L_block->dateTime; deplist = deps = createDepList(L_block, object->name); for (;deps; deps = deps->next) { tempTime = deps->depTime; rc = invokeBuildEx(deps->name, // build the dependent L_block->flags, &tempTime, NULL, &pLocalBatchList); status += rc; if (fOptionK && rc) { MAKEOBJECT *obj = findTarget(deps->name); assert(obj != NULL); if (OFF(obj->flags3, F3_ERROR_IN_CHILD)) { fSlashKStatus = FALSE; makeError(0, BUILD_FAILED_SLASH_K, deps->name); } SET(object->flags3, F3_ERROR_IN_CHILD); } depTime = __max(depTime, tempTime);/*if rebuilt, change time*/ // If target exists then we need it's timestamp to correctly construct $? if (!targTime && OFF(L_block->flags, F2_DOUBLECOLON) && findFirst(object->name, &finddata, &tHandle)) { object->dateTime = targTime = getDateTime(&finddata); } // If dependent was rebuilt, add to $?. [RB] if (ON(object->flags2, F2_FORCE_BUILD) || targTime < tempTime || (fRebuildOnTie && targTime == tempTime) ) { temp = makeNewStrListElement(); temp->text = makeString(deps->name); appendItem(&questionList, temp); } // Always add dependent to $**. Must allocate new item because two // separate lists. [RB] temp = makeNewStrListElement(); temp->text = makeString(deps->name); appendItem(&starList, temp); } if (pLocalBatchList) { // Perform deferred batch builds and free batch list status += execBatchList (pLocalBatchList); freeBatchList(&pLocalBatchList); } // Free dependent list for (deps = deplist; deps ; deps = deplist) { FREE(deps->name); deplist = deps->next; FREE(deps); } // Now, all dependents are built. if (ON(L_block->flags, F2_DOUBLECOLON)) { // do doublecolon commands if (L_block->buildCommands) { dollarQuestion = questionList; dollarStar = dollarAt = object->name; dollarLessThan = dollarDollarAt = NULL; dollarStarStar = starList; if (((fOptionK && OFF(object->flags3, F3_ERROR_IN_CHILD)) || status == 0) && (targTime < depTime) || (fRebuildOnTie && (targTime == depTime)) || (targTime == 0 && depTime == 0) || (!L_block->dependents) ) { // do commands if necessary okDel = okToDelete; okToDelete = TRUE; // if the first dependent is not set use the first one // from the list of dependents pFirstDep = pFirstDep ? pFirstDep : (dollarStarStar ? dollarStarStar->text : NULL); status += doCommands(object->name, L_block->buildCommands, L_block->buildMacros, L_block->flags, pFirstDep); if (OFF(object->flags2, F2_NO_EXECUTE) && findFirst(object->name, &finddata, &tHandle)) newTargTime = getDateTime(&finddata); else if (maxDepTime) newTargTime = maxDepTime; else curTime(&newTargTime); // currentTime // set time for this block L_block->dateTime = newTargTime; built = TRUE; // 5/3/92 BryanT If these both point to the same list, // don't free twice. if (starList != questionList) { freeStringList(starList); freeStringList(questionList); } else { freeStringList(starList); } starList = questionList = NULL; okToDelete = okDel; } if (fOptionK && ON(object->flags3, F3_ERROR_IN_CHILD)) makeError(0, TARGET_ERROR_IN_CHILD, object->name); } } else { // singlecolon; set explComBlock if (L_block->buildCommands) if (explComBlock) makeError(0, TOO_MANY_RULES, object->name); else explComBlock = L_block; maxDepTime = __max(maxDepTime, depTime); } if (ON(L_block->flags, F2_DOUBLECOLON) && !b->next) { CLEAR(object->flags3, F3_BUILDING_THIS_ONE); SET(object->flags3, F3_ALREADY_BUILT); if (status > 0) SET(object->flags3, F3_OUT_OF_DATE); else CLEAR(object->flags3, F3_OUT_OF_DATE); targTime = __max(newTargTime, targTime); object->dateTime = targTime; *targetTime = targTime; return(status); } } dollarLessThan = dollarDollarAt = NULL; if (!(targTime = *targetTime)) { //??????? if (object->dateTime) { targTime = object->dateTime; } else if (findFirst(object->name, &finddata, &tHandle)) { targTime = getDateTime(&finddata); } } if (ON(object->flags2, F2_DISPLAY_FILE_DATES)) { printDate(RecLevel*2, object->name, targTime); } built = FALSE; // look for implicit dependents and use rules to build the target // The order of the if's decides whether the dependent is inferred // from the inference rule or not, even when the explicit command block is // present, currently it is infered (XENIX MAKE compatibility) if (rule = useRule(object, L_name, targTime, &questionList, &starList, &status, &maxDepTime, &pFirstDep) ) { if (!explComBlock) { dollarLessThan = L_name; implComList = rule->buildCommands; } } dollarStar = dollarAt = object->name; dollarQuestion = questionList; dollarStarStar = starList; if (((fOptionK && OFF(object->flags3, F3_ERROR_IN_CHILD)) || status == 0) && (targTime < maxDepTime || (fRebuildOnTie && (targTime == maxDepTime)) || (targTime == 0 && maxDepTime == 0) || ON(object->flags2, F2_FORCE_BUILD) ) ) { okDel = okToDelete; // Yes, can delete while executing commands okToDelete = TRUE; if (explComBlock) { // if the first dependent is not set use the first one from the // list of dependents pFirstDep = pFirstDep ? pFirstDep : (dollarStarStar ? dollarStarStar->text : NULL); status += doCommands(object->name, // do singlecolon commands explComBlock->buildCommands, explComBlock->buildMacros, explComBlock->flags, pFirstDep); } else if (implComList) { if (rule->fBatch && OFF(gFlags, F1_NO_BATCH)) { addBatch(ppBatchList, rule, object, dollarLessThan); } else { status += doCommands(object->name, // do rule's commands implComList, rule->buildMacros, object->flags2, pFirstDep); } } else if (ON(gFlags, F1_TOUCH_TARGETS)) { // for /t with no commands... if (L_block) status += doCommands(object->name, L_block->buildCommands, L_block->buildMacros, L_block->flags, pFirstDep); } // if Option K specified don't exit ... pass on return code else if (!fInmakefile && targTime == 0) { // lose // Haituanv: If option K, then set the return code 'status' // to 1 to indicate a failure. This fixes Ikura bug #178. if (fOptionK) { status = 1; #ifdef DEBUG_OPTION_K printf("DEBUG: %s(%d): status = %d\n", __FILE__, __LINE__, status); #endif } else makeError(0, CANT_MAKE_TARGET, object->name); } okToDelete = okDel; // if cmd exec'ed or has 0 deps then currentTime else max of dep times if (explComBlock || implComList || !dollarStarStar) { curTime(&newTargTime); // Add 2 to ensure the time for this node is >= the time the file // system might have used (mainly useful when running a very fast // machine where the file system doesn't have the resolution of the // system timer... We don't have to to this in the curTime // above since it's only hit when nothing is built anywhere... newTargTime +=2; } else newTargTime = maxDepTime; if (blockTime && explComBlock) // set block's time, if a real cmd. block was executed *blockTime = newTargTime; } else if (OFF(gFlags, F1_QUESTION_STATUS) && RecLevel == 1 && !built && OFF(object->flags3, F3_ERROR_IN_CHILD)) makeMessage(TARGET_UP_TO_DATE, object->name); if (fOptionK && status) { // 4-Aug-1993 Haituanv: Ikura bug #178 again: We should set fSlashKStatus=FALSE // so that main() knows the build failed under /K option. fSlashKStatus = FALSE; if (ON(object->flags3, F3_ERROR_IN_CHILD)) makeError(0, TARGET_ERROR_IN_CHILD, object->name); else if (RecLevel == 1) makeError(0, BUILD_FAILED_SLASH_K, object->name); } if (ON(gFlags, F1_QUESTION_STATUS) && RecLevel == 1 ) { // 5/3/92 BryanT If these both point to the same list, don't // free twice. if (starList!= questionList) { freeStringList(starList); freeStringList(questionList); } else { freeStringList(starList); } return(numCommands ? -1 : 0); } CLEAR(object->flags3, F3_BUILDING_THIS_ONE); if (!object->ppBatch) { SET(object->flags3, F3_ALREADY_BUILT); if (status > 0) SET(object->flags3, F3_OUT_OF_DATE); else CLEAR(object->flags3, F3_OUT_OF_DATE); } targTime = __max(newTargTime, targTime); object->dateTime = targTime; *targetTime = targTime; // 5/3/92 BryanT If these both point to the same list, don't // free twice. if (starList!= questionList) { freeStringList(starList); freeStringList(questionList); } else { freeStringList(starList); } return(status); } DEPLIST * createDepList( BUILDBLOCK *bBlock, char *objectName ) { BOOL again; // flag: wildcards found in dependent name char *s, *t; char *source, *save, *token; char *depName, *depPath; char *tempStr; STRINGLIST *sList, *pMacros; DEPLIST *depList = NULL, *pNew; struct _finddata_t finddata; NMHANDLE searchHandle; pMacros = bBlock->dependentMacros; // expand Macros in Dependent list for (sList = bBlock->dependents; sList; sList = sList->next) { for (s = sList->text; *s && *s != '$'; s = _tcsinc(s)) { if (*s == ESCH) s++; } if (*s) { // set $$@ properly, The dependency macros will then expand right dollarDollarAt = objectName; source = expandMacros(sList->text, &pMacros); } else source = sList->text; save = makeString(source); // build list for all dependents for (t = save; nextToken(&t, &token);) { if (*token == '{') { // path list found for (depName = token; *depName && *depName != '}'; depName = _tcsinc(depName)) { if (*depName == ESCH) { depName++; } } if (*depName) { *depName++ = '\0'; ++token; } } else { depName = token; // If no path list, set token = NULL; // token to null. } // depName is now name of dependency file ... again = FALSE; putDateTime(&finddata, 0L); depPath = makeString(depName); if (_tcspbrk(depName, "*?") || token) { // do wildcards in filename if (tempStr = searchPath(token, depName, &finddata, &searchHandle)){ again = TRUE; FREE(depPath); depName = tempStr; // depName gets actual name depPath = prependPath(depName, getFileName(&finddata)); } // depPath gets full path } // Add to the dependent list do { pNew = MakeNewDepListElement(); // if name contains spaces and has no quotes, // add enclosing quotes around it [DS 14575] if (_tcschr(depPath, ' ') && !_tcschr(depPath, '\"')) { pNew->name = (char *)rallocate (_tcslen(depPath)+3); *(pNew->name) = '\"'; *(pNew->name+1) = '\0'; _tcscat (pNew->name, depPath); _tcscat (pNew->name, "\""); } else { pNew->name = makeString(depPath); } if (!fDescRebuildOrder || findFirst(depPath, &finddata, &searchHandle)) { pNew->depTime = getDateTime(&finddata); } else { pNew->depTime = 0L; } if (fDescRebuildOrder) { insertSort(&depList, pNew); } else { appendItem((STRINGLIST**)&depList, (STRINGLIST*)pNew); } FREE(depPath); } while (again && _tcspbrk(depName, "*?") && // do all wildcards findNext(&finddata, searchHandle) && (depPath = prependPath(depName, getFileName(&finddata))) ); } // One dependent (w/wildcards?) was expanded if (source != sList->text) { FREE(source); } FREE(save); } // Now, all dependents are done ... return(depList); } void insertSort( DEPLIST **pDepList, DEPLIST *pElement ) { time_t item; DEPLIST *pList, *current; item = pElement->depTime; pList = current = *pDepList; for (;pList && item <= pList->depTime; pList = pList->next) { current = pList; } if (current == pList) { *pDepList = pElement; } else { current->next = pElement; pElement->next = pList; } } BOOL nextToken( char **pNext, char **pToken ) { char *s = *pNext; while (*s && WHITESPACE(*s)) { ++s; } if (!*(*pToken = s)) { return(FALSE); } // Token begins here *pToken = s; if (*s == '"') { while (*s && *++s != '"') ; if (!*s) { // lexer possible internal error: missed a quote makeError(0, LEXER_INTERNAL); } if (*++s) { *s++ = '\0'; } *pNext = s; return(TRUE); } else if (*s == '{') { // skip to '}' outside quotes for (;*s;) { s++; if (*s == '"') { s++; // Skip the first quote while (*s && *s++ != '"'); // Skip all including the last quote } if (*s == '}') { break; } } if (!*s) { // lexer possible internal error: missed a brace makeError(0, MISSING_CLOSING_BRACE); } if (*++s == '"') { while (*s && *++s != '"') ; if (!*s) { // lexer possible internal error: missed a quote makeError(0, LEXER_INTERNAL); } if (*++s) { *s++ = '\0'; } *pNext = s; return(TRUE); } } while (*s && !WHITESPACE(*s)) { ++s; } if (*s) { *s++ = '\0'; } *pNext = s; return(TRUE); } void freeStringList( STRINGLIST *list ) { STRINGLIST *temp; while (temp = list) { list = list->next; FREE(temp->text); FREE_STRINGLIST(temp); } } // makeTempObject -- make an object to represent implied dependents // // We add implied dependents to the target table, but use a special struct // that has no pointer to a build list -- they never get removed. // time-space trade-off -- can remove them, but will take more proc time. MAKEOBJECT * makeTempObject( char *target, UCHAR flags ) { MAKEOBJECT *object; unsigned i; object = makeNewObject(); object->name = makeString(target); object->flags2 = flags; object->flags3 = 0; object->dateTime = 0L; object->buildList = NULL; i = hash(target, MAXTARGET, (BOOL) TRUE); prependItem((STRINGLIST**)targetTable+i, (STRINGLIST*)object); return(object); } void addBatch( BATCHLIST **ppBatchList, RULELIST *pRule, MAKEOBJECT *pObject, char *dollarLt ) { STRINGLIST *temp; BATCHLIST *pBatch; BATCHLIST *pBatchPrev = 0; for(pBatch = *ppBatchList; pBatch; pBatch = pBatch->next) { if (pBatch->pRule == pRule && pBatch->flags == pObject->flags2) break; pBatchPrev = pBatch; } if (!pBatch) { pBatch = makeNewBatchListElement(); pBatch->pRule = pRule; pBatch->flags = pObject->flags2; if (pBatchPrev) { pBatchPrev->next = pBatch; } else if(*ppBatchList) { (*ppBatchList)->next = pBatch; } else *ppBatchList = pBatch; } temp = makeNewStrListElement(); temp->text = makeString(pObject->name); appendItem(&pBatch->nameList, temp); temp = makeNewStrListElement(); temp->text = makeString(dollarLessThan); appendItem(&pBatch->dollarLt, temp); assert(!pObject->ppBatch); pObject->ppBatch = ppBatchList; } int doBatchCommand ( BATCHLIST *pBatch ) { size_t cbStr = 0; int rc; char *pchBuf; STRINGLIST *pStrList; RULELIST *pRule = pBatch->pRule; assert (pBatch->dollarLt); assert (pBatch->nameList); // form $< for (pStrList = pBatch->dollarLt; pStrList; pStrList = pStrList->next) { cbStr += _tcslen(pStrList->text) + 1; // allow space for quotes if text contains spaces if (_tcschr(pStrList->text, ' ')) cbStr += 2; } pchBuf = (char *)allocate(cbStr + 1); *pchBuf = 0; for (pStrList = pBatch->dollarLt; pStrList; pStrList = pStrList->next) { BOOL fQuote; // Quote only if not quoted and contains spaces [vs98:8677] fQuote = pStrList->text[0] != '"' && _tcschr(pStrList->text, ' '); if (fQuote) _tcscat(pchBuf, "\""); _tcscat(pchBuf, pStrList->text); _tcscat(pchBuf, fQuote ? "\" " : " "); } dollarLessThan = pchBuf; rc = doCommandsEx(pBatch->nameList, pRule->buildCommands, pRule->buildMacros, pBatch->flags, NULL); if (rc == 0) { STRINGLIST *pName; MAKEOBJECT *pObject; for (pName = pBatch->nameList; pName; pName = pName->next) { pObject = findTarget(pName->text); assert (pObject); SET(pObject->flags3, F3_ALREADY_BUILT); CLEAR(pObject->flags3, F3_OUT_OF_DATE); pObject->ppBatch = 0; } } FREE (pchBuf); return rc; } int execBatchList( BATCHLIST *pBList ) { int status = 0; if (pBList) { BATCHLIST *pBatch; for (pBatch = pBList; pBatch; pBatch=pBatch->next) { status += doBatchCommand (pBatch); } } return status; } void freeBatchList( BATCHLIST **ppBList ) { BATCHLIST *pBatch = *ppBList; while (pBatch) { BATCHLIST *pTmp; free_stringlist(pBatch->nameList); free_stringlist(pBatch->dollarLt); pTmp = pBatch; pBatch = pBatch->next; FREE(pTmp); } *ppBList = NULL; } #ifdef DEBUG_ALL void DumpList( STRINGLIST *pList ) { // STRINGLIST *p; printf("* "); while (pList) { printf(pList->text); printf(","); pList = pList->next; } printf("\n"); } #endif