diff options
Diffstat (limited to 'parse.c')
-rw-r--r-- | parse.c | 3754 |
1 files changed, 1812 insertions, 1942 deletions
@@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.420 2020/11/01 00:24:57 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,71 +68,41 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $"; -#else -#include <sys/cdefs.h> -#ifndef lint -#if 0 -static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * parse.c -- - * Functions to parse a makefile. - * - * One function, Parse_Init, must be called before any functions - * in this module are used. After that, the function Parse_File is the - * main entry point and controls most of the other functions in this - * module. +/* + * Parsing of makefiles. * - * Most important structures are kept in Lsts. Directories for - * the .include "..." function are kept in the 'parseIncPath' Lst, while - * those for the .include <...> are kept in the 'sysIncPath' Lst. The - * targets currently being defined are kept in the 'targets' Lst. + * Parse_File is the main entry point and controls most of the other + * functions in this module. * - * The variables 'fname' and 'lineno' are used to track the name - * of the current file and the line number in that file so that error - * messages can be more meaningful. + * The directories for the .include "..." directive are kept in + * 'parseIncPath', while those for .include <...> are kept in 'sysIncPath'. + * The targets currently being defined are kept in 'targets'. * * Interface: - * Parse_Init Initialization function which must be - * called before anything else in this module - * is used. + * Parse_Init Initialize the module + * + * Parse_End Clean up the module * - * Parse_End Cleanup the module + * Parse_File Parse a top-level makefile. Included files are + * handled by Parse_include_file though. * - * Parse_File Function used to parse a makefile. It must - * be given the name of the file, which should - * already have been opened, and a function - * to call to read a character from the file. + * Parse_IsVar Return TRUE if the given line is a variable + * assignment. Used by MainParseArgs to determine if + * an argument is a target or a variable assignment. + * Used internally for pretty much the same thing. * - * Parse_IsVar Returns TRUE if the given line is a - * variable assignment. Used by MainParseArgs - * to determine if an argument is a target - * or a variable assignment. Used internally - * for pretty much the same thing... + * Parse_Error Report a parse error, a warning or an informational + * message. * - * Parse_Error Function called when an error occurs in - * parsing. Used by the variable and - * conditional modules. - * Parse_MainName Returns a Lst of the main target to create. + * Parse_MainName Returns a list of the main target to create. */ #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <stdarg.h> -#include <stdio.h> #include "make.h" -#include "dir.h" -#include "job.h" -#include "pathnames.h" #ifdef HAVE_STDINT_H #include <stdint.h> @@ -149,82 +119,79 @@ __RCSID("$NetBSD: parse.c,v 1.275 2020/09/01 17:38:26 rillig Exp $"); #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.420 2020/11/01 00:24:57 rillig Exp $"); + /* types and constants */ /* * Structure for a file being read ("included file") */ typedef struct IFile { - char *fname; /* name of file */ - int lineno; /* current line number in file */ - int first_lineno; /* line number of start of text */ - int cond_depth; /* 'if' nesting when file opened */ - Boolean depending; /* state of doing_depend on EOF */ - char *P_str; /* point to base of string buffer */ - char *P_ptr; /* point to next char of string buffer */ - char *P_end; /* point to the end of string buffer */ - char *(*nextbuf)(void *, size_t *); /* Function to get more data */ - void *nextbuf_arg; /* Opaque arg for nextbuf() */ - struct loadedfile *lf; /* loadedfile object, if any */ + char *fname; /* name of file */ + Boolean fromForLoop; /* simulated .include by the .for loop */ + int lineno; /* current line number in file */ + int first_lineno; /* line number of start of text */ + unsigned int cond_depth; /* 'if' nesting when file opened */ + Boolean depending; /* state of doing_depend on EOF */ + + /* The buffer from which the file's content is read. */ + char *buf_freeIt; + char *buf_ptr; /* next char to be read */ + char *buf_end; + + char *(*nextbuf)(void *, size_t *); /* Function to get more data */ + void *nextbuf_arg; /* Opaque arg for nextbuf() */ + struct loadedfile *lf; /* loadedfile object, if any */ } IFile; - -/* - * These values are returned by ParseEOF to tell Parse_File whether to - * CONTINUE parsing, i.e. it had only reached the end of an include file, - * or if it's DONE. - */ -#define CONTINUE 1 -#define DONE 0 - /* * Tokens for target attributes */ -typedef enum { - Begin, /* .BEGIN */ - Default, /* .DEFAULT */ - DeleteOnError, /* .DELETE_ON_ERROR */ - End, /* .END */ - dotError, /* .ERROR */ - Ignore, /* .IGNORE */ - Includes, /* .INCLUDES */ - Interrupt, /* .INTERRUPT */ - Libs, /* .LIBS */ - Meta, /* .META */ - MFlags, /* .MFLAGS or .MAKEFLAGS */ - Main, /* .MAIN and we don't have anything user-specified to - * make */ - NoExport, /* .NOEXPORT */ - NoMeta, /* .NOMETA */ - NoMetaCmp, /* .NOMETA_CMP */ - NoPath, /* .NOPATH */ - Not, /* Not special */ - NotParallel, /* .NOTPARALLEL */ - Null, /* .NULL */ - ExObjdir, /* .OBJDIR */ - Order, /* .ORDER */ - Parallel, /* .PARALLEL */ - ExPath, /* .PATH */ - Phony, /* .PHONY */ +typedef enum ParseSpecial { + SP_ATTRIBUTE, /* Generic attribute */ + SP_BEGIN, /* .BEGIN */ + SP_DEFAULT, /* .DEFAULT */ + SP_DELETE_ON_ERROR, /* .DELETE_ON_ERROR */ + SP_END, /* .END */ + SP_ERROR, /* .ERROR */ + SP_IGNORE, /* .IGNORE */ + SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */ + SP_INTERRUPT, /* .INTERRUPT */ + SP_LIBS, /* .LIBS; not mentioned in the manual page */ + SP_MAIN, /* .MAIN and we don't have anything user-specified to + * make */ + SP_META, /* .META */ + SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */ + SP_NOMETA, /* .NOMETA */ + SP_NOMETA_CMP, /* .NOMETA_CMP */ + SP_NOPATH, /* .NOPATH */ + SP_NOT, /* Not special */ + SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */ + SP_NULL, /* .NULL; not mentioned in the manual page */ + SP_OBJDIR, /* .OBJDIR */ + SP_ORDER, /* .ORDER */ + SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */ + SP_PATH, /* .PATH or .PATH.suffix */ + SP_PHONY, /* .PHONY */ #ifdef POSIX - Posix, /* .POSIX */ + SP_POSIX, /* .POSIX; not mentioned in the manual page */ #endif - Precious, /* .PRECIOUS */ - ExShell, /* .SHELL */ - Silent, /* .SILENT */ - SingleShell, /* .SINGLESHELL */ - Stale, /* .STALE */ - Suffixes, /* .SUFFIXES */ - Wait, /* .WAIT */ - Attribute /* Generic attribute */ + SP_PRECIOUS, /* .PRECIOUS */ + SP_SHELL, /* .SHELL */ + SP_SILENT, /* .SILENT */ + SP_SINGLESHELL, /* .SINGLESHELL; not mentioned in the manual page */ + SP_STALE, /* .STALE */ + SP_SUFFIXES, /* .SUFFIXES */ + SP_WAIT /* .WAIT */ } ParseSpecial; -/* - * Other tokens - */ -#define LPAREN '(' -#define RPAREN ')' - +typedef List SearchPathList; +typedef ListNode SearchPathListNode; /* result data */ @@ -236,33 +203,29 @@ static GNode *mainNode; /* eval state */ -/* targets we're working on */ -static Lst targets; +/* During parsing, the targets from the left-hand side of the currently + * active dependency line, or NULL if the current line does not belong to a + * dependency line, for example because it is a variable assignment. + * + * See unit-tests/deptgt.mk, keyword "parse.c:targets". */ +static GNodeList *targets; #ifdef CLEANUP -/* command lines for targets */ -static Lst targCmds; +/* 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. */ +static StringList *targCmds; #endif /* - * specType contains the SPECial TYPE of the current target. It is - * Not if the target is unspecial. If it *is* special, however, the children - * are linked as children of the parent but not vice versa. This variable is - * set in ParseDoDependency - */ -static ParseSpecial specType; - -/* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * seen, then set to each successive source on the line. */ -static GNode *predecessor; +static GNode *order_pred; /* parser state */ -/* true if currently in a dependency line or its commands */ -static Boolean inLine; - /* number of fatal errors */ static int fatals = 0; @@ -270,105 +233,125 @@ static int fatals = 0; * Variables for doing includes */ -/* current file being read */ -static IFile *curFile; +/* The include chain of makefiles. At the bottom is the top-level makefile + * from the command line, and on top of that, there are the included files or + * .for loops, up to and including the current file. + * + * This data could be used to print stack traces on parse errors. As of + * 2020-09-14, this is not done though. It seems quite simple to print the + * tuples (fname:lineno:fromForLoop), from top to bottom. This simple idea is + * made complicated by the fact that the .for loops also use this stack for + * storing information. + * + * The lineno fields of the IFiles with fromForLoop == TRUE look confusing, + * which is demonstrated by the test 'include-main.mk'. They seem sorted + * backwards since they tell the number of completely parsed lines, which for + * a .for loop is right after the terminating .endfor. To compensate for this + * confusion, there is another field first_lineno pointing at the start of the + * .for loop, 1-based for human consumption. + * + * To make the stack trace intuitive, the entry below the first .for loop must + * be ignored completely since neither its lineno nor its first_lineno is + * useful. Instead, the topmost of each chain of .for loop needs to be + * printed twice, once with its first_lineno and once with its lineno. + * + * As of 2020-10-28, using the above rules, the stack trace for the .info line + * in include-subsub.mk would be: + * + * includes[5]: include-subsub.mk:4 + * (lineno, from an .include) + * includes[4]: include-sub.mk:32 + * (lineno, from a .for loop below an .include) + * includes[4]: include-sub.mk:31 + * (first_lineno, from a .for loop, lineno == 32) + * includes[3]: include-sub.mk:30 + * (first_lineno, from a .for loop, lineno == 33) + * includes[2]: include-sub.mk:29 + * (first_lineno, from a .for loop, lineno == 34) + * includes[1]: include-sub.mk:35 + * (not printed since it is below a .for loop) + * includes[0]: include-main.mk:27 + */ +static Vector /* of IFile */ includes; -/* stack of IFiles generated by .includes */ -static Lst includes; +static IFile * +GetInclude(size_t i) +{ + return Vector_Get(&includes, i); +} -/* include paths (lists of directories) */ -Lst parseIncPath; /* dirs for "..." includes */ -Lst sysIncPath; /* dirs for <...> includes */ -Lst defIncPath; /* default for sysIncPath */ +/* The file that is currently being read. */ +static IFile * +CurFile(void) +{ + return GetInclude(includes.len - 1); +} + +/* include paths */ +SearchPath *parseIncPath; /* dirs for "..." includes */ +SearchPath *sysIncPath; /* dirs for <...> includes */ +SearchPath *defSysIncPath; /* default for sysIncPath */ /* parser tables */ /* * 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 ("Not" if the keyword isn't special as a target) while + * 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) */ static const struct { - const char *name; /* Name of keyword */ - ParseSpecial spec; /* Type when used as a target */ - int op; /* Operator when used as a source */ + const char *name; /* Name of keyword */ + ParseSpecial spec; /* Type when used as a target */ + GNodeType op; /* Operator when used as a source */ } parseKeywords[] = { -{ ".BEGIN", Begin, 0 }, -{ ".DEFAULT", Default, 0 }, -{ ".DELETE_ON_ERROR", DeleteOnError, 0 }, -{ ".END", End, 0 }, -{ ".ERROR", dotError, 0 }, -{ ".EXEC", Attribute, OP_EXEC }, -{ ".IGNORE", Ignore, OP_IGNORE }, -{ ".INCLUDES", Includes, 0 }, -{ ".INTERRUPT", Interrupt, 0 }, -{ ".INVISIBLE", Attribute, OP_INVISIBLE }, -{ ".JOIN", Attribute, OP_JOIN }, -{ ".LIBS", Libs, 0 }, -{ ".MADE", Attribute, OP_MADE }, -{ ".MAIN", Main, 0 }, -{ ".MAKE", Attribute, OP_MAKE }, -{ ".MAKEFLAGS", MFlags, 0 }, -{ ".META", Meta, OP_META }, -{ ".MFLAGS", MFlags, 0 }, -{ ".NOMETA", NoMeta, OP_NOMETA }, -{ ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP }, -{ ".NOPATH", NoPath, OP_NOPATH }, -{ ".NOTMAIN", Attribute, OP_NOTMAIN }, -{ ".NOTPARALLEL", NotParallel, 0 }, -{ ".NO_PARALLEL", NotParallel, 0 }, -{ ".NULL", Null, 0 }, -{ ".OBJDIR", ExObjdir, 0 }, -{ ".OPTIONAL", Attribute, OP_OPTIONAL }, -{ ".ORDER", Order, 0 }, -{ ".PARALLEL", Parallel, 0 }, -{ ".PATH", ExPath, 0 }, -{ ".PHONY", Phony, OP_PHONY }, + { ".BEGIN", SP_BEGIN, 0 }, + { ".DEFAULT", SP_DEFAULT, 0 }, + { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, 0 }, + { ".END", SP_END, 0 }, + { ".ERROR", SP_ERROR, 0 }, + { ".EXEC", SP_ATTRIBUTE, OP_EXEC }, + { ".IGNORE", SP_IGNORE, OP_IGNORE }, + { ".INCLUDES", SP_INCLUDES, 0 }, + { ".INTERRUPT", SP_INTERRUPT, 0 }, + { ".INVISIBLE", SP_ATTRIBUTE, OP_INVISIBLE }, + { ".JOIN", SP_ATTRIBUTE, OP_JOIN }, + { ".LIBS", SP_LIBS, 0 }, + { ".MADE", SP_ATTRIBUTE, OP_MADE }, + { ".MAIN", SP_MAIN, 0 }, + { ".MAKE", SP_ATTRIBUTE, OP_MAKE }, + { ".MAKEFLAGS", SP_MFLAGS, 0 }, + { ".META", SP_META, OP_META }, + { ".MFLAGS", SP_MFLAGS, 0 }, + { ".NOMETA", SP_NOMETA, OP_NOMETA }, + { ".NOMETA_CMP", SP_NOMETA_CMP, OP_NOMETA_CMP }, + { ".NOPATH", SP_NOPATH, OP_NOPATH }, + { ".NOTMAIN", SP_ATTRIBUTE, OP_NOTMAIN }, + { ".NOTPARALLEL", SP_NOTPARALLEL, 0 }, + { ".NO_PARALLEL", SP_NOTPARALLEL, 0 }, + { ".NULL", SP_NULL, 0 }, + { ".OBJDIR", SP_OBJDIR, 0 }, + { ".OPTIONAL", SP_ATTRIBUTE, OP_OPTIONAL }, + { ".ORDER", SP_ORDER, 0 }, + { ".PARALLEL", SP_PARALLEL, 0 }, + { ".PATH", SP_PATH, 0 }, + { ".PHONY", SP_PHONY, OP_PHONY }, #ifdef POSIX -{ ".POSIX", Posix, 0 }, + { ".POSIX", SP_POSIX, 0 }, #endif -{ ".PRECIOUS", Precious, OP_PRECIOUS }, -{ ".RECURSIVE", Attribute, OP_MAKE }, -{ ".SHELL", ExShell, 0 }, -{ ".SILENT", Silent, OP_SILENT }, -{ ".SINGLESHELL", SingleShell, 0 }, -{ ".STALE", Stale, 0 }, -{ ".SUFFIXES", Suffixes, 0 }, -{ ".USE", Attribute, OP_USE }, -{ ".USEBEFORE", Attribute, OP_USEBEFORE }, -{ ".WAIT", Wait, 0 }, + { ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS }, + { ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE }, + { ".SHELL", SP_SHELL, 0 }, + { ".SILENT", SP_SILENT, OP_SILENT }, + { ".SINGLESHELL", SP_SINGLESHELL, 0 }, + { ".STALE", SP_STALE, 0 }, + { ".SUFFIXES", SP_SUFFIXES, 0 }, + { ".USE", SP_ATTRIBUTE, OP_USE }, + { ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE }, + { ".WAIT", SP_WAIT, 0 }, }; -/* local functions */ - -static int ParseIsEscaped(const char *, const char *); -static void ParseErrorInternal(const char *, size_t, int, const char *, ...) - MAKE_ATTR_PRINTFLIKE(4,5); -static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list) - MAKE_ATTR_PRINTFLIKE(5, 0); -static int ParseFindKeyword(const char *); -static int ParseLinkSrc(void *, void *); -static int ParseDoOp(void *, void *); -static void ParseDoSrc(int, const char *); -static int ParseFindMain(void *, void *); -static int ParseAddDir(void *, void *); -static int ParseClearPath(void *, void *); -static void ParseDoDependency(char *); -static int ParseAddCmd(void *, void *); -static void ParseHasCommands(void *); -static void ParseDoInclude(char *); -static void ParseSetParseFile(const char *); -static void ParseSetIncludedFile(void); -#ifdef GMAKEEXPORT -static void ParseGmakeExport(char *); -#endif -static int ParseEOF(void); -static char *ParseReadLine(void); -static void ParseFinishLine(void); -static void ParseMark(GNode *); - /* file loader */ struct loadedfile { @@ -379,9 +362,6 @@ struct loadedfile { Boolean used; /* XXX: have we used the data yet */ }; -/* - * Constructor/destructor for loadedfile - */ static struct loadedfile * loadedfile_create(const char *path) { @@ -457,58 +437,28 @@ load_getsize(int fd, size_t *ret) return FALSE; } - *ret = (size_t) st.st_size; + *ret = (size_t)st.st_size; return TRUE; } -/* - * Read in a file. - * - * Until the path search logic can be moved under here instead of - * being in the caller in another source file, we need to have the fd - * passed in already open. Bleh. - * - * If the path is NULL use stdin and (to insure against fd leaks) - * assert that the caller passed in -1. - */ -static struct loadedfile * -loadfile(const char *path, int fd) -{ - struct loadedfile *lf; #ifdef HAVE_MMAP - static long pagesize = 0; -#endif - ssize_t result; - size_t bufpos; - - lf = loadedfile_create(path); - - if (path == NULL) { - assert(fd == -1); - fd = STDIN_FILENO; - } else { -#if 0 /* notyet */ - fd = open(path, O_RDONLY); - if (fd < 0) { - ... - Error("%s: %s", path, strerror(errno)); - exit(1); - } -#endif - } +static Boolean +loadedfile_mmap(struct loadedfile *lf, int fd) +{ + static unsigned long pagesize = 0; -#ifdef HAVE_MMAP if (load_getsize(fd, &lf->len)) { + /* found a size, try mmap */ #ifdef _SC_PAGESIZE if (pagesize == 0) - pagesize = sysconf(_SC_PAGESIZE); + pagesize = (unsigned long)sysconf(_SC_PAGESIZE); #endif - if (pagesize <= 0) { + if (pagesize == 0 || pagesize == (unsigned long)-1) { pagesize = 0x1000; } /* round size up to a page */ - lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize); + lf->maplen = pagesize * ((lf->len + pagesize - 1) / pagesize); /* * XXX hack for dealing with empty files; remove when @@ -535,10 +485,51 @@ loadfile(const char *path, int fd) lf->maplen = 0; lf->buf = b; } - goto done; + return TRUE; + } + } + return FALSE; +} +#endif + +/* + * Read in a file. + * + * Until the path search logic can be moved under here instead of + * being in the caller in another source file, we need to have the fd + * passed in already open. Bleh. + * + * If the path is NULL use stdin and (to insure against fd leaks) + * assert that the caller passed in -1. + */ +static struct loadedfile * +loadfile(const char *path, int fd) +{ + struct loadedfile *lf; + ssize_t result; + size_t bufpos; + + lf = loadedfile_create(path); + + if (path == NULL) { + assert(fd == -1); + fd = STDIN_FILENO; + } else { +#if 0 /* notyet */ + fd = open(path, O_RDONLY); + if (fd < 0) { + ... + Error("%s: %s", path, strerror(errno)); + exit(1); } +#endif } + +#ifdef HAVE_MMAP + if (loadedfile_mmap(lf, fd)) + goto done; #endif + /* cannot mmap; load the traditional way */ lf->maplen = 0; @@ -566,7 +557,7 @@ loadfile(const char *path, int fd) if (result == 0) { break; } - bufpos += result; + bufpos += (size_t)result; } assert(bufpos <= lf->len); lf->len = bufpos; @@ -591,22 +582,11 @@ done: /* old code */ -/*- - *---------------------------------------------------------------------- - * ParseIsEscaped -- - * Check if the current character is escaped on the current line - * - * Results: - * 0 if the character is not backslash escaped, 1 otherwise - * - * Side Effects: - * None - *---------------------------------------------------------------------- - */ -static int +/* Check if the current character is escaped on the current line. */ +static Boolean ParseIsEscaped(const char *line, const char *c) { - int active = 0; + Boolean active = FALSE; for (;;) { if (line == c) return active; @@ -616,32 +596,29 @@ ParseIsEscaped(const char *line, const char *c) } } -/*- - *---------------------------------------------------------------------- - * ParseFindKeyword -- - * Look in the table of keywords for one matching the given string. - * - * Input: - * str String to find - * - * Results: - * The index of the keyword, or -1 if it isn't there. - * - * Side Effects: - * None - *---------------------------------------------------------------------- - */ +/* Add the filename and lineno to the GNode so that we remember where it + * was first defined. */ +static void +ParseMark(GNode *gn) +{ + IFile *curFile = CurFile(); + gn->fname = curFile->fname; + gn->lineno = curFile->lineno; +} + +/* Look in the table of keywords for one matching the given string. + * Return the index of the keyword, or -1 if it isn't there. */ static int ParseFindKeyword(const char *str) { - int start, end, cur; - int diff; + int start, end, cur; + int diff; start = 0; - end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1; + end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; do { - cur = start + ((end - start) / 2); + cur = start + (end - start) / 2; diff = strcmp(str, parseKeywords[cur].name); if (diff == 0) { @@ -655,89 +632,72 @@ ParseFindKeyword(const char *str) return -1; } -/*- - * ParseVErrorInternal -- - * Error message abort function for parsing. Prints out the context - * of the error (line number and file) as well as the message with - * two optional arguments. - * - * Results: - * None - * - * Side Effects: - * "fatals" is incremented if the level is PARSE_FATAL. - */ -/* VARARGS */ static void -ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type, - const char *fmt, va_list ap) +PrintLocation(FILE *f, const char *filename, size_t lineno) { - static Boolean fatal_warning_error_printed = FALSE; char dirbuf[MAXPATHLEN+1]; + const char *dir, *base; + void *dir_freeIt, *base_freeIt; - (void)fprintf(f, "%s: ", progname); + if (*filename == '/' || strcmp(filename, "(stdin)") == 0) { + (void)fprintf(f, "\"%s\" line %zu: ", filename, lineno); + return; + } - if (cfname != NULL) { - (void)fprintf(f, "\""); - if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) { - char *cp, *cp2; - const char *dir, *fname; - - /* - * Nothing is more annoying than not knowing - * which Makefile is the culprit; we try ${.PARSEDIR} - * and apply realpath(3) if not absolute. - */ - dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp); - if (dir == NULL) - dir = "."; - if (*dir != '/') { - dir = realpath(dir, dirbuf); - } - fname = Var_Value(".PARSEFILE", VAR_GLOBAL, &cp2); - if (fname == NULL) { - if ((fname = strrchr(cfname, '/'))) - fname++; - else - fname = cfname; - } - (void)fprintf(f, "%s/%s", dir, fname); - bmake_free(cp2); - bmake_free(cp); - } else - (void)fprintf(f, "%s", cfname); + /* Find out which makefile is the culprit. + * We try ${.PARSEDIR} and apply realpath(3) if not absolute. */ + + dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &dir_freeIt); + if (dir == NULL) + dir = "."; + if (*dir != '/') + dir = realpath(dir, dirbuf); - (void)fprintf(f, "\" line %d: ", (int)clineno); + base = Var_Value(".PARSEFILE", VAR_GLOBAL, &base_freeIt); + if (base == NULL) { + const char *slash = strrchr(filename, '/'); + base = slash != NULL ? slash + 1 : filename; } + + (void)fprintf(f, "\"%s/%s\" line %zu: ", dir, base, lineno); + bmake_free(base_freeIt); + bmake_free(dir_freeIt); +} + +/* Print a parse error message, including location information. + * + * Increment "fatals" if the level is PARSE_FATAL, and continue parsing + * until the end of the current top-level makefile, then exit (see + * Parse_File). */ +static void +ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, + ParseErrorLevel type, const char *fmt, va_list ap) +{ + static Boolean fatal_warning_error_printed = FALSE; + + (void)fprintf(f, "%s: ", progname); + + if (cfname != NULL) + PrintLocation(f, cfname, clineno); if (type == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); + if (type == PARSE_INFO) return; - if (type == PARSE_FATAL || parseWarnFatal) - fatals += 1; - if (parseWarnFatal && !fatal_warning_error_printed) { + if (type == PARSE_FATAL || opts.parseWarnFatal) + fatals++; + if (opts.parseWarnFatal && !fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); fatal_warning_error_printed = TRUE; } } -/*- - * ParseErrorInternal -- - * Error function - * - * Results: - * None - * - * Side Effects: - * None - */ -/* VARARGS */ static void -ParseErrorInternal(const char *cfname, size_t clineno, int type, - const char *fmt, ...) +ParseErrorInternal(const char *cfname, size_t clineno, ParseErrorLevel type, + const char *fmt, ...) { va_list ap; @@ -746,38 +706,32 @@ ParseErrorInternal(const char *cfname, size_t clineno, int type, ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap); va_end(ap); - if (debug_file != stderr && debug_file != stdout) { + if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap); + ParseVErrorInternal(opts.debug_file, cfname, clineno, type, + fmt, ap); va_end(ap); } } -/*- - * Parse_Error -- - * External interface to ParseErrorInternal; uses the default filename - * Line number. +/* External interface to ParseErrorInternal; uses the default filename and + * line number. * - * Results: - * None - * - * Side Effects: - * None - */ -/* VARARGS */ + * Fmt is given without a trailing newline. */ void -Parse_Error(int type, const char *fmt, ...) +Parse_Error(ParseErrorLevel type, const char *fmt, ...) { va_list ap; const char *fname; size_t lineno; - if (curFile == NULL) { + if (includes.len == 0) { fname = NULL; lineno = 0; } else { + IFile *curFile = CurFile(); fname = curFile->fname; - lineno = curFile->lineno; + lineno = (size_t)curFile->lineno; } va_start(ap, fmt); @@ -785,136 +739,95 @@ Parse_Error(int type, const char *fmt, ...) ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); - if (debug_file != stderr && debug_file != stdout) { + if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap); + ParseVErrorInternal(opts.debug_file, fname, lineno, type, + fmt, ap); va_end(ap); } } -/* - * ParseMessage - * Parse a .info .warning or .error directive +/* Parse a .info .warning or .error directive. * - * The input is the line minus the ".". We substitute - * variables, print the message and exit(1) (for .error) or just print - * a warning if the directive is malformed. + * The input is the line minus the ".". We substitute variables, print the + * message and exit(1) (for .error) or just print a warning if the directive + * is malformed. */ static Boolean -ParseMessage(char *line) +ParseMessage(const char *directive) { - int mtype; + const char *p = directive; + int mtype = *p == 'i' ? PARSE_INFO : + *p == 'w' ? PARSE_WARNING : PARSE_FATAL; + char *arg; - switch(*line) { - case 'i': - mtype = PARSE_INFO; - break; - case 'w': - mtype = PARSE_WARNING; - break; - case 'e': - mtype = PARSE_FATAL; - break; - default: - Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line); - return FALSE; - } + while (ch_isalpha(*p)) + p++; + if (!ch_isspace(*p)) + return FALSE; /* missing argument */ - while (isalpha((unsigned char)*line)) - line++; - if (!isspace((unsigned char)*line)) - return FALSE; /* not for us */ - while (isspace((unsigned char)*line)) - line++; + cpp_skip_whitespace(&p); + (void)Var_Subst(p, VAR_CMDLINE, VARE_WANTRES, &arg); + /* TODO: handle errors */ - line = Var_Subst(line, VAR_CMD, VARE_WANTRES); - Parse_Error(mtype, "%s", line); - free(line); + Parse_Error(mtype, "%s", arg); + free(arg); if (mtype == PARSE_FATAL) { - /* Terminate immediately. */ + PrintOnError(NULL, NULL); exit(1); } return TRUE; } -/*- - *--------------------------------------------------------------------- - * ParseLinkSrc -- - * Link the parent node to its new child. Used in a Lst_ForEach by - * ParseDoDependency. If the specType isn't 'Not', the parent - * isn't linked as a parent of the child. - * - * Input: - * pgnp The parent node - * cgpn The child node - * - * Results: - * Always = 0 +/* Add the child to the parent's children. * - * Side Effects: - * New elements are added to the parents list of cgn and the - * children list of cgn. the unmade field of pgn is updated - * to reflect the additional child. - *--------------------------------------------------------------------- - */ -static int -ParseLinkSrc(void *pgnp, void *cgnp) + * Additionally, add the parent to the child's parents, but only if the + * target is not special. An example for such a special target is .END, + * which does not need to be informed once the child target has been made. */ +static void +LinkSource(GNode *pgn, GNode *cgn, Boolean isSpecial) { - GNode *pgn = (GNode *)pgnp; - GNode *cgn = (GNode *)cgnp; - if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(pgn->cohorts)) - pgn = LstNode_Datum(Lst_Last(pgn->cohorts)); + pgn = pgn->cohorts->last->datum; + Lst_Append(pgn->children, cgn); - if (specType == Not) + pgn->unmade++; + + /* Special targets like .END don't need any children. */ + if (!isSpecial) Lst_Append(cgn->parents, pgn); - pgn->unmade += 1; + if (DEBUG(PARSE)) { - fprintf(debug_file, "# %s: added child %s - %s\n", __func__, - pgn->name, cgn->name); + debug_printf("# %s: added child %s - %s\n", + __func__, pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); } - return 0; } -/*- - *--------------------------------------------------------------------- - * ParseDoOp -- - * Apply the parsed operator to the given target node. Used in a - * Lst_ForEach call by ParseDoDependency once all targets have - * been found and their operator parsed. If the previous and new - * operators are incompatible, a major error is taken. - * - * Input: - * gnp The node to which the operator is to be applied - * opp The operator to apply - * - * Results: - * Always 0 - * - * Side Effects: - * The type field of the node is altered to reflect any new bits in - * the op. - *--------------------------------------------------------------------- - */ -static int -ParseDoOp(void *gnp, void *opp) +/* Add the node to each target from the current dependency group. */ +static void +LinkToTargets(GNode *gn, Boolean isSpecial) +{ + GNodeListNode *ln; + for (ln = targets->first; ln != NULL; ln = ln->next) + LinkSource(ln->datum, gn, isSpecial); +} + +static Boolean +TryApplyDependencyOperator(GNode *gn, GNodeType op) { - GNode *gn = (GNode *)gnp; - int op = *(int *)opp; /* - * If the dependency mask of the operator and the node don't match and - * the node has actually had an operator applied to it before, and - * the operator actually has some dependency information in it, complain. + * If the node occurred on the left-hand side of a dependency and the + * operator also defines a dependency, they must match. */ - if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && - !OP_NOP(gn->type) && !OP_NOP(op)) + if ((op & OP_OPMASK) && (gn->type & OP_OPMASK) && + ((op & OP_OPMASK) != (gn->type & OP_OPMASK))) { Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); - return 1; + return FALSE; } if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { @@ -926,7 +839,7 @@ ParseDoOp(void *gnp, void *opp) * and the new instance is linked to all parents of the initial * instance. */ - GNode *cohort; + GNode *cohort; /* * Propagate copied bits to the initial node. They'll be propagated @@ -934,7 +847,7 @@ ParseDoOp(void *gnp, void *opp) */ gn->type |= op & ~OP_OPMASK; - cohort = Targ_FindNode(gn->name, TARG_NOHASH); + cohort = Targ_NewInternalNode(gn->name); if (doing_depend) ParseMark(cohort); /* @@ -947,9 +860,9 @@ ParseDoOp(void *gnp, void *opp) cohort->type = op | OP_INVISIBLE; Lst_Append(gn->cohorts, cohort); cohort->centurion = gn; - gn->unmade_cohorts += 1; + gn->unmade_cohorts++; snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", - gn->unmade_cohorts); + (unsigned int)gn->unmade_cohorts % 1000000); } else { /* * We don't want to nuke any previous flags (whatever they were) so we @@ -958,47 +871,34 @@ ParseDoOp(void *gnp, void *opp) gn->type |= op; } - return 0; + return TRUE; } -/*- - *--------------------------------------------------------------------- - * ParseDoSrc -- - * Given the name of a source, figure out if it is an attribute - * and apply it to the targets if it is. Else decide if there is - * some attribute which should be applied *to* the source because - * of some special target and apply it if so. Otherwise, make the - * source be a child of the targets in the list 'targets' - * - * Input: - * tOp operator (if any) from special targets - * src name of the source to handle - * - * Results: - * None - * - * Side Effects: - * Operator bits may be added to the list of targets or to the source. - * The targets may have a new source added to their lists of children. - *--------------------------------------------------------------------- - */ static void -ParseDoSrc(int tOp, const char *src) +ApplyDependencyOperator(GNodeType op) +{ + GNodeListNode *ln; + for (ln = targets->first; ln != NULL; ln = ln->next) + if (!TryApplyDependencyOperator(ln->datum, op)) + break; +} + +static Boolean +ParseDoSrcKeyword(const char *src, ParseSpecial specType) { - GNode *gn = NULL; static int wait_number = 0; char wait_src[16]; + GNode *gn; - if (*src == '.' && isupper ((unsigned char)src[1])) { + if (*src == '.' && ch_isupper(src[1])) { int keywd = ParseFindKeyword(src); if (keywd != -1) { int op = parseKeywords[keywd].op; if (op != 0) { - if (targets != NULL) - Lst_ForEach(targets, ParseDoOp, &op); - return; + ApplyDependencyOperator(op); + return TRUE; } - if (parseKeywords[keywd].spec == Wait) { + if (parseKeywords[keywd].spec == SP_WAIT) { /* * We add a .WAIT node in the dependency list. * After any dynamic dependencies (and filename globbing) @@ -1009,257 +909,591 @@ ParseDoSrc(int tOp, const char *src) * We give each .WAIT node a unique name (mainly for diag). */ snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); - gn = Targ_FindNode(wait_src, TARG_NOHASH); + gn = Targ_NewInternalNode(wait_src); if (doing_depend) ParseMark(gn); gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; - if (targets != NULL) - Lst_ForEach(targets, ParseLinkSrc, gn); - return; + LinkToTargets(gn, specType != SP_NOT); + return TRUE; } } } + return FALSE; +} - switch (specType) { - case Main: - /* - * If we have noted the existence of a .MAIN, it means we need - * to add the sources of said target to the list of things - * to create. The string 'src' is likely to be free, so we - * must make a new copy of it. Note that this will only be - * invoked if the user didn't specify a target on the command - * line. This is to allow #ifmake's to succeed, or something... - */ - Lst_Append(create, bmake_strdup(src)); - /* - * Add the name to the .TARGETS variable as well, so the user can - * employ that, if desired. - */ - Var_Append(".TARGETS", src, VAR_GLOBAL); +static void +ParseDoSrcMain(const char *src) +{ + /* + * If we have noted the existence of a .MAIN, it means we need + * to add the sources of said target to the list of things + * to create. The string 'src' is likely to be free, so we + * must make a new copy of it. Note that this will only be + * invoked if the user didn't specify a target on the command + * line. This is to allow #ifmake's to succeed, or something... + */ + Lst_Append(opts.create, bmake_strdup(src)); + /* + * Add the name to the .TARGETS variable as well, so the user can + * employ that, if desired. + */ + Var_Append(".TARGETS", src, VAR_GLOBAL); +} + +static void +ParseDoSrcOrder(const char *src) +{ + GNode *gn; + /* + * Create proper predecessor/successor links between the previous + * source and the current one. + */ + gn = Targ_GetNode(src); + if (doing_depend) + ParseMark(gn); + if (order_pred != NULL) { + Lst_Append(order_pred->order_succ, gn); + Lst_Append(gn->order_pred, order_pred); + if (DEBUG(PARSE)) { + debug_printf("# %s: added Order dependency %s - %s\n", + __func__, order_pred->name, gn->name); + Targ_PrintNode(order_pred, 0); + Targ_PrintNode(gn, 0); + } + } + /* + * The current source now becomes the predecessor for the next one. + */ + order_pred = gn; +} + +static void +ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType) +{ + GNode *gn; + + /* + * If the source is not an attribute, we need to find/create + * a node for it. After that we can apply any operator to it + * from a special target or link it to its parents, as + * appropriate. + * + * In the case of a source that was the object of a :: operator, + * the attribute is applied to all of its instances (as kept in + * the 'cohorts' list of the node) or all the cohorts are linked + * to all the targets. + */ + + /* Find/create the 'src' node and attach to all targets */ + gn = Targ_GetNode(src); + if (doing_depend) + ParseMark(gn); + if (tOp) { + gn->type |= tOp; + } else { + LinkToTargets(gn, specType != SP_NOT); + } +} + +/* Given the name of a source in a dependency line, figure out if it is an + * attribute (such as .SILENT) and apply it to the targets if it is. Else + * decide if there is some attribute which should be applied *to* the source + * because of some special target (such as .PHONY) and apply it if so. + * Otherwise, make the source a child of the targets in the list 'targets'. + * + * Input: + * tOp operator (if any) from special targets + * src name of the source to handle + */ +static void +ParseDoSrc(GNodeType tOp, const char *src, ParseSpecial specType) +{ + if (ParseDoSrcKeyword(src, specType)) return; - case Order: - /* - * Create proper predecessor/successor links between the previous - * source and the current one. - */ - gn = Targ_FindNode(src, TARG_CREATE); + if (specType == SP_MAIN) + ParseDoSrcMain(src); + else if (specType == SP_ORDER) + ParseDoSrcOrder(src); + else + ParseDoSrcOther(src, tOp, specType); +} + +/* If we have yet to decide on a main target to make, in the absence of any + * user input, we want the first target on the first dependency line that is + * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made. */ +static void +FindMainTarget(void) +{ + GNodeListNode *ln; + + if (mainNode != NULL) + return; + + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (!(gn->type & OP_NOTARGET)) { + mainNode = gn; + Targ_SetMain(gn); + return; + } + } +} + +/* + * We got to the end of the line while we were still looking at targets. + * + * Ending a dependency line without an operator is a Bozo no-no. As a + * heuristic, this is also often triggered by undetected conflicts from + * cvs/rcs merges. + */ +static void +ParseErrorNoDependency(const char *lstart) +{ + if ((strncmp(lstart, "<<<<<<", 6) == 0) || + (strncmp(lstart, "======", 6) == 0) || + (strncmp(lstart, ">>>>>>", 6) == 0)) + Parse_Error(PARSE_FATAL, + "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); + else if (lstart[0] == '.') { + const char *dirstart = lstart + 1; + const char *dirend; + cpp_skip_whitespace(&dirstart); + dirend = dirstart; + while (ch_isalnum(*dirend) || *dirend == '-') + dirend++; + Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", + (int)(dirend - dirstart), dirstart); + } else + Parse_Error(PARSE_FATAL, "Need an operator"); +} + +static void +ParseDependencyTargetWord(/*const*/ char **pp, const char *lstart) +{ + /*const*/ char *cp = *pp; + + while (*cp != '\0') { + if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || *cp == '(') && + !ParseIsEscaped(lstart, cp)) + break; + + if (*cp == '$') { + /* + * Must be a dynamic source (would have been expanded + * otherwise), so call the Var module to parse the puppy + * so we can safely advance beyond it...There should be + * no errors in this, as they would have been discovered + * in the initial Var_Subst and we wouldn't be here. + */ + const char *nested_p = cp; + const char *nested_val; + void *freeIt; + + (void)Var_Parse(&nested_p, VAR_CMDLINE, VARE_UNDEFERR|VARE_WANTRES, + &nested_val, &freeIt); + /* TODO: handle errors */ + free(freeIt); + cp += nested_p - cp; + } else + cp++; + } + + *pp = cp; +} + +/* + * Certain special targets have special semantics: + * .PATH Have to set the dirSearchPath + * variable too + * .MAIN Its sources are only used if + * nothing has been specified to + * create. + * .DEFAULT Need to create a node to hang + * commands on, but we don't want + * it in the graph, nor do we want + * it to be the Main Target, so we + * create it, set OP_NOTMAIN and + * add it to the list, setting + * DEFAULT to the new node for + * later use. We claim the node is + * A transformation rule to make + * life easier later, when we'll + * use Make_HandleUse to actually + * apply the .DEFAULT commands. + * .PHONY The list of targets + * .NOPATH Don't search for file in the path + * .STALE + * .BEGIN + * .END + * .ERROR + * .DELETE_ON_ERROR + * .INTERRUPT Are not to be considered the + * main target. + * .NOTPARALLEL Make only one target at a time. + * .SINGLESHELL Create a shell for each command. + * .ORDER Must set initial predecessor to NULL + */ +static void +ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, + const char *line, + SearchPathList **inout_paths) +{ + switch (*inout_specType) { + case SP_PATH: + if (*inout_paths == NULL) { + *inout_paths = Lst_New(); + } + Lst_Append(*inout_paths, dirSearchPath); + break; + case SP_MAIN: + if (!Lst_IsEmpty(opts.create)) { + *inout_specType = SP_NOT; + } + break; + case SP_BEGIN: + case SP_END: + case SP_STALE: + case SP_ERROR: + case SP_INTERRUPT: { + GNode *gn = Targ_GetNode(line); if (doing_depend) ParseMark(gn); - if (predecessor != NULL) { - Lst_Append(predecessor->order_succ, gn); - Lst_Append(gn->order_pred, predecessor); - if (DEBUG(PARSE)) { - fprintf(debug_file, "# %s: added Order dependency %s - %s\n", - __func__, predecessor->name, gn->name); - Targ_PrintNode(predecessor, 0); - Targ_PrintNode(gn, 0); - } + gn->type |= OP_NOTMAIN|OP_SPECIAL; + Lst_Append(targets, gn); + break; + } + case SP_DEFAULT: { + GNode *gn = Targ_NewGN(".DEFAULT"); + gn->type |= OP_NOTMAIN|OP_TRANSFORM; + Lst_Append(targets, gn); + DEFAULT = gn; + break; + } + case SP_DELETE_ON_ERROR: + deleteOnError = TRUE; + break; + case SP_NOTPARALLEL: + opts.maxJobs = 1; + break; + case SP_SINGLESHELL: + opts.compatMake = TRUE; + break; + case SP_ORDER: + order_pred = NULL; + break; + default: + break; + } +} + +/* + * .PATH<suffix> has to be handled specially. + * Call on the suffix module to give us a path to modify. + */ +static Boolean +ParseDoDependencyTargetPath(const char *line, SearchPathList **inout_paths) +{ + SearchPath *path; + + path = Suff_GetPath(&line[5]); + if (path == NULL) { + Parse_Error(PARSE_FATAL, + "Suffix '%s' not defined (yet)", + &line[5]); + return FALSE; + } else { + if (*inout_paths == NULL) { + *inout_paths = Lst_New(); } + Lst_Append(*inout_paths, path); + } + return TRUE; +} + +/* + * See if it's a special target and if so set specType to match it. + */ +static Boolean +ParseDoDependencyTarget(const char *line, ParseSpecial *inout_specType, + GNodeType *out_tOp, SearchPathList **inout_paths) +{ + int keywd; + + if (!(*line == '.' && ch_isupper(line[1]))) + return TRUE; + + /* + * See if the target is a special target that must have it + * or its sources handled specially. + */ + keywd = ParseFindKeyword(line); + if (keywd != -1) { + if (*inout_specType == SP_PATH && parseKeywords[keywd].spec != SP_PATH) { + Parse_Error(PARSE_FATAL, "Mismatched special targets"); + return FALSE; + } + + *inout_specType = parseKeywords[keywd].spec; + *out_tOp = parseKeywords[keywd].op; + + ParseDoDependencyTargetSpecial(inout_specType, line, inout_paths); + + } else if (strncmp(line, ".PATH", 5) == 0) { + *inout_specType = SP_PATH; + if (!ParseDoDependencyTargetPath(line, inout_paths)) + return FALSE; + } + return TRUE; +} + +static void +ParseDoDependencyTargetMundane(char *line, StringList *curTargs) +{ + if (Dir_HasWildcards(line)) { /* - * The current source now becomes the predecessor for the next one. + * Targets are to be sought only in the current directory, + * so create an empty path for the thing. Note we need to + * use Dir_Destroy in the destruction of the path as the + * Dir module could have added a directory to the path... */ - predecessor = gn; - break; + SearchPath *emptyPath = Lst_New(); - default: + Dir_Expand(line, emptyPath, curTargs); + + Lst_Destroy(emptyPath, Dir_Destroy); + } else { /* - * If the source is not an attribute, we need to find/create - * a node for it. After that we can apply any operator to it - * from a special target or link it to its parents, as - * appropriate. - * - * In the case of a source that was the object of a :: operator, - * the attribute is applied to all of its instances (as kept in - * the 'cohorts' list of the node) or all the cohorts are linked - * to all the targets. + * No wildcards, but we want to avoid code duplication, + * so create a list with the word on it. */ + Lst_Append(curTargs, line); + } + + /* Apply the targets. */ - /* Find/create the 'src' node and attach to all targets */ - gn = Targ_FindNode(src, TARG_CREATE); + while (!Lst_IsEmpty(curTargs)) { + char *targName = Lst_Dequeue(curTargs); + GNode *gn = Suff_IsTransform(targName) + ? Suff_AddTransform(targName) + : Targ_GetNode(targName); if (doing_depend) ParseMark(gn); - if (tOp) { - gn->type |= tOp; - } else { - if (targets != NULL) - Lst_ForEach(targets, ParseLinkSrc, gn); + + Lst_Append(targets, gn); + } +} + +static void +ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart) +{ + Boolean warning = FALSE; + char *cp = *pp; + + while (*cp && (ParseIsEscaped(lstart, cp) || + (*cp != '!' && *cp != ':'))) { + if (ParseIsEscaped(lstart, cp) || + (*cp != ' ' && *cp != '\t')) { + warning = TRUE; } + cp++; + } + if (warning) { + Parse_Error(PARSE_WARNING, "Extra target ignored"); + } + *pp = cp; +} + +static void +ParseDoDependencyCheckSpec(ParseSpecial specType) +{ + switch (specType) { + default: + Parse_Error(PARSE_WARNING, + "Special and mundane targets don't mix. Mundane ones ignored"); + break; + case SP_DEFAULT: + case SP_STALE: + case SP_BEGIN: + case SP_END: + case SP_ERROR: + case SP_INTERRUPT: + /* + * These four create nodes on which to hang commands, so + * targets shouldn't be empty... + */ + case SP_NOT: + /* + * Nothing special here -- targets can be empty if it wants. + */ break; } } -/*- - *----------------------------------------------------------------------- - * ParseFindMain -- - * Find a real target in the list and set it to be the main one. - * Called by ParseDoDependency when a main target hasn't been found - * yet. - * - * Input: - * gnp Node to examine - * - * Results: - * 0 if main not found yet, 1 if it is. - * - * Side Effects: - * mainNode is changed and Targ_SetMain is called. - * - *----------------------------------------------------------------------- - */ -static int -ParseFindMain(void *gnp, void *dummy MAKE_ATTR_UNUSED) +static Boolean +ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op) { - GNode *gn = (GNode *)gnp; - if (!(gn->type & OP_NOTARGET)) { - mainNode = gn; - Targ_SetMain(gn); - return 1; - } else { - return 0; + const char *cp = *pp; + + if (*cp == '!') { + *out_op = OP_FORCE; + (*pp)++; + return TRUE; + } + + if (*cp == ':') { + if (cp[1] == ':') { + *out_op = OP_DOUBLEDEP; + (*pp) += 2; + } else { + *out_op = OP_DEPENDS; + (*pp)++; + } + return TRUE; + } + + { + const char *msg = lstart[0] == '.' ? "Unknown directive" + : "Missing dependency operator"; + Parse_Error(PARSE_FATAL, "%s", msg); + return FALSE; } } -/*- - *----------------------------------------------------------------------- - * ParseAddDir -- - * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going - * - * Results: - * === 0 - * - * Side Effects: - * See Dir_AddDir. - * - *----------------------------------------------------------------------- - */ -static int -ParseAddDir(void *path, void *name) +static void +ClearPaths(SearchPathList *paths) { - (void)Dir_AddDir((Lst) path, (char *)name); - return 0; + if (paths != NULL) { + SearchPathListNode *ln; + for (ln = paths->first; ln != NULL; ln = ln->next) + Dir_ClearPath(ln->datum); + } + + Dir_SetPATH(); } -/*- - *----------------------------------------------------------------------- - * ParseClearPath -- - * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going - * - * Results: - * === 0 - * - * Side Effects: - * See Dir_ClearPath - * - *----------------------------------------------------------------------- - */ -static int -ParseClearPath(void *path, void *dummy MAKE_ATTR_UNUSED) +static void +ParseDoDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) { - Dir_ClearPath((Lst) path); - return 0; + switch (specType) { + case SP_SUFFIXES: + Suff_ClearSuffixes(); + break; + case SP_PRECIOUS: + allPrecious = TRUE; + break; + case SP_IGNORE: + opts.ignoreErrors = TRUE; + break; + case SP_SILENT: + opts.beSilent = TRUE; + break; + case SP_PATH: + ClearPaths(paths); + break; +#ifdef POSIX + case SP_POSIX: + Var_Set("%POSIX", "1003.2", VAR_GLOBAL); + break; +#endif + default: + break; + } } -/*- - *--------------------------------------------------------------------- - * ParseDoDependency -- - * Parse the dependency line in line. +static void +AddToPaths(const char *dir, SearchPathList *paths) +{ + if (paths != NULL) { + SearchPathListNode *ln; + for (ln = paths->first; ln != NULL; ln = ln->next) + (void)Dir_AddDir(ln->datum, dir); + } +} + +/* + * If the target was one that doesn't take files as its sources + * but takes something like suffixes, we take each + * space-separated word on the line as a something and deal + * with it accordingly. * - * Input: - * line the line to parse + * If the target was .SUFFIXES, we take each source as a + * suffix and add it to the list of suffixes maintained by the + * Suff module. * - * Results: - * None + * If the target was a .PATH, we add the source as a directory + * to search on the search path. * - * Side Effects: - * The nodes of the sources are linked as children to the nodes of the - * targets. Some nodes may be created. + * If it was .INCLUDES, the source is taken to be the suffix of + * files which will be #included and whose search path should + * be present in the .INCLUDES variable. * - * We parse a dependency line by first extracting words from the line and - * finding nodes in the list of all targets with that name. This is done - * until a character is encountered which is an operator character. Currently - * these are only ! and :. At this point the operator is parsed and the - * pointer into the line advanced until the first source is encountered. - * The parsed operator is applied to each node in the 'targets' list, - * which is where the nodes found for the targets are kept, by means of - * the ParseDoOp function. - * The sources are read in much the same way as the targets were except - * that now they are expanded using the wildcarding scheme of the C-Shell - * and all instances of the resulting words in the list of all targets - * are found. Each of the resulting nodes is then linked to each of the - * targets as one of its children. - * Certain targets are handled specially. These are the ones detailed - * by the specType variable. - * The storing of transformation rules is also taken care of here. - * A target is recognized as a transformation rule by calling - * Suff_IsTransform. If it is a transformation rule, its node is gotten - * from the suffix module via Suff_AddTransform rather than the standard - * Targ_FindNode in the target module. - *--------------------------------------------------------------------- + * If it was .LIBS, the source is taken to be the suffix of + * files which are considered libraries and whose search path + * should be present in the .LIBS variable. + * + * If it was .NULL, the source is the suffix to use when a file + * has no valid suffix. + * + * If it was .OBJDIR, the source is a new definition for .OBJDIR, + * and will cause make to do a new chdir to that path. */ static void -ParseDoDependency(char *line) +ParseDoDependencySourceSpecial(ParseSpecial specType, char *word, + SearchPathList *paths) { - char *cp; /* our current position */ - GNode *gn = NULL; /* a general purpose temporary node */ - int op; /* the operator on the line */ - char savec; /* a place to save a character */ - Lst paths; /* List of search paths to alter when parsing - * a list of .PATH targets */ - int tOp; /* operator from special target */ - Lst sources; /* list of archive source names after - * expansion */ - Lst curTargs; /* list of target names to be found and added - * to the targets list */ - char *lstart = line; - - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseDoDependency(%s)\n", line); - tOp = 0; - - specType = Not; - paths = NULL; - - curTargs = Lst_Init(); + switch (specType) { + case SP_SUFFIXES: + Suff_AddSuffix(word, &mainNode); + break; + case SP_PATH: + AddToPaths(word, paths); + break; + case SP_INCLUDES: + Suff_AddInclude(word); + break; + case SP_LIBS: + Suff_AddLib(word); + break; + case SP_NULL: + Suff_SetNull(word); + break; + case SP_OBJDIR: + Main_SetObjdir("%s", word); + break; + default: + break; + } +} - /* - * First, grind through the targets. - */ +static Boolean +ParseDoDependencyTargets(char **inout_cp, + char **inout_line, + const char *lstart, + ParseSpecial *inout_specType, + GNodeType *inout_tOp, + SearchPathList **inout_paths, + StringList *curTargs) +{ + char *cp = *inout_cp; + char *line = *inout_line; + char savec; - do { + for (;;) { /* * Here LINE points to the beginning of the next word, and * LSTART points to the actual beginning of the line. */ /* Find the end of the next word. */ - for (cp = line; *cp && (ParseIsEscaped(lstart, cp) || - !(isspace((unsigned char)*cp) || - *cp == '!' || *cp == ':' || *cp == LPAREN)); - cp++) { - if (*cp == '$') { - /* - * Must be a dynamic source (would have been expanded - * otherwise), so call the Var module to parse the puppy - * so we can safely advance beyond it...There should be - * no errors in this, as they would have been discovered - * in the initial Var_Subst and we wouldn't be here. - */ - int length; - void *freeIt; - - (void)Var_Parse(cp, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES, - &length, &freeIt); - free(freeIt); - cp += length - 1; - } - } + cp = line; + ParseDependencyTargetWord(&cp, lstart); /* * If the word is followed by a left parenthesis, it's the * name of an object file inside an archive (ar file). */ - if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) { + if (!ParseIsEscaped(lstart, cp) && *cp == '(') { /* * Archives must be handled specially to make sure the OP_ARCHV * flag is set in their 'type' field, for one thing, and because @@ -1270,10 +1504,10 @@ ParseDoDependency(char *line) * went well and FALSE if there was an error in the * specification. On error, line should remain untouched. */ - if (!Arch_ParseArchive(&line, targets, VAR_CMD)) { + if (!Arch_ParseArchive(&line, targets, VAR_CMDLINE)) { Parse_Error(PARSE_FATAL, - "Error in archive specification: \"%s\"", line); - goto out; + "Error in archive specification: \"%s\"", line); + return FALSE; } else { /* Done with this word; on to the next. */ cp = line; @@ -1282,198 +1516,25 @@ ParseDoDependency(char *line) } if (!*cp) { - /* - * We got to the end of the line while we were still - * looking at targets. - * - * Ending a dependency line without an operator is a Bozo - * no-no. As a heuristic, this is also often triggered by - * undetected conflicts from cvs/rcs merges. - */ - if ((strncmp(line, "<<<<<<", 6) == 0) || - (strncmp(line, "======", 6) == 0) || - (strncmp(line, ">>>>>>", 6) == 0)) - Parse_Error(PARSE_FATAL, - "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); - else if (lstart[0] == '.') { - const char *dirstart = lstart + 1; - const char *dirend; - while (isspace((unsigned char)*dirstart)) - dirstart++; - dirend = dirstart; - while (isalnum((unsigned char)*dirend) || *dirend == '-') - dirend++; - Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", - (int)(dirend - dirstart), dirstart); - } else - Parse_Error(PARSE_FATAL, "Need an operator"); - goto out; + ParseErrorNoDependency(lstart); + return FALSE; } /* Insert a null terminator. */ savec = *cp; *cp = '\0'; - /* - * Got the word. See if it's a special target and if so set - * specType to match it. - */ - if (*line == '.' && isupper ((unsigned char)line[1])) { - /* - * See if the target is a special target that must have it - * or its sources handled specially. - */ - int keywd = ParseFindKeyword(line); - if (keywd != -1) { - if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { - Parse_Error(PARSE_FATAL, "Mismatched special targets"); - goto out; - } - - specType = parseKeywords[keywd].spec; - tOp = parseKeywords[keywd].op; - - /* - * Certain special targets have special semantics: - * .PATH Have to set the dirSearchPath - * variable too - * .MAIN Its sources are only used if - * nothing has been specified to - * create. - * .DEFAULT Need to create a node to hang - * commands on, but we don't want - * it in the graph, nor do we want - * it to be the Main Target, so we - * create it, set OP_NOTMAIN and - * add it to the list, setting - * DEFAULT to the new node for - * later use. We claim the node is - * A transformation rule to make - * life easier later, when we'll - * use Make_HandleUse to actually - * apply the .DEFAULT commands. - * .PHONY The list of targets - * .NOPATH Don't search for file in the path - * .STALE - * .BEGIN - * .END - * .ERROR - * .DELETE_ON_ERROR - * .INTERRUPT Are not to be considered the - * main target. - * .NOTPARALLEL Make only one target at a time. - * .SINGLESHELL Create a shell for each command. - * .ORDER Must set initial predecessor to NULL - */ - switch (specType) { - case ExPath: - if (paths == NULL) { - paths = Lst_Init(); - } - Lst_Append(paths, dirSearchPath); - break; - case Main: - if (!Lst_IsEmpty(create)) { - specType = Not; - } - break; - case Begin: - case End: - case Stale: - case dotError: - case Interrupt: - gn = Targ_FindNode(line, TARG_CREATE); - if (doing_depend) - ParseMark(gn); - gn->type |= OP_NOTMAIN|OP_SPECIAL; - Lst_Append(targets, gn); - break; - case Default: - gn = Targ_NewGN(".DEFAULT"); - gn->type |= (OP_NOTMAIN|OP_TRANSFORM); - Lst_Append(targets, gn); - DEFAULT = gn; - break; - case DeleteOnError: - deleteOnError = TRUE; - break; - case NotParallel: - maxJobs = 1; - break; - case SingleShell: - compatMake = TRUE; - break; - case Order: - predecessor = NULL; - break; - default: - break; - } - } else if (strncmp(line, ".PATH", 5) == 0) { - /* - * .PATH<suffix> has to be handled specially. - * Call on the suffix module to give us a path to - * modify. - */ - Lst path; - - specType = ExPath; - path = Suff_GetPath(&line[5]); - if (path == NULL) { - Parse_Error(PARSE_FATAL, - "Suffix '%s' not defined (yet)", - &line[5]); - goto out; - } else { - if (paths == NULL) { - paths = Lst_Init(); - } - Lst_Append(paths, path); - } - } - } + if (!ParseDoDependencyTarget(line, inout_specType, inout_tOp, + inout_paths)) + return FALSE; /* * Have word in line. Get or create its node and stick it at * the end of the targets list */ - if (specType == Not && *line != '\0') { - if (Dir_HasWildcards(line)) { - /* - * Targets are to be sought only in the current directory, - * so create an empty path for the thing. Note we need to - * use Dir_Destroy in the destruction of the path as the - * Dir module could have added a directory to the path... - */ - Lst emptyPath = Lst_Init(); - - Dir_Expand(line, emptyPath, curTargs); - - Lst_Destroy(emptyPath, Dir_Destroy); - } else { - /* - * No wildcards, but we want to avoid code duplication, - * so create a list with the word on it. - */ - Lst_Append(curTargs, line); - } - - /* Apply the targets. */ - - while(!Lst_IsEmpty(curTargs)) { - char *targName = Lst_Dequeue(curTargs); - - if (!Suff_IsTransform (targName)) { - gn = Targ_FindNode(targName, TARG_CREATE); - } else { - gn = Suff_AddTransform(targName); - } - if (doing_depend) - ParseMark(gn); - - Lst_Append(targets, gn); - } - } else if (specType == ExPath && *line != '.' && *line != '\0') { + if (*inout_specType == SP_NOT && *line != '\0') { + ParseDoDependencyTargetMundane(line, curTargs); + } else if (*inout_specType == SP_PATH && *line != '.' && *line != '\0') { Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); } @@ -1484,28 +1545,150 @@ ParseDoDependency(char *line) * If it is a special type and not .PATH, it's the only target we * allow on this line... */ - if (specType != Not && specType != ExPath) { - Boolean warning = FALSE; - - while (*cp && (ParseIsEscaped(lstart, cp) || - (*cp != '!' && *cp != ':'))) { - if (ParseIsEscaped(lstart, cp) || - (*cp != ' ' && *cp != '\t')) { - warning = TRUE; - } - cp++; + if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) { + ParseDoDependencyTargetExtraWarn(&cp, lstart); + } else { + pp_skip_whitespace(&cp); + } + line = cp; + if (*line == '\0') + break; + if ((*line == '!' || *line == ':') && !ParseIsEscaped(lstart, line)) + break; + } + + *inout_cp = cp; + *inout_line = line; + return TRUE; +} + +static void +ParseDoDependencySourcesSpecial(char *start, char *end, + ParseSpecial specType, SearchPathList *paths) +{ + char savec; + + while (*start) { + while (*end && !ch_isspace(*end)) + end++; + savec = *end; + *end = '\0'; + ParseDoDependencySourceSpecial(specType, start, paths); + *end = savec; + if (savec != '\0') + end++; + pp_skip_whitespace(&end); + start = end; + } +} + +static Boolean +ParseDoDependencySourcesMundane(char *start, char *end, + ParseSpecial specType, GNodeType tOp) +{ + while (*start) { + /* + * The targets take real sources, so we must beware of archive + * specifications (i.e. things with left parentheses in them) + * and handle them accordingly. + */ + for (; *end && !ch_isspace(*end); end++) { + if (*end == '(' && end > start && end[-1] != '$') { + /* + * Only stop for a left parenthesis if it isn't at the + * start of a word (that'll be for variable changes + * later) and isn't preceded by a dollar sign (a dynamic + * source). + */ + break; + } + } + + if (*end == '(') { + GNodeList *sources = Lst_New(); + if (!Arch_ParseArchive(&start, sources, VAR_CMDLINE)) { + Parse_Error(PARSE_FATAL, + "Error in source archive spec \"%s\"", start); + return FALSE; } - if (warning) { - Parse_Error(PARSE_WARNING, "Extra target ignored"); + + while (!Lst_IsEmpty(sources)) { + GNode *gn = Lst_Dequeue(sources); + ParseDoSrc(tOp, gn->name, specType); } + Lst_Free(sources); + end = start; } else { - while (*cp && isspace ((unsigned char)*cp)) { - cp++; + if (*end) { + *end = '\0'; + end++; } + + ParseDoSrc(tOp, start, specType); } - line = cp; - } while (*line && (ParseIsEscaped(lstart, line) || - (*line != '!' && *line != ':'))); + pp_skip_whitespace(&end); + start = end; + } + return TRUE; +} + +/* Parse a dependency line consisting of targets, followed by a dependency + * operator, optionally followed by sources. + * + * The nodes of the sources are linked as children to the nodes of the + * targets. Nodes are created as necessary. + * + * The operator is applied to each node in the global 'targets' list, + * which is where the nodes found for the targets are kept, by means of + * the ParseDoOp function. + * + * The sources are parsed in much the same way as the targets, except + * that they are expanded using the wildcarding scheme of the C-Shell, + * and all instances of the resulting words in the list of all targets + * are found. Each of the resulting nodes is then linked to each of the + * targets as one of its children. + * + * Certain targets and sources such as .PHONY or .PRECIOUS are handled + * specially. These are the ones detailed by the specType variable. + * + * The storing of transformation rules such as '.c.o' is also taken care of + * here. A target is recognized as a transformation rule by calling + * Suff_IsTransform. If it is a transformation rule, its node is gotten + * from the suffix module via Suff_AddTransform rather than the standard + * Targ_FindNode in the target module. + */ +static void +ParseDoDependency(char *line) +{ + char *cp; /* our current position */ + GNodeType op; /* the operator on the line */ + SearchPathList *paths; /* search paths to alter when parsing + * a list of .PATH targets */ + int tOp; /* operator from special target */ + StringList *curTargs; /* target names to be found and added + * to the targets list */ + char *lstart = line; + + /* + * specType contains the SPECial TYPE of the current target. It is SP_NOT + * if the target is unspecial. If it *is* special, however, the children + * are linked as children of the parent but not vice versa. + */ + ParseSpecial specType = SP_NOT; + + DEBUG1(PARSE, "ParseDoDependency(%s)\n", line); + tOp = 0; + + paths = NULL; + + curTargs = Lst_New(); + + /* + * First, grind through the targets. + */ + if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths, + curTargs)) + goto out; /* * Don't need the list of target names anymore... @@ -1513,58 +1696,21 @@ ParseDoDependency(char *line) Lst_Free(curTargs); curTargs = NULL; - if (targets != NULL && !Lst_IsEmpty(targets)) { - switch(specType) { - default: - Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); - break; - case Default: - case Stale: - case Begin: - case End: - case dotError: - case Interrupt: - /* - * These four create nodes on which to hang commands, so - * targets shouldn't be empty... - */ - case Not: - /* - * Nothing special here -- targets can be empty if it wants. - */ - break; - } - } + if (!Lst_IsEmpty(targets)) + ParseDoDependencyCheckSpec(specType); /* - * Have now parsed all the target names. Must parse the operator next. The - * result is left in op . + * Have now parsed all the target names. Must parse the operator next. */ - if (*cp == '!') { - op = OP_FORCE; - } else if (*cp == ':') { - if (cp[1] == ':') { - op = OP_DOUBLEDEP; - cp++; - } else { - op = OP_DEPENDS; - } - } else { - Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" - : "Missing dependency operator"); + if (!ParseDoDependencyParseOp(&cp, lstart, &op)) goto out; - } - - /* Advance beyond the operator */ - cp++; /* * Apply the operator to the target. This is how we remember which * operator a target was defined with. It fails if the operator * used isn't consistent across all references. */ - if (targets != NULL) - Lst_ForEach(targets, ParseDoOp, &op); + ApplyDependencyOperator(op); /* * Onward to the sources. @@ -1572,9 +1718,7 @@ ParseDoDependency(char *line) * LINE will now point to the first source word, if any, or the * end of the string if not. */ - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } + pp_skip_whitespace(&cp); line = cp; /* @@ -1587,33 +1731,8 @@ ParseDoDependency(char *line) * a .PATH removes all directories from the search path(s). */ if (!*line) { - switch (specType) { - case Suffixes: - Suff_ClearSuffixes(); - break; - case Precious: - allPrecious = TRUE; - break; - case Ignore: - ignoreErrors = TRUE; - break; - case Silent: - beSilent = TRUE; - break; - case ExPath: - if (paths != NULL) - Lst_ForEach(paths, ParseClearPath, NULL); - Dir_SetPATH(); - break; -#ifdef POSIX - case Posix: - Var_Set("%POSIX", "1003.2", VAR_GLOBAL); - break; -#endif - default: - break; - } - } else if (specType == MFlags) { + ParseDoDependencySourcesEmpty(specType, paths); + } else if (specType == SP_MFLAGS) { /* * Call on functions in main.c to deal with these arguments and * set the initial character to a null-character so the loop to @@ -1621,153 +1740,38 @@ ParseDoDependency(char *line) */ Main_ParseArgLine(line); *line = '\0'; - } else if (specType == ExShell) { + } else if (specType == SP_SHELL) { if (!Job_ParseShell(line)) { Parse_Error(PARSE_FATAL, "improper shell specification"); goto out; } *line = '\0'; - } else if (specType == NotParallel || specType == SingleShell || - specType == DeleteOnError) { + } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || + specType == SP_DELETE_ON_ERROR) { *line = '\0'; } /* * NOW GO FOR THE SOURCES */ - if (specType == Suffixes || specType == ExPath || - specType == Includes || specType == Libs || - specType == Null || specType == ExObjdir) + if (specType == SP_SUFFIXES || specType == SP_PATH || + specType == SP_INCLUDES || specType == SP_LIBS || + specType == SP_NULL || specType == SP_OBJDIR) { - while (*line) { - /* - * If the target was one that doesn't take files as its sources - * but takes something like suffixes, we take each - * space-separated word on the line as a something and deal - * with it accordingly. - * - * If the target was .SUFFIXES, we take each source as a - * suffix and add it to the list of suffixes maintained by the - * Suff module. - * - * If the target was a .PATH, we add the source as a directory - * to search on the search path. - * - * If it was .INCLUDES, the source is taken to be the suffix of - * files which will be #included and whose search path should - * be present in the .INCLUDES variable. - * - * If it was .LIBS, the source is taken to be the suffix of - * files which are considered libraries and whose search path - * should be present in the .LIBS variable. - * - * If it was .NULL, the source is the suffix to use when a file - * has no valid suffix. - * - * If it was .OBJDIR, the source is a new definition for .OBJDIR, - * and will cause make to do a new chdir to that path. - */ - while (*cp && !isspace ((unsigned char)*cp)) { - cp++; - } - savec = *cp; - *cp = '\0'; - switch (specType) { - case Suffixes: - Suff_AddSuffix(line, &mainNode); - break; - case ExPath: - if (paths != NULL) - Lst_ForEach(paths, ParseAddDir, line); - break; - case Includes: - Suff_AddInclude(line); - break; - case Libs: - Suff_AddLib(line); - break; - case Null: - Suff_SetNull(line); - break; - case ExObjdir: - Main_SetObjdir("%s", line); - break; - default: - break; - } - *cp = savec; - if (savec != '\0') { - cp++; - } - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - line = cp; - } + ParseDoDependencySourcesSpecial(line, cp, specType, paths); if (paths) { Lst_Free(paths); paths = NULL; } - if (specType == ExPath) + if (specType == SP_PATH) Dir_SetPATH(); } else { assert(paths == NULL); - while (*line) { - /* - * The targets take real sources, so we must beware of archive - * specifications (i.e. things with left parentheses in them) - * and handle them accordingly. - */ - for (; *cp && !isspace ((unsigned char)*cp); cp++) { - if (*cp == LPAREN && cp > line && cp[-1] != '$') { - /* - * Only stop for a left parenthesis if it isn't at the - * start of a word (that'll be for variable changes - * later) and isn't preceded by a dollar sign (a dynamic - * source). - */ - break; - } - } - - if (*cp == LPAREN) { - sources = Lst_Init(); - if (!Arch_ParseArchive(&line, sources, VAR_CMD)) { - Parse_Error(PARSE_FATAL, - "Error in source archive spec \"%s\"", line); - goto out; - } - - while (!Lst_IsEmpty(sources)) { - gn = Lst_Dequeue(sources); - ParseDoSrc(tOp, gn->name); - } - Lst_Free(sources); - cp = line; - } else { - if (*cp) { - *cp = '\0'; - cp += 1; - } - - ParseDoSrc(tOp, line); - } - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - line = cp; - } + if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp)) + goto out; } - if (mainNode == NULL && targets != NULL) { - /* - * If we have yet to decide on a main target to make, in the - * absence of any user input, we want the first target on - * the first dependency line that is actually a real target - * (i.e. isn't a .USE or .EXEC rule) to be made. - */ - Lst_ForEach(targets, ParseFindMain, NULL); - } + FindMainTarget(); out: if (paths != NULL) @@ -1776,42 +1780,94 @@ out: Lst_Free(curTargs); } -/*- - *--------------------------------------------------------------------- - * Parse_IsVar -- - * Return TRUE if the passed line is a variable assignment. A variable - * assignment consists of a single word followed by optional whitespace - * followed by either a += or an = operator. - * This function is used both by the Parse_File function and main when - * parsing the command-line arguments. - * - * Input: - * line the line to check +typedef struct VarAssignParsed { + const char *nameStart; /* unexpanded */ + const char *nameEnd; /* before operator adjustment */ + const char *eq; /* the '=' of the assignment operator */ +} VarAssignParsed; + +/* Determine the assignment operator and adjust the end of the variable + * name accordingly. */ +static void +AdjustVarassignOp(const VarAssignParsed *pvar, const char *value, + VarAssign *out_var) +{ + const char *op = pvar->eq; + const char * const name = pvar->nameStart; + VarAssignOp type; + + if (op > name && op[-1] == '+') { + type = VAR_APPEND; + op--; + + } else if (op > name && op[-1] == '?') { + op--; + type = VAR_DEFAULT; + + } else if (op > name && op[-1] == ':') { + op--; + type = VAR_SUBST; + + } else if (op > name && op[-1] == '!') { + op--; + type = VAR_SHELL; + + } else { + type = VAR_NORMAL; +#ifdef SUNSHCMD + while (op > name && ch_isspace(op[-1])) + op--; + + if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' && op[-1] == 'h') { + type = VAR_SHELL; + op -= 3; + } +#endif + } + + { + const char *nameEnd = pvar->nameEnd < op ? pvar->nameEnd : op; + out_var->varname = bmake_strsedup(pvar->nameStart, nameEnd); + out_var->op = type; + out_var->value = value; + } +} + +/* Parse a variable assignment, consisting of a single-word variable name, + * optional whitespace, an assignment operator, optional whitespace and the + * variable value. * - * Results: - * TRUE if it is. FALSE if it ain't + * Note: There is a lexical ambiguity with assignment modifier characters + * in variable names. This routine interprets the character before the = + * as a modifier. Therefore, an assignment like + * C++=/usr/bin/CC + * is interpreted as "C+ +=" instead of "C++ =". * - * Side Effects: - * none - *--------------------------------------------------------------------- - */ + * Used for both lines in a file and command line arguments. */ Boolean -Parse_IsVar(char *line) +Parse_IsVar(const char *p, VarAssign *out_var) { - Boolean wasSpace = FALSE; /* set TRUE if found a space */ + VarAssignParsed pvar; + const char *firstSpace = NULL; char ch; int level = 0; -#define ISEQOPERATOR(c) \ - (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!')) - /* - * Skip to variable name - */ - while (*line == ' ' || *line == '\t') - line++; + /* Skip to variable name */ + while (*p == ' ' || *p == '\t') + p++; + + /* During parsing, the '+' of the '+=' operator is initially parsed + * as part of the variable name. It is later corrected, as is the ':sh' + * modifier. Of these two (nameEnd and op), the earlier one determines the + * actual end of the variable name. */ + pvar.nameStart = p; +#ifdef CLEANUP + pvar.nameEnd = NULL; + pvar.eq = NULL; +#endif /* Scan for one of the assignment operators outside a variable expansion */ - while ((ch = *line++) != 0) { + while ((ch = *p++) != 0) { if (ch == '(' || ch == '{') { level++; continue; @@ -1820,233 +1876,200 @@ Parse_IsVar(char *line) level--; continue; } + if (level != 0) continue; - while (ch == ' ' || ch == '\t') { - ch = *line++; - wasSpace = TRUE; - } + + if (ch == ' ' || ch == '\t') + if (firstSpace == NULL) + firstSpace = p - 1; + while (ch == ' ' || ch == '\t') + ch = *p++; + #ifdef SUNSHCMD - if (ch == ':' && strncmp(line, "sh", 2) == 0) { - line += 2; + if (ch == ':' && strncmp(p, "sh", 2) == 0) { + p += 2; continue; } #endif - if (ch == '=') + if (ch == '=') { + pvar.eq = p - 1; + pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1; + cpp_skip_whitespace(&p); + AdjustVarassignOp(&pvar, p, out_var); return TRUE; - if (*line == '=' && ISEQOPERATOR(ch)) + } + if (*p == '=' && (ch == '+' || ch == ':' || ch == '?' || ch == '!')) { + pvar.eq = p; + pvar.nameEnd = firstSpace != NULL ? firstSpace : p; + p++; + cpp_skip_whitespace(&p); + AdjustVarassignOp(&pvar, p, out_var); return TRUE; - if (wasSpace) + } + if (firstSpace != NULL) return FALSE; } return FALSE; } -/*- - *--------------------------------------------------------------------- - * Parse_DoVar -- - * Take the variable assignment in the passed line and do it in the - * global context. - * - * Note: There is a lexical ambiguity with assignment modifier characters - * in variable names. This routine interprets the character before the = - * as a modifier. Therefore, an assignment like - * C++=/usr/bin/CC - * is interpreted as "C+ +=" instead of "C++ =". - * - * Input: - * line a line guaranteed to be a variable assignment. - * This reduces error checks - * ctxt Context in which to do the assignment - * - * Results: - * none - * - * Side Effects: - * the variable structure of the given variable name is altered in the - * global context. - *--------------------------------------------------------------------- - */ -void -Parse_DoVar(char *line, GNode *ctxt) -{ - char *cp; /* pointer into line */ - enum { - VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL - } type; /* Type of assignment */ - char *opc; /* ptr to operator character to - * null-terminate the variable name */ - Boolean freeCp = FALSE; /* TRUE if cp needs to be freed, - * i.e. if any variable expansion was - * performed */ - int depth; - - /* - * Skip to variable name - */ - while (*line == ' ' || *line == '\t') - line++; - - /* - * Skip to operator character, nulling out whitespace as we go - * XXX Rather than counting () and {} we should look for $ and - * then expand the variable. - */ - for (depth = 0, cp = line; depth > 0 || *cp != '='; cp++) { - if (*cp == '(' || *cp == '{') { - depth++; - continue; - } - if (*cp == ')' || *cp == '}') { - depth--; - continue; - } - if (depth == 0 && isspace ((unsigned char)*cp)) { - *cp = '\0'; +static void +VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt) +{ + if (DEBUG(LINT)) { + if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { + /* Check for syntax errors such as unclosed expressions or + * unknown modifiers. */ + char *expandedValue; + + (void)Var_Subst(uvalue, ctxt, VARE_NONE, &expandedValue); + /* TODO: handle errors */ + free(expandedValue); } } - opc = cp-1; /* operator is the previous character */ - *cp++ = '\0'; /* nuke the = */ +} +static void +VarAssign_EvalSubst(const char *name, const char *uvalue, GNode *ctxt, + const char **out_avalue, void **out_avalue_freeIt) +{ + const char *avalue = uvalue; + char *evalue; /* - * Check operator type + * Allow variables in the old value to be undefined, but leave their + * expressions alone -- this is done by forcing oldVars to be false. + * XXX: This can cause recursive variables, but that's not hard to do, + * and this allows someone to do something like + * + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * + * And not get an error. */ - switch (*opc) { - case '+': - type = VAR_APPEND; - *opc = '\0'; - break; - - case '?': - /* - * If the variable already has a value, we don't do anything. - */ - *opc = '\0'; - if (Var_Exists(line, ctxt)) { - return; - } else { - type = VAR_NORMAL; - } - break; - - case ':': - type = VAR_SUBST; - *opc = '\0'; - break; - - case '!': - type = VAR_SHELL; - *opc = '\0'; - break; + Boolean oldOldVars = oldVars; - default: -#ifdef SUNSHCMD - while (opc > line && *opc != ':') - opc--; + oldVars = FALSE; - if (strncmp(opc, ":sh", 3) == 0) { - type = VAR_SHELL; - *opc = '\0'; - break; - } -#endif - type = VAR_NORMAL; - break; - } + /* + * make sure that we set the variable the first time to nothing + * so that it gets substituted! + */ + if (!Var_Exists(name, ctxt)) + Var_Set(name, "", ctxt); - while (isspace((unsigned char)*cp)) - cp++; + (void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_ASSIGN, &evalue); + /* TODO: handle errors */ + oldVars = oldOldVars; + avalue = evalue; + Var_Set(name, avalue, ctxt); - if (DEBUG(LINT)) { - if (type != VAR_SUBST && strchr(cp, '$') != NULL) { - /* sanity check now */ - char *cp2; + *out_avalue = avalue; + *out_avalue_freeIt = evalue; +} - cp2 = Var_Subst(cp, ctxt, VARE_ASSIGN); - free(cp2); - } +static void +VarAssign_EvalShell(const char *name, const char *uvalue, GNode *ctxt, + const char **out_avalue, void **out_avalue_freeIt) +{ + const char *cmd, *errfmt; + char *cmdOut; + void *cmd_freeIt = NULL; + + cmd = uvalue; + if (strchr(cmd, '$') != NULL) { + char *ecmd; + (void)Var_Subst(cmd, VAR_CMDLINE, VARE_UNDEFERR | VARE_WANTRES, &ecmd); + /* TODO: handle errors */ + cmd = cmd_freeIt = ecmd; } - if (type == VAR_APPEND) { - Var_Append(line, cp, ctxt); - } else if (type == VAR_SUBST) { - /* - * Allow variables in the old value to be undefined, but leave their - * invocation alone -- this is done by forcing oldVars to be false. - * XXX: This can cause recursive variables, but that's not hard to do, - * and this allows someone to do something like - * - * CFLAGS = $(.INCLUDES) - * CFLAGS := -I.. $(CFLAGS) - * - * And not get an error. - */ - Boolean oldOldVars = oldVars; - - oldVars = FALSE; - - /* - * make sure that we set the variable the first time to nothing - * so that it gets substituted! - */ - if (!Var_Exists(line, ctxt)) - Var_Set(line, "", ctxt); + cmdOut = Cmd_Exec(cmd, &errfmt); + Var_Set(name, cmdOut, ctxt); + *out_avalue = *out_avalue_freeIt = cmdOut; - cp = Var_Subst(cp, ctxt, VARE_WANTRES|VARE_ASSIGN); - oldVars = oldOldVars; - freeCp = TRUE; + if (errfmt) + Parse_Error(PARSE_WARNING, errfmt, cmd); - Var_Set(line, cp, ctxt); - } else if (type == VAR_SHELL) { - char *res; - const char *error; + free(cmd_freeIt); +} - if (strchr(cp, '$') != NULL) { - /* - * There's a dollar sign in the command, so perform variable - * expansion on the whole thing. The resulting string will need - * freeing when we're done. - */ - cp = Var_Subst(cp, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES); - freeCp = TRUE; +/* Perform a variable assignment. + * + * The actual value of the variable is returned in *out_avalue and + * *out_avalue_freeIt. Especially for VAR_SUBST and VAR_SHELL this can differ + * from the literal value. + * + * Return whether the assignment was actually done. The assignment is only + * skipped if the operator is '?=' and the variable already exists. */ +static Boolean +VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, + GNode *ctxt, const char **out_avalue, void **out_avalue_freeIt) +{ + const char *avalue = uvalue; + void *avalue_freeIt = NULL; + + if (op == VAR_APPEND) { + Var_Append(name, uvalue, ctxt); + } else if (op == VAR_SUBST) { + VarAssign_EvalSubst(name, uvalue, ctxt, &avalue, &avalue_freeIt); + } else if (op == VAR_SHELL) { + VarAssign_EvalShell(name, uvalue, ctxt, &avalue, &avalue_freeIt); + } else { + if (op == VAR_DEFAULT && Var_Exists(name, ctxt)) { + *out_avalue_freeIt = NULL; + return FALSE; } - res = Cmd_Exec(cp, &error); - Var_Set(line, res, ctxt); - free(res); - - if (error) - Parse_Error(PARSE_WARNING, error, cp); - } else { - /* - * Normal assignment -- just do it. - */ - Var_Set(line, cp, ctxt); + /* Normal assignment -- just do it. */ + Var_Set(name, uvalue, ctxt); } - if (strcmp(line, MAKEOVERRIDES) == 0) + + *out_avalue = avalue; + *out_avalue_freeIt = avalue_freeIt; + return TRUE; +} + +static void +VarAssignSpecial(const char *name, const char *avalue) +{ + if (strcmp(name, MAKEOVERRIDES) == 0) Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ - else if (strcmp(line, ".CURDIR") == 0) { + else if (strcmp(name, ".CURDIR") == 0) { /* - * Somone is being (too?) clever... + * Someone is being (too?) clever... * Let's pretend they know what they are doing and - * re-initialize the 'cur' Path. + * re-initialize the 'cur' CachedDir. */ - Dir_InitCur(cp); + Dir_InitCur(avalue); Dir_SetPATH(); - } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) { + } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) { Job_SetPrefix(); - } else if (strcmp(line, MAKE_EXPORTED) == 0) { - Var_Export(cp, FALSE); + } else if (strcmp(name, MAKE_EXPORTED) == 0) { + Var_Export(avalue, FALSE); } - if (freeCp) - free(cp); +} + +/* Perform the variable variable assignment in the given context. */ +void +Parse_DoVar(VarAssign *var, GNode *ctxt) +{ + const char *avalue; /* actual value (maybe expanded) */ + void *avalue_freeIt; + + VarCheckSyntax(var->op, var->value, ctxt); + if (VarAssign_Eval(var->varname, var->op, var->value, ctxt, + &avalue, &avalue_freeIt)) + VarAssignSpecial(var->varname, avalue); + + free(avalue_freeIt); + free(var->varname); } /* * ParseMaybeSubMake -- - * Scan the command string to see if it a possible submake node + * Scan the command string to see if it a possible submake node * Input: * cmd the command to scan * Results: @@ -2067,41 +2090,27 @@ ParseMaybeSubMake(const char *cmd) MKV("$(.MAKE)"), MKV("make"), }; - for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) { + for (i = 0; i < sizeof vals / sizeof vals[0]; i++) { char *ptr; if ((ptr = strstr(cmd, vals[i].name)) == NULL) continue; - if ((ptr == cmd || !isalnum((unsigned char)ptr[-1])) - && !isalnum((unsigned char)ptr[vals[i].len])) + if ((ptr == cmd || !ch_isalnum(ptr[-1])) + && !ch_isalnum(ptr[vals[i].len])) return TRUE; } return FALSE; } -/*- - * ParseAddCmd -- - * Lst_ForEach function to add a command line to all targets - * - * Input: - * gnp the node to which the command is to be added - * cmd the command to add +/* Append the command to the target node. * - * Results: - * Always 0 - * - * Side Effects: - * A new element is added to the commands list of the node, - * and the node can be marked as a submake node if the command is - * determined to be that. - */ -static int -ParseAddCmd(void *gnp, void *cmd) + * The node may be marked as a submake node if the command is determined to + * be that. */ +static void +ParseAddCmd(GNode *gn, char *cmd) { - GNode *gn = (GNode *)gnp; - /* Add to last (ie current) cohort for :: targets */ - if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(gn->cohorts)) - gn = LstNode_Datum(Lst_Last(gn->cohorts)); + if ((gn->type & OP_DOUBLEDEP) && gn->cohorts->last != NULL) + gn = gn->cohorts->last->datum; /* if target already supplied, ignore commands */ if (!(gn->type & OP_HAS_COMMANDS)) { @@ -2110,7 +2119,7 @@ ParseAddCmd(void *gnp, void *cmd) gn->type |= OP_SUBMAKE; ParseMark(gn); } else { -#ifdef notyet +#if 0 /* XXX: We cannot do this until we fix the tree */ Lst_Append(gn->commands, cmd); Parse_Error(PARSE_WARNING, @@ -2119,95 +2128,39 @@ ParseAddCmd(void *gnp, void *cmd) 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, - "using previous script for \"%s\" defined here", - gn->name); + "duplicate script for target \"%s\" ignored", + gn->name); + ParseErrorInternal(gn->fname, (size_t)gn->lineno, PARSE_WARNING, + "using previous script for \"%s\" defined here", + gn->name); #endif } - return 0; } -/*- - *----------------------------------------------------------------------- - * ParseHasCommands -- - * Callback procedure for Parse_File when destroying the list of - * targets on the last dependency line. Marks a target as already - * having commands if it does, to keep from having shell commands - * on multiple dependency lines. - * - * Input: - * gnp Node to examine - * - * Results: - * None - * - * Side Effects: - * OP_HAS_COMMANDS may be set for the target. - * - *----------------------------------------------------------------------- - */ -static void -ParseHasCommands(void *gnp) -{ - GNode *gn = (GNode *)gnp; - if (!Lst_IsEmpty(gn->commands)) { - gn->type |= OP_HAS_COMMANDS; - } -} - -/*- - *----------------------------------------------------------------------- - * Parse_AddIncludeDir -- - * Add a directory to the path searched for included makefiles - * bracketed by double-quotes. Used by functions in main.c - * - * Input: - * dir The name of the directory to add - * - * Results: - * None. - * - * Side Effects: - * The directory is appended to the list. - * - *----------------------------------------------------------------------- - */ +/* Add a directory to the path searched for included makefiles bracketed + * by double-quotes. */ void -Parse_AddIncludeDir(char *dir) +Parse_AddIncludeDir(const char *dir) { (void)Dir_AddDir(parseIncPath, dir); } -/*- - *--------------------------------------------------------------------- - * ParseDoInclude -- - * Push to another file. - * - * The input is the line minus the `.'. A file spec is a string - * enclosed in <> or "". The former is looked for only in sysIncPath. - * The latter in . and the directories specified by -I command line - * options - * - * Results: - * None +/* Push to another file. * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFILE are altered for the new file - *--------------------------------------------------------------------- + * The input is the line minus the '.'. A file spec is a string enclosed in + * <> or "". The <> file is looked for only in sysIncPath. The "" file is + * first searched in the parsedir and then in the directories specified by + * the -I command line options. */ - static void Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) { struct loadedfile *lf; - char *fullname; /* full pathname of file */ - char *newName; - char *prefEnd, *incdir; - int fd; - int i; + char *fullname; /* full pathname of file */ + char *newName; + char *prefEnd, *incdir; + int fd; + int i; /* * Now we know the file's name and its search path, we attempt to @@ -2225,7 +2178,7 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) * we can locate the beast. */ - incdir = bmake_strdup(curFile->fname); + incdir = bmake_strdup(CurFile()->fname); prefEnd = strrchr(incdir, '/'); if (prefEnd != NULL) { *prefEnd = '\0'; @@ -2252,7 +2205,7 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) * If we have a suffix specific path we should use that. */ char *suff; - Lst suffPath = NULL; + SearchPath *suffPath = NULL; if ((suff = strrchr(file, '.'))) { suffPath = Suff_GetPath(suff); @@ -2274,8 +2227,8 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) /* * Look for it on the system path */ - fullname = Dir_FindFile(file, - Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); + SearchPath *path = Lst_IsEmpty(sysIncPath) ? defSysIncPath : sysIncPath; + fullname = Dir_FindFile(file, path); } if (fullname == NULL) { @@ -2296,21 +2249,20 @@ Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) /* load it */ lf = loadfile(fullname, fd); - ParseSetIncludedFile(); /* Start reading from this file next */ Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf); - curFile->lf = lf; + CurFile()->lf = lf; if (depinc) - doing_depend = depinc; /* only turn it on */ + doing_depend = depinc; /* only turn it on */ } static void ParseDoInclude(char *line) { - char endc; /* the character which ends the file spec */ - char *cp; /* current position in file spec */ - int silent = *line != 'i'; - char *file = &line[7 + silent]; + char endc; /* the character which ends the file spec */ + char *cp; /* current position in file spec */ + int silent = *line != 'i'; + char *file = line + (silent ? 8 : 7); /* Skip to delimiter character so we know where to look */ while (*file == ' ' || *file == '\t') @@ -2318,7 +2270,7 @@ ParseDoInclude(char *line) if (*file != '"' && *file != '<') { Parse_Error(PARSE_FATAL, - ".include filename must be delimited by '\"' or '<'"); + ".include filename must be delimited by '\"' or '<'"); return; } @@ -2339,8 +2291,8 @@ ParseDoInclude(char *line) if (*cp != endc) { Parse_Error(PARSE_FATAL, - "Unclosed %cinclude filename. '%c' expected", - '.', endc); + "Unclosed %cinclude filename. '%c' expected", + '.', endc); return; } *cp = '\0'; @@ -2349,153 +2301,147 @@ ParseDoInclude(char *line) * Substitute for any variables in the file name before trying to * find the thing. */ - file = Var_Subst(file, VAR_CMD, VARE_WANTRES); + (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &file); + /* TODO: handle errors */ Parse_include_file(file, endc == '>', *line == 'd', silent); free(file); } - -/*- - *--------------------------------------------------------------------- - * ParseSetIncludedFile -- - * Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE - * and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR - * - * Results: - * None - * - * Side Effects: - * The .INCLUDEDFROMFILE variable is overwritten by the contents - * of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten - * by the contents of .PARSEDIR - *--------------------------------------------------------------------- - */ +/* Split filename into dirname + basename, then assign these to the + * given variables. */ static void -ParseSetIncludedFile(void) +SetFilenameVars(const char *filename, const char *dirvar, const char *filevar) { - const char *pf, *pd; - char *pf_freeIt, *pd_freeIt; + const char *slash, *dirname, *basename; + void *freeIt; - pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &pf_freeIt); - Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL); - pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &pd_freeIt); - Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL); + slash = strrchr(filename, '/'); + if (slash == NULL) { + dirname = curdir; + basename = filename; + freeIt = NULL; + } else { + dirname = freeIt = bmake_strsedup(filename, slash); + basename = slash + 1; + } - if (DEBUG(PARSE)) - fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' " - "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf); + Var_Set(dirvar, dirname, VAR_GLOBAL); + Var_Set(filevar, basename, VAR_GLOBAL); - bmake_free(pf_freeIt); - bmake_free(pd_freeIt); + DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n", + __func__, dirvar, dirname, filevar, basename); + free(freeIt); } -/*- - *--------------------------------------------------------------------- - * ParseSetParseFile -- - * Set the .PARSEDIR and .PARSEFILE variables to the dirname and - * basename of the given filename - * - * Results: - * None + +/* Return the immediately including file. * - * Side Effects: - * The .PARSEDIR and .PARSEFILE variables are overwritten by the - * dirname and basename of the given filename. - *--------------------------------------------------------------------- - */ + * This is made complicated since the .for loop is implemented as a special + * kind of .include; see For_Run. */ +static const char * +GetActuallyIncludingFile(void) +{ + size_t i; + const IFile *incs = GetInclude(0); + + for (i = includes.len; i >= 2; i--) + if (!incs[i - 1].fromForLoop) + return incs[i - 2].fname; + return NULL; +} + +/* Set .PARSEDIR, .PARSEFILE, .INCLUDEDFROMDIR and .INCLUDEDFROMFILE. */ static void ParseSetParseFile(const char *filename) { - char *slash, *dirname; - const char *pd, *pf; + const char *including; - slash = strrchr(filename, '/'); - if (slash == NULL) { - Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL); - Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL); - dirname = NULL; + SetFilenameVars(filename, ".PARSEDIR", ".PARSEFILE"); + + including = GetActuallyIncludingFile(); + if (including != NULL) { + SetFilenameVars(including, + ".INCLUDEDFROMDIR", ".INCLUDEDFROMFILE"); } else { - dirname = bmake_strsedup(filename, slash); - Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL); - Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL); + Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); + Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); } - if (DEBUG(PARSE)) - fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n", - __func__, pd, pf); - free(dirname); } -/* - * Track the makefiles we read - so makefiles can - * set dependencies on them. - * Avoid adding anything more than once. - */ +static Boolean +StrContainsWord(const char *str, const char *word) +{ + size_t strLen = strlen(str); + size_t wordLen = strlen(word); + const char *p, *end; + + if (strLen < wordLen) + return FALSE; /* str is too short to contain word */ + + end = str + strLen - wordLen; + for (p = str; p != NULL; p = strchr(p, ' ')) { + if (*p == ' ') + p++; + if (p > end) + return FALSE; /* cannot contain word */ + + if (memcmp(p, word, wordLen) == 0 && + (p[wordLen] == '\0' || p[wordLen] == ' ')) + return TRUE; + } + return FALSE; +} +/* XXX: Searching through a set of words with this linear search is + * inefficient for variables that contain thousands of words. */ +static Boolean +VarContainsWord(const char *varname, const char *word) +{ + void *val_freeIt; + const char *val = Var_Value(varname, VAR_GLOBAL, &val_freeIt); + Boolean found = val != NULL && StrContainsWord(val, word); + bmake_free(val_freeIt); + return found; +} + +/* Track the makefiles we read - so makefiles can set dependencies on them. + * Avoid adding anything more than once. */ static void ParseTrackInput(const char *name) { - char *fp = NULL; - - const char *old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp); - if (old) { - size_t name_len = strlen(name); - const char *ep = old + strlen(old) - name_len; - /* does it contain name? */ - for (; old != NULL; old = strchr(old, ' ')) { - if (*old == ' ') - old++; - if (old >= ep) - break; /* cannot contain name */ - if (memcmp(old, name, name_len) == 0 - && (old[name_len] == 0 || old[name_len] == ' ')) - goto cleanup; - } - } - Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL); - cleanup: - bmake_free(fp); + if (!VarContainsWord(MAKE_MAKEFILES, name)) + Var_Append(MAKE_MAKEFILES, name, VAR_GLOBAL); } -/*- - *--------------------------------------------------------------------- - * Parse_setInput -- - * Start Parsing from the given source +/* Start Parsing from the given source. * - * Results: - * None - * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFile are altered for the new file - *--------------------------------------------------------------------- - */ + * The given file is added to the includes stack. */ void Parse_SetInput(const char *name, int line, int fd, - char *(*nextbuf)(void *, size_t *), void *arg) + char *(*nextbuf)(void *, size_t *), void *arg) { + IFile *curFile; char *buf; size_t len; + Boolean fromForLoop = name == NULL; - if (name == NULL) - name = curFile->fname; + if (fromForLoop) + name = CurFile()->fname; else ParseTrackInput(name); if (DEBUG(PARSE)) - fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n", - __func__, name, line, fd, nextbuf, arg); + debug_printf("%s: file %s, line %d, fd %d, nextbuf %s, arg %p\n", + __func__, name, line, fd, + nextbuf == loadedfile_nextbuf ? "loadedfile" : "other", + arg); if (fd == -1 && nextbuf == NULL) /* sanity */ return; - if (curFile != NULL) - /* Save exiting file info */ - Lst_Prepend(includes, curFile); - - /* Allocate and fill in new structure */ - curFile = bmake_malloc(sizeof *curFile); + curFile = Vector_Push(&includes); /* * Once the previous state has been saved, we can get down to reading @@ -2504,6 +2450,7 @@ Parse_SetInput(const char *name, int line, int fd, * place. */ curFile->fname = bmake_strdup(name); + curFile->fromForLoop = fromForLoop; curFile->lineno = line; curFile->first_lineno = line; curFile->nextbuf = nextbuf; @@ -2522,58 +2469,31 @@ Parse_SetInput(const char *name, int line, int fd, free(curFile); return; } - curFile->P_str = buf; - curFile->P_ptr = buf; - curFile->P_end = buf+len; + curFile->buf_freeIt = buf; + curFile->buf_ptr = buf; + curFile->buf_end = buf + len; curFile->cond_depth = Cond_save_depth(); ParseSetParseFile(name); } -/*- - *----------------------------------------------------------------------- - * IsInclude -- - * Check if the line is an include directive - * - * Results: - * TRUE if it is. - * - * Side Effects: - * None - * - *----------------------------------------------------------------------- - */ +/* Check if the directive is an include directive. */ static Boolean -IsInclude(const char *line, Boolean sysv) +IsInclude(const char *dir, Boolean sysv) { - static const char inc[] = "include"; - static const size_t inclen = sizeof(inc) - 1; - - /* 'd' is not valid for sysv */ - int o = strchr(sysv ? "s-" : "ds-", *line) != NULL; + if (dir[0] == 's' || dir[0] == '-' || (dir[0] == 'd' && !sysv)) + dir++; - if (strncmp(line + o, inc, inclen) != 0) + if (strncmp(dir, "include", 7) != 0) return FALSE; /* Space is not mandatory for BSD .include */ - return !sysv || isspace((unsigned char)line[inclen + o]); + return !sysv || ch_isspace(dir[7]); } #ifdef SYSVINCLUDE -/*- - *----------------------------------------------------------------------- - * IsSysVInclude -- - * Check if the line is a SYSV include directive - * - * Results: - * TRUE if it is. - * - * Side Effects: - * None - * - *----------------------------------------------------------------------- - */ +/* Check if the line is a SYSV include directive. */ static Boolean IsSysVInclude(const char *line) { @@ -2582,13 +2502,13 @@ IsSysVInclude(const char *line) if (!IsInclude(line, TRUE)) return FALSE; - /* Avoid interpeting a dependency line as an include */ + /* Avoid interpreting a dependency line as an include */ for (p = line; (p = strchr(p, ':')) != NULL;) { if (*++p == '\0') { /* end of line -> dependency */ return FALSE; } - if (*p == ':' || isspace((unsigned char)*p)) { + if (*p == ':' || ch_isspace(*p)) { /* :: operator or ': ' -> dependency */ return FALSE; } @@ -2596,56 +2516,35 @@ IsSysVInclude(const char *line) return TRUE; } -/*- - *--------------------------------------------------------------------- - * ParseTraditionalInclude -- - * Push to another file. - * - * The input is the current line. The file name(s) are - * following the "include". - * - * Results: - * None - * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFILE are altered for the new file - *--------------------------------------------------------------------- - */ +/* Push to another file. The line points to the word "include". */ static void ParseTraditionalInclude(char *line) { - char *cp; /* current position in file spec */ - int done = 0; - int silent = line[0] != 'i'; - char *file = &line[silent + 7]; - char *all_files; + char *cp; /* current position in file spec */ + int done = 0; + int silent = line[0] != 'i'; + char *file = line + (silent ? 8 : 7); + char *all_files; - if (DEBUG(PARSE)) { - fprintf(debug_file, "%s: %s\n", __func__, file); - } + DEBUG2(PARSE, "%s: %s\n", __func__, file); - /* - * Skip over whitespace - */ - while (isspace((unsigned char)*file)) - file++; + pp_skip_whitespace(&file); /* * Substitute for any variables in the file name before trying to * find the thing. */ - all_files = Var_Subst(file, VAR_CMD, VARE_WANTRES); + (void)Var_Subst(file, VAR_CMDLINE, VARE_WANTRES, &all_files); + /* TODO: handle errors */ if (*file == '\0') { - Parse_Error(PARSE_FATAL, - "Filename missing from \"include\""); + Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); goto out; } for (file = all_files; !done; file = cp + 1) { /* Skip to end of line or next whitespace */ - for (cp = file; *cp && !isspace((unsigned char) *cp); cp++) + for (cp = file; *cp && !ch_isspace(*cp); cp++) continue; if (*cp) @@ -2661,130 +2560,104 @@ out: #endif #ifdef GMAKEEXPORT -/*- - *--------------------------------------------------------------------- - * ParseGmakeExport -- - * Parse export <variable>=<value> - * - * And set the environment with it. - * - * Results: - * None - * - * Side Effects: - * None - *--------------------------------------------------------------------- - */ +/* Parse "export <variable>=<value>", and actually export it. */ static void ParseGmakeExport(char *line) { - char *variable = &line[6]; - char *value; + char *variable = line + 6; + char *value; - if (DEBUG(PARSE)) { - fprintf(debug_file, "%s: %s\n", __func__, variable); - } + DEBUG2(PARSE, "%s: %s\n", __func__, variable); - /* - * Skip over whitespace - */ - while (isspace((unsigned char)*variable)) - variable++; + pp_skip_whitespace(&variable); for (value = variable; *value && *value != '='; value++) continue; if (*value != '=') { Parse_Error(PARSE_FATAL, - "Variable/Value missing from \"export\""); + "Variable/Value missing from \"export\""); return; } - *value++ = '\0'; /* terminate variable */ + *value++ = '\0'; /* terminate variable */ /* * Expand the value before putting it in the environment. */ - value = Var_Subst(value, VAR_CMD, VARE_WANTRES); + (void)Var_Subst(value, VAR_CMDLINE, VARE_WANTRES, &value); + /* TODO: handle errors */ + setenv(variable, value, 1); free(value); } #endif -/*- - *--------------------------------------------------------------------- - * ParseEOF -- - * Called when EOF is reached in the current file. If we were reading - * an include file, the includes stack is popped and things set up - * to go back to reading the previous file at the previous location. +/* Called when EOF is reached in the current file. If we were reading an + * include file, the includes stack is popped and things set up to go back + * to reading the previous file at the previous location. * * Results: - * CONTINUE if there's more to do. DONE if not. - * - * Side Effects: - * The old curFILE, is closed. The includes list is shortened. - * lineno, curFILE, and fname are changed if CONTINUE is returned. - *--------------------------------------------------------------------- + * TRUE to continue parsing, i.e. it had only reached the end of an + * included file, FALSE if the main file has been parsed completely. */ -static int +static Boolean ParseEOF(void) { char *ptr; size_t len; + IFile *curFile = CurFile(); assert(curFile->nextbuf != NULL); doing_depend = curFile->depending; /* restore this */ /* get next input buffer, if any */ ptr = curFile->nextbuf(curFile->nextbuf_arg, &len); - curFile->P_ptr = ptr; - curFile->P_str = ptr; - curFile->P_end = ptr + len; + curFile->buf_ptr = ptr; + curFile->buf_freeIt = ptr; + curFile->buf_end = ptr + len; curFile->lineno = curFile->first_lineno; if (ptr != NULL) { /* Iterate again */ - return CONTINUE; + return TRUE; } /* Ensure the makefile (or loop) didn't have mismatched conditionals */ Cond_restore_depth(curFile->cond_depth); if (curFile->lf != NULL) { - loadedfile_destroy(curFile->lf); - curFile->lf = NULL; + loadedfile_destroy(curFile->lf); + curFile->lf = NULL; } /* Dispose of curFile info */ /* Leak curFile->fname because all the gnodes have pointers to it */ - free(curFile->P_str); - free(curFile); + free(curFile->buf_freeIt); + Vector_Pop(&includes); - if (Lst_IsEmpty(includes)) { - curFile = NULL; + if (includes.len == 0) { /* We've run out of input */ Var_Delete(".PARSEDIR", VAR_GLOBAL); Var_Delete(".PARSEFILE", VAR_GLOBAL); Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); - return DONE; + return FALSE; } - curFile = Lst_Dequeue(includes); - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n", - curFile->fname, curFile->lineno); + curFile = CurFile(); + DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n", + curFile->fname, curFile->lineno); - /* Restore the PARSEDIR/PARSEFILE variables */ ParseSetParseFile(curFile->fname); - return CONTINUE; + return TRUE; } #define PARSE_RAW 1 #define PARSE_SKIP 2 static char * -ParseGetLine(int flags, int *length) +ParseGetLine(int flags) { - IFile *cf = curFile; + IFile *cf = CurFile(); char *ptr; char ch; char *line; @@ -2796,29 +2669,32 @@ ParseGetLine(int flags, int *length) /* Loop through blank lines and comment lines */ for (;;) { cf->lineno++; - line = cf->P_ptr; + line = cf->buf_ptr; ptr = line; line_end = line; escaped = NULL; comment = NULL; for (;;) { - if (cf->P_end != NULL && ptr == cf->P_end) { + /* XXX: can buf_end ever be null? */ + if (cf->buf_end != NULL && ptr == cf->buf_end) { /* end of buffer */ ch = 0; break; } ch = *ptr; if (ch == 0 || (ch == '\\' && ptr[1] == 0)) { - if (cf->P_end == NULL) + /* XXX: can buf_end ever be null? */ + if (cf->buf_end == NULL) /* End of string (aka for loop) data */ break; /* see if there is more we can parse */ - while (ptr++ < cf->P_end) { + while (ptr++ < cf->buf_end) { if ((ch = *ptr) == '\n') { if (ptr > line && ptr[-1] == '\\') continue; Parse_Error(PARSE_WARNING, - "Zero byte read from file, skipping rest of line."); + "Zero byte read from file, " + "skipping rest of line."); break; } } @@ -2852,13 +2728,13 @@ ParseGetLine(int flags, int *length) ptr++; if (ch == '\n') break; - if (!isspace((unsigned char)ch)) + if (!ch_isspace(ch)) /* We are not interested in trailing whitespace */ line_end = ptr; } /* Save next 'to be processed' location */ - cf->P_ptr = ptr; + cf->buf_ptr = ptr; /* Check we have a non-comment, non-blank line */ if (line_end == line || comment == line) { @@ -2874,7 +2750,6 @@ ParseGetLine(int flags, int *length) if (flags & PARSE_RAW) { /* Leave '\' (etc) in line buffer (eg 'for' lines) */ - *length = line_end - line; return line; } @@ -2894,10 +2769,8 @@ ParseGetLine(int flags, int *length) } /* If we didn't see a '\\' then the in-situ data is fine */ - if (escaped == NULL) { - *length = line_end - line; + if (escaped == NULL) return line; - } /* Remove escapes from '\n' and '#' */ tp = ptr = escaped; @@ -2936,36 +2809,30 @@ ParseGetLine(int flags, int *length) } /* Delete any trailing spaces - eg from empty continuations */ - while (tp > escaped && isspace((unsigned char)tp[-1])) + while (tp > escaped && ch_isspace(tp[-1])) tp--; *tp = 0; - *length = tp - line; return line; } -/*- - *--------------------------------------------------------------------- - * ParseReadLine -- - * Read an entire line from the input file. Called only by Parse_File. +/* Read an entire line from the input file. Called only by Parse_File. * * Results: - * A line w/o its newline + * A line without its newline. * * Side Effects: * Only those associated with reading a character - *--------------------------------------------------------------------- */ static char * ParseReadLine(void) { - char *line; /* Result */ - int lineLength; /* Length of result */ - int lineno; /* Saved line # */ - int rval; + char *line; /* Result */ + int lineno; /* Saved line # */ + int rval; for (;;) { - line = ParseGetLine(0, &lineLength); + line = ParseGetLine(0); if (line == NULL) return NULL; @@ -2976,12 +2843,12 @@ ParseReadLine(void) * The line might be a conditional. Ask the conditional module * about it and act accordingly */ - switch (Cond_Eval(line)) { + switch (Cond_EvalLine(line)) { case COND_SKIP: /* Skip to next conditional that evaluates to COND_PARSE. */ do { - line = ParseGetLine(PARSE_SKIP, &lineLength); - } while (line && Cond_Eval(line) != COND_PARSE); + line = ParseGetLine(PARSE_SKIP); + } while (line && Cond_EvalLine(line) != COND_PARSE); if (line == NULL) break; continue; @@ -2997,13 +2864,13 @@ ParseReadLine(void) /* Syntax error - error printed, ignore line */ continue; /* Start of a .for loop */ - lineno = curFile->lineno; + lineno = CurFile()->lineno; /* Accumulate loop lines until matching .endfor */ do { - line = ParseGetLine(PARSE_RAW, &lineLength); + line = ParseGetLine(PARSE_RAW); if (line == NULL) { Parse_Error(PARSE_FATAL, - "Unexpected end of file in for loop."); + "Unexpected end of file in for loop."); break; } } while (For_Accum(line)); @@ -3016,309 +2883,325 @@ ParseReadLine(void) } } -/*- - *----------------------------------------------------------------------- - * ParseFinishLine -- - * Handle the end of a dependency group. - * - * Results: - * Nothing. - * - * Side Effects: - * inLine set FALSE. 'targets' list destroyed. - * - *----------------------------------------------------------------------- - */ static void -ParseFinishLine(void) +FinishDependencyGroup(void) { - if (inLine) { - if (targets != NULL) { - Lst_ForEach(targets, Suff_EndTransform, NULL); - Lst_Destroy(targets, ParseHasCommands); + if (targets != NULL) { + GNodeListNode *ln; + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + + Suff_EndTransform(gn); + + /* Mark the target as already having commands if it does, to + * keep from having shell commands on multiple dependency lines. */ + if (!Lst_IsEmpty(gn->commands)) + gn->type |= OP_HAS_COMMANDS; } + + Lst_Free(targets); targets = NULL; - inLine = FALSE; } } +/* Add the command to each target from the current dependency spec. */ +static void +ParseLine_ShellCommand(const char *p) +{ + cpp_skip_whitespace(&p); + if (*p == '\0') + return; /* skip empty commands */ -/*- - *--------------------------------------------------------------------- - * Parse_File -- - * Parse a file into its component parts, incorporating it into the - * current dependency graph. This is the main function and controls - * almost every other function in this module - * - * Input: - * name the name of the file being read - * fd Open file to makefile to parse - * - * Results: - * None - * - * Side Effects: - * closes fd. - * Loads. Nodes are added to the list of all targets, nodes and links - * are added to the dependency graph. etc. etc. etc. - *--------------------------------------------------------------------- - */ -void -Parse_File(const char *name, int fd) + if (targets == NULL) { + Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", p); + return; + } + + { + char *cmd = bmake_strdup(p); + GNodeListNode *ln; + + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + ParseAddCmd(gn, cmd); + } +#ifdef CLEANUP + Lst_Append(targCmds, cmd); +#endif + } +} + +static Boolean +ParseDirective(char *line) { - char *cp; /* pointer into the line */ - char *line; /* the line we're working on */ - struct loadedfile *lf; + char *cp; - lf = loadfile(name, fd); + if (*line == '.') { + /* + * Lines that begin with the special character may be + * include or undef directives. + * On the other hand they can be suffix rules (.c.o: ...) + * or just dependencies for filenames that start '.'. + */ + cp = line + 1; + pp_skip_whitespace(&cp); + if (IsInclude(cp, FALSE)) { + ParseDoInclude(cp); + return TRUE; + } + if (strncmp(cp, "undef", 5) == 0) { + const char *varname; + cp += 5; + pp_skip_whitespace(&cp); + varname = cp; + for (; !ch_isspace(*cp) && *cp != '\0'; cp++) + continue; + *cp = '\0'; + Var_Delete(varname, VAR_GLOBAL); + /* TODO: undefine all variables, not only the first */ + /* TODO: use Str_Words, like everywhere else */ + return TRUE; + } else if (strncmp(cp, "export", 6) == 0) { + cp += 6; + pp_skip_whitespace(&cp); + Var_Export(cp, TRUE); + return TRUE; + } else if (strncmp(cp, "unexport", 8) == 0) { + Var_UnExport(cp); + return TRUE; + } else if (strncmp(cp, "info", 4) == 0 || + strncmp(cp, "error", 5) == 0 || + strncmp(cp, "warning", 7) == 0) { + if (ParseMessage(cp)) + return TRUE; + } + } + return FALSE; +} - inLine = FALSE; - fatals = 0; +static Boolean +ParseVarassign(const char *line) +{ + VarAssign var; + if (Parse_IsVar(line, &var)) { + FinishDependencyGroup(); + Parse_DoVar(&var, VAR_GLOBAL); + return TRUE; + } + return FALSE; +} - if (name == NULL) - name = "(stdin)"; +static char * +FindSemicolon(char *p) +{ + int level = 0; - Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); - curFile->lf = lf; + for (; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') { + p++; + continue; + } - do { - for (; (line = ParseReadLine()) != NULL; ) { - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseReadLine (%d): '%s'\n", - curFile->lineno, line); - if (*line == '.') { - /* - * Lines that begin with the special character may be - * include or undef directives. - * On the other hand they can be suffix rules (.c.o: ...) - * or just dependencies for filenames that start '.'. - */ - for (cp = line + 1; isspace((unsigned char)*cp); cp++) { - continue; - } - if (IsInclude(cp, FALSE)) { - ParseDoInclude(cp); - continue; - } - if (strncmp(cp, "undef", 5) == 0) { - char *cp2; - for (cp += 5; isspace((unsigned char) *cp); cp++) - continue; - for (cp2 = cp; !isspace((unsigned char) *cp2) && - *cp2 != '\0'; cp2++) - continue; - *cp2 = '\0'; - Var_Delete(cp, VAR_GLOBAL); - continue; - } else if (strncmp(cp, "export", 6) == 0) { - for (cp += 6; isspace((unsigned char) *cp); cp++) - continue; - Var_Export(cp, TRUE); - continue; - } else if (strncmp(cp, "unexport", 8) == 0) { - Var_UnExport(cp); - continue; - } else if (strncmp(cp, "info", 4) == 0 || - strncmp(cp, "error", 5) == 0 || - strncmp(cp, "warning", 7) == 0) { - if (ParseMessage(cp)) - continue; - } - } + if (*p == '$' && (p[1] == '(' || p[1] == '{')) { + level++; + continue; + } - if (*line == '\t') { - /* - * If a line starts with a tab, it can only hope to be - * a creation command. - */ - cp = line + 1; - shellCommand: - for (; isspace ((unsigned char)*cp); cp++) { - continue; - } - if (*cp) { - if (!inLine) - Parse_Error(PARSE_FATAL, - "Unassociated shell command \"%s\"", - cp); - /* - * So long as it's not a blank line and we're actually - * in a dependency spec, add the command to the list of - * commands of all targets in the dependency spec - */ - if (targets) { - cp = bmake_strdup(cp); - Lst_ForEach(targets, ParseAddCmd, cp); -#ifdef CLEANUP - Lst_Append(targCmds, cp); -#endif - } - } - continue; - } + if (level > 0 && (*p == ')' || *p == '}')) { + level--; + continue; + } + + if (level == 0 && *p == ';') { + break; + } + } + return p; +} + +/* dependency -> target... op [source...] + * op -> ':' | '::' | '!' */ +static void +ParseDependency(char *line) +{ + VarEvalFlags eflags; + 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 avoid ';' inside substitution patterns. + */ + { + char *semicolon = FindSemicolon(line); + if (*semicolon != '\0') { + /* Terminate the dependency list at the ';' */ + *semicolon = '\0'; + shellcmd = semicolon + 1; + } + } + + /* + * 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 no 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. */ + eflags = DEBUG(LINT) ? VARE_WANTRES : VARE_UNDEFERR | VARE_WANTRES; + (void)Var_Subst(line, VAR_CMDLINE, eflags, &expanded_line); + /* TODO: handle errors */ + + /* Need a fresh list for the target nodes */ + if (targets != NULL) + Lst_Free(targets); + targets = Lst_New(); + + ParseDoDependency(expanded_line); + free(expanded_line); + + if (shellcmd != NULL) + ParseLine_ShellCommand(shellcmd); +} + +static void +ParseLine(char *line) +{ + if (ParseDirective(line)) + return; + + if (*line == '\t') { + ParseLine_ShellCommand(line + 1); + return; + } #ifdef SYSVINCLUDE - if (IsSysVInclude(line)) { - /* - * It's an S3/S5-style "include". - */ - ParseTraditionalInclude(line); - continue; - } + if (IsSysVInclude(line)) { + /* + * It's an S3/S5-style "include". + */ + ParseTraditionalInclude(line); + return; + } #endif + #ifdef GMAKEEXPORT - if (strncmp(line, "export", 6) == 0 && - isspace((unsigned char) line[6]) && - strchr(line, ':') == NULL) { - /* - * It's a Gmake "export". - */ - ParseGmakeExport(line); - continue; - } + if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) && + strchr(line, ':') == NULL) { + /* + * It's a Gmake "export". + */ + ParseGmakeExport(line); + return; + } #endif - if (Parse_IsVar(line)) { - ParseFinishLine(); - Parse_DoVar(line, VAR_GLOBAL); - continue; - } -#ifndef POSIX - /* - * To make life easier on novices, if the line is indented we - * first make sure the line has a dependency operator in it. - * If it doesn't have an operator and we're in a dependency - * line's script, we assume it's actually a shell command - * and add it to the current list of targets. - */ - cp = line; - if (isspace((unsigned char) line[0])) { - while (isspace((unsigned char) *cp)) - cp++; - while (*cp && (ParseIsEscaped(line, cp) || - *cp != ':' && *cp != '!')) { - cp++; - } - if (*cp == '\0') { - if (inLine) { - Parse_Error(PARSE_WARNING, - "Shell command needs a leading tab"); - goto shellCommand; - } - } - } -#endif - ParseFinishLine(); + if (ParseVarassign(line)) + return; - /* - * For some reason - probably to make the parser impossible - - * a ';' can be used to separate commands from dependencies. - * Attempt to avoid ';' inside substitution patterns. - */ - { - int level = 0; + FinishDependencyGroup(); - for (cp = line; *cp != 0; cp++) { - if (*cp == '\\' && cp[1] != 0) { - cp++; - continue; - } - if (*cp == '$' && - (cp[1] == '(' || cp[1] == '{')) { - level++; - continue; - } - if (level > 0) { - if (*cp == ')' || *cp == '}') { - level--; - continue; - } - } else if (*cp == ';') { - break; - } - } - } - if (*cp != 0) - /* Terminate the dependency list at the ';' */ - *cp++ = 0; - else - cp = NULL; + ParseDependency(line); +} - /* - * We now know it's a dependency line so it needs to have all - * variables expanded before being parsed. Tell the variable - * module to complain if some variable is undefined... - */ - line = Var_Subst(line, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES); +/* Parse a top-level makefile into its component parts, incorporating them + * into the global dependency graph. + * + * Input: + * name The name of the file being read + * fd The open file to parse; will be closed at the end + */ +void +Parse_File(const char *name, int fd) +{ + char *line; /* the line we're working on */ + struct loadedfile *lf; - /* - * Need a non-circular list for the target nodes - */ - if (targets != NULL) - Lst_Free(targets); + lf = loadfile(name, fd); + + assert(targets == NULL); + fatals = 0; - targets = Lst_Init(); - inLine = TRUE; + if (name == NULL) + name = "(stdin)"; - ParseDoDependency(line); - free(line); + Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); + CurFile()->lf = lf; - /* If there were commands after a ';', add them now */ - if (cp != NULL) { - goto shellCommand; - } + do { + while ((line = ParseReadLine()) != NULL) { + DEBUG2(PARSE, "ParseReadLine (%d): '%s'\n", + CurFile()->lineno, line); + ParseLine(line); } /* * Reached EOF, but it may be just EOF of an include file... */ - } while (ParseEOF() == CONTINUE); + } while (ParseEOF()); + + FinishDependencyGroup(); if (fatals) { (void)fflush(stdout); (void)fprintf(stderr, - "%s: Fatal errors encountered -- cannot continue", - progname); + "%s: Fatal errors encountered -- cannot continue", + progname); PrintOnError(NULL, NULL); exit(1); } } -/*- - *--------------------------------------------------------------------- - * Parse_Init -- - * initialize the parsing module - * - * Results: - * none - * - * Side Effects: - * the parseIncPath list is initialized... - *--------------------------------------------------------------------- - */ +/* Initialize the parsing module. */ void Parse_Init(void) { mainNode = NULL; - parseIncPath = Lst_Init(); - sysIncPath = Lst_Init(); - defIncPath = Lst_Init(); - includes = Lst_Init(); + parseIncPath = Lst_New(); + sysIncPath = Lst_New(); + defSysIncPath = Lst_New(); + Vector_Init(&includes, sizeof(IFile)); #ifdef CLEANUP - targCmds = Lst_Init(); + targCmds = Lst_New(); #endif } +/* Clean up the parsing module. */ void Parse_End(void) { #ifdef CLEANUP Lst_Destroy(targCmds, free); - if (targets) - Lst_Free(targets); - Lst_Destroy(defIncPath, Dir_Destroy); + assert(targets == NULL); + Lst_Destroy(defSysIncPath, Dir_Destroy); Lst_Destroy(sysIncPath, Dir_Destroy); Lst_Destroy(parseIncPath, Dir_Destroy); - Lst_Free(includes); /* Should be empty now */ + assert(includes.len == 0); + Vector_Done(&includes); #endif } @@ -3337,12 +3220,12 @@ Parse_End(void) * *----------------------------------------------------------------------- */ -Lst +GNodeList * Parse_MainName(void) { - Lst mainList; /* result list */ + GNodeList *mainList; - mainList = Lst_Init(); + mainList = Lst_New(); if (mainNode == NULL) { Punt("no target to make."); @@ -3350,27 +3233,14 @@ Parse_MainName(void) } else if (mainNode->type & OP_DOUBLEDEP) { Lst_Append(mainList, mainNode); Lst_AppendAll(mainList, mainNode->cohorts); - } - else + } else Lst_Append(mainList, mainNode); Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL); return mainList; } -/*- - *----------------------------------------------------------------------- - * ParseMark -- - * Add the filename and lineno to the GNode so that we remember - * where it was first defined. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -static void -ParseMark(GNode *gn) +int +Parse_GetFatals(void) { - gn->fname = curFile->fname; - gn->lineno = curFile->lineno; + return fatals; } |