]> git.ipfire.org Git - pakfire.git/commitdiff
Add support for rich dependencies
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 9 Nov 2021 11:40:21 +0000 (11:40 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 9 Nov 2021 11:40:21 +0000 (11:40 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/libpakfire/util.c
tests/libpakfire/dependencies.c

index feff53fff84b9dec19044cfaa28b696bee4b4a49..a0fa330c4ffda30435a028938212f2682db91443 100644 (file)
 
 #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;
 }
 
index dd0094bcf51ce21943d41f0da1e27828d4178894..8d028f3b90586c901ce598249249f75d74ec82e4 100644 (file)
@@ -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: