diff options
Diffstat (limited to 'usr.bin/make/parse.c')
-rw-r--r-- | usr.bin/make/parse.c | 2545 |
1 files changed, 2545 insertions, 0 deletions
diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c new file mode 100644 index 0000000..134d905 --- /dev/null +++ b/usr.bin/make/parse.c @@ -0,0 +1,2545 @@ +/*- + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)parse.c 8.3 (Berkeley) 3/19/94 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * parse.c -- + * Functions to parse a makefile. + * + * 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. + * + * Interface: + * + * 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 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 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. + */ + +#include <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <err.h> + +#include "arch.h" +#include "buf.h" +#include "cond.h" +#include "config.h" +#include "dir.h" +#include "for.h" +#include "globals.h" +#include "GNode.h" +#include "hash_tables.h" +#include "job.h" +#include "make.h" +#include "parse.h" +#include "pathnames.h" +#include "shell.h" +#include "str.h" +#include "suff.h" +#include "targ.h" +#include "util.h" +#include "var.h" + +/* + * These values are returned by ParsePopInput 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 + +/* targets we're working on */ +static Lst targets = Lst_Initializer(targets); + +/* true if currently in a dependency line or its commands */ +static Boolean inLine; + +static int fatals = 0; + +/* + * The main target to create. This is the first target on the + * first dependency line in the first makefile. + */ +static GNode *mainNode; + +/* + * Definitions for handling #include specifications + */ +struct IFile { + char *fname; /* name of previous file */ + int lineno; /* saved line number */ + FILE *F; /* the open stream */ + char *str; /* the string when parsing a string */ + char *ptr; /* the current pointer when parsing a string */ + TAILQ_ENTRY(IFile) link;/* stack the files */ +}; + +/* stack of IFiles generated by * #includes */ +static TAILQ_HEAD(, IFile) includes = TAILQ_HEAD_INITIALIZER(includes); + +/* access current file */ +#define CURFILE (TAILQ_FIRST(&includes)) + +/* list of directories for "..." includes */ +struct Path parseIncPath = TAILQ_HEAD_INITIALIZER(parseIncPath); + +/* list of directories for <...> includes */ +struct Path sysIncPath = TAILQ_HEAD_INITIALIZER(sysIncPath); + +/* + * 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 + */ +typedef enum { + Begin, /* .BEGIN */ + Default, /* .DEFAULT */ + End, /* .END */ + ExportVar, /* .EXPORTVAR */ + Ignore, /* .IGNORE */ + Includes, /* .INCLUDES */ + Interrupt, /* .INTERRUPT */ + Libs, /* .LIBS */ + MFlags, /* .MFLAGS or .MAKEFLAGS */ + Main, /* .MAIN and we don't have anyth. user-spec. to make */ + Not, /* Not special */ + NotParallel, /* .NOTPARALELL */ + Null, /* .NULL */ + Order, /* .ORDER */ + Parallel, /* .PARALLEL */ + ExPath, /* .PATH */ + Phony, /* .PHONY */ + Posix, /* .POSIX */ + MakefileDeps, /* .MAKEFILEDEPS */ + Precious, /* .PRECIOUS */ + ExShell, /* .SHELL */ + Silent, /* .SILENT */ + SingleShell, /* .SINGLESHELL */ + Suffixes, /* .SUFFIXES */ + Wait, /* .WAIT */ + Warn, /* .WARN */ + Attribute /* Generic attribute */ +} ParseSpecial; + +static ParseSpecial specType; +static int waiting; + +/* + * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER + * seen, then set to each successive source on the line. + */ +static GNode *predecessor; + +/* + * 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 + * 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 keyword { + const char *name; /* Name of keyword */ + ParseSpecial spec; /* Type when used as a target */ + int op; /* Operator when used as a source */ +} parseKeywords[] = { + /* KEYWORD-START-TAG */ + { ".BEGIN", Begin, 0 }, + { ".DEFAULT", Default, 0 }, + { ".END", End, 0 }, + { ".EXEC", Attribute, OP_EXEC }, + { ".EXPORTVAR", ExportVar, 0 }, + { ".IGNORE", Ignore, OP_IGNORE }, + { ".INCLUDES", Includes, 0 }, + { ".INTERRUPT", Interrupt, 0 }, + { ".INVISIBLE", Attribute, OP_INVISIBLE }, + { ".JOIN", Attribute, OP_JOIN }, + { ".LIBS", Libs, 0 }, + { ".MAIN", Main, 0 }, + { ".MAKE", Attribute, OP_MAKE }, + { ".MAKEFILEDEPS", MakefileDeps, 0 }, + { ".MAKEFLAGS", MFlags, 0 }, + { ".MFLAGS", MFlags, 0 }, + { ".NOTMAIN", Attribute, OP_NOTMAIN }, + { ".NOTPARALLEL", NotParallel, 0 }, + { ".NO_PARALLEL", NotParallel, 0 }, + { ".NULL", Null, 0 }, + { ".OPTIONAL", Attribute, OP_OPTIONAL }, + { ".ORDER", Order, 0 }, + { ".PARALLEL", Parallel, 0 }, + { ".PATH", ExPath, 0 }, + { ".PHONY", Phony, OP_PHONY }, + { ".POSIX", Posix, 0 }, + { ".PRECIOUS", Precious, OP_PRECIOUS }, + { ".RECURSIVE", Attribute, OP_MAKE }, + { ".SHELL", ExShell, 0 }, + { ".SILENT", Silent, OP_SILENT }, + { ".SINGLESHELL", SingleShell, 0 }, + { ".SUFFIXES", Suffixes, 0 }, + { ".USE", Attribute, OP_USE }, + { ".WAIT", Wait, 0 }, + { ".WARN", Warn, 0 }, + /* KEYWORD-END-TAG */ +}; +#define NKEYWORDS (sizeof(parseKeywords) / sizeof(parseKeywords[0])) + +static void parse_include(char *, int, int); +static void parse_sinclude(char *, int, int); +static void parse_message(char *, int, int); +static void parse_undef(char *, int, int); +static void parse_for(char *, int, int); +static void parse_endfor(char *, int, int); + +static const struct directive { + const char *name; + int code; + Boolean skip_flag; /* execute even when skipped */ + void (*func)(char *, int, int); +} directives[] = { + /* DIRECTIVES-START-TAG */ + { "elif", COND_ELIF, TRUE, Cond_If }, + { "elifdef", COND_ELIFDEF, TRUE, Cond_If }, + { "elifmake", COND_ELIFMAKE, TRUE, Cond_If }, + { "elifndef", COND_ELIFNDEF, TRUE, Cond_If }, + { "elifnmake", COND_ELIFNMAKE, TRUE, Cond_If }, + { "else", COND_ELSE, TRUE, Cond_Else }, + { "endfor", 0, FALSE, parse_endfor }, + { "endif", COND_ENDIF, TRUE, Cond_Endif }, + { "error", 1, FALSE, parse_message }, + { "for", 0, FALSE, parse_for }, + { "if", COND_IF, TRUE, Cond_If }, + { "ifdef", COND_IFDEF, TRUE, Cond_If }, + { "ifmake", COND_IFMAKE, TRUE, Cond_If }, + { "ifndef", COND_IFNDEF, TRUE, Cond_If }, + { "ifnmake", COND_IFNMAKE, TRUE, Cond_If }, + { "include", 0, FALSE, parse_include }, + { "sinclude", 0, FALSE, parse_sinclude }, + { "undef", 0, FALSE, parse_undef }, + { "warning", 0, FALSE, parse_message }, + /* DIRECTIVES-END-TAG */ +}; +#define NDIRECTS (sizeof(directives) / sizeof(directives[0])) + +/*- + * ParseFindKeyword + * Look in the table of keywords for one matching the given string. + * + * Results: + * The pointer to keyword table entry or NULL. + */ +static const struct keyword * +ParseFindKeyword(const char *str) +{ + int kw; + + kw = keyword_hash(str, strlen(str)); + if (kw < 0 || kw >= (int)NKEYWORDS || + strcmp(str, parseKeywords[kw].name) != 0) + return (NULL); + return (&parseKeywords[kw]); +} + +/*- + * Parse_Error -- + * 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 */ +void +Parse_Error(int type, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (CURFILE != NULL) + fprintf(stderr, "\"%s\", line %d: ", + CURFILE->fname, CURFILE->lineno); + if (type == PARSE_WARNING) + fprintf(stderr, "warning: "); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + fflush(stderr); + if (type == PARSE_FATAL) + fatals += 1; +} + +/** + * ParsePushInput + * + * Push a new input source onto the input stack. If ptr is NULL + * the fullname is used to fopen the file. If it is not NULL, + * ptr is assumed to point to the string to be parsed. If opening the + * file fails, the fullname is freed. + */ +static void +ParsePushInput(char *fullname, FILE *fp, char *ptr, int lineno) +{ + struct IFile *nf; + + nf = emalloc(sizeof(*nf)); + nf->fname = fullname; + nf->lineno = lineno; + + if (ptr == NULL) { + /* the input source is a file */ + if ((nf->F = fp) == NULL) { + nf->F = fopen(fullname, "r"); + if (nf->F == NULL) { + Parse_Error(PARSE_FATAL, "Cannot open %s", + fullname); + free(fullname); + free(nf); + return; + } + } + nf->str = nf->ptr = NULL; + Var_Append(".MAKEFILE_LIST", fullname, VAR_GLOBAL); + } else { + nf->str = nf->ptr = ptr; + nf->F = NULL; + } + TAILQ_INSERT_HEAD(&includes, nf, link); +} + +/** + * ParsePopInput + * 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.F is closed. The includes list is shortened. + * curFile.lineno, curFile.F, and curFile.fname are changed if + * CONTINUE is returned. + */ +static int +ParsePopInput(void) +{ + struct IFile *ifile; /* the state on the top of the includes stack */ + + assert(!TAILQ_EMPTY(&includes)); + + ifile = TAILQ_FIRST(&includes); + TAILQ_REMOVE(&includes, ifile, link); + + free(ifile->fname); + if (ifile->F != NULL) { + fclose(ifile->F); + Var_Append(".MAKEFILE_LIST", "..", VAR_GLOBAL); + } + if (ifile->str != NULL) { + free(ifile->str); + } + free(ifile); + + return (TAILQ_EMPTY(&includes) ? DONE : CONTINUE); +} + +/** + * parse_warn + * Parse the .WARN pseudo-target. + */ +static void +parse_warn(char *line) +{ + ArgArray aa; + int i; + + brk_string(&aa, line, TRUE); + + for (i = 1; i < aa.argc; i++) + Main_ParseWarn(aa.argv[i], 0); +} + +/*- + *--------------------------------------------------------------------- + * ParseLinkSrc -- + * Link the parent nodes to their new child. Used by + * ParseDoDependency. If the specType isn't 'Not', the parent + * isn't linked as a parent of the child. + * + * Side Effects: + * New elements are added to the parents lists of cgn and the + * children list of cgn. the unmade field of pgn is updated + * to reflect the additional child. + *--------------------------------------------------------------------- + */ +static void +ParseLinkSrc(Lst *parents, GNode *cgn) +{ + LstNode *ln; + GNode *pgn; + + LST_FOREACH(ln, parents) { + pgn = Lst_Datum(ln); + if (Lst_Member(&pgn->children, cgn) == NULL) { + Lst_AtEnd(&pgn->children, cgn); + if (specType == Not) { + Lst_AtEnd(&cgn->parents, pgn); + } + pgn->unmade += 1; + } + } +} + +/*- + *--------------------------------------------------------------------- + * ParseDoOp -- + * Apply the parsed operator to all target nodes. Used in + * ParseDoDependency once all targets have been found and their + * operator parsed. If the previous and new operators are incompatible, + * a major error is taken. + * + * Side Effects: + * The type field of the node is altered to reflect any new bits in + * the op. + *--------------------------------------------------------------------- + */ +static void +ParseDoOp(int op) +{ + GNode *cohort; + LstNode *ln; + GNode *gn; + + LST_FOREACH(ln, &targets) { + gn = Lst_Datum(ln); + + /* + * 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 ((op & OP_OPMASK) != (gn->type & OP_OPMASK) && + !OP_NOP(gn->type) && !OP_NOP(op)) { + Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", + gn->name); + return; + } + + if (op == OP_DOUBLEDEP && + (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { + /* + * If the node was the object of a :: operator, we need + * to create a new instance of it for the children and + * commands on this dependency line. The new instance + * is placed on the 'cohorts' list of the initial one + * (note the initial one is not on its own cohorts list) + * and the new instance is linked to all parents of the + * initial instance. + */ + cohort = Targ_NewGN(gn->name); + + /* + * Duplicate links to parents so graph traversal is + * simple. Perhaps some type bits should be duplicated? + * + * Make the cohort invisible as well to avoid + * duplicating it into other variables. True, parents + * of this target won't tend to do anything with their + * local variables, but better safe than sorry. + */ + ParseLinkSrc(&gn->parents, cohort); + cohort->type = OP_DOUBLEDEP|OP_INVISIBLE; + Lst_AtEnd(&gn->cohorts, cohort); + + /* + * Replace the node in the targets list with the + * new copy + */ + Lst_Replace(ln, cohort); + gn = cohort; + } + /* + * We don't want to nuke any previous flags (whatever they were) + * so we just OR the new operator into the old + */ + gn->type |= op; + } +} + +/*- + *--------------------------------------------------------------------- + * 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' + * + * 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, char *src, Lst *allsrc) +{ + GNode *gn = NULL; + const struct keyword *kw; + + if (src[0] == '.' && isupper ((unsigned char)src[1])) { + if ((kw = ParseFindKeyword(src)) != NULL) { + if (kw->op != 0) { + ParseDoOp(kw->op); + return; + } + if (kw->spec == Wait) { + waiting++; + return; + } + } + } + + 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_AtEnd(&create, estrdup(src)); + /* + * Add the name to the .TARGETS variable as well, so the user + * can employ that, if desired. + */ + Var_Append(".TARGETS", src, VAR_GLOBAL); + return; + + case Order: + /* + * Create proper predecessor/successor links between the + * previous source and the current one. + */ + gn = Targ_FindNode(src, TARG_CREATE); + if (predecessor != NULL) { + Lst_AtEnd(&predecessor->successors, gn); + Lst_AtEnd(&gn->preds, predecessor); + } + /* + * The current source now becomes the predecessor for the next + * one. + */ + predecessor = gn; + break; + + default: + /* + * 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. + */ + gn = Targ_FindNode(src, TARG_CREATE); + if (tOp) { + gn->type |= tOp; + } else { + ParseLinkSrc(&targets, gn); + } + if ((gn->type & OP_OPMASK) == OP_DOUBLEDEP) { + GNode *cohort; + LstNode *ln; + + for (ln = Lst_First(&gn->cohorts); ln != NULL; + ln = Lst_Succ(ln)) { + cohort = Lst_Datum(ln); + if (tOp) { + cohort->type |= tOp; + } else { + ParseLinkSrc(&targets, cohort); + } + } + } + break; + } + + gn->order = waiting; + Lst_AtEnd(allsrc, gn); + if (waiting) { + LstNode *ln; + GNode *p; + + /* + * Check if GNodes needs to be synchronized. + * This has to be when two nodes are on different sides of a + * .WAIT directive. + */ + LST_FOREACH(ln, allsrc) { + p = Lst_Datum(ln); + + if (p->order >= gn->order) + break; + /* + * XXX: This can cause loops, and loops can cause + * unmade targets, but checking is tedious, and the + * debugging output can show the problem + */ + Lst_AtEnd(&p->successors, gn); + Lst_AtEnd(&gn->preds, p); + } + } +} + + +/*- + *--------------------------------------------------------------------- + * ParseDoDependency -- + * Parse the dependency line in line. + * + * Results: + * None + * + * Side Effects: + * The nodes of the sources are linked as children to the nodes of the + * targets. Some nodes may be created. + * + * 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. + *--------------------------------------------------------------------- + */ +static void +ParseDoDependency(char *line) +{ + char *cp; /* our current position */ + char *lstart = line; /* original input line */ + GNode *gn; /* a general purpose temporary node */ + int op; /* the operator on the line */ + char savec; /* a place to save a character */ + Lst paths; /* Search paths to alter when parsing .PATH targets */ + int tOp; /* operator from special target */ + LstNode *ln; + const struct keyword *kw; + + tOp = 0; + + specType = Not; + waiting = 0; + Lst_Init(&paths); + + do { + for (cp = line; + *cp && !isspace((unsigned char)*cp) && *cp != '('; + 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. + */ + size_t length = 0; + Boolean freeIt; + char *result; + + result = Var_Parse(cp, VAR_CMD, TRUE, + &length, &freeIt); + + if (freeIt) { + free(result); + } + cp += length - 1; + + } else if (*cp == '!' || *cp == ':') { + /* + * We don't want to end a word on ':' or '!' if + * there is a better match later on in the + * string (greedy matching). + * This allows the user to have targets like: + * fie::fi:fo: fum + * foo::bar: + * where "fie::fi:fo" and "foo::bar" are the + * targets. In real life this is used for perl5 + * library man pages where "::" separates an + * object from its class. Ie: + * "File::Spec::Unix". This behaviour is also + * consistent with other versions of make. + */ + char *p = cp + 1; + + if (*cp == ':' && *p == ':') + p++; + + /* Found the best match already. */ + if (*p == '\0' || isspace(*p)) + break; + + p += strcspn(p, "!:"); + + /* No better match later on... */ + if (*p == '\0') + break; + } + continue; + } + if (*cp == '(') { + /* + * Archives must be handled specially to make sure the + * OP_ARCHV flag is set in their 'type' field, for one + * thing, and because things like "archive(file1.o + * file2.o file3.o)" are permissible. Arch_ParseArchive + * will set 'line' to be the first non-blank after the + * archive-spec. It creates/finds nodes for the members + * and places them on the given list, returning TRUE + * if all 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)) { + Parse_Error(PARSE_FATAL, + "Error in archive specification: \"%s\"", + line); + return; + } else { + cp = line; + continue; + } + } + savec = *cp; + + if (!*cp) { + /* + * 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 || + strncmp(line, ">>>>>>", 6) == 0) { + Parse_Error(PARSE_FATAL, "Makefile appears to " + "contain unresolved cvs/rcs/??? merge " + "conflicts"); + } else + Parse_Error(PARSE_FATAL, lstart[0] == '.' ? + "Unknown directive" : "Need an operator"); + return; + } + *cp = '\0'; + /* + * Have a word in line. See if it's a special target and 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. + */ + if ((kw = ParseFindKeyword(line)) != NULL) { + if (specType == ExPath && kw->spec != ExPath) { + Parse_Error(PARSE_FATAL, + "Mismatched special targets"); + return; + } + + specType = kw->spec; + tOp = kw->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 + * .BEGIN + * .END + * .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: + Lst_AtEnd(&paths, &dirSearchPath); + break; + case Main: + if (!Lst_IsEmpty(&create)) { + specType = Not; + } + break; + case Begin: + case End: + case Interrupt: + gn = Targ_FindNode(line, TARG_CREATE); + gn->type |= OP_NOTMAIN; + Lst_AtEnd(&targets, gn); + break; + case Default: + gn = Targ_NewGN(".DEFAULT"); + gn->type |= (OP_NOTMAIN|OP_TRANSFORM); + Lst_AtEnd(&targets, gn); + DEFAULT = gn; + break; + case NotParallel: + jobLimit = 1; + break; + case SingleShell: + compatMake = 1; + 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. + */ + struct Path *path; + + specType = ExPath; + path = Suff_GetPath(&line[5]); + if (path == NULL) { + Parse_Error(PARSE_FATAL, "Suffix '%s' " + "not defined (yet)", &line[5]); + return; + } else + Lst_AtEnd(&paths, path); + } + } + + /* + * Have word in line. Get or create its node and stick it at + * the end of the targets list + */ + if (specType == Not && *line != '\0') { + + /* target names to be found and added to targets list */ + Lst curTargs = Lst_Initializer(curTargs); + + 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 Path_Clear in the + * destruction of the path as the Dir module + * could have added a directory to the path... + */ + struct Path emptyPath = + TAILQ_HEAD_INITIALIZER(emptyPath); + + Path_Expand(line, &emptyPath, &curTargs); + Path_Clear(&emptyPath); + + } else { + /* + * No wildcards, but we want to avoid code + * duplication, so create a list with the word + * on it. + */ + Lst_AtEnd(&curTargs, line); + } + + while (!Lst_IsEmpty(&curTargs)) { + char *targName = Lst_DeQueue(&curTargs); + + if (!Suff_IsTransform (targName)) { + gn = Targ_FindNode(targName, + TARG_CREATE); + } else { + gn = Suff_AddTransform(targName); + } + + Lst_AtEnd(&targets, gn); + } + } else if (specType == ExPath && *line != '.' && *line != '\0'){ + Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", + line); + } + + *cp = savec; + /* + * 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 warnFlag = FALSE; + + while (*cp != '!' && *cp != ':' && *cp) { + if (*cp != ' ' && *cp != '\t') { + warnFlag = TRUE; + } + cp++; + } + if (warnFlag) { + Parse_Error(PARSE_WARNING, + "Extra target ignored"); + } + } else { + while (*cp && isspace((unsigned char)*cp)) { + cp++; + } + } + line = cp; + } while (*line != '!' && *line != ':' && *line); + + if (!Lst_IsEmpty(&targets)) { + switch (specType) { + default: + Parse_Error(PARSE_WARNING, "Special and mundane " + "targets don't mix. Mundane ones ignored"); + break; + case Default: + case Begin: + case End: + 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; + } + } + + /* + * Have now parsed all the target names. Must parse the operator next. + * The result is left in op. + */ + 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"); + return; + } + + cp++; /* Advance beyond operator */ + + ParseDoOp(op); + + /* + * Get to the first source + */ + while (*cp && isspace((unsigned char)*cp)) { + cp++; + } + line = cp; + + /* + * Several special targets take different actions if present with no + * sources: + * a .SUFFIXES line with no sources clears out all old suffixes + * a .PRECIOUS line makes all targets precious + * a .IGNORE line ignores errors for all targets + * a .SILENT line creates silence when making all targets + * 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: + LST_FOREACH(ln, &paths) + Path_Clear(Lst_Datum(ln)); + break; + case MakefileDeps: + mfAutoDeps = TRUE; + break; + case Posix: + is_posix = TRUE; + Var_SetGlobal("%POSIX", "1003.2"); + break; + default: + break; + } + + } else if (specType == 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 + * get sources won't get anything + */ + Main_ParseArgLine(line, 0); + *line = '\0'; + + } else if (specType == Warn) { + parse_warn(line); + *line = '\0'; + + } else if (specType == ExShell) { + if (!Shell_Parse(line)) { + Parse_Error(PARSE_FATAL, + "improper shell specification"); + return; + } + *line = '\0'; + + } else if (specType == NotParallel || specType == SingleShell) { + *line = '\0'; + } + + /* + * NOW GO FOR THE SOURCES + */ + if (specType == Suffixes || specType == ExPath || + specType == Includes || specType == Libs || + specType == Null) { + 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. + */ + char savech; + while (*cp && !isspace((unsigned char)*cp)) { + cp++; + } + savech = *cp; + *cp = '\0'; + switch (specType) { + case Suffixes: + Suff_AddSuffix(line); + break; + case ExPath: + LST_FOREACH(ln, &paths) + Path_AddDir(Lst_Datum(ln), line); + break; + case Includes: + Suff_AddInclude(line); + break; + case Libs: + Suff_AddLib(line); + break; + case Null: + Suff_SetNull(line); + break; + default: + break; + } + *cp = savech; + if (savech != '\0') { + cp++; + } + while (*cp && isspace((unsigned char)*cp)) { + cp++; + } + line = cp; + } + Lst_Destroy(&paths, NOFREE); + + } else if (specType == ExportVar) { + Var_SetEnv(line, VAR_GLOBAL); + + } else { + /* list of sources in order */ + Lst curSrcs = Lst_Initializer(curSrc); + + 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. + */ + while (*cp && !isspace((unsigned char)*cp)) { + if (*cp == '(' && 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; + } else { + cp++; + } + } + + if (*cp == '(') { + GNode *gnp; + + /* list of archive source names after exp. */ + Lst sources = Lst_Initializer(sources); + + if (!Arch_ParseArchive(&line, &sources, + VAR_CMD)) { + Parse_Error(PARSE_FATAL, "Error in " + "source archive spec \"%s\"", line); + return; + } + + while (!Lst_IsEmpty(&sources)) { + gnp = Lst_DeQueue(&sources); + ParseDoSrc(tOp, gnp->name, &curSrcs); + } + cp = line; + } else { + if (*cp) { + *cp = '\0'; + cp += 1; + } + + ParseDoSrc(tOp, line, &curSrcs); + } + while (*cp && isspace((unsigned char)*cp)) { + cp++; + } + line = cp; + } + Lst_Destroy(&curSrcs, NOFREE); + } + + if (mainNode == 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(ln, &targets) { + gn = Lst_Datum(ln); + if ((gn->type & (OP_NOTMAIN | OP_USE | + OP_EXEC | OP_TRANSFORM)) == 0) { + mainNode = gn; + Targ_SetMain(gn); + break; + } + } + } +} + +/*- + *--------------------------------------------------------------------- + * 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. + * + * Results: + * TRUE if it is. FALSE if it ain't + * + * Side Effects: + * none + *--------------------------------------------------------------------- + */ +Boolean +Parse_IsVar(char *line) +{ + Boolean wasSpace = FALSE; /* set TRUE if found a space */ + Boolean haveName = FALSE; /* Set TRUE if have a variable name */ + + int level = 0; +#define ISEQOPERATOR(c) \ + ((c) == '+' || (c) == ':' || (c) == '?' || (c) == '!') + + /* + * Skip to variable name + */ + for (; *line == ' ' || *line == '\t'; line++) + continue; + + for (; *line != '=' || level != 0; line++) { + switch (*line) { + case '\0': + /* + * end-of-line -- can't be a variable assignment. + */ + return (FALSE); + + case ' ': + case '\t': + /* + * there can be as much white space as desired so long + * as there is only one word before the operator + */ + wasSpace = TRUE; + break; + + case '(': + case '{': + level++; + break; + + case '}': + case ')': + level--; + break; + + default: + if (wasSpace && haveName) { + if (ISEQOPERATOR(*line)) { + /* + * We must have a finished word + */ + if (level != 0) + return (FALSE); + + /* + * When an = operator [+?!:] is found, + * the next character must be an = or + * it ain't a valid assignment. + */ + if (line[1] == '=') + return (haveName); +#ifdef SUNSHCMD + /* + * This is a shell command + */ + if (strncmp(line, ":sh", 3) == 0) + return (haveName); +#endif + } + /* + * This is the start of another word, so not + * assignment. + */ + return (FALSE); + + } else { + haveName = TRUE; + wasSpace = FALSE; + } + break; + } + } + + return (haveName); +} + +/*- + *--------------------------------------------------------------------- + * 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++ =". + * + * 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 */ + + /* + * Skip to variable name + */ + while (*line == ' ' || *line == '\t') { + line++; + } + + /* + * Skip to operator character, nulling out whitespace as we go + */ + for (cp = line + 1; *cp != '='; cp++) { + if (isspace((unsigned char)*cp)) { + *cp = '\0'; + } + } + opc = cp - 1; /* operator is the previous character */ + *cp++ = '\0'; /* nuke the = */ + + /* + * Check operator type + */ + 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; + + default: +#ifdef SUNSHCMD + while (*opc != ':') { + if (opc == line) + break; + else + --opc; + } + + if (strncmp(opc, ":sh", 3) == 0) { + type = VAR_SHELL; + *opc = '\0'; + break; + } +#endif + type = VAR_NORMAL; + break; + } + + while (isspace((unsigned char)*cp)) { + cp++; + } + + 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); + + cp = Buf_Peel(Var_Subst(cp, ctxt, FALSE)); + + oldVars = oldOldVars; + + Var_Set(line, cp, ctxt); + free(cp); + + } else if (type == VAR_SHELL) { + /* + * TRUE if the command needs to be freed, i.e. + * if any variable expansion was performed + */ + Boolean freeCmd = FALSE; + Buffer *buf; + const char *error; + + 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, + * so set freeCmd to TRUE. + */ + cp = Buf_Peel(Var_Subst(cp, VAR_CMD, TRUE)); + freeCmd = TRUE; + } + + buf = Cmd_Exec(cp, &error); + Var_Set(line, Buf_Data(buf), ctxt); + Buf_Destroy(buf, TRUE); + + if (error) + Parse_Error(PARSE_WARNING, error, cp); + + if (freeCmd) + free(cp); + + } else { + /* + * Normal assignment -- just do it. + */ + Var_Set(line, cp, ctxt); + } + if (strcmp(line, MAKE_JOB_PREFIX) == 0) + Job_SetPrefix(); +} + +/*- + *----------------------------------------------------------------------- + * 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. + * + * Results: + * None + * + * Side Effects: + * OP_HAS_COMMANDS may be set for the target. + * + *----------------------------------------------------------------------- + */ +static void +ParseHasCommands(void *gnp) +{ + GNode *gn = 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 + * + * Results: + * None. + * + * Side Effects: + * The directory is appended to the list. + * + *----------------------------------------------------------------------- + */ +void +Parse_AddIncludeDir(char *dir) +{ + + Path_AddDir(&parseIncPath, dir); +} + +/*- + *--------------------------------------------------------------------- + * Parse_FromString -- + * Start Parsing from the given string + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, curFile.lineno, + * curFile.fname and curFile.F are altered for the new file + *--------------------------------------------------------------------- + */ +void +Parse_FromString(char *str, int lineno) +{ + + DEBUGF(FOR, ("%s\n---- at line %d\n", str, lineno)); + + ParsePushInput(estrdup(CURFILE->fname), NULL, str, lineno); +} + +#ifdef SYSVINCLUDE +/*- + *--------------------------------------------------------------------- + * ParseTraditionalInclude -- + * Push to another file. + * + * The input is the line minus the "include". The file name is + * the string following the "include". + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, curFile.lineno, + * curFile.fname and curFile.F are altered for the new file + *--------------------------------------------------------------------- + */ +static void +ParseTraditionalInclude(char *file) +{ + char *fullname; /* full pathname of file */ + char *cp; /* current position in file spec */ + + /* + * Skip over whitespace + */ + while (*file == ' ' || *file == '\t') { + file++; + } + + if (*file == '\0') { + Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); + return; + } + + /* + * Skip to end of line or next whitespace + */ + for (cp = file; *cp && *cp != '\n' && *cp != '\t' && *cp != ' '; cp++) { + continue; + } + + *cp = '\0'; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + file = Buf_Peel(Var_Subst(file, VAR_CMD, FALSE)); + + /* + * Now we know the file's name, we attempt to find the durn thing. + * Search for it first on the -I search path, then on the .PATH + * search path, if not found in a -I directory. + */ + fullname = Path_FindFile(file, &parseIncPath); + if (fullname == NULL) { + fullname = Path_FindFile(file, &dirSearchPath); + } + + if (fullname == NULL) { + /* + * Still haven't found the makefile. Look for it on the system + * path as a last resort. + */ + fullname = Path_FindFile(file, &sysIncPath); + } + + if (fullname == NULL) { + Parse_Error(PARSE_FATAL, "Could not find %s", file); + /* XXXHB free(file) */ + return; + } + + /* XXXHB free(file) */ + + /* + * We set up the name of the file to be the absolute + * name of the include file so error messages refer to the right + * place. + */ + ParsePushInput(fullname, NULL, NULL, 0); +} +#endif + +/*- + *--------------------------------------------------------------------- + * ParseReadc -- + * Read a character from the current file + * + * Results: + * The character that was read + * + * Side Effects: + *--------------------------------------------------------------------- + */ +static int +ParseReadc(void) +{ + + if (CURFILE->F != NULL) + return (fgetc(CURFILE->F)); + + if (CURFILE->str != NULL && *CURFILE->ptr != '\0') + return (*CURFILE->ptr++); + + return (EOF); +} + + +/*- + *--------------------------------------------------------------------- + * ParseUnreadc -- + * Put back a character to the current file + * + * Results: + * None. + * + * Side Effects: + *--------------------------------------------------------------------- + */ +static void +ParseUnreadc(int c) +{ + + if (CURFILE->F != NULL) { + ungetc(c, CURFILE->F); + return; + } + if (CURFILE->str != NULL) { + *--(CURFILE->ptr) = c; + return; + } +} + +/* ParseSkipLine(): + * Grab the next line unless it begins with a dot (`.') and we're told to + * ignore such lines. + */ +static char * +ParseSkipLine(int skip, int keep_newline) +{ + char *line; + int c, lastc; + Buffer *buf; + + buf = Buf_Init(MAKE_BSIZE); + + do { + Buf_Clear(buf); + lastc = '\0'; + + while (((c = ParseReadc()) != '\n' || lastc == '\\') + && c != EOF) { + if (skip && c == '#' && lastc != '\\') { + /* + * let a comment be terminated even by an + * escaped \n. This is consistent to comment + * handling in ParseReadLine + */ + while ((c = ParseReadc()) != '\n' && c != EOF) + ; + break; + } + if (c == '\n') { + if (keep_newline) + Buf_AddByte(buf, (Byte)c); + else + Buf_ReplaceLastByte(buf, (Byte)' '); + CURFILE->lineno++; + + while ((c = ParseReadc()) == ' ' || c == '\t') + continue; + + if (c == EOF) + break; + } + + Buf_AddByte(buf, (Byte)c); + lastc = c; + } + + if (c == EOF) { + Parse_Error(PARSE_FATAL, + "Unclosed conditional/for loop"); + Buf_Destroy(buf, TRUE); + return (NULL); + } + + CURFILE->lineno++; + Buf_AddByte(buf, (Byte)'\0'); + line = Buf_Data(buf); + } while (skip == 1 && line[0] != '.'); + + Buf_Destroy(buf, FALSE); + return (line); +} + +/*- + *--------------------------------------------------------------------- + * ParseReadLine -- + * Read an entire line from the input file. Called only by Parse_File. + * To facilitate escaped newlines and what have you, a character is + * buffered in 'lastc', which is '\0' when no characters have been + * read. When we break out of the loop, c holds the terminating + * character and lastc holds a character that should be added to + * the line (unless we don't read anything but a terminator). + * + * Results: + * A line w/o its newline + * + * Side Effects: + * Only those associated with reading a character + *--------------------------------------------------------------------- + */ +static char * +ParseReadLine(void) +{ + Buffer *buf; /* Buffer for current line */ + int c; /* the current character */ + int lastc; /* The most-recent character */ + Boolean semiNL; /* treat semi-colons as newlines */ + Boolean ignDepOp; /* TRUE if should ignore dependency operators + * for the purposes of setting semiNL */ + Boolean ignComment; /* TRUE if should ignore comments (in a + * shell command */ + char *line; /* Result */ + char *ep; /* to strip trailing blanks */ + + again: + semiNL = FALSE; + ignDepOp = FALSE; + ignComment = FALSE; + + lastc = '\0'; + + /* + * Handle tab at the beginning of the line. A leading tab (shell + * command) forces us to ignore comments and dependency operators and + * treat semi-colons as semi-colons (by leaving semiNL FALSE). + * This also discards completely blank lines. + */ + for (;;) { + c = ParseReadc(); + if (c == EOF) { + if (ParsePopInput() == DONE) { + /* End of all inputs - return NULL */ + return (NULL); + } + continue; + } + + if (c == '\t') { + ignComment = ignDepOp = TRUE; + lastc = c; + break; + } + if (c != '\n') { + ParseUnreadc(c); + break; + } + CURFILE->lineno++; + } + + buf = Buf_Init(MAKE_BSIZE); + + while (((c = ParseReadc()) != '\n' || lastc == '\\') && c != EOF) { + test_char: + switch (c) { + case '\n': + /* + * Escaped newline: read characters until a + * non-space or an unescaped newline and + * replace them all by a single space. This is + * done by storing the space over the backslash + * and dropping through with the next nonspace. + * If it is a semi-colon and semiNL is TRUE, + * it will be recognized as a newline in the + * code below this... + */ + CURFILE->lineno++; + lastc = ' '; + while ((c = ParseReadc()) == ' ' || c == '\t') { + continue; + } + if (c == EOF || c == '\n') { + goto line_read; + } else { + /* + * Check for comments, semiNL's, etc. -- + * easier than ParseUnreadc(c); + * continue; + */ + goto test_char; + } + /*NOTREACHED*/ + break; + + case ';': + /* + * Semi-colon: Need to see if it should be + * interpreted as a newline + */ + if (semiNL) { + /* + * To make sure the command that may + * be following this semi-colon begins + * with a tab, we push one back into the + * input stream. This will overwrite the + * semi-colon in the buffer. If there is + * no command following, this does no + * harm, since the newline remains in + * the buffer and the + * whole line is ignored. + */ + ParseUnreadc('\t'); + goto line_read; + } + break; + case '=': + if (!semiNL) { + /* + * Haven't seen a dependency operator + * before this, so this must be a + * variable assignment -- don't pay + * attention to dependency operators + * after this. + */ + ignDepOp = TRUE; + } else if (lastc == ':' || lastc == '!') { + /* + * Well, we've seen a dependency + * operator already, but it was the + * previous character, so this is really + * just an expanded variable assignment. + * Revert semi-colons to being just + * semi-colons again and ignore any more + * dependency operators. + * + * XXX: Note that a line like + * "foo : a:=b" will blow up, but who'd + * write a line like that anyway? + */ + ignDepOp = TRUE; + semiNL = FALSE; + } + break; + case '#': + if (!ignComment) { + if (lastc != '\\') { + /* + * If the character is a hash + * mark and it isn't escaped + * (or we're being compatible), + * the thing is a comment. + * Skip to the end of the line. + */ + do { + c = ParseReadc(); + } while (c != '\n' && c != EOF); + goto line_read; + } else { + /* + * Don't add the backslash. + * Just let the # get copied + * over. + */ + lastc = c; + continue; + } + } + break; + + case ':': + case '!': + if (!ignDepOp) { + /* + * A semi-colon is recognized as a + * newline only on dependency lines. + * Dependency lines are lines with a + * colon or an exclamation point. + * Ergo... + */ + semiNL = TRUE; + } + break; + + default: + break; + } + /* + * Copy in the previous character (there may be none if this + * was the first character) and save this one in + * lastc. + */ + if (lastc != '\0') + Buf_AddByte(buf, (Byte)lastc); + lastc = c; + } + line_read: + CURFILE->lineno++; + + if (lastc != '\0') { + Buf_AddByte(buf, (Byte)lastc); + } + Buf_AddByte(buf, (Byte)'\0'); + line = Buf_Peel(buf); + + /* + * Strip trailing blanks and tabs from the line. + * Do not strip a blank or tab that is preceded by + * a '\' + */ + ep = line; + while (*ep) + ++ep; + while (ep > line + 1 && (ep[-1] == ' ' || ep[-1] == '\t')) { + if (ep > line + 1 && ep[-2] == '\\') + break; + --ep; + } + *ep = 0; + + if (line[0] == '\0') { + /* empty line - just ignore */ + free(line); + goto again; + } + + return (line); +} + +/*- + *----------------------------------------------------------------------- + * ParseFinishLine -- + * Handle the end of a dependency group. + * + * Results: + * Nothing. + * + * Side Effects: + * inLine set FALSE. 'targets' list destroyed. + * + *----------------------------------------------------------------------- + */ +static void +ParseFinishLine(void) +{ + const LstNode *ln; + + if (inLine) { + LST_FOREACH(ln, &targets) { + if (((const GNode *)Lst_Datum(ln))->type & OP_TRANSFORM) + Suff_EndTransform(Lst_Datum(ln)); + } + Lst_Destroy(&targets, ParseHasCommands); + inLine = FALSE; + } +} + +/** + * xparse_include + * Parse an .include directive and push the file onto the input stack. + * The input is the line minus the .include. 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 + */ +static void +xparse_include(char *file, int sinclude) +{ + char *fullname; /* full pathname of file */ + char endc; /* the character which ends the file spec */ + char *cp; /* current position in file spec */ + Boolean isSystem; /* TRUE if makefile is a system makefile */ + char *prefEnd, *Fname; + char *newName; + + /* + * Skip to delimiter character so we know where to look + */ + while (*file == ' ' || *file == '\t') { + file++; + } + + if (*file != '"' && *file != '<') { + Parse_Error(PARSE_FATAL, + ".include filename must be delimited by '\"' or '<'"); + return; + } + + /* + * Set the search path on which to find the include file based on the + * characters which bracket its name. Angle-brackets imply it's + * a system Makefile while double-quotes imply it's a user makefile + */ + if (*file == '<') { + isSystem = TRUE; + endc = '>'; + } else { + isSystem = FALSE; + endc = '"'; + } + + /* + * Skip to matching delimiter + */ + for (cp = ++file; *cp != endc; cp++) { + if (*cp == '\0') { + Parse_Error(PARSE_FATAL, + "Unclosed .include filename. '%c' expected", endc); + return; + } + } + *cp = '\0'; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + file = Buf_Peel(Var_Subst(file, VAR_CMD, FALSE)); + + /* + * Now we know the file's name and its search path, we attempt to + * find the durn thing. A return of NULL indicates the file don't + * exist. + */ + if (!isSystem) { + /* + * Include files contained in double-quotes are first searched + * for relative to the including file's location. We don't want + * to cd there, of course, so we just tack on the old file's + * leading path components and call Path_FindFile to see if + * we can locate the beast. + */ + + /* Make a temporary copy of this, to be safe. */ + Fname = estrdup(CURFILE->fname); + + prefEnd = strrchr(Fname, '/'); + if (prefEnd != NULL) { + *prefEnd = '\0'; + if (file[0] == '/') + newName = estrdup(file); + else + newName = str_concat(Fname, file, STR_ADDSLASH); + fullname = Path_FindFile(newName, &parseIncPath); + if (fullname == NULL) { + fullname = Path_FindFile(newName, + &dirSearchPath); + } + free(newName); + *prefEnd = '/'; + } else { + fullname = NULL; + } + free(Fname); + if (fullname == NULL) { + /* + * Makefile wasn't found in same directory as included + * makefile. Search for it first on the -I search path, + * then on the .PATH search path, if not found in a -I + * directory. + * XXX: Suffix specific? + */ + fullname = Path_FindFile(file, &parseIncPath); + if (fullname == NULL) { + fullname = Path_FindFile(file, &dirSearchPath); + } + } + } else { + fullname = NULL; + } + + if (fullname == NULL) { + /* + * System makefile or still haven't found the makefile. + * Look for it on the system path. + */ + fullname = Path_FindFile(file, &sysIncPath); + } + + if (fullname == NULL) { + *cp = endc; + if (!sinclude) + Parse_Error(PARSE_FATAL, "Could not find %s", file); + else + Main_AddSourceMakefile(file); + free(file); + return; + } + Main_AddSourceMakefile(fullname); + free(file); + + /* + * We set up the name of the file to be the absolute + * name of the include file so error messages refer to the right + * place. + */ + ParsePushInput(fullname, NULL, NULL, 0); + DEBUGF(DIR, (".include %s\n", fullname)); +} + +static void +parse_include(char *file, int code __unused, int lineno __unused) +{ + xparse_include(file, 0); +} + +static void +parse_sinclude(char *file, int code __unused, int lineno __unused) +{ + xparse_include(file, 1); +} + +/** + * parse_message + * Parse a .warning or .error directive + * + * The input is the line minus the ".error"/".warning". We substitute + * variables, print the message and exit(1) (for .error) or just print + * a warning if the directive is malformed. + */ +static void +parse_message(char *line, int iserror, int lineno __unused) +{ + + if (!isspace((u_char)*line)) { + Parse_Error(PARSE_WARNING, "invalid syntax: .%s%s", + iserror ? "error" : "warning", line); + return; + } + + while (isspace((u_char)*line)) + line++; + + line = Buf_Peel(Var_Subst(line, VAR_CMD, FALSE)); + Parse_Error(iserror ? PARSE_FATAL : PARSE_WARNING, "%s", line); + free(line); + + if (iserror) { + /* Terminate immediately. */ + exit(1); + } +} + +/** + * parse_undef + * Parse an .undef directive. + */ +static void +parse_undef(char *line, int code __unused, int lineno __unused) +{ + char *cp; + + while (isspace((u_char)*line)) + line++; + + for (cp = line; !isspace((u_char)*cp) && *cp != '\0'; cp++) { + ; + } + *cp = '\0'; + + cp = Buf_Peel(Var_Subst(line, VAR_CMD, FALSE)); + Var_Delete(cp, VAR_GLOBAL); + free(cp); +} + +/** + * parse_for + * Parse a .for directive. + */ +static void +parse_for(char *line, int code __unused, int lineno) +{ + + if (!For_For(line)) { + /* syntax error */ + return; + } + line = NULL; + + /* + * Skip after the matching endfor. + */ + do { + free(line); + line = ParseSkipLine(0, 1); + if (line == NULL) { + Parse_Error(PARSE_FATAL, + "Unexpected end of file in for loop.\n"); + return; + } + } while (For_Eval(line)); + free(line); + + /* execute */ + For_Run(lineno); +} + +/** + * parse_endfor + * Parse endfor. This may only happen if there was no matching .for. + */ +static void +parse_endfor(char *line __unused, int code __unused, int lineno __unused) +{ + + Parse_Error(PARSE_FATAL, "for-less endfor"); +} + +/** + * parse_directive + * Got a line starting with a '.'. Check if this is a directive + * and parse it. + * + * return: + * TRUE if line was a directive, FALSE otherwise. + */ +static Boolean +parse_directive(char *line) +{ + char *start; + char *cp; + int dir; + + /* + * Get the keyword: + * .[[:space:]]*\([[:alpha:]][[:alnum:]_]*\).* + * \1 is the keyword. + */ + for (start = line; isspace((u_char)*start); start++) { + ; + } + + if (!isalpha((u_char)*start)) { + return (FALSE); + } + + cp = start + 1; + while (isalnum((u_char)*cp) || *cp == '_') { + cp++; + } + + dir = directive_hash(start, cp - start); + if (dir < 0 || dir >= (int)NDIRECTS || + (size_t)(cp - start) != strlen(directives[dir].name) || + strncmp(start, directives[dir].name, cp - start) != 0) { + /* not actually matched */ + return (FALSE); + } + + if (!skipLine || directives[dir].skip_flag) + (*directives[dir].func)(cp, directives[dir].code, + CURFILE->lineno); + return (TRUE); +} + +/*- + *--------------------------------------------------------------------- + * 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 + * + * Results: + * None + * + * Side Effects: + * 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, FILE *stream) +{ + char *cp; /* pointer into the line */ + char *line; /* the line we're working on */ + + inLine = FALSE; + fatals = 0; + + ParsePushInput(estrdup(name), stream, NULL, 0); + + while ((line = ParseReadLine()) != NULL) { + if (*line == '.' && parse_directive(line + 1)) { + /* directive consumed */ + goto nextLine; + } + if (skipLine || *line == '#') { + /* Skipping .if block or comment. */ + goto nextLine; + } + + if (*line == '\t') { + /* + * If a line starts with a tab, it can only + * hope to be a creation command. + */ + for (cp = line + 1; isspace((unsigned char)*cp); cp++) { + continue; + } + if (*cp) { + if (inLine) { + LstNode *ln; + GNode *gn; + + /* + * 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. + */ + LST_FOREACH(ln, &targets) { + gn = Lst_Datum(ln); + + /* + * if target already + * supplied, ignore + * commands + */ + if (!(gn->type & OP_HAS_COMMANDS)) + Lst_AtEnd(&gn->commands, cp); + else + Parse_Error(PARSE_WARNING, "duplicate script " + "for target \"%s\" ignored", gn->name); + } + continue; + } else { + Parse_Error(PARSE_FATAL, + "Unassociated shell command \"%s\"", + cp); + } + } +#ifdef SYSVINCLUDE + } else if (strncmp(line, "include", 7) == 0 && + isspace((unsigned char)line[7]) && + strchr(line, ':') == NULL) { + /* + * It's an S3/S5-style "include". + */ + ParseTraditionalInclude(line + 7); + goto nextLine; +#endif + } else if (Parse_IsVar(line)) { + ParseFinishLine(); + Parse_DoVar(line, VAR_GLOBAL); + + } else { + /* + * 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... + * 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. XXX this comment seems wrong. + */ + cp = line; + if (isspace((unsigned char)line[0])) { + while (*cp != '\0' && + isspace((unsigned char)*cp)) { + cp++; + } + if (*cp == '\0') { + goto nextLine; + } + } + + ParseFinishLine(); + + cp = Buf_Peel(Var_Subst(line, VAR_CMD, TRUE)); + + free(line); + line = cp; + + /* + * Need a non-circular list for the target nodes + */ + Lst_Destroy(&targets, NOFREE); + inLine = TRUE; + + ParseDoDependency(line); + } + + nextLine: + free(line); + } + + ParseFinishLine(); + + /* + * Make sure conditionals are clean + */ + Cond_End(); + + if (fatals) + errx(1, "fatal errors encountered -- cannot continue"); +} + +/*- + *----------------------------------------------------------------------- + * Parse_MainName -- + * Return a Lst of the main target to create for main()'s sake. If + * no such target exists, we Punt with an obnoxious error message. + * + * Results: + * A Lst of the single node to create. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +void +Parse_MainName(Lst *listmain) +{ + + if (mainNode == NULL) { + Punt("no target to make."); + /*NOTREACHED*/ + } else if (mainNode->type & OP_DOUBLEDEP) { + Lst_AtEnd(listmain, mainNode); + Lst_Concat(listmain, &mainNode->cohorts, LST_CONCNEW); + } else + Lst_AtEnd(listmain, mainNode); +} |