Skip to content

Commit

Permalink
Support rpm version comparison in expressions
Browse files Browse the repository at this point in the history
Adds rpm version as a new expression value type, denoted by v"" (similar
to Python u"", b"" etc), which are compared using rpm version comparison
algorithm rather than regular string comparison.

For example in specs:

    %if v"%{python_version}" < v"3.9"
    %endif

...but also command lines, arbitrary macros etc:

    rpm --eval '%[v"1:1.2" < v"2.0"]'

Fixes: #1217
  • Loading branch information
pmatilai committed May 19, 2020
1 parent 8854c23 commit 6e97d6c
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 11 deletions.
10 changes: 6 additions & 4 deletions doc/manual/macros
Expand Up @@ -241,15 +241,17 @@ newline is deleted). Note the 2nd % needed to escape the arguments to

Expression expansion can be performed using "%[expression]". An
expression consists of terms that can be combined using
operators. Rpm supports two kinds of terms, numbers made up
from digits and strings enclosed in double quotes. Rpm will
expand macros when evaluating terms.
operators. Rpm supports three kinds of terms, numbers made up
from digits, strings enclosed in double quotes (eg "somestring") and
versions enclosed in double quotes preceded by (eg v"3:1.2-1").
Rpm will expand macros when evaluating terms.

You can use the standard operators to combine terms: logical
operators &&, ||, !, relational operators !=, ==, <, > , <=, >=,
arithmetic operators +, -, /, *, the ternary operator ? :, and
parentheses. For example, "%[ 3 + 4 * (1 + %two) ]" will expand
to "15" if "%two" expands to "2".
to "15" if "%two" expands to "2". Version terms are compared using
rpm version comparison algorith, rather than regular string comparison.

Note that the "%[expression]" expansion is different to the
"%{expr:expression}" macro. With the latter, the macros in the
Expand Down
57 changes: 50 additions & 7 deletions rpmio/expression.c
Expand Up @@ -15,6 +15,7 @@

#include <rpm/rpmlog.h>
#include <rpm/rpmmacro.h>
#include <rpm/rpmver.h>
#include "rpmio/rpmmacro_internal.h"
#include "debug.h"

Expand All @@ -30,6 +31,7 @@
typedef enum {
VALUE_TYPE_INTEGER,
VALUE_TYPE_STRING,
VALUE_TYPE_VERSION,
} valueType;

/**
Expand All @@ -40,6 +42,7 @@ typedef struct _value {
union {
char *s;
int i;
rpmver v;
} data;
} *Value;

Expand All @@ -55,10 +58,17 @@ static int valueCmpString(Value v1, Value v2)
return strcmp(v1->data.s, v2->data.s);
}

static int valueCmpVersion(Value v1, Value v2)
{
return rpmverCmp(v1->data.v, v2->data.v);
}

static void valueReset(Value v)
{
if (v->type == VALUE_TYPE_STRING)
v->data.s = _free(v->data.s);
else if (v->type == VALUE_TYPE_VERSION)
v->data.v = rpmverFree(v->data.v);
}

/**
Expand All @@ -85,6 +95,18 @@ static Value valueMakeString(char *s)
return v;
}

static Value valueMakeVersion(const char *s)
{
Value v = NULL;
rpmver rv = rpmverParse(s);

if (rv) {
v = (Value) xmalloc(sizeof(*v));
v->type = VALUE_TYPE_VERSION;
v->data.v = rv;
}
return v;
}
/**
*/
static void valueSetInteger(Value v, int i)
Expand Down Expand Up @@ -141,6 +163,7 @@ static void valueDump(const char *msg, Value v, FILE *fp)

#define valueIsInteger(v) ((v)->type == VALUE_TYPE_INTEGER)
#define valueIsString(v) ((v)->type == VALUE_TYPE_STRING)
#define valueIsVersion(v) ((v)->type == VALUE_TYPE_VERSION)
#define valueSameType(v1,v2) ((v1)->type == (v2)->type)


Expand Down Expand Up @@ -193,6 +216,7 @@ static void exprErr(const struct _parseState *state, const char *msg,
#define TOK_LOGICAL_OR 18
#define TOK_TERNARY_COND 19
#define TOK_TERNARY_ALT 20
#define TOK_VERSION 21

#if defined(DEBUG_PARSER)
typedef struct exprTokTableEntry {
Expand Down Expand Up @@ -221,6 +245,7 @@ ETTE_t exprTokTable[] = {
{ "||", TOK_LOGICAL_OR },
{ "?", TOK_TERNARY_COND },
{ ":", TOK_TERNARY_ALT},
{ "V", TOK_VERSION},
{ NULL, 0 }
};

Expand Down Expand Up @@ -394,15 +419,19 @@ static int rdToken(ParseState state)
v = valueMakeInteger(atoi(temp));
free(temp);

} else if (risalpha(*p)) {
exprErr(state, _("bare words are no longer supported, please use \"...\""), p+1);
goto err;

} else if (*p == '\"') {
} else if (*p == '\"' || (*p == 'v' && *(p+1) == '\"')) {
char *temp;
size_t ts;
int qtok;

if (*p == 'v') {
qtok = TOK_VERSION;
p += 2;
} else {
qtok = TOK_STRING;
p++;
}

p++;
for (ts=0; p[ts]; ts++) {
if (p[ts] == '%' && expand)
ts = skipMacro(p, ts + 1) - 1;
Expand All @@ -418,7 +447,19 @@ static int rdToken(ParseState state)
goto err;
p += ts;
token = TOK_STRING;
v = valueMakeString( temp );
if (qtok == TOK_STRING) {
v = valueMakeString(temp);
} else {
v = valueMakeVersion(temp);
free(temp); /* version doesn't take ownership of the string */
if (v == 0) {
exprErr(state, _("invalid version"), p+1);
goto err;
}
}
} else if (risalpha(*p)) {
exprErr(state, _("bare words are no longer supported, please use \"...\""), p+1);
goto err;

} else {
exprErr(state, _("parse error in expression"), p+1);
Expand Down Expand Up @@ -674,6 +715,8 @@ static Value doRelational(ParseState state)

if (valueIsInteger(v1))
cmp = valueCmpInteger;
else if (valueIsVersion(v1))
cmp = valueCmpVersion;
else
cmp = valueCmpString;

Expand Down
21 changes: 21 additions & 0 deletions tests/rpmmacro.at
Expand Up @@ -465,16 +465,37 @@ AT_KEYWORDS([macros])
AT_CHECK([[
runroot rpm --define "aaa hello" --eval '%[%aaa]'
runroot rpm --eval '%[%{foo]'
runroot rpm --eval '%[v""]'
]],
[1],
[],
[error: macro expansion returned a bare word, please use "...": %aaa
error: ^
error: expanded string: hello
error: Unterminated {: {foo
error: invalid version: v""
error: ^
])
AT_CLEANUP

AT_SETUP([expression version comparison])
AT_KEYWORDS([macros])
AT_CHECK([[
runroot rpm \
--eval '%[v"1.0" == v"1.0"]' \
--eval '%[v"1.0~rc" < v"1.0"]' \
--eval '%[v"1.0~rc" > v"1.0"]' \
--eval '%[v"1.0-rc" > v"1.0"]' \
]],
[0],
[1
1
0
1
],
[])
AT_CLEANUP

AT_SETUP([simple lua --eval])
AT_KEYWORDS([macros lua])
AT_CHECK([
Expand Down

0 comments on commit 6e97d6c

Please sign in to comment.