Logo Search packages:      
Sourcecode: ksh version File versions

option.c

/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1984-2007 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * make options support
 *
 * option name mappings are here
 * option flag mappings are in options.h
 */

#include "make.h"
#include "options.h"

#define OPT_OFFSET      10
#define OPT_NON         '-'
#define OPT_SEP         ';'

static const char usage1[] =
"+"
"[-?%s\n]"
USAGE_LICENSE
"[+NAME?nmake - configure, manage and update file hierarchies]"
"[+DESCRIPTION?\bnmake\b reads input \amakefiles\a and triggers shell"
"     actions to build target files that are out of date with prerequisite"
"     files. Most information used to build targets is contained in"
"     the global \abase rules\a that are augmented by user \amakefiles\a."
"     Each operand may be an option, script, or target. An option operand"
"     is preceded by \b-\b or \b+\b. A script operand contains at least"
"     one of \bspace\b, \btab\b, \bnewline\b, \b:\b, \b=\b, \b\"\b, or"
"     \b\\\b and is parsed as a separate, complete makefile. Otherwise"
"     the operand is a \atarget\a that is generated according to the"
"     \amakefile\a and \aglobal\a rules. \atarget\a operands are made"
"     in order from left to right and override the default targets.]"
"[+?Command line options, scripts and targets may appear in any order,"
"     with the exception that no option operand may appear after a"
"     \b--\b operand.]"
"[+?Options are qualified by the base name of the makefile that defined"
"     them. Unqualified options are defined by \bnmake\b itself.]"
;

static Option_t         options[] = /* option table               */
{

{ "accept", OPT_accept, (char*)&state.accept,         0,
      "Accept filesystem timestamps of existing targets." },
{ "alias",  OPT_alias,  (char*)&state.alias,          0,
      "Enable directory aliasing." },
{ "base",         OPT_base,   (char*)&state.base,     0,
      "Compile base or global rules." },
{ "believe",      OPT_believe,      (char*)&state.believe,        0,
      "Believe the state file time of files lower than view level"
      " \alevel-1\a. The file system time will be checked for files with"
      " no state or files in views equal to or higher than \alevel\a."
      " \alevel=0\a causes the file system time to be checked for"
      " files on all view levels. The top view is level 0.", "level:=0" },
{ "compatibility",OPT_compatibility,(char*)&state.compatibility,0,
      "Disable compatibility messages." },
{ "compile",      OPT_compile,      (char*)&state.compileonly,    0,
      "Compile the input makefile and exit." },
{ "corrupt",OPT_corrupt,      (char*)&state.corrupt,        0,
      "\aaction\a determines the action to take for corrupt or invalid"
      " top view state files. The top view default is \berror\b and the"
      " lower view default is \baccept\b. \aaction\a may be one of:",
      "[action:!accept]"
      "{"
      "     [+accept?print a warning and set \b--accept\b]"
      "     [+error?print a diagnostic and exit]"
      "     [+ignore?print a warning and set \b--noreadstate\b]"
      "}" },
{ "cross",  OPT_cross,  (char*)&state.cross,          0,
      "Don't run generated executables." },
{ "debug",  OPT_debug,  0,                      0,
      "Set the debug trace level to \alevel\a. Higher levels produce"
      " more output.", "level" },
{ "errorid",      OPT_errorid,      (char*)&state.errorid,        0,
      "Add \aid\a to the error message command identifier.", "id:=make" },
{ "exec",         OPT_exec,   (char*)&state.exec,     0,
      "Enable shell action execution. \b--noexec\b"
      " disables all but \b.ALWAYS\b shell actions and also disables"
      " make object and state file generation/updates." },
{ "expandview",   OPT_expandview,   (char*)&state.expandview,     0,
      "Expand \a3d\a filesystem paths." },
{ "explain",      OPT_explain,      (char*)&state.explain,        0,
      "Explain each action." },
{ "file",         OPT_file,   (char*)&internal.makefiles,   0,
      "Read the makefile \afile\a. If \b--file\b is not specified then"
      " the makefile names specified by \b$(MAKEFILES)\b are attempted in"
      " order from left to right. The file \b-\b is equivalent"
      " to \b/dev/null\b.", "file" },
{ "force",  OPT_force,  (char*)&state.force,          0,
      "Force all targets to be out of date." },
{ "global", OPT_global, (char*)&internal.globalfiles, 0,
      "Read the global makefile \afile\a. The \b--file\b search is not"
      " affected.", "file" },
{ "ignore", OPT_ignore, (char*)&state.ignore,         0,
      "Ignore shell action errors." },
{ "ignorelock",   OPT_ignorelock,   (char*)&state.ignorelock,     0,
      "Ignore state file locks." },
{ "include",      OPT_include,      0,                      0,
      "Add \adirectory\a to the makefile search list.", "directory" },
{ "intermediate", OPT_intermediate,(char*)&state.intermediate,    0,
      "Force intermediate target generation." },
{ "jobs",         OPT_jobs,   (char*)&state.jobs,           0,
      "Set the shell action concurrency level to \alevel\a."
      " Level \b1\b allows dependency checking while an action is"
      " executing; level \b0\b stops all activity while an action"
      " is executing.", "level:=${" CO_ENV_PROC "::-1}" },
{ "keepgoing",    OPT_keepgoing,    (char*)&state.keepgoing,      0,
      "Continue after error with sibling prerequisites." },
{ "list",         OPT_list,   (char*)&state.list,           0,
      "List the current rules and variables on the standard output in"
      " makefile form." },
{ "mam",          OPT_mam,    (char*)&state.mam.options,    0,
      "Write \amake abstract machine\a output to \afile\a if specified or"
      " to the standard output otherwise. See \bmam\b(5) for details on"
      " the \amake abstract machine\a language. If \aparent\a !=0 then it is"
      " the process id of a parent \amam\a process. \adirectory\a is"
      " the working directory of the current \amam\a process relative"
      " to the root \amam\a process, \b.\b if not specified. \atype\a"
      " must be one of:",
      "[type[,subtype]][::file[::parent[::directory]]]]]]]"
      "{"
            "[+dynamic?\amam\a trace of an actual build]"
            "[+regress?\amam\a for regression testing; labels, path"
            " names and time stamps are canonicalized for easy comparison]"
            "[+static?\amam\a representation of the makefile assertions;"
            " used for makefile conversion]"
            "[+----?0 or more comma separated subtypes ----]"
            "[+port?used by the base rules to generate portable"
            " makefiles; some paths are parameterized; on by default]"
      "}" },
{ "never",  OPT_never,  (char*)&state.never,          0,
      "Don't execute any shell actions. \b--noexec\b executes \b.ALWAYS\b"
      " shell actions." },
{ "option", OPT_option, 0,                      0,
      "Define a new option. The definition is a delimiter separated field"
      " list. Any non-alpha-numberic delimiter other than \b-\b may be used."
      " \b;\b is used in this description. Makefile \bset option\b"
      " definitions must be '...' quoted. Two adjacent delimiters specifies"
      " the literal delimiter character and a \b-\b field value specifies an"
      " empty field. \achar\a is the single character option name, \aname\a"
      " is the long option name, \aset\a is an optional \b.FUNCTION\b that"
      " is called when the option value is changed by \bset\b, \avalues\a is"
      " an \boptget\b(3) value list, and \aflags\a are a combination of:",
      "['char;name;flags;set;description;values']"
      "{"
      "     [+a?multiple values appended]"
      "     [+b?boolean value]"
      "     [+i?internal value inverted]"
      "     [+n?numeric value]"
      "     [+o?\a-char\a means \b--no\b\aname\a]"
      "     [+s?string value]"
      "     [+v?optional option argument]"
      "     [+x?not expanded in \b$(-)\b]"
      "}" },
{ "override",     OPT_override,     (char*)&state.override,       0,
      "Implicit rules or metarules override explicit rules." },
{ "questionable", OPT_questionable,(char*)&state.questionable,    0,
      "Enable questionable features defined by \amask\a. Questionable"
      " features are artifacts of previous implementations (\bnmake\b has"
      " been around since 1984-11-01) that will eventually be dropped."
      " The questionable \amask\a registry is in the \bmain.c\b \bnmake\b"
      " source file.", "mask" },
{ "readonly",     OPT_readonly,     (char*)&state.readonly,       0,
      "Current assignments and assertions will be marked \breadonly\b." },
{ "readstate",    OPT_readstate,    (char*)&state.readstate,      0,
      "Ignore state files lower than view level \alevel\a. \alevel=0\a"
      " ignores state files on all view levels. The top view is level 0.",
      "level:=0" },
{ "regress",      OPT_regress,      (char*)&state.regress,        0,
      "Massage output for regression testing." },
{ "reread", OPT_reread, (char*)&state.reread,         0,
      "Ignore any previously generated \b.mo\b files and re-read all"
      " input makefiles." },
{ "ruledump",     OPT_ruledump,     (char*)&state.ruledump,       0,
      "Dump rule information in tabular form on the standard"
      " error when \bnmake\b exits." },
{ "scan",         OPT_scan,   (char*)&state.scan,     0,
      "Scan for and/or check implicit file prerequisites. On by default." },
{ "serialize",    OPT_serialize,    (char*)&state.serialize,      0,
      "Serialize concurrent output by caching job stdout and stderr"
      " output until job completion." },
{ "silent", OPT_silent, (char*)&state.silent,         0,
      "Do not trace shell actions as they are executed." },
{ "strictview",   OPT_strictview,   (char*)&state.strictview,     0,
      "Set \bVPATH\b \b.SOURCE\b rule interpretation to follow strict"
      " \a3d\a filesystem semantics, where directories in the top views"
      " take precedence. On by default when running in \a2d\a with"
      " \bVPATH\b defined, off by default otherwise." },
{ "target-context",OPT_targetcontext,(char*)&state.targetcontext, 0,
      "Expand and execute shell actions in the target directory context."
      " This allows a single makefile to control a directory tree while"
      " generating target files at the source file directory level. By"
      " default target files are generated in the current directory." },
{ "target-prefix",      OPT_targetprefix,(char*)&state.targetprefix,    0,
      "Allow metarules to match \aseparator\a in the target to \b/\b"
      " in the source. Used to disambiguate source file base name clashes"
      " when target files are generated in the current directory."
      " \aseparator\a must not contain metarule or shell pattern characters.",
      "separator" },
{ "test",         OPT_test,   (char*)&state.test,     0,
      "Enable test code defined by \amask\a. Test code is implementation"
      " specific. The test \amask\a registry is in the \bmain.c\b \bnmake\b"
      " source file.", "mask" },
{ "tolerance",    OPT_tolerance,    (char*)&state.tolerance,      0,
      "Set the time comparison tolerance to \aseconds\a. Times within"
      " the tolerance range compare equal. Useful on systems that can't"
      " quite get the file system and local clocks in sync. A tolerance"
      " of more that 5 seconds soon becomes intolerable.", "seconds" },
{ "touch",  OPT_touch,  (char*)&state.touch,          0,
      "Touch the time stamps of out of date targets rather than execute"
      " the shell action." },
{ "vardump",      OPT_vardump,      (char*)&state.vardump,        0,
      "Dump variable information in tabular form on the standard"
      " error when \bnmake\b exits." },
{ "warn",         OPT_warn,   (char*)&state.warn,     0,
      "Enable verbose warning messages." },
{ "writeobject",  OPT_writeobject,(char*)&state.writeobject,      0,
      "Generate a \b.mo\b make object file in \afile\a that can be read"
      " instead of the input makefiles on the next \bnmake\b invocation."
      " On by default. \b--nowriteobject\b prevents the generation."
      " The default name is used if \afile\a is omitted or \b-\b."
      " If \afile\a is a directory then the default is placed in that"
      " directory.",
      "file:=$(MAKEFILE::B::S=.mo)" },
{ "writestate",   OPT_writestate,   (char*)&state.writestate,     0,
      "Generate a \b.ms\b make state file in \afile\a when \bnmake\b exits."
      "The state contains the time stamps of all prerequisites and targets"
      " that have been accessed since the state file was first generated."
      " On by default. \b--nowritestate\b prevents the generation."
      " The default name is used if \afile\a is omitted or \b-\b."
      " If \afile\a is a directory then the default is placed in that"
      " directory.",
      "file:=$(MAKEFILE::B::S=.ms)" },

{ "byname", OPT_byname, 0,                      0,
      "(obsolete) Set options by name.", "name[=value]]" },
{ "define", OPT_define, 0,                      0,
      "(obsolete) Pass macro definition to the makefile preprocessor.",
      "name[=value]]" },
{ "preprocess",   OPT_preprocess,   0,                      0,
      "(obsolete) Preprocess all makefiles." },
{ "undef",  OPT_undef,  0,                      0,
      "(obsolete) Pass macro deletion to the makefile preprocessor.",
      "name" },

};

