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>
28 endseg(const char *seg, const char *end)
30 for (; seg < end; seg++)
31 if (*seg == '.' || *seg == '-' || *seg == '_')
37 endpart(const char *seg, const char *end)
41 if (*seg >= '0' && *seg <= '9')
43 for (seg++; seg < end; seg++)
44 if (!(*seg >= '0' && *seg <= '9'))
49 for (seg++; seg < end; seg++)
55 for (seg++; seg < end; seg++)
56 if ((*seg >= '0' && *seg <= '9') || *seg == '*')
62 /* C implementation of the version comparison code in conda/models/version.py */
63 /* startswith == 1 : check if s1 starts with s2 */
65 solv_vercmp_conda(const char *s1, const char *q1, const char *s2, const char *q2, int startswith)
67 const char *s1p, *s2p;
68 const char *s1e, *s2e;
70 const char *q2end = 0;
74 for (q2end = q2; q2end > s2; q2end--)
75 if (q2end[-1] != '.' && q2end[-1] != '-' && q2end[-1] != '_')
80 while (s1 < q1 && (*s1 == '.' || *s1 == '-' || *s1 == '_'))
82 while (s2 < q2 && (*s2 == '.' || *s2 == '-' || *s2 == '_'))
84 if (s1 == q1 && s2 == q2)
86 if (startswith && s2 == q2)
88 /* find end of component */
92 for (isfirst = 1; ; isfirst = 0)
94 if (s1 == s1e && s2 == s2e)
98 s1p = endpart(s1, s1e);
99 s2p = endpart(s2, s2e);
100 /* prepend 0 if not numeric */
103 if (s1p != s1 && !(*s1 >= '0' && *s1 <= '9'))
105 if (s2p != s2 && !(*s2 >= '0' && *s2 <= '9'))
108 /* special case "post" */
109 if (s1p - s1 == 4 && !strncasecmp(s1, "post", 4))
111 if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4))
119 if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4))
122 if (isfirst || ((s1 == s1p || (*s1 >= '0' && *s1 <= '9')) && (s2 == s2p || (*s2 >= '0' && *s2 <= '9'))))
124 /* compare numbers */
125 while (s1 < s1p && *s1 == '0')
127 while (s2 < s2p && *s2 == '0')
129 if (s1p - s1 < s2p - s2)
131 if (s1p - s1 > s2p - s2)
133 r = s1p - s1 ? strncmp(s1, s2, s1p - s1) : 0;
137 else if (s1 == s1p || (*s1 >= '0' && *s1 <= '9'))
139 else if (s2 == s2p || (*s2 >= '0' && *s2 <= '9'))
143 /* special case "dev" */
144 if (*s2 != '*' && s1p - s1 == 3 && !strncasecmp(s1, "dev", 3))
146 if (s2p - s2 == 3 && !strncasecmp(s2, "dev", 3))
154 if (*s1 != '*' && s2p - s2 == 3 && !strncasecmp(s2, "dev", 3))
156 /* compare strings */
157 r = s2p - s2 > s1p - s1 ? s1p - s1 : s2p - s2;
159 r = strncasecmp(s1, s2, r);
162 if (s1p - s1 < s2p - s2)
164 if (s1p - s1 > s2p - s2)
174 pool_evrcmp_conda_int(const char *evr1, const char *evr1e, const char *evr2, const char *evr2e, int startswith)
176 static char zero[2] = {'0', 0};
181 /* split and compare epoch */
182 for (s1 = evr1; s1 < evr1e && *s1 >= '0' && *s1 <= '9'; s1++)
184 for (s2 = evr2; s2 < evr2e && *s2 >= '0' && *s2 <= '9'; s2++)
186 if (s1 == evr1 || s1 == evr1e || *s1 != '!')
188 if (s2 == evr1 || s2 == evr2e || *s2 != '!')
192 r = solv_vercmp_conda(s1 ? evr1 : zero, s1 ? s1 : zero + 1,
193 s2 ? evr2 : zero, s2 ? s2 : zero + 1, 0);
201 /* split into version/localversion */
202 for (s1 = evr1, r1 = 0; s1 < evr1e; s1++)
205 for (s2 = evr2, r2 = 0; s2 < evr2e; s2++)
208 r = solv_vercmp_conda(evr1, r1 ? r1 : s1, evr2, r2 ? r2 : s2, r2 ? 0 : startswith);
217 return solv_vercmp_conda(r1 + 1, s1, r2 + 1, s2, startswith);
221 pool_evrcmp_conda(const Pool *pool, const char *evr1, const char *evr2, int mode)
225 return pool_evrcmp_conda_int(evr1, evr1 + strlen(evr1), evr2, evr2 + strlen(evr2), 0);
229 regexmatch(const char *evr, const char *version, size_t versionlen)
232 char *buf = solv_malloc(versionlen + 1);
235 memcpy(buf, version, versionlen);
237 if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB))
239 r = regexec(®, evr, 0, NULL, 0);
245 globmatch(const char *evr, const char *version, size_t versionlen)
248 char *buf = solv_malloc(2 * versionlen + 3);
254 for (i = 0, j = 1; i < versionlen; i++)
256 if (version[i] == '.' || version[i] == '+' || version[i] == '*')
257 buf[j++] = version[i] == '*' ? '.' : '\\';
258 buf[j++] = version[i];
262 if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB))
264 r = regexec(®, evr, 0, NULL, 0);
269 /* return true if solvable s matches the version */
270 /* see conda/models/version.py */
272 solvable_conda_matchversion_single(Solvable *s, const char *version, size_t versionlen)
278 if (versionlen == 0 || (versionlen == 1 && *version == '*'))
279 return 1; /* matches every version */
280 evr = pool_id2str(s->repo->pool, s->evr);
281 if (versionlen >= 2 && version[0] == '^' && version[versionlen - 1] == '$')
282 return regexmatch(evr, version, versionlen);
283 if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~')
287 if (version[0] == '=')
288 flags = version[1] == '=' ? REL_EQ : 8;
289 else if (version[0] == '!' || version[0] == '~')
291 if (version[1] != '=')
293 flags = version[0] == '!' ? REL_LT | REL_GT : 9;
295 else if (version[0] == '<' || version[0] == '>')
297 flags = version[0] == '<' ? REL_LT : REL_GT;
298 if (version[1] == '=')
303 oplen = flags == 8 || flags == REL_LT || flags == REL_GT ? 1 : 2;
304 if (versionlen < oplen + 1)
308 if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~')
309 return 0; /* bad chars after op */
310 if (versionlen >= 2 && version[versionlen - 2] == '.' && version[versionlen - 1] == '*')
312 if (flags == 8 || flags == (REL_GT | REL_EQ))
314 else if (flags == (REL_LT | REL_GT))
324 /* we now have an op and a version */
325 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
327 return (flags & REL_LT) ? 1 : 0;
329 return (flags & REL_EQ) ? 1 : 0;
331 return (flags & REL_GT) ? 1 : 0;
334 if (flags == 8 || flags == 10) /* startswith, not-startswith */
336 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
337 return flags == 8 ? r == 0 : r != 0;
339 else if (flags == 9) /* compatible release op */
341 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
344 /* split off last component */
345 while (versionlen > 0 && version[versionlen - 1] != '.')
350 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
351 return r == 0 ? 1 : 0;
356 /* do we have a '*' in the version */
357 for (i = 0; i < versionlen; i++)
358 if (version[i] == '*')
360 for (i++; i < versionlen; i++)
361 if (version[i] != '*')
364 return globmatch(evr, version, versionlen);
367 if (versionlen > 1 && version[versionlen - 1] == '*')
370 while (versionlen > 0 && version[versionlen - 1] == '*')
372 while (versionlen > 0 && version[versionlen - 1] == '.')
374 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
375 return r == 0 ? 1 : 0;
377 /* do we have a '@' in the version? */
378 for (i = 0; i < versionlen; i++)
379 if (version[i] == '@')
380 return strncmp(evr, version, versionlen) == 0 && evr[versionlen] == 0;
381 r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
382 return r == 0 ? 1 : 0;
386 solvable_conda_matchversion_rec(Solvable *s, const char **versionp, const char *versionend)
388 const char *version = *versionp;
389 int v, vor = 0, vand = -1; /* -1: doing OR, 0,1: doing AND */
391 if (version == versionend)
398 v = solvable_conda_matchversion_rec(s, &version, versionend);
399 if (v == -1 || version == versionend || *version != ')')
403 else if (*version == ')' || *version == '|' || *version == ',')
407 const char *vstart = version;
408 while (version < versionend && *version != '(' && *version != ')' && *version != '|' && *version != ',')
410 if (vand >= 0 ? !vand : vor)
411 v = 0; /* no need to call expensive matchversion if the result does not matter */
413 v = solvable_conda_matchversion_single(s, vstart, version - vstart) ? 1 : 0;
415 if (version == versionend || *version == ')')
418 return vor | (vand >= 0 ? (vand & v) : v);
421 vand = vand >= 0 ? (vand & v) : v;
422 else if (*version == '|')
424 vor |= vand >= 0 ? (vand & v) : v;
433 /* return true if solvable s matches the version */
434 /* see conda/models/match_spec.py */
436 solvable_conda_matchversion(Solvable *s, const char *version)
438 const char *build, *versionend;
440 /* split off build */
441 if ((build = strchr(version, ' ')) != 0)
443 versionend = build++;
444 while (*build == ' ')
448 versionend = version + strlen(version);
449 r = solvable_conda_matchversion_rec(s, &version, versionend);
450 if (r != 1 || version != versionend)