Add new fully recursive macro.c
authorjbj <devnull@localhost>
Wed, 8 Jul 1998 17:50:48 +0000 (17:50 +0000)
committerjbj <devnull@localhost>
Wed, 8 Jul 1998 17:50:48 +0000 (17:50 +0000)
CVS patchset: 2169
CVS date: 1998/07/08 17:50:48

CHANGES
Makefile.in
Makefile.inc.in
build/macro.c
build/macro.h
docs/macros
rpm.spec

diff --git a/CHANGES b/CHANGES
index 14c1616..04c548f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,5 @@
 2.5.3 -> 3.0
+       - add new fully recursive macro.c
        - add {init,add,expand}Macro args everywhere (new macro.c compatibility)
        - create /usr/lib/rpm directory and move rpmrc et al there
 
index a3d0f85..905e4c8 100644 (file)
@@ -198,7 +198,7 @@ archive:
        @sleep 5
        @cvs -Q tag -F $(CVSTAG) .
        @rm -rf /tmp/rpm-$(VERSION) /tmp/rpm
-       @cd /tmp; cvs -Q -d $(CVSROOT) export -r$(CVSTAG) rpm
+       @cd /tmp; cvs -Q -d $(CVSROOT) export -r$(CVSTAG) rpm || :
        @mv /tmp/rpm /tmp/rpm-$(VERSION)
        @rm /tmp/rpm-$(VERSION)/popt/popt.spec
        @cd /tmp/rpm-$(VERSION); ./autogen.sh ; make depend; make distclean
index 5501f2e..5dc3eb8 100644 (file)
@@ -20,6 +20,6 @@ CFLAGS = -I$(topdir) -I$(topsrcdir) @CFLAGS@ @INCPATH@ $(OPTS) \
          -I$(topsrcdir)/lib -I$(topsrcdir)/misc
 LDFLAGS = @LDFLAGS@ -L$(topdir)/lib -L$(topdir)/build -L$(topdir)/misc \
          -L$(topdir)/popt
-VERSION = 2.5.2
+VERSION = 3.0
 CC = @CC@
 
index fb50c70..46682bd 100644 (file)
-/* macro.c - %macro handling */
-#include "miscfn.h"
-
-#include <stdlib.h>
-#include <string.h>
+#include <assert.h>
 #include <ctype.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
-#include "spec.h"
-#include "macro.h"
-#include "misc.h"
+#define        isblank(_c)     ((_c) == ' ' || (_c) == '\t')
+#define        STREQ(_t, _f, _fn)      ((_fn) == (sizeof(_t)-1) && !strncmp((_t), (_f), (_fn)))
 
 #ifdef DEBUG_MACROS
+typedef        void *  Spec;
 #define rpmError fprintf
 #define RPMERR_BADSPEC stderr
+#define        FREE(_x)        { if (_x) free(_x); ((void *)(_x)) = NULL; }
 #else
+#include "spec.h"
+#include "miscfn.h"
+#include "misc.h"
 #include "lib/rpmlib.h"
 #endif
 
-static void dumpTable(struct MacroContext *mc);
-static void expandMacroTable(struct MacroContext *mc);
-static int compareMacros(const void *ap, const void *bp);
-static struct MacroEntry *findEntry(struct MacroContext *mc, const char *name);
-static int handleDefine(struct MacroContext *mc, char *buf);
-static int parseMacro(char *p, char **macro, char **next);
+#include "macro.h"
+
+typedef struct MacroBuf {
+       const char *s;          /* text to expand */
+       char *t;                /* expansion buffer */
+       size_t nb;              /* no. bytes remaining in expansion buffer */
+       int depth;              /* current expansion depth */
+       int macro_trace;        /* pre-print macro to expand? */
+       int expand_trace;       /* post-print macro expansion? */
+       Spec spec;
+       MacroContext *mc;
+} MacroBuf;
+
+#define SAVECHAR(_mb, _c) { *(_mb)->t = (_c), (_mb)->t++, (_mb)->nb--; }
+#define        DELECHAR(_mb, _c) { if ((_mb)->t[-1] == (_c)) *((_mb)->t--) = '\0', (_mb)->nb++; }
 
-/* This should be a hash table, but I doubt anyone would ever */
-/* notice the increase is speed.                              */
+static int expandMacro(MacroBuf *mb);
+
+int max_macro_depth = 2;
+
+#ifdef DEBUG_MACROS
+int print_macro_trace = 0;
+int print_expand_trace = 0;
+#else
+int print_macro_trace = 0;
+int print_expand_trace = 0;
+#endif
 
-#define MACRO_CHUNK_SIZE 16
+#define        MACRO_CHUNK_SIZE        16
 
-/*************************************************************************/
-/*                                                                       */
-/* Parsing routines                                                      */
-/*                                                                       */
-/*************************************************************************/
+/* =============================================================== */
 
-int expandMacros(Spec spec, struct MacroContext *mc, char *buf, size_t buflen)
+static int
+compareMacroName(const void *ap, const void *bp)
 {
-    char bufA[1024];
-    char *copyTo, *copyFrom;
-    char *name, *rest, *first;
-    struct MacroEntry *p;
-    
-    if (! buf) {
-       return 0;
-    }
+       MacroEntry *ame = *((MacroEntry **)ap);
+       MacroEntry *bme = *((MacroEntry **)bp);
 
-    first = buf;
-    SKIPSPACE(first);
-    
-    copyFrom = buf;
-    copyTo = bufA;
+       if (ame == NULL && bme == NULL) {
+               return 0;
+       }
+       if (ame == NULL) {
+               return 1;
+       }
+       if (bme == NULL) {
+               return -1;
+       }
+       return strcmp(ame->name, bme->name);
+}
 
