From: Michael Tremer Date: Tue, 9 Nov 2021 11:40:21 +0000 (+0000) Subject: Add support for rich dependencies X-Git-Tag: 0.9.28~885 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9c5e8b90e685602c54ecd6e97f5449a8912d7051;p=pakfire.git Add support for rich dependencies Signed-off-by: Michael Tremer --- diff --git a/src/libpakfire/util.c b/src/libpakfire/util.c index feff53fff..a0fa330c4 100644 --- a/src/libpakfire/util.c +++ b/src/libpakfire/util.c @@ -47,6 +47,20 @@ #define NSEC_PER_SEC 1000000000 +static const struct pakfire_rich_operation { + const char* keyword; + const int flags; +} pakfire_rich_operations[] = { + { "and ", REL_AND }, + { "or ", REL_OR }, + { "if ", REL_COND }, + { "unless ", REL_UNLESS }, + { "else ", REL_ELSE }, + { "with ", REL_WITH }, + { "without ", REL_WITHOUT }, + { NULL, 0 }, +}; + const char* pakfire_dep2str(struct pakfire* pakfire, Id id) { Pool* pool = pakfire_get_solv_pool(pakfire); if (!pool) @@ -55,6 +69,52 @@ const char* pakfire_dep2str(struct pakfire* pakfire, Id id) { return pool_dep2str(pool, id); } +/* + Reads the string until the next space is found. + + This function considers opening and closing brackets. +*/ +static size_t skip(const char** s, const char** n) { + const char* p = *s; + + // Store p in n + *n = p; + + int brackets = 0; + + while (*p) { + switch (*p) { + // End on space or comma + case ' ': + case ',': + goto END; + + // Increment counter on opening bracket + case '(': + brackets++; + break; + + // End on the last closing bracket + case ')': + if (brackets-- <= 0) + goto END; + break; + } + + // Move to next character + p++; + } + +END: + *s = p; + + // Return the length of the skipped string + return p - *n; +} + +/* + This function parses any namespaced dependencies +*/ static Id pakfire_parse_namespace(Pool* pool, const char* s) { const char* p = strchr(s, '('); if (!p) @@ -74,38 +134,38 @@ static Id pakfire_parse_namespace(Pool* pool, const char* s) { return pool_rel2id(pool, namespace, id, REL_NAMESPACE, 1); } -Id pakfire_str2dep(struct pakfire* pakfire, const char* s) { - Id id; +/* + This function parses a simple dependency like "foo = 1.2.3" +*/ +static Id pakfire_parse_dep(struct pakfire* pakfire, const char** s) { + Id id = ID_NULL; if (!s) { errno = EINVAL; return 0; } + const char* p = *s; + const char* n = NULL; + // Ignore empty strings - if (!*s) - return 0; + if (!p) + return ID_NULL; Pool* pool = pakfire_get_solv_pool(pakfire); // Consume any leading space - if (isspace(*s)) - s++; - - const char* p = s; - - // Find the first part (before =, >= or <=) - while (*p && !isspace(*p) && *p != '<' && *p != '=' && *p != '>') + if (isspace(*p)) p++; - // The length of the first part - size_t l = p - s; + // Find the first part + size_t l = skip(&p, &n); // Add name to pool - if (pakfire_string_startswith(s, "pakfire(")) - id = pakfire_parse_namespace(pool, s); + if (pakfire_string_startswith(n, "pakfire(")) + id = pakfire_parse_namespace(pool, n); else - id = pool_strn2id(pool, s, l, 1); + id = pool_strn2id(pool, n, l, 1); // Consume any more space if (isspace(*p)) @@ -131,13 +191,129 @@ Id pakfire_str2dep(struct pakfire* pakfire, const char* s) { if (isspace(*p)) p++; + // Find the length of EVR + l = skip(&p, &n); + + // Strip zero epoch + if (pakfire_string_startswith(n, "0:")) + n += 2; + // Add EVR to pool - Id evr = pool_str2id(pool, p, 1); + Id evr = pool_strn2id(pool, n, l, 1); // Combine everything id = pool_rel2id(pool, id, evr, flags, 1); } + *s = p; + + return id; +} + +/* + This function parses any rich dependencies +*/ +static Id pakfire_parse_rich_dep(struct pakfire* pakfire, const char** dep, int flags) { + const char* p = *dep; + Id id; + + // Do not try parsing empty strings + if (!*p) + return ID_NULL; + + // Must be starting with an opening bracket + if (!flags && *p++ != '(') + return ID_NULL; + + switch (*p) { + // A new rich dependency + case '(': + id = pakfire_parse_rich_dep(pakfire, &p, 0); + if (!id) + return id; + break; + + // End + case ')': + return ID_NULL; + + // Parse a regular dependency + default: + id = pakfire_parse_dep(pakfire, &p); + if (!id) + return id; + break; + } + + // Consume any space + if (isspace(*p)) + p++; + + // If we have successfully parsed something, we would expect a closing bracket + if (*p == ')') { + *dep = p + 1; + return id; + } + + // Search for a keyword + for (const struct pakfire_rich_operation* op = pakfire_rich_operations; op->keyword; op++) { + if (pakfire_string_startswith(p, op->keyword)) { + // Skip the keyword + p += strlen(op->keyword); + + // Store the flags + flags = op->flags; + break; + } + } + + // If there was no keyword, we are done + if (!flags) + return ID_NULL; + + // Parse the next bit + Id evr = pakfire_parse_rich_dep(pakfire, &p, flags); + + // Abort if there was a problem + if (!evr) + return ID_NULL; + + // Store until where we have parsed the string + *dep = p; + + Pool* pool = pakfire_get_solv_pool(pakfire); + + // Combine everything + return pool_rel2id(pool, id, evr, flags, 1); +} + +Id pakfire_str2dep(struct pakfire* pakfire, const char* s) { + Id id = ID_NULL; + + // Invalid input + if (!s) { + errno = EINVAL; + return id; + } + + // Ignore empty strings + if (!*s) + return id; + + // Consume any leading space + if (isspace(*s)) + s++; + + // Parse any rich dependencies + if (*s == '(') + id = pakfire_parse_rich_dep(pakfire, &s, 0); + else + id = pakfire_parse_dep(pakfire, &s); + + // Return nothing if we could not parse the entire string + if (id && *s) + id = ID_NULL; + return id; } diff --git a/tests/libpakfire/dependencies.c b/tests/libpakfire/dependencies.c index dd0094bcf..8d028f3b9 100644 --- a/tests/libpakfire/dependencies.c +++ b/tests/libpakfire/dependencies.c @@ -65,6 +65,31 @@ static const char* relations[] = { // Namespaces "pakfire(test)", + // Rich dependencies + "(foo >= 1.0 with foo < 2.0)", + "(kernel with flavor = desktop)", + "(beep or bash or kernel)", + + // Nested rich dependencies + "(beep or (bash and kernel))", + "(beep or (bash and kernel) or foo)", + + NULL, +}; + +static const char* invalid_relations[] = { + "beep something-else", + "beep >= 1 something else", + + // Broken rich deps + "()", + "(a ", + "(a unless)", + "(a unless )", + + // Broken nested rich deps + "(a and (b or c)", + NULL, }; @@ -91,6 +116,17 @@ static int test_dependencies(const struct test* t) { ASSERT_STRING_EQUALS(*relation, result); } + // Check invalid inputs + for (const char** relation = invalid_relations; *relation; relation++) { + printf("Parsing '%s'...\n", *relation); + + // Parse relation + dep = pakfire_str2dep(t->pakfire, *relation); + + // The return value must be ID_NULL + ASSERT(dep == ID_NULL); + } + return EXIT_SUCCESS; FAIL: