mirror of
git://projects.qi-hardware.com/wernermisc.git
synced 2024-12-23 23:25:31 +02:00
326cd33967
- TODO: various updates - pkg.h (enum relop): marked rel_lt as deprecated - prereq.c (resolve): instrumentation for chasing a dependencies bug
313 lines
6.6 KiB
C
313 lines
6.6 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#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.
|
|
*/
|
|
|
|
#if 1
|
|
while (next_deps) {
|
|
assert(top->pkg->flags & QPKG_ADDING);
|
|
top->pkg->flags &= ~QPKG_ADDING;
|
|
top = top->next;
|
|
next_deps = next_deps->next;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
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;
|
|
}
|