static const char usage2[] =
"\n"
"[ script ... ] [ target ... ]\n"
"\n"
"[+DIAGNOSTICS?Diagnostic messages are printed on the standard error and are"
"     classified by levels. The level determines if the diagnostic"
"     is printed, if it causes \bnmake\b to exit, and if it affects"
"     the \bnmake\b exit status. The levels are:]{"
"           [+<0?Debug message, enabled when the absolute value of"
"                 \alevel\a is greater than or equal to the"
"                 \b--debug\b level. Debug diagnostics are prefixed"
"                 by \bdebug\b\alevel\a\b:\b.]"
"           [+1?Warning message, disabled by \b--silent\b. Warning"
"                 diagnostics are prefixed by \bwarning\a\b:\b]"
"           [+2?Non-fatal error message. Processing continues after"
"                 the diagnostic, but the eventual \bnmake\b exit"
"                 status will be non-zero.]"
"           [+>2?Fatal error message. \bnmake\b exits after the"
"                 diagnostic (and internal cleanup) with exit"
"                 status \alevel\a-2.]"
"}"
"[+SEE ALSO?\b3d\b(1), \bar\b(1), \bcc\b(1), \bcoshell\b(1), \bcpp\b(1),"
"     \bprobe\b(1), \bsh\b(1)]"
;

struct Oplist_s; typedef struct Oplist_s Oplist_t;

struct Oplist_s                     /* linked option list         */
{
      char*       option;           /* option value for set()     */
      Oplist_t*   next;       /* next in list               */
};

typedef struct Optstate_s           /* option state               */
{
      Oplist_t*   hidden;           /* options hidden by cmd line */
      Oplist_t*   lasthidden; /* tail of hidden       */
      Oplist_t*   delayed;    /* delayed unknown options    */
      Oplist_t*   lastdelayed;      /* tail of delayed            */

      Option_t*   head;       /* head of external option list     */
      Option_t*   tail;       /* tail of external option list     */

      Hash_table_t*     table;

      Sfio_t*           usage;            /* generated optget() usage   */
      int         usageindex; /* next user index            */
} Optstate_t;

static Optstate_t opt;

