From 94f5fbeec363059ca9eb986ee294ffcdf20bcb04 Mon Sep 17 00:00:00 2001 From: jbj Date: Wed, 8 Jul 1998 17:50:48 +0000 Subject: [PATCH] Add new fully recursive macro.c CVS patchset: 2169 CVS date: 1998/07/08 17:50:48 --- CHANGES | 1 + Makefile.in | 2 +- Makefile.inc.in | 2 +- build/macro.c | 1346 ++++++++++++++++++++++++++++++++++++++++++++++--------- build/macro.h | 47 +- docs/macros | 132 +++++- rpm.spec | 2 +- 7 files changed, 1267 insertions(+), 265 deletions(-) diff --git a/CHANGES b/CHANGES index 14c1616..04c548f 100644 --- 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 diff --git a/Makefile.in b/Makefile.in index a3d0f85..905e4c8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/Makefile.inc.in b/Makefile.inc.in index 5501f2e..5dc3eb8 100644 --- a/Makefile.inc.in +++ b/Makefile.inc.in @@ -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@ diff --git a/build/macro.c b/build/macro.c index fb50c70..46682bd 100644 --- a/build/macro.c +++ b/build/macro.c @@ -1,306 +1,1204 @@ -/* macro.c - %macro handling */ -#include "miscfn.h" - -#include -#include +#include #include #include +#include +#include +#include -#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 = ¯obuf; + 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 = ¯obuf; + + 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 */ diff --git a/build/macro.h b/build/macro.h index 85b7968..a35a8d9 100644 --- a/build/macro.h +++ b/build/macro.h @@ -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 */ diff --git a/docs/macros b/docs/macros index cd02378..0e9a08e 100644 --- a/docs/macros +++ b/docs/macros @@ -1,41 +1,133 @@ 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 ). +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 ). +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 +%define [(opts)] -All whitespace surrounding is removed. Name may be composed -of alphanumeric characters, and the character `_'. Macro expansion is -performed on so that my reference other macros -that have already been defined. +All whitespace surrounding 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 and test to see if is + compressed. The expansion is + cat # if not compressed + gzip -dc # if gzip'ed + bzip2 -dc # if bzip'ed + %{expand:...} like eval, expand ... to and (re-)expand + + %{S:...} expand ... to file name + %{P:...} expand ... to file name + %{F:...} expand ... to 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: -% +% ... or %{} -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 % form, if a parameterized macro, will do argc/argv processing +of the rest of the line as described above. diff --git a/rpm.spec b/rpm.spec index bafa87a..711772d 100644 --- 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 -- 2.7.4