Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for macro aliases #2722

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions docs/manual/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,31 @@ expansion of the macro which contains nested macros.

## Defining a Macro

To define a macro use:
The usual macro definition syntax is as follows:

```
%define <name>[(opts)] <body>
```

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. Since rpm >= 4.17 `-` as opts disables all option
processing, otherwise the opts (i.e. string between parentheses) are passed
exactly as is to getopt(3) for argc/argv processing at the beginning of
a macro invocation (only short options are supported). `--` can be used
to separate options from arguments. While a parameterized macro is being
expanded, the following shell-like macros are available:
3 characters in length. The body is (re-)expanded on each macro invocation.

### Parametric macros
A macro which contains an `(opts)` field is called parametric.
The opts (i.e. string between parentheses) are passed exactly as is
to to getopt(3) for argc/argv processing at the beginning of
a macro invocation. Only short options are supported.
`--` can be used to separate options from arguments.
Since rpm >= 4.17 `-` as opts disables all option processing.

Macros `%define`d inside a parametric macro have a scope local to the
macro, otherwise all macros are global.

### Automatic macros

While a parameterized macro is being expanded, the following shell-like
automatic macros are available:

| Macro | Description
| ------ | -----------
Expand All @@ -55,6 +64,36 @@ With rpm >= 4.17 and disabled option processing the mentioned macros are defined
At the end of invocation of a parameterized macro, the above macros are
automatically discarded.

### Global macros

A macro can be declared into the global scope as follows:

```
%global <name>[(opts)] <body>
```

An important and useful feature of `%global` is that the body is expanded
at the time of definition, as opposed to time of use with regular macros.
This is important inside parametric macros because otherwise the body could
be referring to macros that are out of scope at the time of use, but also
useful to avoid re-expansion of expensive macros.

### Aliases (rpm >= 4.20)

A macro alias can be declared as follows:

```
%alias <name> <target>
```

A macro alias is just that: an another name for a macro. Aliases can point
to any other macro type and using it behaves exactly as if accessing the
original macro, parameters and all, with one exception: `%undefine <name>`
undefines the alias, rather than the target macro. An alias always exists
in the same scope as the target macro, so an alias defined on automatic
or locally scoped macros inside parametric macros become automatically
undefined when leaving the parametric macro.

## Writing a Macro

Within the body of a macro, there are several constructs that permit
Expand All @@ -81,6 +120,7 @@ various common operations.
| %define ... | define a macro
| %undefine ... | undefine a macro
| %global ... | define a macro whose body is available in global context
| %alias ... | define an alias on another macro or get alias definition | 4.20.0
| %{load:...} | load a macro file | 4.12.0
| %{expand:...} | like eval, expand ... to <body> and (re-)expand <body>
| %{expr:...} | evaluate an [expression](#expression-expansion) | 4.15.0
Expand Down
81 changes: 71 additions & 10 deletions rpmio/macro.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ enum macroFlags_e {
ME_LITERAL = (1 << 2),
ME_PARSE = (1 << 3),
ME_FUNC = (1 << 4),
ME_ALIAS = (1 << 5),
};

/*! The structure used to store a macro. */
Expand Down Expand Up @@ -159,7 +160,7 @@ static rpmMacroContext rpmmctxRelease(rpmMacroContext mc)
* @param mc macro context
* @param name macro name
* @param namelen no. of bytes
* @param pos found/insert position
* @retval pos found/insert position
* @return address of slot in macro table with name (or NULL)
*/
static rpmMacroEntry *
Expand Down Expand Up @@ -196,6 +197,30 @@ findEntry(rpmMacroContext mc, const char *name, size_t namelen, size_t *pos)
return NULL;
}

static rpmMacroEntry *
findAlias(rpmMacroContext mc, const char *name, size_t namelen, size_t *pos)
{
const char *m = name;
size_t mlen = namelen;
rpmMacroEntry *mep = NULL;
int depth = 0;

while ((mep = findEntry(mc, m, mlen, pos))) {
if (!((*mep)->flags & ME_ALIAS))
break;
/* XXX can't flag an error from here, just fail as not found */
if (++depth > max_macro_depth) {
mep = NULL;
break;
}
m = (*mep)->body;
mlen = strlen(m);

}

return mep;
}

/**
* Create a new entry in the macro table.
* @param mc macro context
Expand Down Expand Up @@ -629,14 +654,22 @@ validName(rpmMacroBuf mb, const char *name, size_t namelen, const char *action)
{
rpmMacroEntry *mep;
int rc = 0;
int c;
const char *c = name;

/* Names must start with alphabetic, or _ and be at least 2 chars */
if (!((c = *name) && (risalpha(c) || (c == '_' && namelen > 1)))) {
if (!(risalpha(*c) || (*c == '_' && namelen > 1))) {
rpmMacroBufErr(mb, 1, _("Macro %%%s has illegal name (%s)\n"), name, action);
goto exit;
}

/* The rest of the name can be alphanumerics or _ */
for (c = name+1; c-name < namelen; c++) {
if (!(risalnum(*c) || (*c == '_'))) {
rpmMacroBufErr(mb, 1, _("Macro %%%s has illegal name (%s)\n"), name, action);
goto exit;
}
}

mep = findEntry(mb->mc, name, namelen, NULL);
if (mep && (*mep)->flags & (ME_FUNC|ME_AUTO)) {
rpmMacroBufErr(mb, 1, _("Macro %%%s is a built-in (%s)\n"), name, action);
Expand Down Expand Up @@ -940,8 +973,8 @@ freeArgs(rpmMacroBuf mb)
if (me->level < mb->level)
continue;
/* Warn on defined but unused non-automatic, scoped macros */
if (!(me->flags & (ME_AUTO|ME_USED))) {
rpmMacroBufErr(mb, 0, _("Macro %%%s defined but not used within scope\n"),
if (!(me->flags & (ME_AUTO|ME_USED|ME_ALIAS))) {
rpmMacroBufErr(mb, 0, ("Macro %%%s defined but not used within scope\n"),
me->name);
/* Only whine once */
me->flags |= ME_USED;
Expand Down Expand Up @@ -1051,7 +1084,7 @@ grabArgs(rpmMacroBuf mb, const rpmMacroEntry me, ARGV_t *argvp,
static void doBody(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed)
{
if (*argv[1]) {
rpmMacroEntry *mep = findEntry(mb->mc, argv[1], 0, NULL);
rpmMacroEntry *mep = findAlias(mb->mc, argv[1], 0, NULL);
if (mep) {
rpmMacroBufAppendStr(mb, (*mep)->body);
} else {
Expand Down Expand Up @@ -1342,6 +1375,33 @@ static void doRpmver(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *pars
rpmMacroBufAppendStr(mb, VERSION);
}

static void doAlias(rpmMacroBuf mb, rpmMacroEntry me, ARGV_t argv, size_t *parsed)
{
int ac = argvCount(argv);
rpmMacroEntry *mep = NULL;
switch (ac) {
case 2:
mep = findAlias(mb->mc, argv[1], 0, NULL);
if (mep && strcmp(argv[1], (*mep)->name))
rpmMacroBufAppendStr(mb, (*mep)->name);
break;
case 3:
if (validName(mb, argv[1], strlen(argv[1]), "%alias")) {
mep = findAlias(mb->mc, argv[2], 0, NULL);
if (mep && strcmp(argv[1], argv[2])) {
/* Aliases are always on the level of the aliased macro */
pushMacro(mb->mc, argv[1], NULL, argv[2],
(*mep)->level, ME_ALIAS);
} else {
rpmMacroBufErr(mb, 1, _("Invalid alias on %%%s\n"), argv[2]);
}
}
break;
default:
rpmMacroBufErr(mb, 1, _("invalid number of arguments for %%alias"));
}
}

static struct builtins_s {
const char * name;
macroFunc func;
Expand All @@ -1350,6 +1410,7 @@ static struct builtins_s {
} const builtinmacros[] = {
{ "P", doSP, 1, 0 },
{ "S", doSP, 1, 0 },
{ "alias", doAlias, -1, 0 },
{ "basename", doFoo, 1, 0 },
{ "define", doDef, 1, ME_PARSE },
{ "dirname", doFoo, 1, 0 },
Expand Down Expand Up @@ -1596,7 +1657,7 @@ expandMacro(rpmMacroBuf mb, const char *src, size_t slen)
printMacro(mb, s, se);

/* Expand defined macros */
mep = findEntry(mb->mc, f, fn, NULL);
mep = findAlias(mb->mc, f, fn, NULL);
me = (mep ? *mep : NULL);

if (me) {
Expand Down Expand Up @@ -1917,7 +1978,7 @@ int rpmExpandThisMacro(rpmMacroContext mc, const char *n, ARGV_const_t args, ch
int rc = 1; /* assume failure */

mc = rpmmctxAcquire(mc);
mep = findEntry(mc, n, 0, NULL);
mep = findAlias(mc, n, 0, NULL);
if (mep) {
rpmMacroBuf mb = mbCreate(mc, flags);
rc = expandThisMacro(mb, *mep, args, flags);
Expand Down Expand Up @@ -2008,7 +2069,7 @@ int rpmMacroIsDefined(rpmMacroContext mc, const char *n)
{
int defined = 0;
if ((mc = rpmmctxAcquire(mc)) != NULL) {
if (findEntry(mc, n, 0, NULL))
if (findAlias(mc, n, 0, NULL))
defined = 1;
rpmmctxRelease(mc);
}
Expand All @@ -2019,7 +2080,7 @@ int rpmMacroIsParametric(rpmMacroContext mc, const char *n)
{
int parametric = 0;
if ((mc = rpmmctxAcquire(mc)) != NULL) {
rpmMacroEntry *mep = findEntry(mc, n, 0, NULL);
rpmMacroEntry *mep = findAlias(mc, n, 0, NULL);
if (mep && (*mep)->opts)
parametric = 1;
rpmmctxRelease(mc);
Expand Down
101 changes: 101 additions & 0 deletions tests/rpmmacro.at
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,107 @@ runroot rpm --eval '1. %{this}' --define "this that" --eval '2. %{this}' --undef
])
RPMTEST_CLEANUP

AT_SETUP([macro alias])
AT_KEYWORDS([macros])
RPMTEST_CHECK([
runroot rpm \
--define "this that" \
--eval "%alias clone this" \
--eval "%this" \
--eval "%clone" \
--define "this what" \
--eval "%this" \
--eval "%clone" \
--undefine clone \
--eval "%this" \
--eval "%clone"
],
[0],
[
that
that
what
what
what
%clone
],
[])

RPMTEST_CHECK([
runroot rpm \
--define "this(a) that %{?-a:arg} %1" \
--eval "%alias clone this" \
--eval "%clone -a 123" \
--eval "%clone -b"
],
[1],
[
that arg 123
],
[this: invalid option -- 'b'
error: Unknown option b in this(a)
])

RPMTEST_CHECK([
runroot rpm \
--define "this that" \
--eval "%alias me this" \
--eval "%alias myself me" \
--eval "%myself"
],
[0],
[

that
],
[])

RPMTEST_CHECK([
runroot rpm \
--define "param() %{alias one 1}%{one}" \
--eval "%param 123" \
--eval "%one"
],
[0],
[123
%one
],
[])

RPMTEST_CHECK([
runroot rpm \
--define "this that" \
--eval "%alias what this" \
--eval "%alias what" \
--eval "%alias this"
],
[0],
[
this

],
[])

RPMTEST_CHECK([
runroot rpm \
--define "this that" \
--eval "%alias what() this"
],
[1],
[],
[error: Macro %what() has illegal name (%alias)
])

RPMTEST_CHECK([
runroot rpm \
--eval "%alias iam notthere"
],
[1],
[],
[error: Invalid alias on %notthere
])
RPMTEST_CLEANUP

AT_SETUP([simple true conditional rpm --eval])
AT_KEYWORDS([macros])
RPMTEST_CHECK([
Expand Down