static Option_t*
getoption(const char* name)
{
      register Option_t*      op;
      register int            c;

      if (!(op = (Option_t*)hashget(opt.table, name)) && (strchr(name, '-') || strchr(name, '_')))
      {
            while (c = *name++)
                  if (c != '-' && c != '_')
                        sfputc(internal.tmp, c);
            op = (Option_t*)hashget(opt.table, sfstruse(internal.tmp));
      }
      return op;
}

static void
putoption(register Option_t* op, int index)
{
      register char*    s;
      register int      c;
      char        buf[16];

      hashput(opt.table, op->name, (char*)op);
      if (strchr(op->name, '-') || strchr(op->name, '_'))
      {
            s = op->name;
            while (c = *s++)
                  if (c != '-' && c != '_')
                        sfputc(internal.tmp, c);
            hashput(opt.table, strdup(sfstruse(internal.tmp)), (char*)op);
      }
      if (op->flags & Of)
            sfsprintf(buf, sizeof(buf), "+%d", OPT(op->flags));
      else
      {
            buf[0] = '-';
            buf[1] = OPT(op->flags);
            buf[2] = 0;
      }
      hashput(opt.table, strdup(buf), (char*)op);
      if (index >= elementsof(options))
      {
            sfsprintf(buf, sizeof(buf), "-%d", index);
            hashput(opt.table, strdup(buf), (char*)op);
      }
}

/*
 * initialize the option hash table
 */

void
optinit(void)
{
      register int      i;

      opt.table = hashalloc(NiL, HASH_name, "options", 0);
      for (i = 0; i < elementsof(options); i++)
      {
            options[i].flags |= Om;
            switch (OPT(options[i].flags))
            {
            case OPT(OPT_debug):
                  options[i].value = (char*)&error_info.trace;
                  break;
            }
            putoption(&options[i], i);
            if (opt.tail)
                  opt.tail->next = &options[i];
            else
                  opt.head = &options[i];
            opt.tail = &options[i];
      }
}

/*
 * return option table entry given OPT_[a-z]+ flag
 * type==0 panics if not in table
 */

Option_t*
optflag(register int flag)
{
      register Option_t*      op;
      char              buf[8];

      if (flag & Of)
            sfsprintf(buf, sizeof(buf), "+%d", OPT(flag));
      else
      {
            buf[0] = '-';
            buf[1] = OPT(flag);
            buf[2] = 0;
      }
      if (!(op = getoption(buf)) && (flag & ~((1<<8)-1)))
            error(ERROR_PANIC, "%s: unknown option flag", buf);
      return op;
}

/*
 * call op->set with new value
 */

static void
setcall(register Option_t* op, int readonly)
{
      Rule_t*           r;
      char*       oset;
      int         oreadonly;
      char        buf[16];

      if (op->set && (r = getrule(op->set)))
      {
            oset = op->set;
            op->set = 0;
            oreadonly = state.readonly;
            state.readonly = readonly;
            switch (op->flags & (Ob|On|Os))
            {
            case Ob:
                  call(r, *((unsigned char*)op->value) ? "1" : null);
                  break;
            case On:
                  sfsprintf(buf, sizeof(buf), "%d", *((int*)op->value));
                  call(r, buf);
                  break;
            case Os:
                  call(r, *((char**)op->value));
                  break;
            }
            state.readonly = oreadonly;
            op->set = oset;
      }
}

/*
 * copy a declare() string entry s to sp
 */

static void
declarestr(register Sfio_t* sp, register const char* s)
{
      register int      c;

      if (s && *s)
            while (c = *s++)
            {
                  if (c == OPT_SEP)
                        sfputc(sp, c);
                  sfputc(sp, c);
            }
      else
            sfputc(sp, OPT_NON);
      sfputc(sp, OPT_SEP);
}

/*
 * generate external option declaration
 */

static void
declare(Sfio_t* sp, register Option_t* op)
{
      if (!(op->flags & Of))
            sfputc(internal.tmp, OPT(op->flags));
      sfputc(internal.tmp, OPT_SEP);
      declarestr(internal.tmp, op->name);
      if (op->flags & Oa)
            sfputc(internal.tmp, 'a');
      if (op->flags & Ob)
            sfputc(internal.tmp, 'b');
      if (op->flags & On)
            sfputc(internal.tmp, 'n');
      if (op->flags & Oo)
            sfputc(internal.tmp, 'o');
      if (op->flags & Os)
            sfputc(internal.tmp, 's');
      if (op->flags & Ov)
            sfputc(internal.tmp, 'v');
      if (op->flags & Ox)
            sfputc(internal.tmp, 'x');
      sfputc(internal.tmp, OPT_SEP);
      declarestr(internal.tmp, op->set);
      declarestr(internal.tmp, op->description);
      declarestr(internal.tmp, op->arg);
      shquote(sp, fmtesc(sfstruse(internal.tmp)));
}

/*
 * generate optget() usage for op
 */

static void
genusage(register Option_t* op, int index, int last)
{
      long  pos;

      if (op)
      {
            sfputc(opt.usage, '[');
            if (!(op->flags & Of))
            {
                  sfputc(opt.usage, OPT(op->flags));
                  if (op->flags & Oo)
                        sfputc(opt.usage, '!');
                  sfputc(opt.usage, '=');
            }
            sfprintf(opt.usage, "%d:%s?%s]", index + OPT_OFFSET, op->name, op->description);
            if (op->arg)
            {
                  if (op->flags & On)
                        sfputc(opt.usage, '#');
                  else
                        sfputc(opt.usage, ':');
                  if (op->flags & Ov)
                        sfputc(opt.usage, '?');
                  if (*op->arg != '[')
                        sfputc(opt.usage, '[');
                  sfputr(opt.usage, op->arg, -1);
                  if (*op->arg != '[')
                        sfputc(opt.usage, ']');
            }
            sfputc(opt.usage, '\n');
      }
      pos = sfstrtell(opt.usage);
      if (last)
            sfprintf(opt.usage, usage2, version);
      else
            sfputc(opt.usage, 0);
      sfstrseek(opt.usage, pos, SEEK_SET);
}

/*
 * mam output discipline to parameterize local paths
 */

static ssize_t
mamwrite(Sfio_t* fp, const void* buf, size_t n, Sfdisc_t* dp)
{
      char*       s;
      size_t            z;

      static char*      tmp;
      static int  siz;

      z = n;
      if (n > 1 && ((char*)buf)[n-1] == '\n')
      {
            if (n >= siz)
            {
                  siz = roundof(n + 1, 1024);
                  tmp = newof(tmp, char, siz, 0);
            }
            memcpy(tmp, buf, n);
            tmp[n-1] = 0;
            if (s = call(makerule(external.mamaction), tmp))
            {
                  z = strlen(s);
                  if (z >= siz)
                  {
                        siz = roundof(z + 1, 1024);
                        tmp = newof(tmp, char, siz, 0);
                  }
                  memcpy(tmp, s, z);
                  tmp[z++] = '\n';
                  buf = (const char*)tmp;
            }
      }
      return sfwr(fp, buf, z, dp) == z ? n : -1;
}

/*
 * make sure the current time is > the start time
 * to differentiate strtime() "recent" vs. "current"
 * if the clock or filesystem doesn't support subsecond
 * granularity then we sleep until the next integral
 * second ticks off
 *
 * the filesystem checks assume that file time stamp
 * subseconds==0 rarely happens by chance -- the penalty
 * for a wrong guess is slightly slower but still correct
 * regression tests
 *
 * this also assumes that regression tests run faster than
 * the disk inode flush frequency on systems where the
 * cache time resolution is higher than the disk
 */