-    while (*copyFrom) {
-       if (*copyFrom != '%') {
-           *copyTo++ = *copyFrom++;
+static void
+expandMacroTable(MacroContext *mc)
+{
+       int i;
+       if (mc->macroTable == NULL) {
+               mc->macrosAllocated = MACRO_CHUNK_SIZE;
+               mc->macroTable = (MacroEntry **)malloc(sizeof(*(mc->macroTable)) *
+                               mc->macrosAllocated);
+               mc->firstFree = 0;
        } else {
-           if (parseMacro(copyFrom+1, &name, &rest)) {
-               return 1;
-           }
-           if (copyFrom == first && !strcmp(name, "define")) {
-               if (handleDefine(mc, rest)) {
-                   return 1;
-               }
-               /* result is empty */
-               *buf = '\0';
-               return 0;
-           }
-           if (!strcmp(name, "%")) {
-               *copyTo++ = '%';
-               copyFrom = rest;
-           } else {
-               /* a real live macro! */
-               p = findEntry(mc, name);
-               if (! p) {
-                   /* undefined - just leave it */
-                   *copyTo++ = '%';
-                   copyFrom = name;
-               } else {
-                   copyFrom = p->expansion;
-               }
-               while (*copyFrom) {
-                   *copyTo++ = *copyFrom++;
-               }
-               copyFrom = rest;
-           }
+               mc->macrosAllocated += MACRO_CHUNK_SIZE;
+               mc->macroTable = (MacroEntry **)realloc(mc->macroTable, sizeof(*(mc->macroTable)) *
+                               mc->macrosAllocated);
        }
-    }
-    *copyTo = '\0';
-    strcpy(buf, bufA);
-    return 0;
+       memset(&mc->macroTable[mc->firstFree], 0, MACRO_CHUNK_SIZE * sizeof(*(mc->macroTable)));
 }
 
-static int parseMacro(char *p, char **macro, char **next)
+static void
+sortMacroTable(MacroContext *mc)
 {
-    /* This static var is gross, but we can get away with it */
-    /* because the result is used immediately, even when we  */
-    /* are recursing.                                        */
+       qsort(mc->macroTable, mc->firstFree, sizeof(*(mc->macroTable)),
+               compareMacroName);
+}
 
-    static char macroBuf[1024];
-    char save;
+static void
+dumpMacroTable(MacroContext *mc)
+{
+       int i;
+       int nempty = 0;
+       int nactive = 0;
     
-    /* Find end of macro, handling %{...} construct */
+       fprintf(stderr, "========================\n");
+       for (i = 0; i < mc->firstFree; i++) {
+               MacroEntry *me;
+               if ((me = mc->macroTable[i]) == NULL) {
+                       nempty++;
+                       continue;
+               }
+               fprintf(stderr, "%3d%c %s", me->level,
+                       (me->used > 0 ? '=' : ':'), me->name);
+               if (me->opts)
+                       fprintf(stderr, "(%s)", me->opts);
+               if (me->body)
+                       fprintf(stderr, "\t%s", me->body);
+               fprintf(stderr, "\n");
+               nactive++;
+       }
+       fprintf(stderr, "======================== active %d empty %d\n",
+               nactive, nempty);
+}
 
-    if (! p) {
-       /* empty macro name */
-       rpmError(RPMERR_BADSPEC, "Empty macro name");
-       return 2;
-    }
+static MacroEntry **
+findEntry(MacroContext *mc, const char *name, size_t namelen)
+{
+       MacroEntry keybuf, *key, **ret;
+       char namebuf[1024];
+
+       if (! mc->firstFree)
+               return NULL;
+
+       if (namelen > 0) {
+               strncpy(namebuf, name, namelen);
+               namebuf[namelen] = '\0';
+               name = namebuf;
+       }
     
-    if (*p == '{') {
-       *next = strchr(p, '}');
-       if (! *next) {
-           /* unterminated */
-           rpmError(RPMERR_BADSPEC, "Unterminated {: %s", p);
-           return 1;
-       }
-       **next = '\0';
-       *macro = strtok(p+1, " \n\t");
-       if (! *macro) {
-           /* empty macro name */
-           rpmError(RPMERR_BADSPEC, "Empty macro name");
-           return 2;
-       }
-       (*next)++;
-       return 0;
-    }
+       key = &keybuf;
+       memset(key, 0, sizeof(*key));
+       key->name = (char *)name;
+       ret = (MacroEntry **)bsearch(&key, mc->macroTable, mc->firstFree,
+                       sizeof(*(mc->macroTable)), compareMacroName);
+       /* XXX TODO: find 1st empty slot and return that */
+       return ret;
+}
 
-    if (*p == '%') {
-       *next = p + 1;
-       *macro = "%";
-       return 0;
+/* =============================================================== */
+
+/* fgets analogue that reads \ continuations. Last newline always trimmed. */
+
+static char *
+rdcl(char *buf, size_t size, FILE *fp, int escapes)
+{
+       char *q = buf;
+       size_t nb = 0;
+
+       do {
+               *q = '\0';                      /* next char in buf */
+               nb = 0;
+               if (fgets(q, size, fp) == NULL) /* read next line */
+                       break;
+               nb = strlen(q);
+               q += nb - 1;                    /* last char in buf */
+               *q-- = '\0';                    /* trim newline */
+               if (nb < 2 || *q != '\\')       /* continue? */
+                       break;
+               if (escapes)                    /* copy escape too */
+                       q++;
+               else
+                       nb--;
+               *q++ = '\n';                    /* next char in buf */
+               size -= nb;
+       } while (size > 0);
+       return (nb > 0 ? buf : NULL);
+}
+
+/* Return text between pl and matching pr */
+
+static const char *
+matchchar(const char *p, char pl, char pr)
+{
+       int lvl = 0;
+       char c;
+
+       while ((c = *p++) != '\0') {
+               if (c == '\\') {                /* Ignore escaped chars */
+                       p++;
+                       continue;
+               }
+               if (c == pr) {
+                       if (--lvl <= 0) return --p;
+               } else if (c == pl)
+                       lvl++;
+       }
+       return (const char *)NULL;
+}
+
+static void
+printMacro(MacroBuf *mb, const char *s, const char *se)
+{
+       const char *senl;
+       const char *ellipsis;
+       int choplen;
+
+       if (s >= se) {  /* XXX just in case */
+               fprintf(stderr, "%3d>%*s(empty)", mb->depth,
+                       (2 * mb->depth + 1), "");
+               return;
+       }
+
+       if (s[-1] == '{')
+               s--;
+
+       /* If not end-of-string, print only to newline or end-of-string ... */
+       if (*(senl = se) != '\0' && (senl = strchr(senl, '\n')) == NULL)
+               senl = se + strlen(se);
+
+       /* Limit trailing non-trace output */
+       choplen = 61 - (2 * mb->depth);
+       if ((senl - s) > choplen) {
+               senl = s + choplen;
+               ellipsis = "...";
+       } else
+               ellipsis = "";
+
+       /* Substitute caret at end-of-macro position */
+       fprintf(stderr, "%3d>%*s%%%.*s^", mb->depth,
+               (2 * mb->depth + 1), "", (int)(se - s), s);
+       if (se[1] != '\0' && (senl - (se+1)) > 0)
+               fprintf(stderr, "%-.*s%s", (int)(senl - (se+1)), se+1, ellipsis);
+       fprintf(stderr, "\n");
+}
+
+static void
+printExpansion(MacroBuf *mb, const char *t, const char *te)
+{
+       const char *ellipsis;
+       int choplen;
+
+       if (!(te > t)) {
+               fprintf(stderr, "%3d<%*s(empty)\n", mb->depth, (2 * mb->depth + 1), "");
+               return;
+       }
+
+       /* Shorten output which contains newlines */
+       while (te > t && te[-1] == '\n')
+               te--;
+       ellipsis = "";
+       if (mb->depth > 0) {
+               const char *tenl;
+               while ((tenl = strchr(t, '\n')) && tenl < te)
+                       t = ++tenl;
+
+               /* Limit expand output */
+               choplen = 61 - (2 * mb->depth);
+               if ((te - t) > choplen) {
+                       te = t + choplen;
+                       ellipsis = "...";
+               }
+       }
+
+       fprintf(stderr, "%3d<%*s", mb->depth, (2 * mb->depth + 1), "");
+       if (te > t)
+               fprintf(stderr, "%.*s%s", (int)(te - t), t, ellipsis);
+       fprintf(stderr, "\n");
+}
+
+#define        SKIPBLANK(_s, _c)       \
+       while (((_c) = *(_s)) && isblank(_c)) \
+               (_s)++;
+
+#define        SKIPNONBLANK(_s, _c)    \
+       while (((_c) = *(_s)) && !(isblank(_c) || c == '\n')) \
+               (_s)++;
+
+#define        COPYNAME(_ne, _s, _c)   \
+    {  SKIPBLANK(_s,_c);       \
+       while(((_c) = *(_s)) && (isalnum(_c) || (_c) == '_')) \
+               *(_ne)++ = *(_s)++; \
+       *(_ne) = '\0';          \
     }
 
-    if (isspace(*p) || ! *p) {
-       /* illegal % syntax */
-       rpmError(RPMERR_BADSPEC, "Illegal %% syntax: %s", p);
-       return 3;
+#define        COPYOPTS(_oe, _s, _c)   \
+    {  while(((_c) = *(_s)) && (_c) != ')') \
+               *(_oe)++ = *(_s)++; \
+       *(_oe) = '\0';          \
     }
 
-    *next = *macro = p;
-    while (**next && (isalnum(**next) || **next == '_')) {
-       (*next)++;
+#define        COPYBODY(_be, _s, _c)   \
+    {  while(((_c) = *(_s)) && (_c) != '\n') { \
+               if ((_c) == '\\') \
+                       (_s)++; \
+               *(_be)++ = *(_s)++; \
+       }                       \
+       *(_be) = '\0';          \
     }
-    if (! **next) {
+
+/* Save source and expand field into target */
+static int
+expandT(MacroBuf *mb, const char *f, size_t flen)
+{
+       char *sbuf;
+       const char *s = mb->s;
+       int rc;
+
+       sbuf = alloca(flen + 1);
+       memset(sbuf, 0, (flen + 1));
+
+       strncpy(sbuf, f, flen);
+       sbuf[flen] = '\0';
+       mb->s = sbuf;
+       rc = expandMacro(mb);
+       mb->s = s;
+       return rc;
+}
+
+#if 0
+/* Save target and expand sbuf into target */
+static int
+expandS(MacroBuf *mb, char *tbuf, size_t tbuflen)
+{
+       const char *t = mb->t;
+       size_t nb = mb->nb;
+       int rc;
+
+       mb->t = tbuf;
+       mb->nb = tbuflen;
+       rc = expandMacro(mb);
+       mb->t = t;
+       mb->nb = nb;
+       return rc;
+}
+#endif
+
+static int
+expandU(MacroBuf *mb, char *u, size_t ulen)
+{
+       const char *s = mb->s;
+       char *t = mb->t;
+       size_t nb = mb->nb;
+       char *tbuf;
+       int rc;
+
+       tbuf = alloca(ulen + 1);
+       memset(tbuf, 0, (ulen + 1));
+
+       mb->s = u;
+       mb->t = tbuf;
+       mb->nb = ulen;
+       rc = expandMacro(mb);
+
+       tbuf[ulen] = '\0';      /* XXX just in case */
+       if (ulen > mb->nb)
+               strncpy(u, tbuf, (ulen - mb->nb + 1));
+
+       mb->s = s;
+       mb->t = t;
+       mb->nb = nb;
+
+       return rc;
+}
+
+static int
+doShellEscape(MacroBuf *mb, const char *cmd, size_t clen)
+{
+       char pcmd[BUFSIZ];
+       FILE *shf;
+       int rc;
+       int c;
+
+       strncpy(pcmd, cmd, clen);
+       pcmd[clen] = '\0';
+       rc = expandU(mb, pcmd, sizeof(pcmd));
+       if (rc)
+               return rc;
+
+       if ((shf = popen(pcmd, "r")) == NULL)
+               return 1;
+       while(mb->nb > 0 && (c = fgetc(shf)) != EOF)
+               SAVECHAR(mb, c);
+       pclose(shf);
+
+       DELECHAR(mb, '\n');     /* XXX delete trailing newline (if any) */
        return 0;
-    }
-    save = **next;
-    **next = '\0';
-    strcpy(macroBuf, *macro);
-    **next = save;
-    *macro = macroBuf;
-    return 0;
 }
 
-static int handleDefine(struct MacroContext *mc, char *buf)
+static const char *
+doDefine(MacroBuf *mb, const char *se, int level, int expandbody)
 {
-    char *last, *name, *expansion;
+       const char *s = se;
+       char buf[BUFSIZ], *n = buf, *ne = n;
+       char *o = NULL, *oe;
+       char *b, *be;
+       int c;
+       int oc = ')';
 
-    /* get the name */
+       /* Copy name */
+       COPYNAME(ne, s, c);
 
-    name = buf;
-    while (*name && isspace(*name)) {
-       name++;
-    }
-    if (! *name) {
-       /* missing macro name */
-       rpmError(RPMERR_BADSPEC, "Unfinished %%define");
-       return 1;
-    }
-    expansion = name;
-    while (*expansion && !isspace(*expansion)) {
-       expansion++;
-    }
-    if (*expansion) {
-       *expansion++ = '\0';
-    }
-    
-    /* get the expansion */
+       /* Copy opts (if present) */
+       oe = ne + 1;
+       if (*s == '(') {
+               s++;    /* skip ( */
+               o = oe;
+               COPYOPTS(oe, s, oc);
+               s++;    /* skip ) */
+       }
+
+       /* Copy body, skipping over escaped newlines */
+       b = be = oe + 1;
+       SKIPBLANK(s, c);
+       if (c == '{') { /* XXX permit silent {...} grouping */
+               if ((se = matchchar(s, c, '}')) == NULL) {
+                       rpmError(RPMERR_BADSPEC, "Macro %%%s has unterminated body", n);
+                       se = s; /* XXX W2DO? */
+                       return se;
+               }
+               s++;    /* XXX skip { */
+               strncpy(b, s, (se - s));
+               b[se - s] = '\0';
+               be += strlen(b);
+               se++;   /* XXX skip } */
+               s = se; /* move scan forward */
+       } else {        /* otherwise free-field */
+               COPYBODY(be, s, c);
+
+               /* Trim trailing blanks/newlines */
+               while (--be >= b && (c = *be) && (isblank(c) || c == '\n'))
+                       ;
+               *(++be) = '\0'; /* one too far */
+       }
+
+       /* Move scan over body */
+       if (*s == '\n')
+               s++;
+       se = s;
+
+       /* Names must start with alphabetic or _ and be at least 3 chars */
+       if (!((c = *n) && (isalpha(c) || c == '_') && (ne - n) > 2)) {
+               rpmError(RPMERR_BADSPEC, "Macro %%%s has illegal name (%%define)", n);
+               return se;
+       }
+
+       /* Options must be terminated with ')' */
+       if (o && oc != ')') {
+               rpmError(RPMERR_BADSPEC, "Macro %%%s has unterminated opts", n);
+               return se;
+       }
+
+       if ((be - b) < 1) {
+               rpmError(RPMERR_BADSPEC, "Macro %%%s has empty body", n);
+               return se;
+       }
+
+       if (expandbody && expandU(mb, b, (&buf[sizeof(buf)] - b))) {
+               rpmError(RPMERR_BADSPEC, "Macro %%%s failed to expand", n);
+               return se;
+       }
+
+       addMacro(mb->mc, n, o, b, (level - 1));
+
+       return se;
+}
+
+static const char *
+doUndefine(MacroContext *mc, const char *se)
+{
+       const char *s = se;
+       char buf[BUFSIZ], *n = buf, *ne = n;
+       int c;
+
+       COPYNAME(ne, s, c);
+
+       /* Move scan over body */
+       if (*s == '\n')
+               s++;
+       se = s;
+
+       /* Names must start with alphabetic or _ and be at least 3 chars */
+       if (!((c = *n) && (isalpha(c) || c == '_') && (ne - n) > 2)) {
+               rpmError(RPMERR_BADSPEC, "Macro %%%s has illegal name (%%undefine)", n);
+               return se;
+       }
+
+       delMacro(mc, n);
+
+       return se;
+}
+
+static void
+dumpME(const char *msg, MacroEntry *me)
+{
+       if (msg)
+               fprintf(stderr, "%s", msg);
+       fprintf(stderr, "\tme %x", me);
+       if (me)
+               fprintf(stderr,"\tname %x(%s) prev %x",
+                       me->name, me->name, me->prev);
+       fprintf(stderr, "\n");
+}
+
+static void
+pushMacro(MacroEntry **mep, const char *n, const char *o, const char *b, int level)
+{
+       MacroEntry *prev = (*mep ? *mep : NULL);
+       MacroEntry *me = malloc(sizeof(*me));
+
+       me->prev = prev;
+       me->name = (prev ? prev->name : strdup(n));
+       me->opts = (o ? strdup(o) : NULL);
+       me->body = (b ? strdup(b) : NULL);
+       me->used = 0;
+       me->level = level;
+       *mep = me;
+}
+
+static void
+popMacro(MacroEntry **mep)
+{
+       MacroEntry *me = (*mep ? *mep : NULL);
+
+       if (me) {
+               /* XXX cast to workaround const */
+               if ((*mep = me->prev) == NULL)
+                       FREE((char *)me->name);
+               FREE((char *)me->opts);
+               FREE((char *)me->body);
+               FREE(me);
+       }
+}
+
+static void
+freeArgs(MacroBuf *mb)
+{
+       MacroContext *mc = mb->mc;
+       int c;
+
+       /* Delete dynamic macro definitions */
+       for (c = 0; c < mc->firstFree; c++) {
+               MacroEntry *me;
+               int skiptest = 0;
+               if ((me = mc->macroTable[c]) == NULL)
+                       continue;
+               if (me->level < mb->depth)
+                       continue;
+               if (strlen(me->name) == 1 && strchr("#*0", *me->name)) {
+                       if (*me->name == '*' && me->used > 0)
+                               skiptest = 1;
+                       ; /* XXX skip test for %# %* %0 */
+               } else if (!skiptest && me->used <= 0) {
+#if NOTYET
+                       rpmError(RPMERR_BADSPEC, "Macro %%%s (%s) was not used below level %d",
+                               me->name, me->body, me->level);
+#endif
+               }
+               popMacro(&mc->macroTable[c]);
+       }
+}
+
+static const char *
+grabArgs(MacroBuf *mb, const MacroEntry *me, const char *se)
+{
+    const char *s = se;
+    char buf[BUFSIZ], *b, *be;
+    char aname[16];
+    const char *opts, *o;
+    int argc = 0;
+    const char **argv;
+    int optc = 0;
+    const char **optv;
+    int opte;
+    int c;
+
+    /* Copy macro name as argv[0] */
+    argc = 0;
+    b = be = buf;
+    strcpy(b, me->name);
+    be += strlen(b);
+    *be = '\0';
+    argc++;    /* XXX count argv[0] */
+
+    addMacro(mb->mc, "0", NULL, b, mb->depth);
     
-    while (*expansion && isspace(*expansion)) {
-       expansion++;
+    /* Copy args into buf until newline */
+    *be++ = ' ';
+    b = be;    /* Save beginning of args */
+    while (c = *se) {
+       char *a;
+       se++;
+       if (c == '\n')
+               break;
+       if (isblank(c))
+               continue;
+       if (argc > 1)
+               *be++ = ' ';
+       a = be;
+       while (!(isblank(c) || c == '\n')) {
+               *be++ = c;
+               if ((c = *se) == '\0')
+                       break;
+               se++;
+       }
+       *be = '\0';
+       argc++;
     }
-    if (*expansion) {
-       /* strip blanks from end */
-       last = expansion + strlen(expansion) - 1;
-       while (isspace(*last)) {
-           *last-- = '\0';
+
+    /* Add unexpanded args as macro */
+    addMacro(mb->mc, "*", NULL, b, mb->depth);
+
+#ifdef NOTYET
+    /* XXX if macros can be passed as args ... */
+    expandU(mb, buf, sizeof(buf));
+#endif
+
+    /* Build argv array */
+    argv = (const char **)alloca((argc + 1) * sizeof(char *));
+    b = be = buf;
+    for (c = 0; c < argc; c++) {
+       b = be;
+       if ((be = strchr(b, ' ')) == NULL)
+               be = b + strlen(b);
+       *be++ = '\0';
+       argv[c] = b;
+    }
+    argv[argc] = NULL;
+
+    opts = me->opts;
+
+    /* First count number of options ... */
+    optind = 0;
+    optc = 0;
+    optc++;    /* XXX count argv[0] too */
+    while((c = getopt(argc, (char **)argv, opts)) != -1) {
+       if (!(c != '?' && (o = strchr(opts, c)))) {
+               rpmError(RPMERR_BADSPEC, "Unknown option %c in %s(%s)",
+                       c, me->name, opts);
+               return se;
        }
+       optc++;
     }
 
-    /* XXX HACK: 1st and last args for compatibility, currently unused*/
-    if (expandMacros(NULL, mc, expansion, 0)) {
+    /* ... then allocate storage ... */
+    opte = optc + (argc - optind);
+    optv = (const char **)alloca((opte + 1) * sizeof(char *));
+    optv[0] = me->name;
+    optv[opte] = NULL;
 
-       return 1;
+    /* ... and finally copy the options */
+    optind = 0;
+    optc = 0;
+    optc++;    /* XXX count optv[0] */
+    while((c = getopt(argc, (char **)argv, opts)) != -1) {
+       o = strchr(opts, c);
+       b = be;
+       *be++ = '-';
+       *be++ = c;
+       if (o[1] == ':') {
+               *be++ = ' ';
+               strcpy(be, optarg);
+               be += strlen(be);
+       }
+       *be++ = '\0';
+       sprintf(aname, "-%c", c);
+       addMacro(mb->mc, aname, NULL, b, mb->depth);
+       if (o[1] == ':') {
+               sprintf(aname, "-%c*", c);
+               addMacro(mb->mc, aname, NULL, optarg, mb->depth);
+       }
+       optv[optc] = b;
+       optc++;
     }
-    addMacro(mc, name, NULL, expansion, -1);
 
-    return 0;
-}
+    for (c = optind; c < argc; c++) {
+       sprintf(aname, "%d", (c - optind + 1));
+       addMacro(mb->mc, aname, NULL, argv[c], mb->depth);
+       optv[optc] = argv[c];
+       optc++;
+    }
+    sprintf(aname, "%d", (argc - optind + 1));
+    addMacro(mb->mc, "#", NULL, aname, mb->depth);
 
-/*************************************************************************/
-/*                                                                       */
-/* Table handling routines                                               */
-/*                                                                       */
-/*************************************************************************/
+    return se;
+}
 
-void initMacros(struct MacroContext *mc, const char *macrofile)
+static void
+doOutput(MacroBuf *mb, int waserror, const char *msg, size_t msglen)
 {
-    mc->macrosAllocated = 0;
-    mc->firstFree = 0;
-    mc->macroTable = NULL;
-    expandMacroTable(mc);
+       char buf[BUFSIZ];
+
+       strncpy(buf, msg, msglen);
+       buf[msglen] = '\0';
+       expandU(mb, buf, sizeof(buf));
+       if (waserror)
+               rpmError(RPMERR_BADSPEC, "%s", buf);
+       else
+               fprintf(stderr, "%s", buf);
 }
 
-void freeMacros(struct MacroContext *mc)
+static void
+doFoo(MacroBuf *mb, const char *f, size_t fn, const char *g, size_t glen)
 {
-    int i;
-    
-    for (i = 0; i < mc->firstFree; i++) {
-       FREE(mc->macroTable[i].name);
-       FREE(mc->macroTable[i].expansion);
-    }
-    FREE(mc->macroTable);
+       char buf[BUFSIZ], *b = NULL, *be;
+       int c;
+
+       buf[0] = '\0';
+       if (g) {
+               strncpy(buf, g, glen);
+               buf[glen] = '\0';
+               expandU(mb, buf, sizeof(buf));
+       }
+       if (STREQ("basename", f, fn)) {
+               if ((b = strrchr(buf, '/')) == NULL)
+                       b = buf;
+#if NOTYET
+       /* XXX watchout for conflict with %dir */
+       } else if (STREQ("dirname", f, fn)) {
+               if ((b = strrchr(buf, '/')) != NULL)
+                       *b = '\0';
+               b = buf;
+#endif
+       } else if (STREQ("suffix", f, fn)) {
+               if ((b = strrchr(buf, '.')) != NULL)
+                       b++;
+       } else if (STREQ("expand", f, fn)) {
+               b = buf;
+       } else if (STREQ("uncompress", f, fn)) {
+               int compressed = 1;
+               for (b = buf; (c = *b) && isblank(c);)
+                       b++;
+               for (be = b; (c = *be) && !isblank(c);)
+                       be++;
+               *be++ = '\0';
+#ifndef        DEBUG_MACROS
+               isCompressed(b, &compressed);
+#endif
+               switch(compressed) {
+               default:
+               case 0: /* COMPRESSED_NOT */
+                       sprintf(be, "%%_cat %s", b);
+                       break;
+               case 1: /* COMPRESSED_OTHER */
+                       sprintf(be, "%%_gzip -dc %s", b);
+                       break;
+               case 2: /* COMPRESSED_BZIP2 */
+                       sprintf(be, "%%_bzip2 %s", b);
+                       break;
+               }
+               b = be;
+       } else if (STREQ("S", f, fn)) {
+               for (b = buf; (c = *b) && isdigit(c);)
+                       b++;
+               if (!c) {       /* digit index */
+                       b++;
+                       sprintf(b, "%%SOURCE%s", buf);
+               } else
+                       b = buf;
+       } else if (STREQ("P", f, fn)) {
+               for (b = buf; (c = *b) && isdigit(c);)
+                       b++;
+               if (!c) {       /* digit index */
+                       b++;
+                       sprintf(b, "%%PATCH%s", buf);
+               } else
+                       b = buf;
+       } else if (STREQ("F", f, fn)) {
+               b = buf + strlen(buf) + 1;
+               sprintf(b, "file%s.file", buf);
+#if DEAD
+fprintf(stderr, "FILE: \"%s\"\n", b);
+#endif
+       }
+
+       if (b) {
+               expandT(mb, b, strlen(b));
+       }
 }
 
-void addMacro(struct MacroContext *mc, const char *name, const char *o, const char *expansion, int depth)
+/* The main recursion engine */
+
+static int
+expandMacro(MacroBuf *mb)
 {
-    struct MacroEntry *p;
+    MacroEntry **mep;
+    MacroEntry *me;
+    const char *s = mb->s, *se;
+    const char *f, *fe;
+    const char *g, *ge;
+    size_t fn, gn;
+    char *t = mb->t;   /* save expansion pointer for printExpand */
+    int c;
+    int rc = 0;
+    int negate;
+    int grab;
 
-    p = findEntry(mc, name);
-    if (p) {
-       free(p->expansion);
-       p->expansion = strdup(expansion);
-       return;
+    if (++mb->depth > max_macro_depth) {
+       rpmError(RPMERR_BADSPEC, "Recursion depth(%d) greater than max(%d)",
+               mb->depth, max_macro_depth);
+       mb->depth--;
+       mb->expand_trace = 1;
+       return 1;
     }
-    
-    if (mc->firstFree == mc->macrosAllocated) {
-       expandMacroTable(mc);
+
+    while (rc == 0 && mb->nb > 0 && (c = *s) != '\0') {
+       s++;
+       /* Copy text until next macro */
+       switch(c) {
+       case '%':
+               if (*s != '%')
+                       break;
+               s++;    /* skip first % in %% */
+               /* fall thru */
+       default:
+               SAVECHAR(mb, c);
+               continue;
+               break;
+       }
+
+       /* Expand next macro */
+       f = fe = NULL;
+       g = ge = NULL;
+       if (mb->depth > 1)      /* XXX full expansion for outermost level */
+               t = mb->t;      /* save expansion pointer for printExpand */
+       negate = 0;
+       grab = 0;
+       switch ((c = *s)) {
+       default:                /* %name substitution */
+               while (*s == '!') {
+                       negate = (++negate % 2);
+                       s++;
+               }
+               f = se = s;
+               if (*se == '-')
+                       se++;
+               while((c = *se) && (isalnum(c) || c == '_'))
+                       se++;
+               if (*se == '*')
+                       se++;
+               fe = se;
+               /* For "%name " macros ... */
+               if ((c = *fe) && isblank(c))
+                       grab = 1;
+               break;
+       case '(':               /* %(...) shell escape */
+               if ((se = matchchar(s, c, ')')) == NULL) {
+                       rpmError(RPMERR_BADSPEC, "Unterminated %c: %s", c, s);
+                       rc = 1;
+                       continue;
+               }
+               if (mb->macro_trace)
+                       printMacro(mb, s, se+1);
+
+               s++;    /* skip ( */
+               rc = doShellEscape(mb, s, (se - s));
+               se++;   /* skip ) */
+
+               s = se;
+               continue;
+               break;
+       case '{':               /* %{...}/%{...:...} substitution */
+               if ((se = matchchar(s, c, '}')) == NULL) {
+                       rpmError(RPMERR_BADSPEC, "Unterminated %c: %s", c, s);
+                       rc = 1;
+                       continue;
+               }
+               f = ++s;/* skip { */
+               se++;   /* skip } */
+               while (*f == '!') {
+                       negate = (++negate % 2);
+                       f++;
+               }
+               for (fe = f; (c = *fe) && !strchr(":}", c);)
+                       fe++;
+               if (c == ':') {
+                       g = fe + 1;
+                       ge = se - 1;
+               }
+               break;
+       }
+
+       /* XXX Everything below expects fe > f */
+       fn = (fe - f);
+       gn = (ge - g);
+       if (fn <= 0) {
+               rpmError(RPMERR_BADSPEC, "Empty token");
+               s = se;
+               continue;
+       }
+
+       if (mb->macro_trace)
+               printMacro(mb, s, se);
+
+       /* Expand builtin macros */
+       if (STREQ("global", f, fn)) {
+               s = doDefine(mb, se, 0, 1);
+               continue;
+       }
+       if (STREQ("define", f, fn)) {
+               s = doDefine(mb, se, mb->depth, 0);
+               continue;
+       }
+       if (STREQ("undefine", f, fn)) {
+               s = doUndefine(mb->mc, se);
+               continue;
+       }
+
+       if (STREQ("echo", f, fn) ||
+           STREQ("warn", f, fn) ||
+           STREQ("error", f, fn)) {
+               int waserror = 0;
+               if (STREQ("error", f, fn))
+                       waserror = 1;
+               if (g < ge)
+                       doOutput(mb, waserror, g, gn);
+               else
+                       doOutput(mb, waserror, f, fn);
+               s = se;
+               continue;
+       }
+
+       if (STREQ("trace", f, fn)) {
+               /* XXX TODO restore expand_trace/macro_trace to 0 on return */
+               mb->expand_trace = mb->macro_trace = (negate ? 0 : mb->depth);
+               if (mb->depth == 1) {
+                       print_macro_trace = mb->macro_trace;
+                       print_expand_trace = mb->expand_trace;
+               }
+               s = se;
+               continue;
+       }
+
+       if (STREQ("dump", f, fn)) {
+               dumpMacroTable(mb->mc);
+               if (*se == '\n')
+                       se++;
+               s = se;
+               continue;
+       }
+
+       /* XXX necessary but clunky */
+       if (STREQ("basename", f, fn) ||
+           STREQ("suffix", f, fn) ||
+           STREQ("expand", f, fn) ||
+           STREQ("uncompress", f, fn) ||
+           STREQ("S", f, fn) ||
+           STREQ("P", f, fn) ||
+           STREQ("F", f, fn)) {
+               doFoo(mb, f, fn, g, gn);
+               s = se;
+               continue;
+       }
+
+       /* Expand defined macros */
+       mep = findEntry(mb->mc, f, fn);
+       me = (mep ? *mep : NULL);
+
+       /* XXX Special processing for flags */
+       if (*f == '-') {
+               if (me)
+                       me->used++;     /* Mark macro as used */
+               if ((me == NULL && !negate) ||  /* Without -f, skip %{-f...} */
+                   (me != NULL && negate)) {   /* With -f, skip %{!-f...} */
+                       s = se;
+                       continue;
+               }
+
+               if (g && g < ge) {              /* Expand X in %{-f:X} */
+                       rc = expandT(mb, g, gn);
+               } else
+               if (me->body && *me->body) {    /* Expand %{-f}/%{-f*} */
+                       rc = expandT(mb, me->body, strlen(me->body));
+               }
+               s = se;
+               continue;
+       }
+
+       if (me == NULL) {       /* leave unknown %... as is */
+#ifndef HACK
+#if DEAD
+               /* XXX hack to skip over empty arg list */
+               if (fn == 1 && *f == '*') {
+                       s = se;
+                       continue;
+               }
+#endif
+               /* XXX hack to permit non-overloaded %foo to be passed */
+               c = '%';        /* XXX only need to save % */
+               SAVECHAR(mb, c);
+#else
+               rpmError(RPMERR_BADSPEC, "Macro %%%.*s not found, skipping", fn, f);
+               s = se;
+#endif
+               continue;
+       }
+
+       /* Setup args for "%name " macros with opts */
+       if (me && me->opts != NULL) {
+               if (grab)
+                       se = grabArgs(mb, me, fe);
+               else {
+                   addMacro(mb->mc, "*", NULL, "", mb->depth);
+               }
+       }
+
+       /* Recursively expand body of macro */
+       mb->s = me->body;
+       rc = expandMacro(mb);
+       if (rc == 0)
+               me->used++;     /* Mark macro as used */
+
+       /* Free args for "%name " macros with opts */
+       if (me->opts != NULL)
+               freeArgs(mb);
+
+       s = se;
     }
 
-    p = mc->macroTable + mc->firstFree++;
-    p->name = strdup(name);
-    p->expansion = strdup(expansion);
+    *mb->t = '\0';
+    mb->s = s;
+    mb->depth--;
+    if (rc != 0 || mb->expand_trace)
+       printExpansion(mb, t, mb->t);
+    return rc;
+}
 
-    qsort(mc->macroTable, mc->firstFree, sizeof(*(mc->macroTable)),
-         compareMacros);
+/* =============================================================== */
+const char *
+getMacroBody(MacroContext *mc, const char *name)
+{
+    MacroEntry **mep = findEntry(mc, name, 0);
+    MacroEntry *me = (mep ? *mep : NULL);
+    return ( me ? me->body : (const char *)NULL );
 }
 
-static struct MacroEntry *findEntry(struct MacroContext *mc, const char *name)
+/* =============================================================== */
+int
+expandMacros(Spec spec, MacroContext *mc, char *s, size_t slen)
 {
-    struct MacroEntry key;
+       MacroBuf macrobuf, *mb = &macrobuf;
+       char *tbuf;
+       int c;
+       int rc;
 
-    if (! mc->firstFree) {
-       return NULL;
-    }
-    
-    key.name = name;
-    return bsearch(&key, mc->macroTable, mc->firstFree,
-                  sizeof(*(mc->macroTable)), compareMacros);
+       if (s == NULL || slen <= 0)
+               return 0;
+
+       tbuf = alloca(slen + 1);
+       memset(tbuf, 0, (slen + 1));
+
+       mb->s = s;
+       mb->t = tbuf;
+       mb->nb = slen;
+       mb->depth = 0;
+       mb->macro_trace = print_macro_trace;
+       mb->expand_trace = print_expand_trace;
+
+       mb->spec = spec;
+       mb->mc = mc;
+
+       rc = expandMacro(mb);
+
+       if (mb->nb <= 0)
+               rpmError(RPMERR_BADSPEC, "Target buffer overflow");
+
+       tbuf[slen] = '\0';      /* XXX just in case */
+       strncpy(s, tbuf, (slen - mb->nb + 1));
+
+       return rc;
 }
 
-static int compareMacros(const void *ap, const void *bp)
+void
+addMacro(MacroContext *mc, const char *n, const char *o, const char *b, int level)
 {
-    return strcmp(((struct MacroEntry *)ap)->name,
-                 ((struct MacroEntry *)bp)->name);
+       MacroEntry **mep;
+
+       /* If new name, expand macro table */
+       if ((mep = findEntry(mc, n, 0)) == NULL) {
+               if (mc->firstFree == mc->macrosAllocated)
+                       expandMacroTable(mc);
+               mep = mc->macroTable + mc->firstFree++;
+       }
+
+       /* Push macro over previous definition */
+       pushMacro(mep, n, o, b, level);
+
+       /* If new name, sort macro table */
+       if ((*mep)->prev == NULL)
+               sortMacroTable(mc);
 }
 
-static void expandMacroTable(struct MacroContext *mc)
+void
+delMacro(MacroContext *mc, const char *name)
 {
-    mc->macrosAllocated += MACRO_CHUNK_SIZE;
-    if (mc->macroTable) {
-       mc->macroTable = realloc(mc->macroTable, sizeof(*(mc->macroTable)) *
-                                mc->macrosAllocated);
-    } else {
-       mc->macroTable = malloc(sizeof(*(mc->macroTable)) *
-                               mc->macrosAllocated);
-    }
+       MacroEntry **mep = findEntry(mc, name, 0);
+
+       /* If name exists, pop entry */
+       if ((mep = findEntry(mc, name, 0)) != NULL)
+               popMacro(mep);
 }
 
-/***********************************************************************/
+void
+initMacros(MacroContext *mc, const char *macrofile)
+{
+       char *m, *mfile, *me;
+
+       mc->macroTable = NULL;
+       expandMacroTable(mc);
+
+       max_macro_depth = 2;    /* XXX Assume good ol' macro expansion */
+
+       if (macrofile == NULL)
+               return;
+
+       for (mfile = m = strdup(macrofile); *mfile; mfile = me) {
+               FILE *fp;
+               char buf[BUFSIZ];
+               MacroBuf macrobuf, *mb = &macrobuf;
+
+               if ((me = strchr(mfile, ':')) != NULL)
+                       *me++ = '\0';
+               else
+                       me = mfile + strlen(mfile);
+
+               if ((fp=fopen(mfile, "r")) == NULL)
+                       continue;
+
+               /* XXX Assume new fangled macro expansion */
+               max_macro_depth = 16;
+
+               while(rdcl(buf, sizeof(buf), fp, 1) != NULL) {
+                       char c, *n;
+
+                       n = buf;
+                       SKIPBLANK(n, c);
+
+                       if (c != '%')
+                               continue;
+                       n++;
+                       mb->mc = mc;    /* XXX just enough to get by */
+                       doDefine(mb, n, 0, 0);
+               }
+               fclose(fp);
+       }
+       if (m)
+               free(m);
+}
 
-static void dumpTable(struct MacroContext *mc)
+void
+freeMacros(MacroContext *mc)
 {
-    int i;
+       int i;
     
-    for (i = 0; i < mc->firstFree; i++) {
-       printf("%s->%s.\n", mc->macroTable[i].name,
-              mc->macroTable[i].expansion);
-    }
+       for (i = 0; i < mc->firstFree; i++) {
+               MacroEntry *me;
+               while ((me = mc->macroTable[i]) != NULL) {
+                       /* XXX cast to workaround const */
+                       if ((mc->macroTable[i] = me->prev) == NULL)
+                               FREE((char *)me->name);
+                       FREE((char *)me->opts);
+                       FREE((char *)me->body);
+                       FREE(me);
+               }
+       }
+       FREE(mc->macroTable);
 }
 
+/* =============================================================== */
+
 #ifdef DEBUG_MACROS
-void main(void)
+
+MacroContext mc = { NULL, 0, 0};
+char *macrofile = "./paths:./environment:./macros";
+char *testfile = "./test";
+
+int
+main(int argc, char *argv[])
 {
-    char buf[1024];
-    int x;
+       char buf[BUFSIZ];
+       FILE *fp;
+       int x;
 
-    while(gets(buf)) {
-       x = expandMacros(buf);
-       printf("%d->%s<-\n", x, buf);
-    }
-}
-#endif
+       initMacros(&mc, macrofile);
+       dumpMacroTable(&mc);
+
+       if ((fp = fopen(testfile, "r")) != NULL) {
+               while(fgets(buf, sizeof(buf), fp)) {
+                       buf[strlen(buf)-1] = '\0';
+                       x = expandMacros(NULL, &mc, buf, sizeof(buf));
+                       fprintf(stderr, "%d->%s\n", x, buf);
+                       memset(buf, 0, sizeof(buf));
+               }
+               fclose(fp);
+       }
 
+       while(fgets(buf, sizeof(buf), stdin)) {
+               buf[strlen(buf)-1] = '\0';
+               x = expandMacros(NULL, &mc, buf, sizeof(buf));
+               fprintf(stderr, "%d->%s\n <-\n", x, buf);
+               memset(buf, 0, sizeof(buf));
+       }
+
+       return 0;
+}
+#endif /* DEBUG_MACROS */
index 85b7968..a35a8d9 100644 (file)
@@ -1,25 +1,36 @@
-#ifndef _MACRO_H_
-#define _MACRO_H_
+#ifndef _MACRO_H
+#define        _MACRO_H
 
-/* macro.h - %macro handling */
+typedef struct MacroEntry {
+       struct MacroEntry *prev;
+       const char *name;       /* Macro name */
+       const char *opts;       /* Macro parameters (ala getopt) */
+       const char *body;       /* Macro body */
+       int     used;           /* No. of expansions */
+       int     level;
+} MacroEntry;
 
-struct MacroEntry {
-    char *name;
-    char *expansion;
-};
+typedef struct MacroContext {
+       MacroEntry **   macroTable;
+       int             macrosAllocated;
+       int             firstFree;
+} MacroContext;
 
-struct MacroContext {
-    struct MacroEntry *macroTable;
-    int macrosAllocated;
-    int firstFree;
-};
+#ifndef        __P
+#ifdef __STDC__
+#define        __P(protos)     protos
+#else
+#define        __P(protos)     ()
+#endif
+#endif
 
-void initMacros(struct MacroContext *mc, const char *macrofile);
-void freeMacros(struct MacroContext *mc);
+void   initMacros      __P((MacroContext *mc, const char *macrofile));
+void   freeMacros      __P((MacroContext *mc));
 
-void addMacro(struct MacroContext *mc, const char *n, const char *o, const char *b, int depth);
+void   addMacro        __P((MacroContext *mc, const char *n, const char *o, const char *b, int depth));
+void   delMacro        __P((MacroContext *mc, const char *n));
+int    expandMacros    __P((Spec spec, MacroContext *mc, char *sbuf, size_t sbuflen));
 
-/* Expand all macros in buf, in place */
-int expandMacros(Spec spec, struct MacroContext *mc, char *sbuf, size_t sbuflen);
+const char *getMacroBody __P((MacroContext *mc, const char *name));
 
-#endif
+#endif /* _MACRO_H */
index cd02378..0e9a08e 100644 (file)
 SPEC FILE MACROS
 ================
 
-RPM 2.3.9 introduces simple spec file macros.  The macros can do
-straight text substitution only.  Macros can be used anywhere in
-a spec file, and in "included file lists" (those read in using
-%files -f <file>).
+RPM 2.4.104 introduces fully recursive spec file macros.  Simple macros
+do straight text substitution. Parameterized macros include an options
+field, and perform argc/argv processing on white space separated tokens
+to the next newline. During macro expansion, both flags and arguments are
+available as macros which are deleted at the end of macro expansion.
+Macros can be used (almost) anywhere in a spec file, and, in particular,
+in "included file lists" (i.e. those read in using %files -f <file>).
+In addition, macros can be nested, hiding the previous definition for the
+duration of the expansion of the macro which contains nested macros.
 
 Defining a Macro
 ----------------
 
 To define a macro use:
 
-%define <name> <expansion>
+%define <name>[(opts)] <body>
 
-All whitespace surrounding <expansion> is removed.  Name may be composed
-of alphanumeric characters, and the character `_'.  Macro expansion is
-performed on <expansion> so that <expansion> my reference other macros
-that have already been defined.
+All whitespace surrounding <body> is removed.  Name may be composed
+of alphanumeric characters, and the character `_' and must be at least
+3 characters in length. A macro without an (opts) field is "simple" in that
+only recursive macro expansion is performed. A parameterized macro contains
+an (opts) field. The opts (i.e. string between parantheses) is passed
+exactly as is to getopts(3) for argc/argv processing at the beginning of
+a macro invocation.  While a parameterized macro is being expanded, the
+following shell-like macros are available:
+
+       %0      the name of the macro being invoked
+       %*      all arguments
+       %#      the number of arguments
+       %{-f}   if present at invocation, the flag f itself
+       %{-f*}  if present at invocation, the argument to flag f
+       %1, %2  the arguments themselves (after getopt(3) processing)
+
+At the end of invocation of a parameterized macro, the above macros are
+(at the moment, silently) discarded.
+
+Writing a Macro
+---------------
+
+Within the body of a macro, there are several constructs that permit
+testing for the presence of optional parameters. The simplest construct
+is "%{-f}" which expands (literally) to "-f" if -f was mentioned when the
+macro was invoked. There are also provisions for including text if flag
+was present using "%{-f:X}". This macro expands to (the expansion of) X
+if the flag was present. The negative form, "%{!-f:Y}", expanding to (the
+expansion of) Y if -f was *not* present, is also supported.
+
+In addition to the "%{...}" form, shell expansion can be performed using
+"%(shell command)". The expansion of "%(...)" is the output of (the expansion
+of) ... fed to /bin/sh. For example, "%(date +%%y%%m%%d)" expands to the
+string "YYMMDD" (final newline is deleted). Note the 2nd % needed to escape
+the arguments to /bin/date.  
+
+Builtin Macros
+--------------
+There are several builtin macros (with reserved names) that are needed
+to perform useful operations. The current list is
+
+       %trace          toggle print of debugging information before/after
+                       expansion
+       %dump           print the active (i.e. non-covered) macro table
+
+       %{echo:...}     print ... to stderr
+       %{warn:...}     print ... to stderr
+       %{error:...}    print ... to stderr and return BADSPEC
+       %define ...     define a macro
+       %undefine ...   undefine a macro
+       %global ...     define a macro whose body is available in global context
+
+       %{uncompress:...} expand ... to <file> and test to see if <file> is
+                       compressed.  The expansion is
+                               cat <file>              # if not compressed
+                               gzip -dc <file>         # if gzip'ed
+                               bzip2 -dc <file>        # if bzip'ed
+       %{expand:...}   like eval, expand ... to <body> and (re-)expand <body>
+
+       %{S:...}        expand ... to <source> file name
+       %{P:...}        expand ... to <patch> file name
+       %{F:...}        expand ... to <file> file name
+
+Macros may also be automatically included from /usr/lib/rpm/macros.
+In addition, rpm itself defines numerous macros. To display the current
+set, add "%dump" to the beginning of any spec file, process with rpm, and
+examine the output from stderr.
+
+Example of a Macro
+------------------
+
+Here is an example %patch definition from /usr/lib/rpm/macros:
+
+%patch(b:p:P:REz:) \
+%define patch_file     %{P:%{-P:%{-P*}}%{!-P:%%PATCH0}} \
+%define patch_suffix   %{!-z:%{-b:--suffix %{-b*}}}%{!-b:%{-z:--suffix %{-z*}}}%{!-z:%{!-b: }}%{-z:%{-b:%{error:Can't specify both -z(%{-z*}) and -b(%{-b*})}}} \
+       %{uncompress:%patch_file} | patch %{-p:-p%{-p*}} %patch_suffix %{-R} %{-E} \
+       ...
+
+The first line defines %patch with its options. The body of %patch is
+
+       %{uncompress:%patch_file} | patch %{-p:-p%{-p*}} %patch_suffix %{-R} %{-E}
+
+The body contains 7 macros, which expand as follows
+
+       %{uncompress:...}       copy uncompressed patch to stdout
+         %patch_file           ... the name of the patch file
+       %{-p:...}               if "-p N" was present, (re-)generate "-pN" flag
+         -p%{-p*}              ... note patch-2.1 insists on contiguous "-pN"
+       %patch_suffix           override (default) ".orig" suffix if desired
+       %{-R}                   supply -R (reversed) flag if desired
+       %{-E}                   supply -E (delete empty?) flag if desired
+
+There are two "private" helper macros:
+
+       %patch_file     the gory details of generating the patch file name
+       %patch_suffix   the gory details of overriding the (default) ".orig"
 
 Using a Macro
 -------------
 
 To use a macro, write:
 
-%<name>
+%<name> ...
 
 or
 
 %{<name>}
 
-The later allows you to place the expansion adjacent to other text.
-
-Predefined Macros
------------------
-
-The following macros are defined as the values they reference are
-specified in the spec file:
-
-PACKAGE_VERSION
-PACKAGE_RELEASE
+The %{...} form allows you to place the expansion adjacent to other text.
+The %<name> form, if a parameterized macro, will do argc/argv processing
+of the rest of the line as described above.
index bafa87a..711772d 100644 (file)
--- a/rpm.spec
+++ b/rpm.spec
@@ -1,6 +1,6 @@
 Summary: Red Hat Package Manager
 Name: rpm
-%define version 2.5.2
+%define version 3.0
 Version: %{version}
 Release: 1
 Group: Utilities/System