/* * 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 "util.h" #include "id.h" #include "pkg.h" #include "qpkg.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; /* ----- List of packages considered for installation ---------------------- */ 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; } /* ----- Check dependencies and conflicts ---------------------------------- */ 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; /* @@@ error in the database, not qpkg's fault */ 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 old_conflicts(const struct pkg *pkg, const struct list *conf) { const struct ref *ref; while (conf) { for (ref = conf->refs; ref; ref = ref->next) if (satisfies(pkg, ref)) return 1; conf = conf->next; } return 0; } static int new_conflicts(const struct ref *refs) { const struct ref *ref; const struct pkg *pkg; for (ref = refs; ref; ref = ref->next) for (pkg = ref->pkg->jrb->val; pkg; pkg = pkg->more) if ((pkg->flags & (QPKG_INSTALLED | QPKG_ADDING)) || pkg->mark) if (satisfies(pkg, ref)) return 1; return 0; } /* ----- Recurse through lists and layers of dependencies ------------------ */ static void print_debug(const struct pkg *pkg, const struct stack *top, int level) { const 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"); } static void resolve(struct list *next_deps, 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_deps) { done(); return; } assert(top->pkg->flags & QPKG_ADDING); top->pkg->flags &= ~QPKG_ADDING; top = top->next; dep = next_deps->refs; next_deps = next_deps->next; } for (pkg = dep->pkg->jrb->val; pkg; pkg = pkg->more) { if (best && n_install == n_best) break; if (pkg->flags & QPKG_PROVIDED) continue; if (debug) print_debug(pkg, top, level); 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(!old_conflicts(pkg, conf)); resolve(next_deps, dep->next, top, conf); continue; } if (old_conflicts(pkg, conf)) continue; if (new_conflicts(pkg->conflicts)) continue; level++; append_install(pkg); more_deps.refs = dep->next; more_deps.next = next_deps; 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. * See test/bug-adding for an example. * * In the case of 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. */ } static struct list *installed_conflicts(void) { struct list *list = NULL, *new; const struct jrb *n; const struct pkg *pkg; for (n = jrb_first(packages->root); n != jrb_nil(packages->root); n = jrb_next(n)) { pkg = n->val; if (!pkg) continue; if (!(pkg->flags & QPKG_INSTALLED)) continue; if (!pkg->conflicts) /* optimization */ continue; new = alloc_type(struct list); new->refs = pkg->conflicts; new->next = list; list = new; } return list; } static void free_conflicts(struct list *conf) { struct list *next; while (conf) { next = conf->next; free(conf); conf = next; } } struct pkg **prereq(struct pkg *pkg) { struct stack stack = { .pkg = pkg, .next = NULL }; struct list conf = { .refs = pkg->conflicts, .next = installed_conflicts() }; if (old_conflicts(pkg, &conf) || new_conflicts(pkg->conflicts)) { fprintf(stderr, "%.*s conflicts with installed packages\n", ID2PF(pkg->id)); exit(1); } pkg->flags |= QPKG_ADDING; resolve(NULL, pkg->depends, &stack, &conf); pkg->flags &= ~QPKG_ADDING; free(installs); free_conflicts(conf.next); return best; }