2 * Copyright (c) 2019, SUSE LLC
4 * This program is licensed under the BSD license, read LICENSE.BSD
5 * for further information
11 * evr comparison and package matching for conda
19 #include <sys/types.h>
32 endseg(const char *seg, const char *end)
34 for (; seg < end; seg++)
35 if (*seg == '.' || *seg == '-' || *seg == '_')
41 endpart(const char *seg, const char *end)
45 if (*seg >= '0' && *seg <= '9')
47 for (seg++; seg < end; seg++)
48 if (!(*seg >= '0' && *seg <= '9'))
53 for (seg++; seg < end; seg++)
59 for (seg++; seg < end; seg++)
60 if ((*seg >= '0' && *seg <= '9') || *seg == '*')
66 /* C implementation of the version comparison code in conda/models/version.py */
67 /* startswith == 1 : check if s1 starts with s2 */
69 solv_vercmp_conda(const char *s1, const char *q1, const char *s2, const char *q2, int startswith)
71 const char *s1p, *s2p;
72 const char *s1e, *s2e;
74 const char *q2end = 0;
78 for (q2end = q2; q2end > s2; q2end--)
79 if (q2end[-1] != '.' && q2end[-1] != '-' && q2end[-1] != '_')
84 while (s1 < q1 && (*s1 == '.' || *s1 == '-' || *s1 == '_'))
86 while (s2 < q2 && (*s2 == '.' || *s2 == '-' || *s2 == '_'))
88 if (s1 == q1 && s2 == q2)
90 if (startswith && s2 == q2)
92 /* find end of component */
96 for (isfirst = 1; ; isfirst = 0)
98 if (s1 == s1e && s2 == s2e)
102 s1p = endpart(s1, s1e);
103 s2p = endpart(s2, s2e);
104 /* prepend 0 if not numeric */
107 if (s1p != s1 && !(*s1 >= '0' && *s1 <= '9'))
109 if (s2p != s2 && !(*s2 >= '0' && *s2 <= '9'))
112 /* special case "post" */
113 if (s1p - s1 == 4 && !strncasecmp(s1, "post", 4))
115 if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4))
123 if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4))
126 if (isfirst || ((s1 == s1p || (*s1 >= '0' && *s1 <= '9')) && (s2 == s2p || (*s2 >= '0' && *s2 <= '9'))))
128 /* compare numbers */
129 while (s1 < s1p && *s1 == '0')
131 while (s2 < s2p && *s2 == '0')
133 if (s1p - s1 < s2p - s2)
135 if (s1p - s1 > s2p - s2)
137 r = s1p - s1 ? strncmp(s1, s2, s1p - s1) : 0;
141 else if (s1 == s1p || (*s1 >= '0' && *s1 <= '9'))
143 else if (s2 == s2p || (*s2 >= '0' && *s2 <= '9'))
147 /* special case "dev" */
148 if (*s2 != '*' && s1p - s1 == 3 && !strncasecmp(s1, "dev", 3))
150 if (s2p - s2 == 3 && !strncasecmp(s2, "dev", 3))
158 if (*s1 != '*' && s2p - s2 == 3 && !strncasecmp(s2, "dev", 3))
160 /* compare strings */
161 r = s2p - s2 > s1p - s1 ? s1p - s1 : s2p - s2;
163 r = strncasecmp(s1, s2, r);
166 if (s1p - s1 < s2p - s2)
168 if (s1p - s1 > s2p - s2)
178 pool_evrcmp_conda_int(const char *evr1, const char *evr1e, const char *evr2, const char *evr2e, int startswith)
180 static char zero[2] = {'0', 0};
185 /* split and compare epoch */
186 for (s1 = evr1; s1 < evr1e && *s1 >= '0' && *s1 <= '9'; s1++)
188 for (s2 = evr2; s2 < evr2e && *s2 >= '0' && *s2 <= '9'; s2++)
190 if (s1 == evr1 || s1 == evr1e || *s1 != '!')
192 if (s2 == evr1 || s2 == evr2e || *s2 != '!')
196 r = solv_vercmp_conda(s1 ? evr1 : zero, s1 ? s1 : zero + 1,
197 s2 ? evr2 : zero, s2 ? s2 : zero + 1, 0);
205 /* split into version/localversion */
206 for (s1 = evr1, r1 = 0; s1 < evr1e; s1++)
209 for (s2 = evr2, r2 = 0; s2 < evr2e; s2++)
212 r = solv_vercmp_conda(evr1, r1 ? r1 : s1, evr2, r2 ? r2 : s2, r2 ? 0 : startswith);
215 if ((!r2 && startswith) || (!r1 && !r2))
221 return solv_vercmp_conda(r1 + 1, s1, r2 + 1, s2, startswith);
225 pool_evrcmp_conda(const Pool *pool, const char *evr1, const char *evr2, int mode)
229 return pool_evrcmp_conda_int(evr1, evr1 + strlen(evr1), evr2, evr2 + strlen(evr2), 0);
233 regexmatch(const char *evr, const char *version, size_t versionlen, int icase)
236 char *buf = solv_malloc(versionlen + 1);
239 memcpy(buf, version, versionlen);
241 if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0)))
246 r = regexec(®, evr, 0, NULL, 0);
253 globmatch(const char *evr, const char *version, size_t versionlen, int icase)
256 char *buf = solv_malloc(2 * versionlen + 3);
262 for (i = 0, j = 1; i < versionlen; i++)
264 if (version[i] == '.' || version[i] == '+' || version[i] == '*')
265 buf[j++] = version[i] == '*' ? '.' : '\\';
266 buf[j++] = version[i];
270 if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0)))
275 r = regexec(®, evr, 0, NULL, 0);
281 /* return true if solvable s matches the version */
282 /* see conda/models/version.py */
284 solvable_conda_matchversion_single(Solvable *s, const char *version, size_t versionlen)
290 if (versionlen == 0 || (versionlen == 1 && *version == '*'))
291 return 1; /* matches every version */
292 evr = pool_id2str(s->repo->pool, s->evr);
293 if (versionlen >= 2 && version[0] == '^' && version[versionlen - 1] == '$')
294 return regexmatch(evr, version, versionlen, 0);
295 if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~')
299 if (version[0] == '=')
300 flags = version[1] == '=' ? REL_EQ : 8;
301 else if (version[0] == '!' || version[0] == '~')
303 if (version[1] != '=')
305 flags = version[0] == '!' ? REL_LT | REL_GT : 9;
307 else if (version[0] == '<' || version[0] == '>')
309 flags = version[0] == '<' ? REL_LT : REL_GT;
310 if (version[1] == '=')
315 oplen = flags == 8 || flags == REL_LT || flags == REL_GT ? 1 : 2;
316 if (versionlen < oplen + 1)
320 if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~')
321 return 0; /* bad chars after op */
322 if (versionlen >= 2 && version[versionlen - 2] == '.' && version[versionlen - 1] == '*')
324 if (flags == 8 || flags == (REL_GT | REL_EQ))
326 else if (flags == (REL_LT | REL_GT))
336 /* we now have an op and a version */
337 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
339 return (flags & REL_LT) ? 1 : 0;
341 return (flags & REL_EQ) ? 1 : 0;
343 return (flags & REL_GT) ? 1 : 0;
346 if (flags == 8 || flags == 10) /* startswith, not-startswith */
348 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
349 return flags == 8 ? r == 0 : r != 0;
351 else if (flags == 9) /* compatible release op */
353 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
356 /* split off last component */
357 while (versionlen > 0 && version[versionlen - 1] != '.')
362 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
363 return r == 0 ? 1 : 0;
368 /* do we have a '*' in the version */
369 for (i = 0; i < versionlen; i++)
370 if (version[i] == '*')
372 for (i++; i < versionlen; i++)
373 if (version[i] != '*')
376 return globmatch(evr, version, versionlen, 1);
379 if (versionlen > 1 && version[versionlen - 1] == '*')
382 while (versionlen > 0 && version[versionlen - 1] == '*')
384 while (versionlen > 0 && version[versionlen - 1] == '.')
386 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
387 return r == 0 ? 1 : 0;
389 /* do we have a '@' in the version? */
390 for (i = 0; i < versionlen; i++)
391 if (version[i] == '@')
392 return strncmp(evr, version, versionlen) == 0 && evr[versionlen] == 0;
393 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
394 return r == 0 ? 1 : 0;
398 solvable_conda_matchversion_rec(Solvable *s, const char **versionp, const char *versionend)
400 const char *version = *versionp;
401 int v, vor = 0, vand = -1; /* -1: doing OR, 0,1: doing AND */
403 if (version == versionend)
410 v = solvable_conda_matchversion_rec(s, &version, versionend);
411 if (v == -1 || version == versionend || *version != ')')
415 else if (*version == ')' || *version == '|' || *version == ',')
419 const char *vstart = version;
420 while (version < versionend && *version != '(' && *version != ')' && *version != '|' && *version != ',')
422 if (vand >= 0 ? !vand : vor)
423 v = 0; /* no need to call expensive matchversion if the result does not matter */
425 v = solvable_conda_matchversion_single(s, vstart, version - vstart) ? 1 : 0;
427 if (version == versionend || *version == ')')
430 return vor | (vand >= 0 ? (vand & v) : v);
433 vand = vand >= 0 ? (vand & v) : v;
434 else if (*version == '|')
436 vor |= vand >= 0 ? (vand & v) : v;
446 solvable_conda_matchbuild(Solvable *s, const char *build, const char *buildend)
449 const char *flavor = solvable_lookup_str(s, SOLVABLE_BUILDFLAVOR);
453 if (build == buildend)
454 return *flavor ? 0 : 1;
455 if (build + 1 == buildend && *build == '*')
457 if (*build == '^' && buildend[-1] == '$')
458 return regexmatch(flavor, build, buildend - build, 0);
459 for (bp = build; bp < buildend; bp++)
461 return globmatch(flavor, build, buildend - build, 0);
462 return strncmp(flavor, build, buildend - build) == 0 && flavor[buildend - build] == 0 ? 1 : 0;
465 /* return true if solvable s matches the version */
466 /* see conda/models/match_spec.py */
468 solvable_conda_matchversion(Solvable *s, const char *version)
470 const char *build, *versionend;
472 /* split off build */
473 if ((build = strchr(version, ' ')) != 0)
475 versionend = build++;
476 while (*build == ' ')
480 versionend = version + strlen(version);
481 r = solvable_conda_matchversion_rec(s, &version, versionend);
482 if (r != 1 || version != versionend)
484 if (build && !solvable_conda_matchbuild(s, build, build + strlen(build)))
490 pool_addrelproviders_conda_slow(Pool *pool, const char *namestr, Id evr, Queue *plist, int mode)
492 size_t namestrlen = strlen(namestr);
493 const char *evrstr = evr == 0 || evr == 1 ? 0 : pool_id2str(pool, evr);
496 FOR_POOL_SOLVABLES(p)
498 Solvable *s = pool->solvables + p;
499 if (!pool_installable(pool, s))
501 if (mode == 1 && !globmatch(pool_id2str(pool, s->name), namestr, namestrlen, 1))
503 if (mode == 2 && !regexmatch(pool_id2str(pool, s->name), namestr, namestrlen, 1))
505 if (!evrstr || solvable_conda_matchversion(s, evrstr))
506 queue_push(plist, p);
511 /* called from pool_addrelproviders, plist is an empty queue
512 * it can either return an offset into the whatprovides array
513 * or fill the plist queue and return zero */
515 pool_addrelproviders_conda(Pool *pool, Id name, Id evr, Queue *plist)
517 const char *namestr = pool_id2str(pool, name), *np;
521 /* is this a regex? */
522 if (*namestr && *namestr == '^')
525 if (namestr[l - 1] == '$')
526 return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 2);
529 if (*namestr && *namestr == '*' && namestr[1] == 0)
530 return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 0);
531 /* does the string contain '*' or uppercase? */
532 for (np = namestr; *np; np++)
535 return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 1);
536 else if (*np >= 'A' && *np <= 'Z')
541 char *nbuf = solv_strdup(namestr), *nbufp;
542 for (nbufp = nbuf; *nbufp; nbufp++)
543 *nbufp = *nbufp >= 'A' && *nbufp <= 'Z' ? *nbufp + ('a' - 'A') : *nbufp;
544 name = pool_str2id(pool, nbuf, 0);
545 wp = name ? pool_whatprovides(pool, name) : 0;
549 wp = pool_whatprovides(pool, name);
550 if (wp && evr && evr != 1)
552 const char *evrstr = pool_id2str(pool, evr);
553 pp = pool->whatprovidesdata + wp;
554 while ((p = *pp++) != 0)
556 if (solvable_conda_matchversion(pool->solvables + p, evrstr))
557 queue_push(plist, p);
565 /* create a CONDA_REL relation from a matchspec */
567 pool_conda_matchspec(Pool *pool, const char *name)
572 char *build, *buildend, *version, *versionend;
576 /* ignore channel and namespace for now */
577 if ((p2 = strrchr(name, ':')))
579 name2 = solv_strdup(name);
580 /* find end of name */
581 for (p = name2; *p && *p != ' ' && *p != '=' && *p != '<' && *p != '>' && *p != '!' && *p != '~'; p++)
583 if (*p >= 'A' && *p <= 'Z')
584 *(char *)p += 'a' - 'A'; /* lower case the name */
591 return 0; /* error: empty package name */
593 nameid = pool_strn2id(pool, name2, p - name2, 1);
598 if (*name2 != '^' && !haveglob)
601 return nameid; /* return a simple dependency if no glob/regex */
603 evrid = pool_str2id(pool, "*", 1);
605 return pool_rel2id(pool, nameid, evrid, REL_CONDA, 1);
609 versionend = p + strlen(p);
610 while (versionend > version && versionend[-1] == ' ')
612 build = buildend = 0;
617 while (p > version && p[-1] != ' ' && p[-1] != '-' && p[-1] != '=' && p[-1] != ',' && p[-1] != '|' && p[-1] != '<' && p[-1] != '>' && p[-1] != '~')
619 if (p <= version + 1 || (p[-1] != ' ' && p[-1] != '='))
620 break; /* no build */
621 /* check char before delimiter */
622 if (p[-2] == '=' || p[-2] == '!' || p[-2] == '|' || p[-2] == ',' || p[-2] == '<' || p[-2] == '>' || p[-2] == '~')
624 /* illegal combination */
628 continue; /* special case space: it may be in the build */
630 break; /* no build */
635 buildend = versionend;
640 /* do weird version translation */
641 if (versionend > version && version[0] == '=')
643 if (versionend - version >= 2 && version[1] == '=')
652 for (p = version + 1; p < versionend; p++)
653 if (*p == '=' || *p == ',' || *p == '|')
657 memmove(version, version + 1, versionend - version - 1);
658 versionend[-1] = '*';
663 printf("version: >%.*s<\n", (int)(versionend - version), version);
664 if (build) printf("build: >%.*s<\n", (int)(buildend - build), build);
666 /* strip spaces from version */
667 for (p = pp = version; pp < versionend; pp++)
673 memcpy(p, build, buildend - build);
674 p += buildend - build;
676 evrid = pool_strn2id(pool, version, p - version, 1);
678 return pool_rel2id(pool, nameid, evrid, REL_CONDA, 1);