static void
regressinit(void)
{
      Stat_t      st;
      Time_t      t;
      int   i;

      error_info.version = 0;
      t = CURTIME;
      if (i = tmxnsec(t) && !stat(".", &st) && !tmxnsec(tmxgetatime(&st)) && !tmxnsec(tmxgetmtime(&st)))
            state.start = tmxsns(tmxsec(t)+1, 0);
      while (state.start >= (t = CURTIME))
            tmsleep(0L, 100000000L);
      if (!i)
            state.start = t;
}

/*
 * return option name and details
 */

static char*
showop(register Option_t* op)
{
      sfprintf(internal.tmp, "%s,", op->name);
      sfprintf(internal.tmp, (op->flags & Of) ? "%03o," : "'%c',", op->flags & ((1<<8) - 1));
      if (op->flags & Oa)
            sfprintf(internal.tmp, "|a");
      if (op->flags & Ob)
            sfprintf(internal.tmp, "|b");
      if (op->flags & Of)
            sfprintf(internal.tmp, "|f");
      if (op->flags & Oi)
            sfprintf(internal.tmp, "|i");
      if (op->flags & On)
            sfprintf(internal.tmp, "|n");
      if (op->flags & Oo)
            sfprintf(internal.tmp, "|o");
      if (op->flags & Os)
            sfprintf(internal.tmp, "|s");
      if (op->flags & Ov)
            sfprintf(internal.tmp, "|v");
      if (op->flags & Ox)
            sfprintf(internal.tmp, "|x");
      if (op->flags & OPT_COMPILE)
            sfprintf(internal.tmp, "|COMPILE");
      if (op->flags & OPT_DECLARE)
            sfprintf(internal.tmp, "|DECLARE");
      if (op->flags & OPT_DEFAULT)
            sfprintf(internal.tmp, "|DEFAULT");
      if (op->flags & OPT_EXTERNAL)
            sfprintf(internal.tmp, "|EXTERNAL");
      if (op->flags & OPT_READONLY)
            sfprintf(internal.tmp, "|READONLY");
      if (op->flags & OPT_SET)
            sfprintf(internal.tmp, "|SET");
      sfputc(internal.tmp, '|');
      return sfstruse(internal.tmp);
}

/*
 * return next option definition field
 */

static char*
field(char** p, int sep, int app, int lenient)
{
      register char*    s;
      register char*    t;
      char*       v;
      register int      c;

      if (!(c = *(s = *p)) || c == app)
            return 0;
      if (c == sep)
      {
            *p = s + 1;
            return 0;
      }
      v = t = s;
      for (;;)
      {
            if (!(c = *t++ = *s++))
            {
                  s--;
                  t--;
                  break;
            }
            else if (c == sep)
            {
                  if (lenient || !*s)
                  {
                        t--;
                        s--;
                        break;
                  }
                  if (*s == OPT_NON && (!*(s + 1) || *(s + 1) == sep))
                  {
                        t--;
                        s++;
                        break;
                  }
                  if (*s != c)
                  {
                        t--;
                        s--;
                        break;
                  }
                  s++;
            }
      }
      if (*s)
            s++;
      *p = s;
      if (*t)
            *t = 0;
      if (!*v || lenient && *v == OPT_NON && !*(v + 1))
            return 0;
      return v;
}

/*
 * set an option given its pointer
 */

static void
setop(register Option_t* op, register int n, char* s, int type)
{
      char*       t;
      Rule_t*           r;
      int         readonly;

      readonly = state.readonly;
      if (OPT(op->flags) != OPT(OPT_option))
      {
            if (type == ':' && (op->flags & OPT_SET))
                  return;
            if (readonly)
                  op->flags |= OPT_READONLY;
            else if (!state.user && (op->flags & OPT_READONLY))
            {
                  Oplist_t*   x;

                  /*
                   * save for listops(*,'@')
                   */

                  sfprintf(internal.tmp, "%s", op->name);
                  if (type == ':' && !(op->flags & OPT_SET))
                        sfputc(internal.tmp, ':');
                  if (s)
                        sfprintf(internal.tmp, "=%s", s);
                  else
                        sfprintf(internal.tmp, "=%d", n);
                  n = sfstrtell(internal.tmp);
                  s = sfstruse(internal.tmp);
                  x = newof(0, Oplist_t, 1, n);
                  x->option = strcpy((char*)(x + 1), s);
                  if (opt.lasthidden)
                        opt.lasthidden = opt.lasthidden->next = x;
                  else
                        opt.hidden = opt.lasthidden = x;
                  return;
            }
            if (error_info.trace <= -3)
                  error(-3, "option(%s,%d,\"%s\")", showop(op), n,  s ? s : null);
      }
      if (type != ':')
      {
            op->flags |= OPT_SET;
            op->flags &= ~OPT_DEFAULT;
      }
      else if (!(op->flags & OPT_SET))
      {
            op->flags |= OPT_DEFAULT;
            if (!state.loading)
                  op->flags |= OPT_COMPILE;
      }
      if (state.reading)
            op->flags |= OPT_COMPILE;
      if (op->flags & Oi)
      {
            if (op->flags & On)
                  n = -n;
            else
                  n = !n;
      }
      else if (op->flags & Ob)
            n = n != 0;
      else if ((op->flags & (Os|Ov)) == Os && n && !s)
            error(3, "-%c: option argument expected", OPT(op->flags));
      if (!n)
            s = 0;
      switch (OPT(op->flags))
      {
      case OPT(OPT_believe):
            if (state.compile < COMPILED)
                  state.believe = n;
            else
                  error(2, "%s: option must be set before %s", op->name, external.makeinit);
            return;
      case OPT(OPT_byname):
            if (s)
                  set(s, 1, NiL);
            return;
      case OPT(OPT_corrupt):
            if (!s)
                  s = "-";
            if ((*s == *(state.corrupt = "accept") || *s == '-') && (!*(s + 1) || !strcmp(s, state.corrupt)))
                  ;
            else if (*s == *(state.corrupt = "error") && (!*(s + 1) || !strcmp(s, state.corrupt)))
                  state.corrupt = 0;
            else if (*s == *(state.corrupt = "ignore") && (!*(s + 1) || !strcmp(s, state.corrupt)))
                  ;
            else
            {
                  state.corrupt = 0;
                  error(2, "%s: invalid corrupt action", s);
            }
            return;
      case OPT(OPT_include):
            addprereq(catrule(internal.source->name, external.source, NiL, 1), makerule(s), PREREQ_APPEND);
            if (!(op->flags & OPT_READONLY))
                  break;
            /*FALLTHROUGH*/
      case OPT(OPT_define):
      case OPT(OPT_undef):
            if (s)
            {
                  sfprintf(internal.tmp, "-%c%s", OPT(op->flags), s);
                  if (!(r = getrule(sfstruse(internal.tmp))))
                        r = makerule(NiL);
                  addprereq(internal.preprocess, r, PREREQ_APPEND);
            }
            return;
      case OPT(OPT_preprocess):
            if (!state.preprocess)
            {
                  state.preprocess = 1;
                  if (!state.compatibility)
                        error(1, "makefile preprocessing is obsolete -- use make statements");
            }
            return;
      case OPT(OPT_errorid):
            if (s && *s)
            {
                  if (state.errorid)
                        sfprintf(internal.tmp, "%s/", state.errorid);
                  sfprintf(internal.tmp, "%s", s);
                  state.errorid = strdup(sfstruse(internal.tmp));
                  sfprintf(internal.tmp, "%s [%s]", idname, state.errorid);
                  error_info.id = strdup(sfstruse(internal.tmp));
            }
            else
            {
                  op->flags &= ~OPT_SET;
                  error_info.id = idname;
            }
            return;
      case OPT(OPT_jobs):
            if (n >= MAXJOBS)
                  n = MAXJOBS - 1;
            if (n < 1)
                  n = 0;
            state.jobs = n;
            return;
      case OPT(OPT_mam):
            if (state.mam.label != null)
            {
                  if (state.mam.label)
                        free(state.mam.label);
                  state.mam.label = null;
            }
            if (state.mam.options)
            {
                  free(state.mam.options);
                  state.mam.options = 0;
            }
            if (state.mam.root)
            {
                  free(state.mam.root);
                  state.mam.root = 0;
            }
            if (state.mam.out)
            {
                  if (state.mam.out != sfstdout && state.mam.out != sfstderr)
                        sfclose(state.mam.out);
                  state.mam.out = 0;
            }
            state.mam.dontcare = state.mam.dynamic = state.mam.regress = state.mam.statix = state.mam.parent = 0;
            state.mam.port = 1;
            if (s)
            {
                  char* o;
                  char* u;
                  Sfio_t*     tmp;

                  tmp = sfstropen();
                  sfputr(tmp, s, 0);
                  s = sfstruse(tmp);
                  if (t = strchr(s, ':'))
                        *t++ = 0;
                  if (o = strchr(s, ','))
                        *o++ = 0;
                  if (*s == *(state.mam.type = "dynamic") && (!*(s + 1) || !strcmp(s, state.mam.type)))
                        state.mam.dynamic = 1;
                  else if (*s == *(state.mam.type = "regress") && (!*(s + 1) || !strcmp(s, state.mam.type)))
                  {
                        state.regress = state.mam.regress = 1;
                        state.silent = 1;
                        if (!table.regress)
                              table.regress = hashalloc(table.rule, HASH_name, "regress-paths", 0);
                        regressinit();
                  }
                  else if (*s == *(state.mam.type = "static") && (!*(s + 1) || !strcmp(s, state.mam.type)))
                        state.mam.statix = 1;
                  else
                        error(3, "%s: invalid mam type: {dynamic,regress,static} expected", s);
                  while (s = o)
                  {
                        if (o = strchr(s, ','))
                              *o++ = 0;
                        if (*s == 'n' && *(s + 1) == 'o')
                        {
                              s += 2;
                              n = 0;
                        }
                        else
                              n = 1;
                        if (*s == *(u = "dontcare") && (!*(s + 1) || !strcmp(s, u)))
                              state.mam.dontcare = n;
                        else if (*s == *(u = "port") && (!*(s + 1) || !strcmp(s, u)))
                              state.mam.port = n;
                        else
                              error(3, "%s: invalid mam option: [no]{dontcare,port} expected", s);
                  }
                  if (t)
                  {
                        s = t;
                        if (t = strchr(s, ':'))
                        {
                              *t++ = 0;
                              if (isdigit(*t))
                              {
                                    while (isdigit(*t))
                                          state.mam.parent = state.mam.parent * 10 + *t++ - '0';
                                    if (!state.mam.regress)
                                    {
                                          sfprintf(internal.tmp, "%05d ", state.pid);
                                          state.mam.label = strdup(sfstruse(internal.tmp));
                                    }
                              }
                              if (*t)
                              {
                                    if (*t != ':')
                                          error(3, "%s: mam label expected", t);
                                    t++;
                              }
                              if (!*t)
                                    t = 0;
                        }
                  }
                  else s = null;
                  if (!*s || streq(s, "-") || streq(s, "/dev/fd/1") || streq(s, "/dev/stdout"))
                  {
                        s = "/dev/stdout";
                        state.mam.out = sfstdout;
                  }
                  else if (streq(s, "/dev/fd/2") || streq(s, "/dev/stderr"))
                        state.mam.out = sfstderr;
                  else if (!(state.mam.out = sfopen(NiL, s, state.mam.parent ? "a" : "w")))
                        error(ERROR_SYSTEM|3, "%s: cannot write mam output file", s);
                  else
                        fcntl(sffileno(state.mam.out), F_SETFD, FD_CLOEXEC);
                  sfset(state.mam.out, SF_LINE, 1);
                  state.mam.disc.writef = mamwrite;
                  if (sfdisc(state.mam.out, &state.mam.disc) != &state.mam.disc)
                        error(3, "%s: cannot push mam output discipline", s);
                  sfprintf(internal.tmp, "%s", state.mam.type);
                  if (state.mam.dontcare)
                        sfprintf(internal.tmp, ",dontcare");
                  sfprintf(internal.tmp, ",%sport", state.mam.port ? null : "no");
                  sfprintf(internal.tmp, ":");
                  if (*s != '/')
                        sfprintf(internal.tmp, "%s/", internal.pwd);
                  sfprintf(internal.tmp, "%s:%d", s, state.pid);
                  if (t)
                  {
                        sfprintf(internal.nam, "$(\"%s\":P=A)", t);
                        expand(tmp, sfstruse(internal.nam));
                        state.mam.root = strdup(sfstruse(tmp));
                        state.mam.rootlen = strlen(state.mam.root);
                        sfprintf(internal.tmp, ":%s", state.mam.root);
                  }
                  state.mam.options = strdup(sfstruse(internal.tmp));
                  sfstrclose(tmp);
            }
            return;
      case OPT(OPT_option):
            s = strdup(s);
            while (*s)
            {
                  char*       name;
                  char*       func;
                  char*       desc;
                  char*       args;
                  Option_t*   nop;
                  int         app;
                  int         sep;
                  char        buf[16];

                  sep = *s;
                  if (isalpha(sep))
                  {
                        n = sep|Ob;
                        sep = *++s;
                  }
                  else
                  {
                        n = '?'|Of|Ob;
                        if (sep == OPT_NON)
                              sep = *++s;
                  }
                  app = (sep == ':') ? ';' : ':';
                  s++;
                  if (!(name = field(&s, sep, app, 1)))
                  {
                        if (n & Of)
                              error(2, "option flag and name omitted");
                        else
                              error(2, "-%c: option name omitted", OPT(n));
                        break;
                  }
                  if (t = field(&s, sep, app, 1))
                  {
                        for (;;)
                        {
                              switch (*t++)
                              {
                              case 0:
                                    break;
                              case 'a':
                                    n |= Oa;
                                    continue;
                              case 'b':
                                    n &= ~(On|Os);
                                    n |= Ob;
                                    continue;
                              case 'n':
                                    n &= ~(Ob|Os);
                                    n |= On;
                                    continue;
                              case 'o':
                                    n |= Oo;
                                    continue;
                              case 's':
                                    n &= ~(Ob|On);
                                    n |= Os;
                                    continue;
                              case 'v':
                                    n |= Ov;
                                    continue;
                              case 'x':
                                    n |= Ox;
                                    continue;
                              /* no complaints here for future extensions */
                              }
                              break;
                        }
                  }
                  func = field(&s, sep, app, 1);
                  desc = field(&s, sep, app, 0);
                  args = field(&s, sep, app, 0);
                  if (!(n & Of))
                  {
                        for (nop = &options[0]; nop < &options[elementsof(options)]; nop++)
                              if (OPT(nop->flags) == OPT(n))
                                    break;
                        if (nop < &options[elementsof(options)])
                        {
                              error(2, "--%s: -%c conflicts with --%s", s, OPT(n), nop->name);
                              return;
                        }
                        buf[0] = '-';
                        buf[1] = OPT(n);
                        buf[2] = 0;
                        nop = getoption(buf);
                  }
                  else
                        nop = 0;
                  if (nop)
                  {
                        if (n & On)
                              *((int*)nop->value) = *((unsigned char*)nop->value);
                        else if (n & Os)
                              *((char**)nop->value) = 0;
                        nop->flags = n;
                  set_insert:
                        nop->name = name;
                        nop->set = func;
                        if ((n & (Os|Oa)) == (Os|Oa) && !op->value)
                              *((Rule_t**)nop->value) = catrule(".OPTION.", nop->name, ".LIST.", 1);
                        if (!desc)
                              desc = "option.";
                        if (*desc != '(')
                        {
                              sfputc(internal.tmp, '(');
                              if ((t = parsefile()) && *t)
                                    edit(internal.tmp, t, DELETE, KEEP, DELETE);
                              else
                                    sfputr(internal.tmp, state.base ? "rules" : state.global ?  "global" : "user", -1);
                              sfprintf(internal.tmp, ") %s", desc);
                              desc = strdup(sfstruse(internal.tmp));
                        }
                        nop->description = desc;
                        if (!args && (nop->flags & (On|Os)))
                              args = (nop->flags & On) ? "number" : "string";
                        if (args && (nop->flags & Ob))
                        {
                              nop->flags &= ~Ob;
                              nop->flags |= Os;
                        }
                        nop->arg = args;
                        putoption(nop, opt.usageindex);
                        genusage(nop, opt.usageindex++, 1);
                        if (nop->set && (nop->flags & OPT_SET))
                              readonly = 1;
                  }
                  else if (!(nop = getoption(name)))
                  {
                        nop = newof(0, Option_t, 1, sizeof(char*));
                        nop->value = (char*)(nop + 1);
                        nop->flags = n|OPT_EXTERNAL;
                        if (!state.loading)
                              nop->flags |= OPT_DECLARE;
                        if (opt.tail)
                              opt.tail->next = nop;
                        else
                              opt.head = nop;
                        opt.tail = nop;
                        goto set_insert;
                  }
                  else if (!(nop->flags & OPT_EXTERNAL))
                        error(1, "--%s is an internal option", nop->name);
                  else
                  {
                        if (nop->flags & Of)
                        {
                              if ((n & (Ob|On|Os)) != (nop->flags & (Ob|On|Os)))
                              {
                                    if (n & Os)
                                          *((char**)nop->value) = 0;
                                    else if ((n & On) && (nop->flags & Ob))
                                          *((int*)nop->value) = *((unsigned char*)nop->value);
                                    else if ((n & Ob) && (nop->flags & On))
                                          *((unsigned char*)nop->value) = *((int*)nop->value) != 0;
                              }
                              nop->flags = n;
                        }
                        else if (OPT(nop->flags) != OPT(n))
                              error(1, "--%s: option flag -%c conflicts with -%c", nop->name, OPT(n), OPT(nop->flags));
                        if ((nop->flags & (Ob|On|Os)) != (n & (Ob|On|Os)))
                              error(1, "--%s: option is %s", nop->name, (nop->flags & Ob) ? "boolean" : (nop->flags & On) ? "numeric" : "string valued");
                  }

                  /*
                   * skip the remaining fields for future extensions
                   * and consume the append char if specified
                   */

                  while (field(&s, sep, app, 0));
                  if (*s == app)
                        s++;
            }
            optcheck(0);
            return;
      case OPT(OPT_never):
            if (state.never = n)
                  state.exec = 0;
            return;
      case OPT(OPT_regress):
            if (state.regress = n)
                  regressinit();
            return;
      case OPT(OPT_reread):
            if (state.reread = n)
                  state.forceread = 1;
            return;
      case OPT(OPT_silent):
            if (state.silent = n)
            {
                  if (!error_info.trace)
                        error_info.trace = 2;
            }
            else
                  if (error_info.trace > 0)
                        error_info.trace = 0;
            return;
      case OPT(OPT_targetprefix):
            if (!n)
                  s = 0;
            else if (s)
                  s = strdup(s);
            if (state.targetprefix = s)
                  for (;;)
                  {
                        switch (*s++)
                        {
                        case 0:
                              break;
                        case '%':
                        case '*':
                        case '?':
                        case '&':
                        case '"':
                        case '\'':
                        case '\\':
                              error(3, "--%s: %s: value must not contain metarule or shell pattern characters", op->name, state.targetprefix);
                              break;
                        default:
                              continue;
                        }
                        break;
                  }
            return;
      case OPT(OPT_tolerance):
            if ((state.tolerance = n) > 60)
                  error(1, "the time comparison tolerance should probably be less than a minute");
            return;
      case OPT(OPT_writeobject):
            if (!n)
                  s = 0;
            else if (state.makefile)
            {
                  error(1, "%s: object file name cannot change after %s read", op->name, state.makefile);
                  return;
            }
            else if (!s)
                  s = "-";
            else
                  s = strdup(s);
            state.writeobject = s;
            return;
      case OPT(OPT_writestate):
            if (!n)
                  s = 0;
            else if (!s)
                  s = "-";
            else
                  s = strdup(s);
            state.writestate = s;
            if (state.statefile)
            {
                  free(state.statefile);
                  state.statefile = 0;
            }
            return;
      }
      if (op->value)
      {
            if (op->flags & Ob)
            {
                  switch (type)
                  {
                  case '^':
                        *((unsigned char*)op->value) ^= n;
                        break;
                  default:
                        *((unsigned char*)op->value) = n != 0;
                        break;
                  }
            }
            else if (op->flags & On)
            {
                  switch (type)
                  {
                  case '+':
                        *((int*)op->value) += n;
                        break;
                  case '-':
                        *((int*)op->value) -= n;
                        break;
                  case '|':
                        *((int*)op->value) |= n;
                        break;
                  case '&':
                        *((int*)op->value) &= n;
                        break;
                  case '^':
                        *((int*)op->value) ^= n;
                        break;
                  default:
                        *((int*)op->value) = n;
                        break;
                  }
            }
            else if (op->flags & Os)
            {
                  if (op->flags & Oa)
                  {
                        if (s)
                        {
                              /*
                               * s is a ':' list
                               */

                              for (;;)
                              {
                                    if (t = strchr(s, ':'))
                                          *t = 0;
                                    addprereq((*(Rule_t**)op->value), makerule(s), PREREQ_APPEND);
                                    if (!t)
                                          break;
                                    *t++ = ':';
                                    s = t;
                              }
                        }
                        else
                        {
                              freelist((*(Rule_t**)op->value)->prereqs);
                              (*(Rule_t**)op->value)->prereqs = 0;
                        }
                  }
                  else
                        *((char**)op->value) = s ? strdup(s) : 0;
            }
      }
      if (op->set)
            setcall(op, readonly);
}

