diff options
Diffstat (limited to 'contrib/bmake/parse.c')
| -rw-r--r-- | contrib/bmake/parse.c | 1019 |
1 files changed, 544 insertions, 475 deletions
diff --git a/contrib/bmake/parse.c b/contrib/bmake/parse.c index 398c594f523e..ecc77366d2d7 100644 --- a/contrib/bmake/parse.c +++ b/contrib/bmake/parse.c @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.670 2022/04/18 16:09:05 sjg Exp $ */ +/* $NetBSD: parse.c,v 1.753 2025/06/28 22:39:27 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -91,7 +91,7 @@ * Parse_Error Report a parse error, a warning or an informational * message. * - * Parse_MainName Returns a list of the single main target to create. + * Parse_MainName Populate the list of targets to create. */ #include <sys/types.h> @@ -105,27 +105,22 @@ #include <stdint.h> #endif -#ifdef HAVE_MMAP -#include <sys/mman.h> - -#ifndef MAP_COPY -#define MAP_COPY MAP_PRIVATE -#endif -#ifndef MAP_FILE -#define MAP_FILE 0 -#endif -#endif - #include "dir.h" #include "job.h" #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.670 2022/04/18 16:09:05 sjg Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.753 2025/06/28 22:39:27 rillig Exp $"); -/* - * A file being read. - */ +/* Detects a multiple-inclusion guard in a makefile. */ +typedef enum { + GS_START, /* at the beginning of the file */ + GS_COND, /* after the guard condition */ + GS_DONE, /* after the closing .endif */ + GS_NO /* the file is not guarded */ +} GuardState; + +/* A file being parsed. */ typedef struct IncludedFile { FStr name; /* absolute or relative to the cwd */ unsigned lineno; /* 1-based */ @@ -135,14 +130,18 @@ typedef struct IncludedFile { unsigned forBodyReadLines; /* the number of physical lines that have * been read from the file above the body of * the .for loop */ - unsigned int cond_depth; /* 'if' nesting when file opened */ + unsigned condMinDepth; /* depth of nested 'if' directives, at the + * beginning of the file */ bool depending; /* state of doing_depend on EOF */ Buffer buf; /* the file's content or the body of the .for * loop; either empty or ends with '\n' */ - char *buf_ptr; /* next char to be read */ + char *buf_ptr; /* next char to be read from buf */ char *buf_end; /* buf_end[-1] == '\n' */ + GuardState guardState; + Guard *guard; + struct ForLoop *forLoop; } IncludedFile; @@ -164,6 +163,7 @@ typedef enum ParseSpecial { SP_NOMETA, /* .NOMETA */ SP_NOMETA_CMP, /* .NOMETA_CMP */ SP_NOPATH, /* .NOPATH */ + SP_NOREADONLY, /* .NOREADONLY */ SP_NOT, /* Not special */ SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */ SP_NULL, /* .NULL; not mentioned in the manual page */ @@ -172,15 +172,15 @@ typedef enum ParseSpecial { SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */ SP_PATH, /* .PATH or .PATH.suffix */ SP_PHONY, /* .PHONY */ -#ifdef POSIX SP_POSIX, /* .POSIX; not mentioned in the manual page */ -#endif SP_PRECIOUS, /* .PRECIOUS */ + SP_READONLY, /* .READONLY */ SP_SHELL, /* .SHELL */ SP_SILENT, /* .SILENT */ SP_SINGLESHELL, /* .SINGLESHELL; not mentioned in the manual page */ SP_STALE, /* .STALE */ SP_SUFFIXES, /* .SUFFIXES */ + SP_SYSPATH, /* .SYSPATH */ SP_WAIT /* .WAIT */ } ParseSpecial; @@ -223,9 +223,9 @@ static GNodeList *targets; #ifdef CLEANUP /* * All shell commands for all targets, in no particular order and possibly - * with duplicates. Kept in a separate list since the commands from .USE or - * .USEBEFORE nodes are shared with other GNodes, thereby giving up the - * easily understandable ownership over the allocated strings. + * with duplicate values. Kept in a separate list since the commands from + * .USE or .USEBEFORE nodes are shared with other GNodes, thereby giving up + * the easily understandable ownership over the allocated strings. */ static StringList targCmds = LST_INIT; #endif @@ -236,7 +236,7 @@ static StringList targCmds = LST_INIT; */ static GNode *order_pred; -static int parseErrors = 0; +int parseErrors; /* * The include chain of makefiles. At index 0 is the top-level makefile from @@ -253,10 +253,7 @@ SearchPath *defSysIncPath; /* default for sysIncPath */ /* * The parseKeywords table is searched using binary search when deciding - * if a target or source is special. The 'spec' field is the ParseSpecial - * type of the keyword (SP_NOT if the keyword isn't special as a target) while - * the 'op' field is the operator to apply to the list of targets if the - * keyword is used as a source ("0" if the keyword isn't special as a source) + * if a target or source is special. */ static const struct { const char name[17]; @@ -284,6 +281,7 @@ static const struct { { ".NOMETA", SP_NOMETA, OP_NOMETA }, { ".NOMETA_CMP", SP_NOMETA_CMP, OP_NOMETA_CMP }, { ".NOPATH", SP_NOPATH, OP_NOPATH }, + { ".NOREADONLY", SP_NOREADONLY, OP_NONE }, { ".NOTMAIN", SP_ATTRIBUTE, OP_NOTMAIN }, { ".NOTPARALLEL", SP_NOTPARALLEL, OP_NONE }, { ".NO_PARALLEL", SP_NOTPARALLEL, OP_NONE }, @@ -294,16 +292,16 @@ static const struct { { ".PARALLEL", SP_PARALLEL, OP_NONE }, { ".PATH", SP_PATH, OP_NONE }, { ".PHONY", SP_PHONY, OP_PHONY }, -#ifdef POSIX { ".POSIX", SP_POSIX, OP_NONE }, -#endif { ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS }, + { ".READONLY", SP_READONLY, OP_NONE }, { ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE }, { ".SHELL", SP_SHELL, OP_NONE }, { ".SILENT", SP_SILENT, OP_SILENT }, { ".SINGLESHELL", SP_SINGLESHELL, OP_NONE }, { ".STALE", SP_STALE, OP_NONE }, { ".SUFFIXES", SP_SUFFIXES, OP_NONE }, + { ".SYSPATH", SP_SYSPATH, OP_NONE }, { ".USE", SP_ATTRIBUTE, OP_USE }, { ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE }, { ".WAIT", SP_WAIT, OP_NONE }, @@ -311,21 +309,47 @@ static const struct { enum PosixState posix_state = PS_NOT_YET; +static HashTable /* full file name -> Guard */ guards; + + +static List * +Lst_New(void) +{ + List *list = bmake_malloc(sizeof *list); + Lst_Init(list); + return list; +} + +static void +Lst_Free(List *list) +{ + + Lst_Done(list); + free(list); +} + static IncludedFile * GetInclude(size_t i) { + assert(i < includes.len); return Vector_Get(&includes, i); } -/* The file that is currently being read. */ +/* The makefile or the body of a .for loop that is currently being read. */ static IncludedFile * CurFile(void) { return GetInclude(includes.len - 1); } +unsigned +CurFile_CondMinDepth(void) +{ + return CurFile()->condMinDepth; +} + static Buffer -loadfile(const char *path, int fd) +LoadFile(const char *path, int fd) { ssize_t n; Buffer buf; @@ -348,7 +372,7 @@ loadfile(const char *path, int fd) assert(buf.len < buf.cap); n = read(fd, buf.data + buf.len, buf.cap - buf.len); if (n < 0) { - Error("%s: read error: %s", path, strerror(errno)); + Error("%s: %s", path, strerror(errno)); exit(2); /* Not 1 so -q can distinguish error */ } if (n == 0) @@ -358,30 +382,53 @@ loadfile(const char *path, int fd) } assert(buf.len <= buf.cap); - if (!Buf_EndsWith(&buf, '\n')) + if (buf.len > 0 && !Buf_EndsWith(&buf, '\n')) Buf_AddByte(&buf, '\n'); return buf; /* may not be null-terminated */ } +const char * +GetParentStackTrace(void) +{ + static bool initialized; + static const char *parentStackTrace; + + if (!initialized) { + const char *env = getenv("MAKE_STACK_TRACE"); + parentStackTrace = env == NULL ? NULL + : env[0] == '\t' ? bmake_strdup(env) + : strcmp(env, "yes") == 0 ? bmake_strdup("") + : NULL; + initialized = true; + } + return parentStackTrace; +} + /* * Print the current chain of .include and .for directives. In Parse_Fatal * or other functions that already print the location, includingInnermost * would be redundant, but in other cases like Error or Fatal it needs to be * included. */ -void -PrintStackTrace(bool includingInnermost) +char * +GetStackTrace(bool includingInnermost) { + const char *parentStackTrace; + Buffer buffer, *buf = &buffer; const IncludedFile *entries; size_t i, n; + bool hasDetails; - entries = GetInclude(0); + Buf_Init(buf); + hasDetails = EvalStack_Details(buf); n = includes.len; if (n == 0) - return; + goto add_parent_stack_trace; - if (!includingInnermost && entries[n - 1].forLoop == NULL) + entries = GetInclude(0); + if (!includingInnermost && !(hasDetails && n > 1) + && entries[n - 1].forLoop == NULL) n--; /* already in the diagnostic */ for (i = n; i-- > 0;) { @@ -389,19 +436,57 @@ PrintStackTrace(bool includingInnermost) const char *fname = entry->name.str; char dirbuf[MAXPATHLEN + 1]; - if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0) - fname = realpath(fname, dirbuf); + if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0) { + const char *realPath = realpath(fname, dirbuf); + if (realPath != NULL) + fname = realPath; + } if (entry->forLoop != NULL) { char *details = ForLoop_Details(entry->forLoop); - debug_printf("\tin .for loop from %s:%u with %s\n", - fname, entry->forHeadLineno, details); + Buf_AddStr(buf, "\tin .for loop from "); + Buf_AddStr(buf, fname); + Buf_AddStr(buf, ":"); + Buf_AddInt(buf, (int)entry->forHeadLineno); + Buf_AddStr(buf, " with "); + Buf_AddStr(buf, details); + Buf_AddStr(buf, "\n"); free(details); } else if (i + 1 < n && entries[i + 1].forLoop != NULL) { /* entry->lineno is not a useful line number */ - } else - debug_printf("\tin %s:%u\n", fname, entry->lineno); + } else { + Buf_AddStr(buf, "\tin "); + Buf_AddStr(buf, fname); + Buf_AddStr(buf, ":"); + Buf_AddInt(buf, (int)entry->lineno); + Buf_AddStr(buf, "\n"); + } } + +add_parent_stack_trace: + parentStackTrace = GetParentStackTrace(); + if ((makelevel > 0 && (n > 0 || !includingInnermost)) + || parentStackTrace != NULL) { + Buf_AddStr(buf, "\tin "); + Buf_AddStr(buf, progname); + Buf_AddStr(buf, " in directory \""); + Buf_AddStr(buf, curdir); + Buf_AddStr(buf, "\"\n"); + } + + if (parentStackTrace != NULL) + Buf_AddStr(buf, parentStackTrace); + + return Buf_DoneData(buf); +} + +void +PrintStackTrace(bool includingInnermost) +{ + char *stackTrace = GetStackTrace(includingInnermost); + fprintf(stderr, "%s", stackTrace); + fflush(stderr); + free(stackTrace); } /* Check if the current character is escaped on the current line. */ @@ -415,8 +500,8 @@ IsEscaped(const char *line, const char *p) } /* - * Add the filename and lineno to the GNode so that we remember where it - * was first defined. + * Remember the location (filename and lineno) where the last command was + * added or where the node was mentioned in a .depend file. */ static void RememberLocation(GNode *gn) @@ -452,13 +537,25 @@ FindKeyword(const char *str) } void -PrintLocation(FILE *f, bool useVars, const char *fname, unsigned lineno) +PrintLocation(FILE *f, bool useVars, const GNode *gn) { char dirbuf[MAXPATHLEN + 1]; FStr dir, base; + const char *fname; + unsigned lineno; + + if (gn != NULL) { + fname = gn->fname; + lineno = gn->lineno; + } else if (includes.len > 0) { + IncludedFile *curFile = CurFile(); + fname = curFile->name.str; + lineno = curFile->lineno; + } else + return; if (!useVars || fname[0] == '/' || strcmp(fname, "(stdin)") == 0) { - (void)fprintf(f, "\"%s\" line %u: ", fname, lineno); + (void)fprintf(f, "%s:%u: ", fname, lineno); return; } @@ -472,22 +569,21 @@ PrintLocation(FILE *f, bool useVars, const char *fname, unsigned lineno) if (base.str == NULL) base.str = str_basename(fname); - (void)fprintf(f, "\"%s/%s\" line %u: ", dir.str, base.str, lineno); + (void)fprintf(f, "%s/%s:%u: ", dir.str, base.str, lineno); FStr_Done(&base); FStr_Done(&dir); } -static void MAKE_ATTR_PRINTFLIKE(6, 0) -ParseVErrorInternal(FILE *f, bool useVars, const char *fname, unsigned lineno, +static void MAKE_ATTR_PRINTFLIKE(5, 0) +ParseVErrorInternal(FILE *f, bool useVars, const GNode *gn, ParseErrorLevel level, const char *fmt, va_list ap) { static bool fatal_warning_error_printed = false; (void)fprintf(f, "%s: ", progname); - if (fname != NULL) - PrintLocation(f, useVars, fname, lineno); + PrintLocation(f, useVars, gn); if (level == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); @@ -504,31 +600,32 @@ ParseVErrorInternal(FILE *f, bool useVars, const char *fname, unsigned lineno, parseErrors++; } - if (DEBUG(PARSE)) + if (level == PARSE_FATAL || DEBUG(PARSE) + || (gn == NULL && includes.len == 0 /* see PrintLocation */)) PrintStackTrace(false); } -static void MAKE_ATTR_PRINTFLIKE(4, 5) -ParseErrorInternal(const char *fname, unsigned lineno, +static void MAKE_ATTR_PRINTFLIKE(3, 4) +ParseErrorInternal(const GNode *gn, ParseErrorLevel level, const char *fmt, ...) { va_list ap; (void)fflush(stdout); va_start(ap, fmt); - ParseVErrorInternal(stderr, false, fname, lineno, level, fmt, ap); + ParseVErrorInternal(stderr, false, gn, level, fmt, ap); va_end(ap); if (opts.debug_file != stdout && opts.debug_file != stderr) { va_start(ap, fmt); - ParseVErrorInternal(opts.debug_file, false, fname, lineno, + ParseVErrorInternal(opts.debug_file, false, gn, level, fmt, ap); va_end(ap); } } /* - * Print a parse error message, including location information. + * Print a message, including location information. * * If the level is PARSE_FATAL, continue parsing until the end of the * current top-level makefile, then exit (see Parse_File). @@ -539,26 +636,15 @@ void Parse_Error(ParseErrorLevel level, const char *fmt, ...) { va_list ap; - const char *fname; - unsigned lineno; - - if (includes.len == 0) { - fname = NULL; - lineno = 0; - } else { - IncludedFile *curFile = CurFile(); - fname = curFile->name.str; - lineno = curFile->lineno; - } (void)fflush(stdout); va_start(ap, fmt); - ParseVErrorInternal(stderr, true, fname, lineno, level, fmt, ap); + ParseVErrorInternal(stderr, true, NULL, level, fmt, ap); va_end(ap); if (opts.debug_file != stdout && opts.debug_file != stderr) { va_start(ap, fmt); - ParseVErrorInternal(opts.debug_file, true, fname, lineno, + ParseVErrorInternal(opts.debug_file, true, NULL, level, fmt, ap); va_end(ap); } @@ -580,7 +666,7 @@ HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg) return; } - (void)Var_Subst(umsg, SCOPE_CMDLINE, VARE_WANTRES, &xmsg); + xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ Parse_Error(level, "%s", xmsg); @@ -594,8 +680,7 @@ HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg) /* * Add the child to the parent's children, and for non-special targets, vice - * versa. Special targets such as .END do not need to be informed once the - * child target has been made. + * versa. */ static void LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) @@ -606,12 +691,15 @@ LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) Lst_Append(&pgn->children, cgn); pgn->unmade++; - /* Special targets like .END don't need any children. */ + /* + * Special targets like .END do not need to be informed once the child + * target has been made. + */ if (!isSpecial) Lst_Append(&cgn->parents, pgn); if (DEBUG(PARSE)) { - debug_printf("# LinkSource: added child %s - %s\n", + debug_printf("Target \"%s\" depends on \"%s\"\n", pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); @@ -644,11 +732,10 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { /* - * If the node was of the left-hand side of a '::' operator, - * we need to create a new instance of it for the children - * and commands on this dependency line since each of these - * dependency groups has its own attributes and commands, - * separate from the others. + * If the node was on the left-hand side of a '::' operator, + * create a new node for the children and commands on this + * dependency line, since each of these dependency groups has + * its own attributes and commands, separate from the others. * * The new instance is placed on the 'cohorts' list of the * initial one (note the initial one is not on its own @@ -661,13 +748,13 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) * Propagate copied bits to the initial node. They'll be * propagated back to the rest of the cohorts later. */ - gn->type |= op & ~OP_OPMASK; + gn->type |= op & (unsigned)~OP_OPMASK; cohort = Targ_NewInternalNode(gn->name); if (doing_depend) RememberLocation(cohort); /* - * Make the cohort invisible as well to avoid duplicating it + * Make the cohort invisible to avoid duplicating it * into other variables. True, parents of this target won't * tend to do anything with their local variables, but better * safe than sorry. @@ -680,13 +767,9 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) cohort->centurion = gn; gn->unmade_cohorts++; snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", - (unsigned int)gn->unmade_cohorts % 1000000); + (unsigned)gn->unmade_cohorts % 1000000); } else { - /* - * We don't want to nuke any previous flags (whatever they - * were) so we just OR the new operator into the old. - */ - gn->type |= op; + gn->type |= op; /* preserve any previous flags */ } return true; @@ -703,22 +786,22 @@ ApplyDependencyOperator(GNodeType op) } /* - * We add a .WAIT node in the dependency list. After any dynamic dependencies + * Add a .WAIT node in the dependency list. After any dynamic dependencies * (and filename globbing) have happened, it is given a dependency on each * previous child, back until the previous .WAIT node. The next child won't * be scheduled until the .WAIT node is built. * - * We give each .WAIT node a unique name (mainly for diagnostics). + * Give each .WAIT node a unique name (mainly for diagnostics). */ static void ApplyDependencySourceWait(bool isSpecial) { static unsigned wait_number = 0; - char wait_src[16]; + char name[6 + 10 + 1]; GNode *gn; - snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); - gn = Targ_NewInternalNode(wait_src); + snprintf(name, sizeof name, ".WAIT_%u", ++wait_number); + gn = Targ_NewInternalNode(name); if (doing_depend) RememberLocation(gn); gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; @@ -792,9 +875,7 @@ ApplyDependencySourceOrder(const char *src) Targ_PrintNode(gn, 0); } } - /* - * The current source now becomes the predecessor for the next one. - */ + /* The current source now becomes the predecessor for the next one. */ order_pred = gn; } @@ -852,7 +933,8 @@ MaybeUpdateMainTarget(void) for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (GNode_IsMainCandidate(gn)) { - DEBUG1(MAKE, "Setting main node to \"%s\"\n", gn->name); + DEBUG1(MAKE, "Setting main node to \"%s\"\n", + gn->name); mainNode = gn; return; } @@ -860,14 +942,10 @@ MaybeUpdateMainTarget(void) } static void -InvalidLineType(const char *line) +InvalidLineType(const char *line, const char *unexpanded_line) { - if (strncmp(line, "<<<<<<", 6) == 0 || - strncmp(line, ">>>>>>", 6) == 0) - Parse_Error(PARSE_FATAL, - "Makefile appears to contain unresolved CVS/RCS/??? merge conflicts"); - else if (line[0] == '.') { - const char *dirstart = line + 1; + if (unexpanded_line[0] == '.') { + const char *dirstart = unexpanded_line + 1; const char *dirend; cpp_skip_whitespace(&dirstart); dirend = dirstart; @@ -875,40 +953,33 @@ InvalidLineType(const char *line) dirend++; Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", (int)(dirend - dirstart), dirstart); - } else - Parse_Error(PARSE_FATAL, "Invalid line type"); + } else if (strcmp(line, unexpanded_line) == 0) + Parse_Error(PARSE_FATAL, "Invalid line \"%s\"", line); + else + Parse_Error(PARSE_FATAL, + "Invalid line \"%s\", expanded to \"%s\"", + unexpanded_line, line); } static void ParseDependencyTargetWord(char **pp, const char *lstart) { - const char *cp = *pp; + const char *p = *pp; - while (*cp != '\0') { - if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || - *cp == '(') && - !IsEscaped(lstart, cp)) + while (*p != '\0') { + if ((ch_isspace(*p) || *p == '!' || *p == ':' || *p == '(') + && !IsEscaped(lstart, p)) break; - if (*cp == '$') { - /* - * Must be a dynamic source (would have been expanded - * otherwise). - * - * There should be no errors in this, as they would - * have been discovered in the initial Var_Subst and - * we wouldn't be here. - */ - FStr val; - - (void)Var_Parse(&cp, SCOPE_CMDLINE, - VARE_PARSE_ONLY, &val); + if (*p == '$') { + FStr val = Var_Parse(&p, SCOPE_CMDLINE, VARE_PARSE); + /* TODO: handle errors */ FStr_Done(&val); } else - cp++; + p++; } - *pp += cp - *pp; + *pp += p - *pp; } /* @@ -927,6 +998,11 @@ HandleDependencyTargetSpecial(const char *targetName, *inout_paths = Lst_New(); Lst_Append(*inout_paths, &dirSearchPath); break; + case SP_SYSPATH: + if (*inout_paths == NULL) + *inout_paths = Lst_New(); + Lst_Append(*inout_paths, sysIncPath); + break; case SP_MAIN: /* * Allow targets from the command line to override the @@ -987,7 +1063,7 @@ HandleDependencyTargetPath(const char *suffixName, path = Suff_GetPath(suffixName); if (path == NULL) { Parse_Error(PARSE_FATAL, - "Suffix '%s' not defined (yet)", suffixName); + "Suffix \"%s\" not defined (yet)", suffixName); return false; } @@ -1037,27 +1113,34 @@ HandleDependencyTarget(const char *targetName, } static void -HandleDependencyTargetMundane(char *targetName) +HandleSingleDependencyTargetMundane(const char *name) { - StringList targetNames = LST_INIT; + GNode *gn = Suff_IsTransform(name) + ? Suff_AddTransform(name) + : Targ_GetNode(name); + if (doing_depend) + RememberLocation(gn); + + Lst_Append(targets, gn); +} +static void +HandleDependencyTargetMundane(const char *targetName) +{ if (Dir_HasWildcards(targetName)) { + StringList targetNames = LST_INIT; + SearchPath *emptyPath = SearchPath_New(); SearchPath_Expand(emptyPath, targetName, &targetNames); SearchPath_Free(emptyPath); - } else - Lst_Append(&targetNames, targetName); - while (!Lst_IsEmpty(&targetNames)) { - char *targName = Lst_Dequeue(&targetNames); - GNode *gn = Suff_IsTransform(targName) - ? Suff_AddTransform(targName) - : Targ_GetNode(targName); - if (doing_depend) - RememberLocation(gn); - - Lst_Append(targets, gn); - } + while (!Lst_IsEmpty(&targetNames)) { + char *targName = Lst_Dequeue(&targetNames); + HandleSingleDependencyTargetMundane(targName); + free(targName); + } + } else + HandleSingleDependencyTargetMundane(targetName); } static void @@ -1073,8 +1156,12 @@ SkipExtraTargets(char **pp, const char *lstart) warning = true; p++; } - if (warning) - Parse_Error(PARSE_WARNING, "Extra target ignored"); + if (warning) { + const char *start = *pp; + cpp_skip_whitespace(&start); + Parse_Error(PARSE_WARNING, "Extra target \"%.*s\" ignored", + (int)(p - start), start); + } *pp += p - *pp; } @@ -1113,22 +1200,94 @@ ParseDependencyOp(char **pp) { if (**pp == '!') return (*pp)++, OP_FORCE; - if ((*pp)[1] == ':') + if (**pp == ':' && (*pp)[1] == ':') return *pp += 2, OP_DOUBLEDEP; - else + else if (**pp == ':') return (*pp)++, OP_DEPENDS; + else + return OP_NONE; } static void -ClearPaths(SearchPathList *paths) +ClearPaths(ParseSpecial special, SearchPathList *paths) { if (paths != NULL) { SearchPathListNode *ln; for (ln = paths->first; ln != NULL; ln = ln->next) SearchPath_Clear(ln->datum); } + if (special == SP_SYSPATH) + Dir_SetSYSPATH(); + else + Dir_SetPATH(); +} + +static char * +FindInDirOfIncludingFile(const char *file) +{ + char *fullname, *incdir, *slash, *newName; + int i; - Dir_SetPATH(); + fullname = NULL; + incdir = bmake_strdup(CurFile()->name.str); + slash = strrchr(incdir, '/'); + if (slash != NULL) { + *slash = '\0'; + /* + * Now do lexical processing of leading "../" on the + * filename. + */ + for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { + slash = strrchr(incdir + 1, '/'); + if (slash == NULL || strcmp(slash, "/..") == 0) + break; + *slash = '\0'; + } + newName = str_concat3(incdir, "/", file + i); + fullname = Dir_FindFile(newName, parseIncPath); + if (fullname == NULL) + fullname = Dir_FindFile(newName, &dirSearchPath); + free(newName); + } + free(incdir); + return fullname; +} + +static char * +FindInQuotPath(const char *file) +{ + const char *suff; + SearchPath *suffPath; + char *fullname; + + fullname = FindInDirOfIncludingFile(file); + if (fullname == NULL && + (suff = strrchr(file, '.')) != NULL && + (suffPath = Suff_GetPath(suff)) != NULL) + fullname = Dir_FindFile(file, suffPath); + if (fullname == NULL) + fullname = Dir_FindFile(file, parseIncPath); + if (fullname == NULL) + fullname = Dir_FindFile(file, &dirSearchPath); + return fullname; +} + +static bool +SkipGuarded(const char *fullname) +{ + Guard *guard = HashTable_FindValue(&guards, fullname); + if (guard != NULL && guard->kind == GK_VARIABLE + && GNode_ValueDirect(SCOPE_GLOBAL, guard->name) != NULL) + goto skip; + if (guard != NULL && guard->kind == GK_TARGET + && Targ_FindNode(guard->name) != NULL) + goto skip; + return false; + +skip: + DEBUG2(PARSE, "Skipping '%s' because '%s' is defined\n", + fullname, guard->name); + return true; } /* @@ -1146,80 +1305,17 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) { Buffer buf; char *fullname; /* full pathname of file */ - char *newName; - char *slash, *incdir; int fd; - int i; fullname = file[0] == '/' ? bmake_strdup(file) : NULL; - if (fullname == NULL && !isSystem) { - /* - * Include files contained in double-quotes are first searched - * relative to the including file's location. We don't want to - * cd there, of course, so we just tack on the old file's - * leading path components and call Dir_FindFile to see if - * we can locate the file. - */ - - incdir = bmake_strdup(CurFile()->name.str); - slash = strrchr(incdir, '/'); - if (slash != NULL) { - *slash = '\0'; - /* - * Now do lexical processing of leading "../" on the - * filename. - */ - for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { - slash = strrchr(incdir + 1, '/'); - if (slash == NULL || strcmp(slash, "/..") == 0) - break; - *slash = '\0'; - } - newName = str_concat3(incdir, "/", file + i); - fullname = Dir_FindFile(newName, parseIncPath); - if (fullname == NULL) - fullname = Dir_FindFile(newName, - &dirSearchPath); - free(newName); - } - free(incdir); - - if (fullname == NULL) { - /* - * Makefile wasn't found in same directory as included - * makefile. - * - * Search for it first on the -I search path, then on - * the .PATH search path, if not found in a -I - * directory. If we have a suffix-specific path, we - * should use that. - */ - const char *suff; - SearchPath *suffPath = NULL; + if (fullname == NULL && !isSystem) + fullname = FindInQuotPath(file); - if ((suff = strrchr(file, '.')) != NULL) { - suffPath = Suff_GetPath(suff); - if (suffPath != NULL) - fullname = Dir_FindFile(file, suffPath); - } - if (fullname == NULL) { - fullname = Dir_FindFile(file, parseIncPath); - if (fullname == NULL) - fullname = Dir_FindFile(file, - &dirSearchPath); - } - } - } - - /* Looking for a system file or file still not found */ if (fullname == NULL) { - /* - * Look for it on the system path - */ SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath; - fullname = Dir_FindFile(file, path); + fullname = Dir_FindInclude(file, path); } if (fullname == NULL) { @@ -1228,21 +1324,22 @@ IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) return; } - /* Actually open the file... */ - fd = open(fullname, O_RDONLY); - if (fd == -1) { + if (SkipGuarded(fullname)) + goto done; + + if ((fd = open(fullname, O_RDONLY)) == -1) { if (!silent) Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); - free(fullname); - return; + goto done; } - buf = loadfile(fullname, fd); + buf = LoadFile(fullname, fd); (void)close(fd); Parse_PushInput(fullname, 1, 0, buf, NULL); if (depinc) doing_depend = depinc; /* only turn it on */ +done: free(fullname); } @@ -1264,9 +1361,9 @@ HandleDependencySourcesEmpty(ParseSpecial special, SearchPathList *paths) opts.silent = true; break; case SP_PATH: - ClearPaths(paths); + case SP_SYSPATH: + ClearPaths(special, paths); break; -#ifdef POSIX case SP_POSIX: if (posix_state == PS_NOW_OR_NEVER) { /* @@ -1275,10 +1372,10 @@ HandleDependencySourcesEmpty(ParseSpecial special, SearchPathList *paths) * otherwise it is an extension. */ Global_Set("%POSIX", "1003.2"); + posix_state = PS_SET; IncludeFile("posix.mk", true, false, true); } break; -#endif default: break; } @@ -1308,6 +1405,7 @@ ParseDependencySourceSpecial(ParseSpecial special, const char *word, Suff_AddSuffix(word); break; case SP_PATH: + case SP_SYSPATH: AddToPaths(word, paths); break; case SP_INCLUDES: @@ -1316,12 +1414,18 @@ ParseDependencySourceSpecial(ParseSpecial special, const char *word, case SP_LIBS: Suff_AddLib(word); break; + case SP_NOREADONLY: + Var_ReadOnly(word, false); + break; case SP_NULL: Suff_SetNull(word); break; case SP_OBJDIR: Main_SetObjdir(false, "%s", word); break; + case SP_READONLY: + Var_ReadOnly(word, true); + break; default: break; } @@ -1332,7 +1436,7 @@ ApplyDependencyTarget(char *name, char *nameEnd, ParseSpecial *inout_special, GNodeType *inout_targetAttr, SearchPathList **inout_paths) { - char savec = *nameEnd; + char savedNameEnd = *nameEnd; *nameEnd = '\0'; if (!HandleDependencyTarget(name, inout_special, @@ -1342,33 +1446,35 @@ ApplyDependencyTarget(char *name, char *nameEnd, ParseSpecial *inout_special, if (*inout_special == SP_NOT && *name != '\0') HandleDependencyTargetMundane(name); else if (*inout_special == SP_PATH && *name != '.' && *name != '\0') - Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", name); + Parse_Error(PARSE_WARNING, "Extra target \"%s\" ignored", + name); - *nameEnd = savec; + *nameEnd = savedNameEnd; return true; } static bool -ParseDependencyTargets(char **inout_cp, +ParseDependencyTargets(char **pp, const char *lstart, ParseSpecial *inout_special, GNodeType *inout_targetAttr, - SearchPathList **inout_paths) + SearchPathList **inout_paths, + const char *unexpanded_line) { - char *cp = *inout_cp; + char *p = *pp; for (;;) { - char *tgt = cp; + char *tgt = p; - ParseDependencyTargetWord(&cp, lstart); + ParseDependencyTargetWord(&p, lstart); /* * If the word is followed by a left parenthesis, it's the * name of one or more files inside an archive. */ - if (!IsEscaped(lstart, cp) && *cp == '(') { - cp = tgt; - if (!Arch_ParseArchive(&cp, targets, SCOPE_CMDLINE)) { + if (!IsEscaped(lstart, p) && *p == '(') { + p = tgt; + if (!Arch_ParseArchive(&p, targets, SCOPE_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", tgt); @@ -1377,27 +1483,27 @@ ParseDependencyTargets(char **inout_cp, continue; } - if (*cp == '\0') { - InvalidLineType(lstart); + if (*p == '\0') { + InvalidLineType(lstart, unexpanded_line); return false; } - if (!ApplyDependencyTarget(tgt, cp, inout_special, + if (!ApplyDependencyTarget(tgt, p, inout_special, inout_targetAttr, inout_paths)) return false; if (*inout_special != SP_NOT && *inout_special != SP_PATH) - SkipExtraTargets(&cp, lstart); + SkipExtraTargets(&p, lstart); else - pp_skip_whitespace(&cp); + pp_skip_whitespace(&p); - if (*cp == '\0') + if (*p == '\0') break; - if ((*cp == '!' || *cp == ':') && !IsEscaped(lstart, cp)) + if ((*p == '!' || *p == ':') && !IsEscaped(lstart, p)) break; } - *inout_cp = cp; + *pp = p; return true; } @@ -1405,17 +1511,17 @@ static void ParseDependencySourcesSpecial(char *start, ParseSpecial special, SearchPathList *paths) { - char savec; while (*start != '\0') { + char savedEnd; char *end = start; while (*end != '\0' && !ch_isspace(*end)) end++; - savec = *end; + savedEnd = *end; *end = '\0'; ParseDependencySourceSpecial(special, start, paths); - *end = savec; - if (savec != '\0') + *end = savedEnd; + if (savedEnd != '\0') end++; pp_skip_whitespace(&end); start = end; @@ -1444,17 +1550,13 @@ ParseDependencySourcesMundane(char *start, * rest of the line is the value. */ if (Parse_IsVar(start, &var)) { - /* - * Check if this makefile has disabled - * setting local variables. - */ - bool target_vars = GetBooleanExpr( + bool targetVarsEnabled = GetBooleanExpr( "${.MAKE.TARGET_LOCAL_VARIABLES}", true); - if (target_vars) + if (targetVarsEnabled) LinkVarToTargets(&var); free(var.varname); - if (target_vars) + if (targetVarsEnabled) return true; } @@ -1533,10 +1635,16 @@ ParseDependencySources(char *p, GNodeType targetAttr, return; } - /* Now go for the sources. */ - if (special == SP_SUFFIXES || special == SP_PATH || - special == SP_INCLUDES || special == SP_LIBS || - special == SP_NULL || special == SP_OBJDIR) { + switch (special) { + case SP_INCLUDES: + case SP_LIBS: + case SP_NOREADONLY: + case SP_NULL: + case SP_OBJDIR: + case SP_PATH: + case SP_READONLY: + case SP_SUFFIXES: + case SP_SYSPATH: ParseDependencySourcesSpecial(p, special, *inout_paths); if (*inout_paths != NULL) { Lst_Free(*inout_paths); @@ -1544,10 +1652,14 @@ ParseDependencySources(char *p, GNodeType targetAttr, } if (special == SP_PATH) Dir_SetPATH(); - } else { + if (special == SP_SYSPATH) + Dir_SetSYSPATH(); + break; + default: assert(*inout_paths == NULL); if (!ParseDependencySourcesMundane(p, special, targetAttr)) return; + break; } MaybeUpdateMainTarget(); @@ -1574,10 +1686,10 @@ ParseDependencySources(char *p, GNodeType targetAttr, * Transformation rules such as '.c.o' are also handled here, see * Suff_AddTransform. * - * Upon return, the value of the line is unspecified. + * Upon return, the value of expandedLine is unspecified. */ static void -ParseDependency(char *line) +ParseDependency(char *expandedLine, const char *unexpandedLine) { char *p; SearchPathList *paths; /* search paths to alter when parsing a list @@ -1586,20 +1698,27 @@ ParseDependency(char *line) ParseSpecial special; /* in special targets, the children are * linked as children of the parent but not * vice versa */ + GNodeType op; - DEBUG1(PARSE, "ParseDependency(%s)\n", line); - p = line; + DEBUG1(PARSE, "ParseDependency(%s)\n", expandedLine); + p = expandedLine; paths = NULL; targetAttr = OP_NONE; special = SP_NOT; - if (!ParseDependencyTargets(&p, line, &special, &targetAttr, &paths)) + if (!ParseDependencyTargets(&p, expandedLine, &special, &targetAttr, + &paths, unexpandedLine)) goto out; if (!Lst_IsEmpty(targets)) CheckSpecialMundaneMixture(special); - ApplyDependencyOperator(ParseDependencyOp(&p)); + op = ParseDependencyOp(&p); + if (op == OP_NONE) { + InvalidLineType(expandedLine, unexpandedLine); + goto out; + } + ApplyDependencyOperator(op); pp_skip_whitespace(&p); @@ -1639,7 +1758,6 @@ AdjustVarassignOp(const char *name, const char *nameEnd, const char *op, } else { type = VAR_NORMAL; -#ifdef SUNSHCMD while (op > name && ch_isspace(op[-1])) op--; @@ -1647,7 +1765,6 @@ AdjustVarassignOp(const char *name, const char *nameEnd, const char *op, op -= 3; type = VAR_SHELL; } -#endif } va.varname = bmake_strsedup(name, nameEnd < op ? nameEnd : op); @@ -1687,10 +1804,7 @@ Parse_IsVar(const char *p, VarAssign *out_var) nameStart = p; firstSpace = NULL; - /* - * Scan for one of the assignment operators outside a variable - * expansion. - */ + /* Scan for one of the assignment operators outside an expression. */ while (*p != '\0') { char ch = *p++; if (ch == '(' || ch == '{') { @@ -1712,12 +1826,10 @@ Parse_IsVar(const char *p, VarAssign *out_var) if (ch == '\0') return false; -#ifdef SUNSHCMD if (ch == ':' && p[0] == 's' && p[1] == 'h') { p += 2; continue; } -#endif if (ch == '=') eq = p - 1; else if (*p == '=' && @@ -1742,16 +1854,14 @@ Parse_IsVar(const char *p, VarAssign *out_var) * Check for syntax errors such as unclosed expressions or unknown modifiers. */ static void -VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *scope) +VarCheckSyntax(VarAssignOp op, const char *uvalue, GNode *scope) { if (opts.strict) { - if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { - char *expandedValue; - - (void)Var_Subst(uvalue, scope, VARE_PARSE_ONLY, - &expandedValue); + if (op != VAR_SUBST && strchr(uvalue, '$') != NULL) { + char *parsedValue = Var_Subst(uvalue, + scope, VARE_PARSE); /* TODO: handle errors */ - free(expandedValue); + free(parsedValue); } } } @@ -1764,7 +1874,7 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, char *evalue; /* - * make sure that we set the variable the first time to nothing + * Make sure that we set the variable the first time to nothing * so that it gets substituted. * * TODO: Add a test that demonstrates why this code is needed, @@ -1775,7 +1885,8 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, if (!Var_ExistsExpand(scope, name)) Var_SetExpand(scope, name, ""); - (void)Var_Subst(uvalue, scope, VARE_KEEP_DOLLAR_UNDEF, &evalue); + evalue = Var_Subst(uvalue, scope, + VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED); /* TODO: handle errors */ Var_SetExpand(scope, name, evalue); @@ -1792,7 +1903,7 @@ VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, char *output, *error; cmd = FStr_InitRefer(uvalue); - Var_Expand(&cmd, SCOPE_CMDLINE, VARE_UNDEFERR); + Var_Expand(&cmd, SCOPE_CMDLINE, VARE_EVAL); output = Cmd_Exec(cmd.str, &error); Var_SetExpand(scope, name, output); @@ -1844,7 +1955,7 @@ VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, static void VarAssignSpecial(const char *name, const char *avalue) { - if (strcmp(name, MAKEOVERRIDES) == 0) + if (strcmp(name, ".MAKEOVERRIDES") == 0) Main_ExportMAKEFLAGS(false); /* re-export MAKEFLAGS */ else if (strcmp(name, ".CURDIR") == 0) { /* @@ -1854,9 +1965,9 @@ VarAssignSpecial(const char *name, const char *avalue) */ Dir_InitCur(avalue); Dir_SetPATH(); - } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) + } else if (strcmp(name, ".MAKE.JOB.PREFIX") == 0) Job_SetPrefix(); - else if (strcmp(name, MAKE_EXPORTED) == 0) + else if (strcmp(name, ".MAKE.EXPORTED") == 0) Var_ExportVars(avalue); } @@ -1875,7 +1986,7 @@ Parse_Var(VarAssign *var, GNode *scope) /* - * See if the command possibly calls a sub-make by using the variable + * See if the command possibly calls a sub-make by using the * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ static bool @@ -1914,16 +2025,10 @@ MaybeSubMake(const char *cmd) return false; } -/* - * Append the command to the target node. - * - * The node may be marked as a submake node if the command is determined to - * be that. - */ +/* Append the command to the target node. */ static void GNode_AddCommand(GNode *gn, char *cmd) { - /* Add to last (ie current) cohort for :: targets */ if ((gn->type & OP_DOUBLEDEP) && gn->cohorts.last != NULL) gn = gn->cohorts.last->datum; @@ -1934,36 +2039,16 @@ GNode_AddCommand(GNode *gn, char *cmd) gn->type |= OP_SUBMAKE; RememberLocation(gn); } else { -#if 0 - /* XXX: We cannot do this until we fix the tree */ - Lst_Append(&gn->commands, cmd); - Parse_Error(PARSE_WARNING, - "overriding commands for target \"%s\"; " - "previous commands defined at %s: %u ignored", - gn->name, gn->fname, gn->lineno); -#else Parse_Error(PARSE_WARNING, "duplicate script for target \"%s\" ignored", gn->name); - ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING, + ParseErrorInternal(gn, PARSE_WARNING, "using previous script for \"%s\" defined here", gn->name); -#endif } } /* - * Add a directory to the path searched for included makefiles bracketed - * by double-quotes. - */ -void -Parse_AddIncludeDir(const char *dir) -{ - (void)SearchPath_Add(parseIncPath, dir); -} - - -/* * Parse a directive like '.include' or '.-include'. * * .include "user-makefile.mk" @@ -1982,29 +2067,25 @@ ParseInclude(char *directive) if (*p != '"' && *p != '<') { Parse_Error(PARSE_FATAL, - ".include filename must be delimited by '\"' or '<'"); + ".include filename must be delimited by \"\" or <>"); return; } - if (*p++ == '<') - endc = '>'; - else - endc = '"'; + endc = *p++ == '<' ? '>' : '"'; file = FStr_InitRefer(p); - /* Skip to matching delimiter */ while (*p != '\0' && *p != endc) p++; if (*p != endc) { Parse_Error(PARSE_FATAL, - "Unclosed .include filename. '%c' expected", endc); + "Unclosed .include filename, \"%c\" expected", endc); return; } *p = '\0'; - Var_Expand(&file, SCOPE_CMDLINE, VARE_WANTRES); + Var_Expand(&file, SCOPE_CMDLINE, VARE_EVAL); IncludeFile(file.str, endc == '>', directive[0] == 'd', silent); FStr_Done(&file); } @@ -2120,8 +2201,8 @@ VarContainsWord(const char *varname, const char *word) static void TrackInput(const char *name) { - if (!VarContainsWord(MAKE_MAKEFILES, name)) - Global_Append(MAKE_MAKEFILES, name); + if (!VarContainsWord(".MAKE.MAKEFILES", name)) + Global_Append(".MAKE.MAKEFILES", name); } @@ -2137,8 +2218,8 @@ Parse_PushInput(const char *name, unsigned lineno, unsigned readLines, else TrackInput(name); - DEBUG3(PARSE, "Parse_PushInput: %s %s, line %u\n", - forLoop != NULL ? ".for loop in": "file", name, lineno); + DEBUG3(PARSE, "Parse_PushInput: %s%s:%u\n", + forLoop != NULL ? ".for loop in ": "", name, lineno); curFile = Vector_Push(&includes); curFile->name = FStr_InitOwn(bmake_strdup(name)); @@ -2148,6 +2229,8 @@ Parse_PushInput(const char *name, unsigned lineno, unsigned readLines, curFile->forBodyReadLines = readLines; curFile->buf = buf; curFile->depending = doing_depend; /* restore this on EOF */ + curFile->guardState = forLoop == NULL ? GS_START : GS_NO; + curFile->guard = NULL; curFile->forLoop = forLoop; if (forLoop != NULL && !For_NextIteration(forLoop, &curFile->buf)) @@ -2155,7 +2238,7 @@ Parse_PushInput(const char *name, unsigned lineno, unsigned readLines, curFile->buf_ptr = curFile->buf.data; curFile->buf_end = curFile->buf.data + curFile->buf.len; - curFile->cond_depth = Cond_save_depth(); + curFile->condMinDepth = cond_depth; SetParseFile(name); } @@ -2174,7 +2257,6 @@ IsInclude(const char *dir, bool sysv) } -#ifdef SYSVINCLUDE /* Check if the line is a SYSV include directive. */ static bool IsSysVInclude(const char *line) @@ -2202,7 +2284,7 @@ IsSysVInclude(const char *line) static void ParseTraditionalInclude(char *line) { - char *cp; /* current position in file spec */ + char *p; /* current position in file spec */ bool done = false; bool silent = line[0] != 'i'; char *file = line + (silent ? 8 : 7); @@ -2212,16 +2294,16 @@ ParseTraditionalInclude(char *line) pp_skip_whitespace(&file); - (void)Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES, &all_files); + all_files = Var_Subst(file, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ - for (file = all_files; !done; file = cp + 1) { + for (file = all_files; !done; file = p + 1) { /* Skip to end of line or next whitespace */ - for (cp = file; *cp != '\0' && !ch_isspace(*cp); cp++) + for (p = file; *p != '\0' && !ch_isspace(*p); p++) continue; - if (*cp != '\0') - *cp = '\0'; + if (*p != '\0') + *p = '\0'; else done = true; @@ -2230,9 +2312,7 @@ ParseTraditionalInclude(char *line) free(all_files); } -#endif -#ifdef GMAKEEXPORT /* Parse "export <variable>=<value>", and actually export it. */ static void ParseGmakeExport(char *line) @@ -2257,18 +2337,16 @@ ParseGmakeExport(char *line) /* * Expand the value before putting it in the environment. */ - (void)Var_Subst(value, SCOPE_CMDLINE, VARE_WANTRES, &value); + value = Var_Subst(value, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ setenv(variable, value, 1); free(value); } -#endif /* - * Called when EOF is reached in the current file. If we were reading an - * include file or a .for loop, the includes stack is popped and things set - * up to go back to reading the previous file at the previous location. + * When the end of the current file or .for loop is reached, continue reading + * the previous file at the previous location. * * Results: * true to continue parsing, i.e. it had only reached the end of an @@ -2288,11 +2366,20 @@ ParseEOF(void) return true; } - /* - * Ensure the makefile (or .for loop) didn't have mismatched - * conditionals. - */ - Cond_restore_depth(curFile->cond_depth); + Cond_EndFile(); + + if (curFile->guardState == GS_DONE) { + HashEntry *he = HashTable_CreateEntry(&guards, + curFile->name.str, NULL); + if (he->value != NULL) { + free(((Guard *)he->value)->name); + free(he->value); + } + HashEntry_Set(he, curFile->guard); + } else if (curFile->guard != NULL) { + free(curFile->guard->name); + free(curFile->guard); + } FStr_Done(&curFile->name); Buf_Done(&curFile->buf); @@ -2310,7 +2397,7 @@ ParseEOF(void) } curFile = CurFile(); - DEBUG2(PARSE, "ParseEOF: returning to file %s, line %u\n", + DEBUG2(PARSE, "ParseEOF: returning to %s:%u\n", curFile->name.str, curFile->readLines + 1); SetParseFile(curFile->name.str); @@ -2353,7 +2440,7 @@ ParseRawLine(IncludedFile *curFile, char **out_line, char **out_line_end, ch = *p; if (ch == '\0' || (ch == '\\' && p[1] == '\0')) { Parse_Error(PARSE_FATAL, "Zero byte read from file"); - return PRLR_ERROR; + exit(2); } /* Treat next character after '\' as literal. */ @@ -2528,23 +2615,9 @@ SkipIrrelevantBranches(void) { const char *line; - while ((line = ReadLowLevelLine(LK_DOT)) != NULL) { + while ((line = ReadLowLevelLine(LK_DOT)) != NULL) if (Cond_EvalLine(line) == CR_TRUE) return true; - /* - * TODO: Check for typos in .elif directives such as .elsif - * or .elseif. - * - * This check will probably duplicate some of the code in - * ParseLine. Most of the code there cannot apply, only - * ParseVarassign and ParseDependencyLine can, and to prevent - * code duplication, these would need to be called with a - * flag called onlyCheckSyntax. - * - * See directive-elif.mk for details. - */ - } - return false; } @@ -2583,9 +2656,9 @@ ParseForLoop(const char *line) /* * Read an entire line from the input file. * - * Empty lines, .if and .for are completely handled by this function, - * leaving only variable assignments, other directives, dependency lines - * and shell commands to the caller. + * Empty lines, .if and .for are handled by this function, while variable + * assignments, other directives, dependency lines and shell commands are + * handled by the caller. * * Return a line without trailing whitespace, or NULL for EOF. The returned * string will be freed at the end of including the file. @@ -2594,20 +2667,38 @@ static char * ReadHighLevelLine(void) { char *line; + CondResult condResult; for (;;) { + IncludedFile *curFile = CurFile(); line = ReadLowLevelLine(LK_NONEMPTY); if (posix_state == PS_MAYBE_NEXT_LINE) posix_state = PS_NOW_OR_NEVER; - else + else if (posix_state != PS_SET) posix_state = PS_TOO_LATE; if (line == NULL) return NULL; + DEBUG3(PARSE, "Parsing %s:%u: %s\n", + curFile->name.str, curFile->lineno, line); + if (curFile->guardState != GS_NO + && ((curFile->guardState == GS_START && line[0] != '.') + || curFile->guardState == GS_DONE)) + curFile->guardState = GS_NO; if (line[0] != '.') return line; - switch (Cond_EvalLine(line)) { + condResult = Cond_EvalLine(line); + if (curFile->guardState == GS_START) { + Guard *guard; + if (condResult != CR_ERROR + && (guard = Cond_ExtractGuard(line)) != NULL) { + curFile->guardState = GS_COND; + curFile->guard = guard; + } else + curFile->guardState = GS_NO; + } + switch (condResult) { case CR_FALSE: /* May also mean a syntax error. */ if (!SkipIrrelevantBranches()) return NULL; @@ -2649,6 +2740,13 @@ FinishDependencyGroup(void) targets = NULL; } +#ifdef CLEANUP +void Parse_RegisterCommand(char *cmd) +{ + Lst_Append(&targCmds, cmd); +} +#endif + /* Add the command to each target from the current dependency spec. */ static void ParseLine_ShellCommand(const char *p) @@ -2671,12 +2769,28 @@ ParseLine_ShellCommand(const char *p) GNode *gn = ln->datum; GNode_AddCommand(gn, cmd); } -#ifdef CLEANUP - Lst_Append(&targCmds, cmd); -#endif + Parse_RegisterCommand(cmd); } } +static void +HandleBreak(const char *arg) +{ + IncludedFile *curFile = CurFile(); + + if (arg[0] != '\0') + Parse_Error(PARSE_FATAL, + "The .break directive does not take arguments"); + + if (curFile->forLoop != NULL) { + /* pretend we reached EOF */ + For_Break(curFile->forLoop); + cond_depth = CurFile_CondMinDepth(); + ParseEOF(); + } else + Parse_Error(PARSE_FATAL, "break outside of for loop"); +} + /* * See if the line starts with one of the known directives, and if so, handle * the directive. @@ -2684,31 +2798,35 @@ ParseLine_ShellCommand(const char *p) static bool ParseDirective(char *line) { - char *cp = line + 1; + char *p = line + 1; const char *arg; Substring dir; - pp_skip_whitespace(&cp); - if (IsInclude(cp, false)) { - ParseInclude(cp); + pp_skip_whitespace(&p); + if (IsInclude(p, false)) { + ParseInclude(p); return true; } - dir.start = cp; - while (ch_islower(*cp) || *cp == '-') - cp++; - dir.end = cp; + dir.start = p; + while (ch_islower(*p) || *p == '-') + p++; + dir.end = p; - if (*cp != '\0' && !ch_isspace(*cp)) + if (*p != '\0' && !ch_isspace(*p)) return false; - pp_skip_whitespace(&cp); - arg = cp; + pp_skip_whitespace(&p); + arg = p; - if (Substring_Equals(dir, "undef")) + if (Substring_Equals(dir, "break")) + HandleBreak(arg); + else if (Substring_Equals(dir, "undef")) Var_Undef(arg); else if (Substring_Equals(dir, "export")) Var_Export(VEM_PLAIN, arg); + else if (Substring_Equals(dir, "export-all")) + Var_Export(VEM_ALL, arg); else if (Substring_Equals(dir, "export-env")) Var_Export(VEM_ENV, arg); else if (Substring_Equals(dir, "export-literal")) @@ -2742,10 +2860,27 @@ Parse_VarAssign(const char *line, bool finishDependencyGroup, GNode *scope) return true; } +void +Parse_GuardElse(void) +{ + IncludedFile *curFile = CurFile(); + if (cond_depth == curFile->condMinDepth + 1) + curFile->guardState = GS_NO; +} + +void +Parse_GuardEndif(void) +{ + IncludedFile *curFile = CurFile(); + if (cond_depth == curFile->condMinDepth + && curFile->guardState == GS_COND) + curFile->guardState = GS_DONE; +} + static char * FindSemicolon(char *p) { - int level = 0; + int depth = 0; for (; *p != '\0'; p++) { if (*p == '\\' && p[1] != '\0') { @@ -2754,31 +2889,21 @@ FindSemicolon(char *p) } if (*p == '$' && (p[1] == '(' || p[1] == '{')) - level++; - else if (level > 0 && (*p == ')' || *p == '}')) - level--; - else if (level == 0 && *p == ';') + depth++; + else if (depth > 0 && (*p == ')' || *p == '}')) + depth--; + else if (depth == 0 && *p == ';') break; } return p; } -/* - * dependency -> [target...] op [source...] [';' command] - * op -> ':' | '::' | '!' - */ static void ParseDependencyLine(char *line) { - VarEvalMode emode; char *expanded_line; const char *shellcmd = NULL; - /* - * For some reason - probably to make the parser impossible - - * a ';' can be used to separate commands from dependencies. - * Attempt to skip over ';' inside substitution patterns. - */ { char *semicolon = FindSemicolon(line); if (*semicolon != '\0') { @@ -2788,41 +2913,7 @@ ParseDependencyLine(char *line) } } - /* - * We now know it's a dependency line so it needs to have all - * variables expanded before being parsed. - * - * XXX: Ideally the dependency line would first be split into - * its left-hand side, dependency operator and right-hand side, - * and then each side would be expanded on its own. This would - * allow for the left-hand side to allow only defined variables - * and to allow variables on the right-hand side to be undefined - * as well. - * - * Parsing the line first would also prevent that targets - * generated from variable expressions are interpreted as the - * dependency operator, such as in "target${:U\:} middle: source", - * in which the middle is interpreted as a source, not a target. - */ - - /* - * In lint mode, allow undefined variables to appear in dependency - * lines. - * - * Ideally, only the right-hand side would allow undefined variables - * since it is common to have optional dependencies. Having undefined - * variables on the left-hand side is more unusual though. Since - * both sides are expanded in a single pass, there is not much choice - * what to do here. - * - * In normal mode, it does not matter whether undefined variables are - * allowed or not since as of 2020-09-14, Var_Parse does not print - * any parse errors in such a case. It simply returns the special - * empty string var_Error, which cannot be detected in the result of - * Var_Subst. - */ - emode = opts.strict ? VARE_WANTRES : VARE_UNDEFERR; - (void)Var_Subst(line, SCOPE_CMDLINE, emode, &expanded_line); + expanded_line = Var_Subst(line, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ /* Need a fresh list for the target nodes */ @@ -2830,7 +2921,7 @@ ParseDependencyLine(char *line) Lst_Free(targets); targets = Lst_New(); - ParseDependency(expanded_line); + ParseDependency(expanded_line, line); free(expanded_line); if (shellcmd != NULL) @@ -2840,13 +2931,6 @@ ParseDependencyLine(char *line) static void ParseLine(char *line) { - /* - * Lines that begin with '.' can be pretty much anything: - * - directives like '.include' or '.if', - * - suffix rules like '.c.o:', - * - dependencies for filenames that start with '.', - * - variable assignments like '.tmp=value'. - */ if (line[0] == '.' && ParseDirective(line)) return; @@ -2855,26 +2939,16 @@ ParseLine(char *line) return; } -#ifdef SYSVINCLUDE if (IsSysVInclude(line)) { - /* - * It's an S3/S5-style "include". - */ ParseTraditionalInclude(line); return; } -#endif -#ifdef GMAKEEXPORT if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) && strchr(line, ':') == NULL) { - /* - * It's a Gmake "export". - */ ParseGmakeExport(line); return; } -#endif if (Parse_VarAssign(line, true, SCOPE_GLOBAL)) return; @@ -2884,17 +2958,14 @@ ParseLine(char *line) ParseDependencyLine(line); } -/* - * Parse a top-level makefile, incorporating its content into the global - * dependency graph. - */ +/* Interpret a top-level makefile. */ void Parse_File(const char *name, int fd) { char *line; Buffer buf; - buf = loadfile(name, fd != -1 ? fd : STDIN_FILENO); + buf = LoadFile(name, fd != -1 ? fd : STDIN_FILENO); if (fd != -1) (void)close(fd); @@ -2904,11 +2975,8 @@ Parse_File(const char *name, int fd) do { while ((line = ReadHighLevelLine()) != NULL) { - DEBUG2(PARSE, "Parsing line %u: %s\n", - CurFile()->lineno, line); ParseLine(line); } - /* Reached EOF, but it may be just EOF of an include file. */ } while (ParseEOF()); FinishDependencyGroup(); @@ -2932,28 +3000,35 @@ Parse_Init(void) sysIncPath = SearchPath_New(); defSysIncPath = SearchPath_New(); Vector_Init(&includes, sizeof(IncludedFile)); + HashTable_Init(&guards); } +#ifdef CLEANUP /* Clean up the parsing module. */ void Parse_End(void) { -#ifdef CLEANUP - Lst_DoneCall(&targCmds, free); + HashIter hi; + + Lst_DoneFree(&targCmds); assert(targets == NULL); SearchPath_Free(defSysIncPath); SearchPath_Free(sysIncPath); SearchPath_Free(parseIncPath); assert(includes.len == 0); Vector_Done(&includes); -#endif + HashIter_Init(&hi, &guards); + while (HashIter_Next(&hi)) { + Guard *guard = hi.entry->value; + free(guard->name); + free(guard); + } + HashTable_Done(&guards); } +#endif -/* - * Return a list containing the single main target to create. - * If no such target exists, we Punt with an obnoxious error message. - */ +/* Populate the list with the single main target to create, or error out. */ void Parse_MainName(GNodeList *mainList) { @@ -2966,9 +3041,3 @@ Parse_MainName(GNodeList *mainList) Global_Append(".TARGETS", mainNode->name); } - -int -Parse_NumErrors(void) -{ - return parseErrors; -} |
