/* * prereq.c - Determine prerequisite packages * * Written 2010 by Werner Almesberger * Copyright 2010 Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include "id.h" #include "qpkg.h" #include "util.h" #include "prereq.h" struct list { const struct ref *refs; struct list *next; }; struct stack { struct pkg *pkg; struct stack *next; }; static struct pkg **best = NULL; static struct pkg **installs = NULL; static int n_best; /* undefined if best == NULL */ static int n_install = 0; static int install_max = 0; static int debug = 0; static int epoch(const char **s, const struct id *id) { const char *end = id->s+id->len; int n = 0; while (*s != end && isdigit(**s)) { n = 10*n+**s-'0'; (*s)++; } if (*s == id->s || (*s != end && **s == ':')) return n; *s = id->s; return 0; } static int comp_versions(const struct id *va, const struct id *vb) { int epoch_a = 0, epoch_b = 0; const char *a = va->s; const char *b = vb->s; const char *ea = a+va->len; const char *eb = b+vb->len; epoch_a = epoch(&a, va); epoch_b = epoch(&b, vb); if (epoch_a != epoch_b) return epoch_a-epoch_b; while (1) { if (a == ea || b == eb) return (a != ea)-(b != eb); if (isdigit(*a) && isdigit(*b)) { int na = 0, nb = 0; while (a != ea && isdigit(*a)) { na = na*10+*a-'0'; a++; } while (b != eb && isdigit(*b)) { nb = nb*10+*b-'0'; b++; } if (na == nb) continue; return na-nb; } if (*a > *b) return 1; if (*a < *b) return 1; a++; b++; } } static void done(void) { int size; if (best && n_best <= n_install) return; free(best); size = sizeof(*installs)*(n_install+1); best = alloc_size(size); memcpy(best, installs, sizeof(*installs)*n_install); n_best = n_install; best[n_best] = NULL; } static void append_install(struct pkg *pkg) { if (n_install == install_max) { install_max = (install_max+1)*2; installs = realloc(installs, sizeof(*installs)*install_max); if (!installs) { perror("realloc"); exit(1); } } installs[n_install++] = pkg; assert(!pkg->mark && !(pkg->flags & QPKG_INSTALLED)); pkg->mark = 1; } static void backtrack(void) { assert(n_install); n_install--; installs[n_install]->mark = 0; } static int satisfies(const struct pkg *pkg, const struct ref *ref) { int cmp; if (pkg->id != ref->pkg) return 0; if (!ref->version) return 1; assert(pkg->version); cmp = comp_versions(pkg->version, ref->version); //fprintf(stderr, "%.*s <%d> %.*s\n", // ID2PF(pkg->version), cmp, ID2PF(ref->version)); switch (ref->relop) { case rel_eq: return !cmp; case rel_ge: return cmp >= 0; case rel_lt: return cmp < 0; default: abort(); } } static int conflicts(const struct pkg *pkg, const struct list *conf) { /* @@@ */ return 0; } static void resolve(struct list *next_dep, const struct ref *dep, struct stack *top, struct list *conf) { static int level = 0; struct list more_deps; struct list more_conf = { .next = conf }; struct stack stack; struct pkg *pkg; while (!dep) { if (!next_dep) { done(); return; } assert(top->pkg->flags & QPKG_ADDING); top->pkg->flags &= ~QPKG_ADDING; top = top->next; dep = next_dep->refs; next_dep = next_dep->next; } for (pkg = dep->pkg->jrb->val; pkg; pkg = pkg->more) { if (best && n_install == n_best) return; if (debug) { struct stack *p; fprintf(stderr, "%*s", level, ""); fprintf(stderr, "%.*s %p", ID2PF(pkg->id), pkg); if (pkg->version) fprintf(stderr, " %.*s", ID2PF(pkg->version)); fprintf(stderr, " ("); for (p = top; p; p = p->next) fprintf(stderr, "%s%.*s", p == top ? "" : " ", ID2PF(p->pkg->id)); fprintf(stderr, ")"); if (pkg->mark) fprintf(stderr, " +"); if (pkg->flags & QPKG_INSTALLED) fprintf(stderr, " ***"); fprintf(stderr, "\n"); } if (!satisfies(pkg, dep)) continue; if (pkg->flags & QPKG_ADDING) { fprintf(stderr, "package %.*s version %.*s " "has cyclic dependency\n", ID2PF(pkg->id), ID2PF(pkg->version)); exit(1); } if ((pkg->flags & QPKG_INSTALLED) || pkg->mark) { assert(!conflicts(pkg, conf)); resolve(next_dep, dep->next, top, conf); continue; } if (conflicts(pkg, conf)) continue; level++; append_install(pkg); more_deps.refs = dep->next; more_deps.next = next_dep; more_conf.refs = pkg->conflicts; more_conf.next = conf; stack.pkg = pkg; stack.next = top; pkg->flags |= QPKG_ADDING; resolve(&more_deps, pkg->depends, &stack, &more_conf); backtrack(); level--; } /* * @@@ this shouldn't be all of the story yet. if we fail a dependency * due to a conflict, the current algorithm may leave packages marked * as QPKG_ADDING, possibly triggering a false cyclic dependency error. * * In the case if cycle detection, we could avoid all the hassle of * maintaining this flag and just walk down the stack to see if we're * already trying to add the package (the stack should be correct with * the current algorithm), but we'll need this kind of accurate * tracking of package state for ordering the dependencies and for * "Provides" anyway. */ } struct pkg **prereq(struct pkg *pkg) { struct stack stack = { .pkg = pkg, .next = NULL }; /* @@@ make list of pre-existing conflicts */ pkg->flags |= QPKG_ADDING; resolve(NULL, pkg->depends, &stack, NULL); pkg->flags &= ~QPKG_ADDING; free(installs); return best; }