/*
 * generate a single option setting in sp given the option pointer
 * setting:
 *     0  only the value is generated, "" if option not set
 *    '-' option name and value suitable for set()
 *    '?' 1 if OPT_SET, "" otherwise
 *    '#' table attributes with the current value
 * flag!=0 if relative to option flag rather than option name
 */

static void
genop(register Sfio_t* sp, register Option_t* op, int setting, int flag)
{
      register long           n;
      char*             v;
      List_t*                 p;

      switch (setting)
      {
      case '?':
            if (op->flags & OPT_SET)
                  sfprintf(sp, "1");
            return;
      case '#':
            sfprintf(sp, "%s=", showop(op));
            break;
      case 0:
            break;
      default:
            flag &= ~Oi;
            break;
      }
      switch (op->flags & (Ob|On|Os))
      {
      case Ob:
            if (op->value)
                  n = *((unsigned char*)op->value);
            else
                  n = 0;
            if ((op->flags & Oi) ^ (flag & Oi))
                  n = !n;
            if ((flag & Of) && (op->flags & Oo))
                  n = !n;
            switch (setting)
            {
            case 0:
                  if (!n)
                        return;
                  /*FALLTHROUGH*/
            case '#':
                  break;
            default:
                  if (op->flags & OPT_DEFAULT)
                        sfprintf(sp, "--%s:=", op->name);
                  else if (n)
                  {
                        sfprintf(sp, "--%s", op->name);
                        return;
                  }
                  else
                  {
                        sfprintf(sp, "--no%s", op->name);
                        return;
                  }
                  break;
            }
            sfputc(sp, n ? '1' : '0');
            break;
      case On:
            if (op->value)
                  n = *((int*)op->value);
            else
                  n = 0;
            if (op->flags & Oi)
                  n = -n;
            if (flag & Oi)
                  n = !n;
            switch (setting)
            {
            case 0:
                  if (!n)
                        return;
                  /*FALLTHROUGH*/
            case '#':
                  break;
            default:
                  if (op->flags & OPT_DEFAULT)
                        sfprintf(sp, "--%s:=", op->name);
                  else if (n)
                        sfprintf(sp, "--%s=", op->name);
                  else
                  {
                        sfprintf(sp, "--no%s", op->name);
                        return;
                  }
                  break;
            }
            sfprintf(sp, (op->flags & Oa) ? "0x%08lx" : "%ld", n);
            break;
      case Os:
            if ((op->flags & Oa) && op->value && !(flag & Oi))
            {
                  p = (*(Rule_t**)op->value)->prereqs;
                  switch (setting)
                  {
                  case 0:
                        if (!p)
                              return;
                        /*FALLTHROUGH*/
                  case '#':
                        break;
                  default:
                        if (op->flags & OPT_DEFAULT)
                              sfprintf(sp, "--%s:=", op->name);
                        else if (p)
                              sfprintf(sp, "--%s=", op->name);
                        else
                        {
                              sfprintf(sp, "--no%s", op->name);
                              return;
                        }
                        break;
                  }
                  if (p)
                        for (;;)
                        {
                              shquote(sp, fmtesc(p->rule->name));
                              if (!(p = p->next))
                                    break;
                              sfputc(sp, ':');
                        }
            }
            else
            {
                  if (op->value && !(flag & Oi))
                        v = *((char**)op->value);
                  else
                        v = 0;
                  switch (setting)
                  {
                  case 0:
                  case '#':
                        if (!v)
                              return;
                        setting = 0;
                        break;
                  default:
                        if (op->flags & OPT_DEFAULT)
                              sfprintf(sp, "--%s:=", op->name);
                        else if (v)
                              sfprintf(sp, "--%s=", op->name);
                        else
                        {
                              sfprintf(sp, "--no%s", op->name);
                              return;
                        }
                        break;
                  }
                  if (v)
                  {
                        if (setting)
                              shquote(sp, fmtesc(v));
                        else
                              sfputr(sp, v, -1);
                  }
            }
            break;
#if DEBUG
      default:
            error(PANIC, "%s: option has%s%s%s", op->name, (op->flags & Ob) ? " Ob" : null, (op->flags & On) ? " On" : null, (op->flags & Os) ? " Os" : null);
            break;
#endif
      }
}

/*
 * check and set delayed options
 * this gives base, global and local makefiles a chance to
 * define the options via --option=definition
 */

void
optcheck(int must)
{
      Oplist_t*   x;
      int         errors;

      if (must)
      {
            errors = error_info.errors;
            while (x = opt.delayed)
            {
                  opt.delayed = x->next;
                  if (*x->option)
                        set(x->option, 1, NiL);
                  free(x);
            }
            if (error_info.errors != errors)
                  finish(2);
      }
      else
            for (x = opt.delayed; x; x = x->next)
                  if (!set(x->option, 0, NiL))
                        *x->option = 0;
}

/*
 * generate option setting list in sp suitable for set()
 * setting:
 *       0  non-Ox OPT_SET options
 *      '+' non-Om non-Ox OPT_SET options
 *    '-' all
 *      '?' non-Ox OPT_DEFAULT
 *    '#' table attributes with the current value
 *    '@' internal object file dump
 */

void
listops(register Sfio_t* sp, int setting)
{
      register Option_t*      op;
      register Oplist_t*      x;
      int               sc;
      int               sep;
      long              mask;
      long              test;
      long              clear;

      sep = 0;
      sc = ' ';
      clear = 0;
      switch (setting)
      {
      case '-':
            mask = 0;
            test = 0;
            break;
      case '+':
            setting = '-';
            mask = Om|Ox|OPT_SET;
            test = Om|OPT_SET;
            break;
      case '?':
            setting = '-';
            mask = Ox|OPT_DEFAULT;
            test = OPT_DEFAULT;
            break;
      case '#':
            sc = '\n';
            mask = 0;
            test = 0;
            break;
      case '@':
            setting = '-';
            mask = Ox|OPT_COMPILE;
            test = OPT_COMPILE;
            clear = ~OPT_COMPILE;
            for (op = opt.head; op; op = op->next)
                  if (op->flags & OPT_DECLARE)
                  {
                        op->flags &= ~OPT_DECLARE;
                        if (sep)
                              sfputc(sp, ':');
                        else
                        {
                              sep = 1;
                              sfprintf(sp, "--%s=", optflag(OPT_option)->name);
                        }
                        declare(sp, op);
                  }
            while (x = opt.hidden)
            {
                  opt.hidden = x->next;
                  if (sep)
                        sfputc(sp, sc);
                  else
                        sep = 1;
                  sfputr(sp, x->option, -1);
                  free(x);
            }
            break;
      default:
            setting = '-';
            mask = Ox|OPT_SET;
            test = OPT_SET;
            break;
      }
      for (op = opt.head; op; op = op->next)
            if ((op->flags & mask) == test)
            {
                  if (sep)
                        sfputc(sp, sc);
                  else
                        sep = 1;
                  genop(sp, op, setting, 0);
                  if (clear)
                        op->flags &= clear;
            }
}

/*
 * generate a single option setting in sp given the option name
 * setting passed to genop()
 * end of s is returned
 */

void
getop(register Sfio_t* sp, char* name, int setting)
{
      register Option_t*      op;
      int               flag;

      if ((op = getoption(name)) && !(flag = 0) || name[0] == 'n' && name[1] == 'o' && (op = getoption(&name[2])) && (flag = Oi) || name[0] && !name[1] && (op = optflag(name[0])) && (flag = Of))
            genop(sp, op, setting, flag);
}

/*
 * set an option by its optget()/optstr() index
 */

static void
optset(int i, char* v, Sfio_t* scope)
{
      register char*    s;
      int         n;
      Option_t*   op;
      Oplist_t*   x;
      char        buf[16];

      if (i > 0)
      {
            if (state.readonly && !state.interpreter)
            {
                  x = newof(0, Oplist_t, 1, strlen(v) + 1);
                  x->option = strcpy((char*)(x + 1), v);
                  if (opt.lastdelayed)
                        opt.lastdelayed = opt.lastdelayed->next = x;
                  else
                        opt.delayed = opt.lastdelayed = x;
            }
            else
                  error((i == '?' && opt_info.option[0] == '-' && opt_info.option[1] != '?') ? (ERROR_USAGE|(state.interpreter ? 2 : 4)) : 2, "%s", opt_info.arg);
      }
      else
      {
            if ((i = -OPT_OFFSET - i) < elementsof(options))
                  op = &options[i];
            else
            {
                  sfsprintf(buf, sizeof(buf), "-%d", i);
                  op = getoption(buf);
            }
            n = (op->flags & On) ? (opt_info.arg ? opt_info.num : 0) : (op->flags & Ob) ? (opt_info.num != 0) : (opt_info.num != 0) == !(op->flags & Oi);
            if (*opt_info.option == '+')
                  n = !n;
            if ((s = opt_info.arg) && (!*s || !(op->flags & Os)))
                  s = 0;
            if (scope)
                  genop(scope, op, '-', 0);
            setop(op, n, s, opt_info.assignment);
      }
}

/*
 * set options by name
 */

int
set(char* s, int must, Sfio_t* scope)
{
      register int      i;
      int         r;
      int         oreadonly;
      Opt_t       info;

      r = 0;
      info = opt_info;
      while (i = optstr(s, sfstrbase(opt.usage)))
      {
            if (i > 0 && !must)
            {
                  r = -1;
                  break;
            }
            s += opt_info.offset;
            if (!must)
            {
                  oreadonly = state.readonly;
                  state.readonly = 1;
            }
            optset(i, opt_info.argv[1], scope);
            if (!must)
                  state.readonly = oreadonly;
      }
      opt_info = info;
      return r;
}

/*
 * set command line options with optget(3)
 * options may appear in any position before --
 * read command line assignments
 * mark the command line scripts and targets
 * index of the first command line script or target is returned
 */

int
scanargs(int argc, char** argv, int* argf)
{
      register int      i;
      register char*    s;
      register int      c;
      int         args;
      int         done;
      char*       e;

      /*
       * generate the optget() usage string from options[]
       */

      if (!(opt.usage = sfstropen()))
            error(ERROR_SYSTEM|3, "out of space [usage]");
      sfprintf(opt.usage, usage1, version);
      for (i = 0; i < elementsof(options); i++)
            genusage(options + i, i, 0);
      genusage(NiL, 0, 1);
      opt.usageindex = i;
      args = 0;
      done = 0;
 again:
      while (i = optget(argv, sfstrbase(opt.usage)))
            optset(i, argv[opt_info.index - (opt_info.offset == 0)], NiL);
      if (!done && streq(argv[opt_info.index - 1], "--"))
            done = 1;
      for (i = opt_info.index; i < argc; i++)
      {
            s = argv[i];
            while (isspace(*s))
                  s++;
            if (!done && (*s == '-' || *s == '+') && *(s + 1))
            {
                  opt_info.index = i;
                  opt_info.offset = 0;
                  goto again;
            }
            if (*s)
            {
                  for (e = s; c = *s; s++)
                        if (c == ',')
                        {
                              s = null;
                              break;
                        }
                        else if (istype(c, C_TERMINAL) && c != '+' && c != '&')
                        {
                              while (isspace(*s))
                                    s++;
                              if (*s == '=' || *(s + 1) == '=')
                              {
                                    argf[i] |= ARG_ASSIGN;
                                    state.reading = 1;
                                    parse(NiL, e, "command line assignment", NiL);
                                    state.reading = 0;
                              }
                              else
                              {
                                    argf[i] |= ARG_SCRIPT;
                                    if (!args)
                                          args = i;
                              }
                              break;
                        }
                  if (!*s)
                  {
                        argf[i] |= ARG_TARGET;
                        if (!args)
                              args = i;
                  }
            }
      }
      return error_info.errors ? -1 : args ? args : argc;
}

/*
 * please reboot your program to finish setup ...
 *
 * old!=0 execs external.old for backwards compatibility
 * otherwise re-exec forcing input files to be read
 */

void
punt(int old)
{
      register char*          s;
      register char**         av;
      int               i;
      List_t*                 p;
      Oplist_t*         x;
      Sfio_t*                 vec;

      if (state.reread > 1)
            error(PANIC, "makefile prerequisites cause unbounded make exec recursion");
      vec = sfstropen();
      if (old)
      {
            /*
             * this chunk must track external.old options
             */

            sfputc(internal.tmp, '-');

            /*
             * options with same flag and meaning
             */

            if (error_info.trace < -3)
                  sfputc(internal.tmp, 'd');
            if (state.ignore)
                  sfputc(internal.tmp, 'i');
            if (state.keepgoing)
                  sfputc(internal.tmp, 'k');
            if (!state.mam.options && !state.exec)
                  sfputc(internal.tmp, 'n');
            if (state.silent)
                  sfputc(internal.tmp, 's');
            if (state.touch)
                  sfputc(internal.tmp, 't');

            /*
             * options with different flag but same meaning
             */

            if (state.vardump)
                  sfputc(internal.tmp, 'p');
            if (!state.mam.options && state.force)
                  sfputc(internal.tmp, 'u');

            /*
             * options with different flag and meaning
             * the external.old meaning prevails
             */

            if (state.base)
                  sfputc(internal.tmp, 'b');
            if (state.explain)
                  sfputc(internal.tmp, 'e');
            if (state.ruledump)
                  sfputc(internal.tmp, 'r');
            putptr(vec, getval(external.old, VAL_PRIMARY));
            s = sfstruse(internal.tmp);
            if (s[1])
                  putptr(vec, strdup(s));

            /*
             * mam arguments -- assume oldmake knows mam
             */

            if (state.mam.options)
            {
                  sfputc(internal.tmp, '-');
                  if (state.never)
                        sfputc(internal.tmp, 'N');
                  else if (!state.exec)
                        sfputc(internal.tmp, 'n');
                  if (state.force)
                        sfputc(internal.tmp, 'F');
                  sfputc(internal.tmp, 'M');
                  sfputr(internal.tmp, state.mam.options, -1);
                  putptr(vec, strdup(sfstruse(internal.tmp)));
            }

            /*
             * delayed (unknown) options
             */

            for (x = opt.delayed; x; x = x->next)
                  putptr(vec, x->option);

            /*
             * makefile arguments
             */

            if (!(p = internal.makefiles->prereqs))
            {
                  putptr(vec, "-f");
                  putptr(vec, state.makefile);
            }
            else for (; p; p = p->next)
            {
                  putptr(vec, "-f");
                  putptr(vec, p->rule->name);
            }

            /*
             * variable assignment arguments
             */

            for (i = 1; i < state.argc; i++)
                  if (state.argf[i] & (ARG_ASSIGN|ARG_TARGET))
                        putptr(vec, state.argv[i]);
            if (!state.silent)
            {
                  /*
                   * echo the exec action external.old style
                   */

#if !__sun__ && !sun
                  sfprintf(sfstderr, "\t");
#endif
                  putptr(vec, 0);
                  av = (char**)sfstrbase(vec);
                  while (*av)
                        sfprintf(sfstderr, "%s ", *av++);
                  sfprintf(sfstderr, "\n");
            }
      }
      else
      {
            /*
             * copy the original argv adding OPT_reread
             * and possibly OPT_preprocess
             */

            for (av = state.argv; *av; putptr(vec, *av++));
            sfprintf(internal.tmp, "--%s=%d", optflag(OPT_reread)->name, state.reread + 1);
            if (state.preprocess)
                  sfprintf(internal.tmp, "--%s", optflag(OPT_preprocess)->name);
            putptr(vec, sfstruse(internal.tmp));
      }
      putptr(vec, 0);

      /*
       * tidy up
       */

      sfsync(sfstdout);
      sfsync(sfstderr);
      if (internal.openfile)
            close(internal.openfd);

      /*
       * start fresh
       */

      av = (char**)sfstrbase(vec);
      execvp(av[0], av);
      error(3, "cannot exec %s", av[0]);
}

/*
 * return 1 if name is an option
 */

int
isoption(const char* name)
{
      return getoption(name) != 0 || name[0] == 'n' && name[1] == 'o' && getoption(&name[2]) != 0;
}

Generated by  Doxygen 1.6.0   Back to index