solver: generate proper error messages
* the solver no longer does look-ahead locking of names (could be possibly optimized later); instead names are now always ordered strictly to properly detect the package names which are unsolveable * basic error tests added, so we can see the most likely problem in dependencies easilycute-signatures
parent
1a04425fad
commit
a5146f1b6c
|
@ -24,7 +24,7 @@ struct apk_changeset {
|
||||||
|
|
||||||
void apk_solver_sort(struct apk_database *db);
|
void apk_solver_sort(struct apk_database *db);
|
||||||
int apk_solver_solve(struct apk_database *db, struct apk_dependency_array *world,
|
int apk_solver_solve(struct apk_database *db, struct apk_dependency_array *world,
|
||||||
struct apk_package_array **solution);
|
struct apk_package_array **solution, int allow_errors);
|
||||||
int apk_solver_generate_changeset(struct apk_database *db,
|
int apk_solver_generate_changeset(struct apk_database *db,
|
||||||
struct apk_package_array *solution,
|
struct apk_package_array *solution,
|
||||||
struct apk_changeset *changeset);
|
struct apk_changeset *changeset);
|
||||||
|
|
366
src/solver.c
366
src/solver.c
|
@ -33,9 +33,12 @@ struct apk_package_state {
|
||||||
struct apk_package *backtrack;
|
struct apk_package *backtrack;
|
||||||
unsigned short flags;
|
unsigned short flags;
|
||||||
unsigned short conflicts;
|
unsigned short conflicts;
|
||||||
|
unsigned short cur_unsatisfiable;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define APK_NAMESTF_AVAILABILITY_CHECKED 1
|
#define APK_NAMESTF_AVAILABILITY_CHECKED 1
|
||||||
|
#define APK_NAMESTF_LOCKED 2
|
||||||
|
#define APK_NAMESTF_NO_OPTIONS 4
|
||||||
struct apk_name_state {
|
struct apk_name_state {
|
||||||
struct list_head unsolved_list;
|
struct list_head unsolved_list;
|
||||||
struct apk_package *chosen;
|
struct apk_package *chosen;
|
||||||
|
@ -51,15 +54,17 @@ struct apk_solver_state {
|
||||||
struct apk_package *latest_decision;
|
struct apk_package *latest_decision;
|
||||||
unsigned int topology_position;
|
unsigned int topology_position;
|
||||||
unsigned int assigned_names;
|
unsigned int assigned_names;
|
||||||
|
unsigned short cur_unsatisfiable;
|
||||||
|
unsigned short allow_errors;
|
||||||
|
|
||||||
struct apk_package_array *best_solution;
|
struct apk_package_array *best_solution;
|
||||||
unsigned int best_cost;
|
unsigned short best_unsatisfiable;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int apply_constraint(struct apk_solver_state *ss, struct apk_dependency *dep);
|
static void apply_constraint(struct apk_solver_state *ss, struct apk_dependency *dep);
|
||||||
static int undo_constraint(struct apk_solver_state *ss, struct apk_dependency *dep);
|
static void undo_constraint(struct apk_solver_state *ss, struct apk_dependency *dep);
|
||||||
static int push_decision(struct apk_solver_state *ss, struct apk_package *pkg,
|
static void push_decision(struct apk_solver_state *ss, struct apk_package *pkg,
|
||||||
int flags);
|
int flags);
|
||||||
|
|
||||||
static inline int pkg_available(struct apk_database *db, struct apk_package *pkg)
|
static inline int pkg_available(struct apk_database *db, struct apk_package *pkg)
|
||||||
{
|
{
|
||||||
|
@ -95,39 +100,28 @@ static void prepare_name(struct apk_solver_state *ss, struct apk_name *name,
|
||||||
ns->flags |= APK_NAMESTF_AVAILABILITY_CHECKED;
|
ns->flags |= APK_NAMESTF_AVAILABILITY_CHECKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int foreach_dependency(struct apk_solver_state *ss, struct apk_dependency_array *deps,
|
static void foreach_dependency(struct apk_solver_state *ss, struct apk_dependency_array *deps,
|
||||||
int (*func)(struct apk_solver_state *ss, struct apk_dependency *dep))
|
void (*func)(struct apk_solver_state *ss, struct apk_dependency *dep))
|
||||||
{
|
{
|
||||||
int i, r = 0;
|
int i;
|
||||||
for (i = 0; i < deps->num; i++)
|
for (i = 0; i < deps->num; i++)
|
||||||
r += func(ss, &deps->item[i]);
|
func(ss, &deps->item[i]);
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int inline can_consider_package(struct apk_solver_state *ss, struct apk_package *pkg)
|
static int inline can_consider_package(struct apk_solver_state *ss, struct apk_package *pkg)
|
||||||
{
|
{
|
||||||
struct apk_package_state *ps = &ss->pkg_state[pkg->topology_sort];
|
struct apk_package_state *ps = &ss->pkg_state[pkg->topology_sort];
|
||||||
if (pkg->topology_sort >= ss->topology_position)
|
if (pkg->topology_sort > ss->topology_position)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (ps->conflicts)
|
if (ps->conflicts)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int is_pkg_preferred(struct apk_solver_state *ss, struct apk_package *pkg)
|
static int get_pkg_expansion_flags(struct apk_solver_state *ss, struct apk_package *pkg)
|
||||||
{
|
{
|
||||||
struct apk_name *name = pkg->name;
|
struct apk_name *name = pkg->name;
|
||||||
int i;
|
int i, options = 0;
|
||||||
|
|
||||||
if (!(apk_flags & APK_UPGRADE)) {
|
|
||||||
/* not upgrading, prefer the installed package; unless we
|
|
||||||
* need additional availability checks */
|
|
||||||
if (pkg->ipkg != NULL) {
|
|
||||||
if (pkg->repos != 0 ||
|
|
||||||
!(apk_flags & APK_PREFER_AVAILABLE))
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check if the suggested package is the most preferred one of
|
/* check if the suggested package is the most preferred one of
|
||||||
* available packages for the name */
|
* available packages for the name */
|
||||||
|
@ -143,31 +137,39 @@ static int is_pkg_preferred(struct apk_solver_state *ss, struct apk_package *pkg
|
||||||
continue;
|
continue;
|
||||||
/* pkg0 available, pkg not */
|
/* pkg0 available, pkg not */
|
||||||
if (pkg0->repos != 0 && pkg->repos == 0)
|
if (pkg0->repos != 0 && pkg->repos == 0)
|
||||||
return FALSE;
|
return APK_PKGSTF_NOINSTALL | APK_PKGSTF_BRANCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(apk_flags & APK_UPGRADE)) {
|
if (!(apk_flags & APK_UPGRADE)) {
|
||||||
/* not upgrading, prefer the installed package */
|
/* not upgrading, prefer the installed package */
|
||||||
if (pkg0->ipkg != NULL)
|
if (pkg->ipkg == NULL && pkg0->ipkg != NULL)
|
||||||
return FALSE;
|
return APK_PKGSTF_NOINSTALL | APK_PKGSTF_BRANCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* upgrading, or neither of the package is installed, so
|
/* upgrading, or neither of the package is installed, so
|
||||||
* we just fall back comparing to versions */
|
* we just fall back comparing to versions */
|
||||||
|
options++;
|
||||||
if (apk_pkg_version_compare(pkg0, pkg) == APK_VERSION_GREATER)
|
if (apk_pkg_version_compare(pkg0, pkg) == APK_VERSION_GREATER)
|
||||||
return FALSE;
|
return APK_PKGSTF_NOINSTALL | APK_PKGSTF_BRANCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* no package greater than the selected */
|
/* no package greater than the selected */
|
||||||
return TRUE;
|
if (options)
|
||||||
|
return APK_PKGSTF_INSTALL | APK_PKGSTF_BRANCH;
|
||||||
|
|
||||||
|
/* no other choice */
|
||||||
|
return APK_PKGSTF_INSTALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int update_name_state(struct apk_solver_state *ss,
|
static int update_name_state(struct apk_solver_state *ss,
|
||||||
struct apk_name *name, struct apk_name_state *ns)
|
struct apk_name *name, struct apk_name_state *ns,
|
||||||
|
int requirers_adjustment)
|
||||||
{
|
{
|
||||||
struct apk_package *pkg_best = NULL;
|
struct apk_package *pkg_best = NULL;
|
||||||
int i, options = 0;
|
int i, options = 0;
|
||||||
|
|
||||||
|
ns->requirers += requirers_adjustment;
|
||||||
|
|
||||||
for (i = 0; i < name->pkgs->num; i++) {
|
for (i = 0; i < name->pkgs->num; i++) {
|
||||||
struct apk_package *pkg0 = name->pkgs->item[i];
|
struct apk_package *pkg0 = name->pkgs->item[i];
|
||||||
|
|
||||||
|
@ -179,16 +181,39 @@ static int update_name_state(struct apk_solver_state *ss,
|
||||||
pkg0->topology_sort > pkg_best->topology_sort)
|
pkg0->topology_sort > pkg_best->topology_sort)
|
||||||
pkg_best = pkg0;
|
pkg_best = pkg0;
|
||||||
}
|
}
|
||||||
ns->chosen = pkg_best;
|
|
||||||
dbg_printf("%s: adjusted preference %d -> %d (options left %d)\n",
|
if (options == 0) {
|
||||||
name->name, ss->topology_position, ns->chosen->topology_sort,
|
if (!(ns->flags & APK_NAMESTF_NO_OPTIONS)) {
|
||||||
options);
|
ss->cur_unsatisfiable += ns->requirers;
|
||||||
|
ns->flags |= APK_NAMESTF_NO_OPTIONS;
|
||||||
|
} else if (requirers_adjustment > 0) {
|
||||||
|
ss->cur_unsatisfiable += requirers_adjustment;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
ns->flags &= ~APK_NAMESTF_NO_OPTIONS;
|
||||||
|
|
||||||
|
if (options == 0 || ns->requirers == 0) {
|
||||||
|
if (list_hashed(&ns->unsolved_list)) {
|
||||||
|
list_del(&ns->unsolved_list);
|
||||||
|
list_init(&ns->unsolved_list);
|
||||||
|
ns->chosen = NULL;
|
||||||
|
}
|
||||||
|
dbg_printf("%s: deleted from unsolved: %d requirers, %d options\n",
|
||||||
|
name->name, ns->requirers, options);
|
||||||
|
} else {
|
||||||
|
dbg_printf("%s: added to unsolved: %d requirers, %d options (next topology %d)\n",
|
||||||
|
name->name, ns->requirers, options, pkg_best->topology_sort);
|
||||||
|
if (!list_hashed(&ns->unsolved_list))
|
||||||
|
list_add(&ns->unsolved_list, &ss->unsolved_list_head);
|
||||||
|
ns->chosen = pkg_best;
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int apply_decision(struct apk_solver_state *ss,
|
static void apply_decision(struct apk_solver_state *ss,
|
||||||
struct apk_package *pkg,
|
struct apk_package *pkg,
|
||||||
struct apk_package_state *ps)
|
struct apk_package_state *ps)
|
||||||
{
|
{
|
||||||
struct apk_name_state *ns = &ss->name_state[pkg->name->id];
|
struct apk_name_state *ns = &ss->name_state[pkg->name->id];
|
||||||
|
|
||||||
|
@ -198,23 +223,17 @@ static int apply_decision(struct apk_solver_state *ss,
|
||||||
if (ps->flags & APK_PKGSTF_INSTALL) {
|
if (ps->flags & APK_PKGSTF_INSTALL) {
|
||||||
ss->assigned_names++;
|
ss->assigned_names++;
|
||||||
ns->chosen = pkg;
|
ns->chosen = pkg;
|
||||||
if (list_hashed(&ns->unsolved_list)) {
|
ns->flags |= APK_NAMESTF_LOCKED;
|
||||||
list_del(&ns->unsolved_list);
|
|
||||||
list_init(&ns->unsolved_list);
|
list_del(&ns->unsolved_list);
|
||||||
dbg_printf("%s: deleting from unsolved list\n",
|
list_init(&ns->unsolved_list);
|
||||||
pkg->name->name);
|
dbg_printf("%s: deleting from unsolved list\n",
|
||||||
}
|
pkg->name->name);
|
||||||
return foreach_dependency(ss, pkg->depends, apply_constraint);
|
|
||||||
|
foreach_dependency(ss, pkg->depends, apply_constraint);
|
||||||
} else {
|
} else {
|
||||||
if (!list_hashed(&ns->unsolved_list)) {
|
ps->conflicts++;
|
||||||
ns->chosen = NULL;
|
update_name_state(ss, pkg->name, ns, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (update_name_state(ss, pkg->name, ns) != 1)
|
|
||||||
return 0;
|
|
||||||
/* the name is required and we are left with only one candidate
|
|
||||||
* after deciding to not install pkg; autoselect the last option */
|
|
||||||
return push_decision(ss, ns->chosen, APK_PKGSTF_INSTALL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,28 +246,33 @@ static void undo_decision(struct apk_solver_state *ss,
|
||||||
dbg_printf("undo_decision: " PKG_VER_FMT " %s\n", PKG_VER_PRINTF(pkg),
|
dbg_printf("undo_decision: " PKG_VER_FMT " %s\n", PKG_VER_PRINTF(pkg),
|
||||||
(ps->flags & APK_PKGSTF_INSTALL) ? "INSTALL" : "NO_INSTALL");
|
(ps->flags & APK_PKGSTF_INSTALL) ? "INSTALL" : "NO_INSTALL");
|
||||||
|
|
||||||
|
ss->cur_unsatisfiable = ps->cur_unsatisfiable;
|
||||||
|
if (ps->flags & APK_PKGSTF_BRANCH)
|
||||||
|
ss->topology_position = pkg->topology_sort;
|
||||||
|
|
||||||
if (ps->flags & APK_PKGSTF_INSTALL) {
|
if (ps->flags & APK_PKGSTF_INSTALL) {
|
||||||
ss->assigned_names--;
|
ss->assigned_names--;
|
||||||
foreach_dependency(ss, pkg->depends, undo_constraint);
|
foreach_dependency(ss, pkg->depends, undo_constraint);
|
||||||
|
|
||||||
if (ns->requirers) {
|
ns->flags &= ~APK_NAMESTF_LOCKED;
|
||||||
list_add(&ns->unsolved_list, &ss->unsolved_list_head);
|
ns->chosen = NULL;
|
||||||
dbg_printf("%s: adding back to unsolved list (requirers: %d)\n",
|
} else {
|
||||||
pkg->name->name, ns->requirers);
|
ps->conflicts--;
|
||||||
} else {
|
|
||||||
ns->chosen = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_name_state(ss, pkg->name, ns, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int push_decision(struct apk_solver_state *ss, struct apk_package *pkg,
|
static void push_decision(struct apk_solver_state *ss, struct apk_package *pkg,
|
||||||
int flags)
|
int flags)
|
||||||
{
|
{
|
||||||
struct apk_package_state *ps = &ss->pkg_state[pkg->topology_sort];
|
struct apk_package_state *ps = &ss->pkg_state[pkg->topology_sort];
|
||||||
|
|
||||||
ps->backtrack = ss->latest_decision;
|
ps->backtrack = ss->latest_decision;
|
||||||
ps->flags = flags;
|
ps->flags = flags;
|
||||||
|
ps->cur_unsatisfiable = ss->cur_unsatisfiable;
|
||||||
ss->latest_decision = pkg;
|
ss->latest_decision = pkg;
|
||||||
|
|
||||||
if (flags & APK_PKGSTF_BRANCH) {
|
if (flags & APK_PKGSTF_BRANCH) {
|
||||||
ss->topology_position = pkg->topology_sort;
|
ss->topology_position = pkg->topology_sort;
|
||||||
dbg_printf("push_decision: adding new BRANCH at topology_position %d\n",
|
dbg_printf("push_decision: adding new BRANCH at topology_position %d\n",
|
||||||
|
@ -256,14 +280,13 @@ static int push_decision(struct apk_solver_state *ss, struct apk_package *pkg,
|
||||||
} else
|
} else
|
||||||
ps->flags |= APK_PKGSTF_ALT_BRANCH;
|
ps->flags |= APK_PKGSTF_ALT_BRANCH;
|
||||||
|
|
||||||
return apply_decision(ss, pkg, ps);
|
apply_decision(ss, pkg, ps);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int next_branch(struct apk_solver_state *ss)
|
static int next_branch(struct apk_solver_state *ss)
|
||||||
{
|
{
|
||||||
struct apk_package *pkg;
|
struct apk_package *pkg;
|
||||||
struct apk_package_state *ps;
|
struct apk_package_state *ps;
|
||||||
int r;
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
pkg = ss->latest_decision;
|
pkg = ss->latest_decision;
|
||||||
|
@ -273,9 +296,10 @@ static int next_branch(struct apk_solver_state *ss)
|
||||||
if (ps->flags & APK_PKGSTF_ALT_BRANCH) {
|
if (ps->flags & APK_PKGSTF_ALT_BRANCH) {
|
||||||
pkg = ps->backtrack;
|
pkg = ps->backtrack;
|
||||||
ss->latest_decision = pkg;
|
ss->latest_decision = pkg;
|
||||||
if (pkg == NULL) /* at root, can't back track */
|
if (pkg == NULL) {
|
||||||
|
dbg_printf("next_branch: no more branches\n");
|
||||||
return 1;
|
return 1;
|
||||||
ss->topology_position = pkg->topology_sort;
|
}
|
||||||
dbg_printf("next_branch: undo decision at topology_position %d\n",
|
dbg_printf("next_branch: undo decision at topology_position %d\n",
|
||||||
ss->topology_position);
|
ss->topology_position);
|
||||||
} else {
|
} else {
|
||||||
|
@ -285,21 +309,28 @@ static int next_branch(struct apk_solver_state *ss)
|
||||||
ps->flags |= APK_PKGSTF_ALT_BRANCH;
|
ps->flags |= APK_PKGSTF_ALT_BRANCH;
|
||||||
ps->flags ^= APK_PKGSTF_INSTALL;
|
ps->flags ^= APK_PKGSTF_INSTALL;
|
||||||
|
|
||||||
r = apply_decision(ss, pkg, ps);
|
apply_decision(ss, pkg, ps);
|
||||||
if (r == 0 /*|| report_errors */)
|
return 0;
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int apply_constraint(struct apk_solver_state *ss, struct apk_dependency *dep)
|
static void apply_constraint(struct apk_solver_state *ss, struct apk_dependency *dep)
|
||||||
{
|
{
|
||||||
struct apk_name *name = dep->name;
|
struct apk_name *name = dep->name;
|
||||||
struct apk_name_state *ns = &ss->name_state[name->id];
|
struct apk_name_state *ns = &ss->name_state[name->id];
|
||||||
struct apk_package *pkg_best = NULL;
|
int i;
|
||||||
int i, options = 0;
|
|
||||||
|
|
||||||
prepare_name(ss, name, ns);
|
prepare_name(ss, name, ns);
|
||||||
|
|
||||||
|
if (ns->flags & APK_NAMESTF_LOCKED) {
|
||||||
|
dbg_printf(PKG_VER_FMT " selected already for %s\n",
|
||||||
|
PKG_VER_PRINTF(ns->chosen), dep->name->name);
|
||||||
|
if (!apk_dep_is_satisfied(dep, ns->chosen))
|
||||||
|
ss->cur_unsatisfiable += 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < name->pkgs->num; i++) {
|
for (i = 0; i < name->pkgs->num; i++) {
|
||||||
struct apk_package *pkg0 = name->pkgs->item[i];
|
struct apk_package *pkg0 = name->pkgs->item[i];
|
||||||
struct apk_package_state *ps0 = &ss->pkg_state[pkg0->topology_sort];
|
struct apk_package_state *ps0 = &ss->pkg_state[pkg0->topology_sort];
|
||||||
|
@ -313,54 +344,23 @@ static int apply_constraint(struct apk_solver_state *ss, struct apk_dependency *
|
||||||
PKG_VER_PRINTF(pkg0),
|
PKG_VER_PRINTF(pkg0),
|
||||||
ps0->conflicts);
|
ps0->conflicts);
|
||||||
}
|
}
|
||||||
if (ps0->conflicts == 0) {
|
|
||||||
options++;
|
|
||||||
if (pkg_best == NULL ||
|
|
||||||
pkg0->topology_sort > pkg_best->topology_sort)
|
|
||||||
pkg_best = pkg0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ns->requirers++;
|
update_name_state(ss, name, ns,
|
||||||
if (!list_hashed(&ns->unsolved_list) && ns->chosen != NULL) {
|
(dep->result_mask != APK_DEPMASK_CONFLICT) ? 1 : 0);
|
||||||
dbg_printf(PKG_VER_FMT " selected already for %s\n", PKG_VER_PRINTF(ns->chosen),
|
|
||||||
dep->name->name);
|
|
||||||
return !apk_dep_is_satisfied(dep, ns->chosen);
|
|
||||||
}
|
|
||||||
|
|
||||||
ns->chosen = pkg_best;
|
|
||||||
if (options == 0) {
|
|
||||||
/* we conflicted with all possible options */
|
|
||||||
if (list_hashed(&ns->unsolved_list)) {
|
|
||||||
dbg_printf("%s: deleting unsolved (unable to satisfy)\n",
|
|
||||||
name->name);
|
|
||||||
list_del(&ns->unsolved_list);
|
|
||||||
list_init(&ns->unsolved_list);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (options == 1) {
|
|
||||||
/* we can short circuit to select the only option
|
|
||||||
* possible */
|
|
||||||
return push_decision(ss, pkg_best, APK_PKGSTF_INSTALL);
|
|
||||||
}
|
|
||||||
/* multiple ways to satisfy the requirement */
|
|
||||||
if (ns->requirers == 1) {
|
|
||||||
list_init(&ns->unsolved_list);
|
|
||||||
list_add(&ns->unsolved_list, &ss->unsolved_list_head);
|
|
||||||
dbg_printf("%s: adding to unsolved list (%d options)\n",
|
|
||||||
name->name, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int undo_constraint(struct apk_solver_state *ss, struct apk_dependency *dep)
|
static void undo_constraint(struct apk_solver_state *ss, struct apk_dependency *dep)
|
||||||
{
|
{
|
||||||
struct apk_name *name = dep->name;
|
struct apk_name *name = dep->name;
|
||||||
struct apk_name_state *ns = &ss->name_state[name->id];
|
struct apk_name_state *ns = &ss->name_state[name->id];
|
||||||
struct apk_package *pkg_best = NULL;
|
int i;
|
||||||
int i, had_options = 0, options = 0;
|
|
||||||
|
if (ns->flags & APK_NAMESTF_LOCKED) {
|
||||||
|
dbg_printf(PKG_VER_FMT " selected already for %s\n",
|
||||||
|
PKG_VER_PRINTF(ns->chosen), dep->name->name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < name->pkgs->num; i++) {
|
for (i = 0; i < name->pkgs->num; i++) {
|
||||||
struct apk_package *pkg0 = name->pkgs->item[i];
|
struct apk_package *pkg0 = name->pkgs->item[i];
|
||||||
|
@ -369,78 +369,46 @@ static int undo_constraint(struct apk_solver_state *ss, struct apk_dependency *d
|
||||||
if (pkg0->topology_sort >= ss->topology_position)
|
if (pkg0->topology_sort >= ss->topology_position)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (ps0->conflicts == 0)
|
|
||||||
had_options++;
|
|
||||||
if (!apk_dep_is_satisfied(dep, pkg0)) {
|
if (!apk_dep_is_satisfied(dep, pkg0)) {
|
||||||
ps0->conflicts--;
|
ps0->conflicts--;
|
||||||
dbg_printf(PKG_VER_FMT ": conflicts-- -> %d\n",
|
dbg_printf(PKG_VER_FMT ": conflicts-- -> %d\n",
|
||||||
PKG_VER_PRINTF(pkg0),
|
PKG_VER_PRINTF(pkg0),
|
||||||
ps0->conflicts);
|
ps0->conflicts);
|
||||||
}
|
}
|
||||||
if (ps0->conflicts == 0) {
|
|
||||||
options++;
|
|
||||||
if (pkg_best == NULL ||
|
|
||||||
pkg0->topology_sort > pkg_best->topology_sort)
|
|
||||||
pkg_best = pkg0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ns->requirers--;
|
update_name_state(ss, name, ns,
|
||||||
|
(dep->result_mask != APK_DEPMASK_CONFLICT) ? -1 : 0);
|
||||||
if (ns->requirers == 0) {
|
|
||||||
if (list_hashed(&ns->unsolved_list)) {
|
|
||||||
list_del(&ns->unsolved_list);
|
|
||||||
list_init(&ns->unsolved_list);
|
|
||||||
ns->chosen = NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ns->chosen = pkg_best;
|
|
||||||
if (had_options == 0 && options != 0) {
|
|
||||||
if (!list_hashed(&ns->unsolved_list)) {
|
|
||||||
list_add(&ns->unsolved_list, &ss->unsolved_list_head);
|
|
||||||
dbg_printf("%s: adding back to unsolved list (with %d options, %d requirers)\n",
|
|
||||||
name->name, options, ns->requirers);
|
|
||||||
} else {
|
|
||||||
ns->chosen = NULL;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int expand_branch(struct apk_solver_state *ss)
|
static int expand_branch(struct apk_solver_state *ss)
|
||||||
{
|
{
|
||||||
int r;
|
struct apk_name_state *ns;
|
||||||
|
struct apk_package *pkg0 = NULL;
|
||||||
|
|
||||||
while (1) {
|
/* FIXME: change unsolved_list to a priority queue */
|
||||||
struct apk_name_state *ns;
|
list_for_each_entry(ns, &ss->unsolved_list_head, unsolved_list) {
|
||||||
struct apk_package *pkg0 = NULL;
|
if (pkg0 == NULL ||
|
||||||
|
ns->chosen->topology_sort > pkg0->topology_sort)
|
||||||
/* FIXME: change unsolved_list to a priority queue */
|
pkg0 = ns->chosen;
|
||||||
list_for_each_entry(ns, &ss->unsolved_list_head, unsolved_list) {
|
|
||||||
if (pkg0 == NULL ||
|
|
||||||
ns->chosen->topology_sort > pkg0->topology_sort)
|
|
||||||
pkg0 = ns->chosen;
|
|
||||||
}
|
|
||||||
if (pkg0 == NULL) {
|
|
||||||
dbg_printf("expand_branch: list is empty\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* someone needs to provide this name -- find next eligible
|
|
||||||
* provider candidate */
|
|
||||||
ns = &ss->name_state[pkg0->name->id];
|
|
||||||
dbg_printf("expand_branch: %s %d\n", pkg0->name->name, pkg0->topology_sort);
|
|
||||||
|
|
||||||
r = push_decision(ss, pkg0,
|
|
||||||
is_pkg_preferred(ss, pkg0) ?
|
|
||||||
(APK_PKGSTF_INSTALL | APK_PKGSTF_BRANCH) :
|
|
||||||
(APK_PKGSTF_NOINSTALL | APK_PKGSTF_BRANCH));
|
|
||||||
|
|
||||||
if (/*no_error_reporting &&*/ r)
|
|
||||||
return r;
|
|
||||||
}
|
}
|
||||||
|
if (pkg0 == NULL) {
|
||||||
|
dbg_printf("expand_branch: list is empty (%d unsatisfied)\n",
|
||||||
|
ss->cur_unsatisfiable);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* someone needs to provide this name -- find next eligible
|
||||||
|
* provider candidate */
|
||||||
|
ns = &ss->name_state[pkg0->name->id];
|
||||||
|
dbg_printf("expand_branch: %s %d\n", pkg0->name->name, pkg0->topology_sort);
|
||||||
|
|
||||||
|
push_decision(ss, pkg0, get_pkg_expansion_flags(ss, pkg0));
|
||||||
|
#if 0
|
||||||
|
is_pkg_preferred(ss, pkg0) ?
|
||||||
|
(APK_PKGSTF_INSTALL | APK_PKGSTF_BRANCH) :
|
||||||
|
(APK_PKGSTF_NOINSTALL | APK_PKGSTF_BRANCH));
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -457,7 +425,8 @@ static void record_solution(struct apk_solver_state *ss)
|
||||||
pkg = ss->latest_decision;
|
pkg = ss->latest_decision;
|
||||||
while (pkg != NULL) {
|
while (pkg != NULL) {
|
||||||
ps = &ss->pkg_state[pkg->topology_sort];
|
ps = &ss->pkg_state[pkg->topology_sort];
|
||||||
if (ps->flags & APK_PKGSTF_INSTALL)
|
if ((ps->flags & APK_PKGSTF_INSTALL) &&
|
||||||
|
(ps->conflicts == 0))
|
||||||
ss->best_solution->item[i++] = pkg;
|
ss->best_solution->item[i++] = pkg;
|
||||||
|
|
||||||
dbg_printf("record_solution: " PKG_VER_FMT ": %sINSTALL\n",
|
dbg_printf("record_solution: " PKG_VER_FMT ": %sINSTALL\n",
|
||||||
|
@ -466,10 +435,20 @@ static void record_solution(struct apk_solver_state *ss)
|
||||||
|
|
||||||
pkg = ps->backtrack;
|
pkg = ps->backtrack;
|
||||||
}
|
}
|
||||||
|
apk_package_array_resize(&ss->best_solution, i);
|
||||||
|
ss->best_unsatisfiable = ss->cur_unsatisfiable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compare_package_name(const void *p1, const void *p2)
|
||||||
|
{
|
||||||
|
const struct apk_package **c1 = (const struct apk_package **) p1;
|
||||||
|
const struct apk_package **c2 = (const struct apk_package **) p2;
|
||||||
|
|
||||||
|
return strcmp((*c1)->name->name, (*c2)->name->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
int apk_solver_solve(struct apk_database *db, struct apk_dependency_array *world,
|
int apk_solver_solve(struct apk_database *db, struct apk_dependency_array *world,
|
||||||
struct apk_package_array **solution)
|
struct apk_package_array **solution, int allow_errors)
|
||||||
{
|
{
|
||||||
struct apk_solver_state *ss;
|
struct apk_solver_state *ss;
|
||||||
int r;
|
int r;
|
||||||
|
@ -477,28 +456,45 @@ int apk_solver_solve(struct apk_database *db, struct apk_dependency_array *world
|
||||||
ss = calloc(1, sizeof(struct apk_solver_state));
|
ss = calloc(1, sizeof(struct apk_solver_state));
|
||||||
ss->db = db;
|
ss->db = db;
|
||||||
ss->topology_position = -1;
|
ss->topology_position = -1;
|
||||||
|
ss->best_unsatisfiable = -1;
|
||||||
|
ss->allow_errors = allow_errors;
|
||||||
list_init(&ss->unsolved_list_head);
|
list_init(&ss->unsolved_list_head);
|
||||||
ss->pkg_state = calloc(db->available.packages.num_items+1, sizeof(struct apk_package_state));
|
ss->pkg_state = calloc(db->available.packages.num_items+1, sizeof(struct apk_package_state));
|
||||||
ss->name_state = calloc(db->available.names.num_items+1, sizeof(struct apk_name_state));
|
ss->name_state = calloc(db->available.names.num_items+1, sizeof(struct apk_name_state));
|
||||||
|
|
||||||
r = foreach_dependency(ss, world, apply_constraint);
|
foreach_dependency(ss, world, apply_constraint);
|
||||||
while (r == 0) {
|
do {
|
||||||
if (expand_branch(ss) == 0) {
|
if (ss->allow_errors || ss->cur_unsatisfiable < ss->best_unsatisfiable) {
|
||||||
/* found solution - it is optimal because we permutate
|
r = expand_branch(ss);
|
||||||
* each preferred local option first, and permutations
|
if (r) {
|
||||||
* happen in topologally sorted order. */
|
if (ss->cur_unsatisfiable == 0) {
|
||||||
break;
|
/* found solution - it is optimal because we permutate
|
||||||
|
* each preferred local option first, and permutations
|
||||||
|
* happen in topologally sorted order. */
|
||||||
|
r = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ss->cur_unsatisfiable < ss->best_unsatisfiable)
|
||||||
|
record_solution(ss);
|
||||||
|
r = next_branch(ss);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r = next_branch(ss);
|
||||||
}
|
}
|
||||||
|
} while (r == 0);
|
||||||
/* conflicting constraints -- backtrack */
|
|
||||||
r = next_branch(ss);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* collect packages */
|
/* collect packages */
|
||||||
if (r == 0) {
|
if (r == 0 && ss->cur_unsatisfiable == 0) {
|
||||||
record_solution(ss);
|
record_solution(ss);
|
||||||
*solution = ss->best_solution;
|
*solution = ss->best_solution;
|
||||||
}
|
r = 0;
|
||||||
|
} else if (ss->allow_errors) {
|
||||||
|
*solution = ss->best_solution;
|
||||||
|
qsort(ss->best_solution->item, ss->best_solution->num,
|
||||||
|
sizeof(struct apk_package *), compare_package_name);
|
||||||
|
r = ss->best_unsatisfiable;
|
||||||
|
} else
|
||||||
|
r = -1;
|
||||||
|
|
||||||
free(ss->name_state);
|
free(ss->name_state);
|
||||||
free(ss->pkg_state);
|
free(ss->pkg_state);
|
||||||
|
|
76
src/test.c
76
src/test.c
|
@ -90,13 +90,67 @@ static inline void print_change(struct apk_package *oldpkg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void print_dep_errors(char *label, struct apk_dependency_array *deps,
|
||||||
|
struct apk_package **name_pkgs)
|
||||||
|
{
|
||||||
|
int i, print_label = 1;
|
||||||
|
char buf[256];
|
||||||
|
apk_blob_t p = APK_BLOB_BUF(buf);
|
||||||
|
|
||||||
|
for (i = 0; i < deps->num; i++) {
|
||||||
|
struct apk_dependency *dep = &deps->item[i];
|
||||||
|
struct apk_package *pkg = name_pkgs[dep->name->id];
|
||||||
|
|
||||||
|
if (pkg != NULL && apk_dep_is_satisfied(dep, pkg))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (print_label) {
|
||||||
|
print_label = 0;
|
||||||
|
printf("%s: ", label);
|
||||||
|
} else {
|
||||||
|
printf(" ");
|
||||||
|
}
|
||||||
|
apk_blob_push_dep(&p, dep);
|
||||||
|
p = apk_blob_pushed(APK_BLOB_BUF(buf), p);
|
||||||
|
fwrite(p.ptr, p.len, 1, stdout);
|
||||||
|
}
|
||||||
|
if (!print_label)
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_errors_in_solution(struct apk_database *db, int unsatisfiable,
|
||||||
|
struct apk_package_array *solution)
|
||||||
|
{
|
||||||
|
struct apk_package **name_pkg;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
printf("%d unsatisfiable dependencies (solution with %d names)\n",
|
||||||
|
unsatisfiable, solution->num);
|
||||||
|
|
||||||
|
name_pkg = alloca(sizeof(struct apk_package*) * db->available.names.num_items);
|
||||||
|
memset(name_pkg, 0, sizeof(struct apk_package*) * db->available.names.num_items);
|
||||||
|
for (i = 0; i < solution->num; i++) {
|
||||||
|
struct apk_package *pkg = solution->item[i];
|
||||||
|
name_pkg[pkg->name->id] = pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_dep_errors("world", db->world, name_pkg);
|
||||||
|
for (i = 0; i < solution->num; i++) {
|
||||||
|
struct apk_package *pkg = solution->item[i];
|
||||||
|
char pkgtext[256];
|
||||||
|
snprintf(pkgtext, sizeof(pkgtext), PKG_VER_FMT, PKG_VER_PRINTF(solution->item[i]));
|
||||||
|
print_dep_errors(pkgtext, pkg->depends, name_pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static int test_main(void *pctx, struct apk_database *db, int argc, char **argv)
|
static int test_main(void *pctx, struct apk_database *db, int argc, char **argv)
|
||||||
{
|
{
|
||||||
struct test_ctx *ctx = (struct test_ctx *) pctx;
|
struct test_ctx *ctx = (struct test_ctx *) pctx;
|
||||||
struct apk_bstream *bs;
|
struct apk_bstream *bs;
|
||||||
struct apk_package_array *solution = NULL;
|
struct apk_package_array *solution = NULL;
|
||||||
struct apk_changeset changeset;
|
struct apk_changeset changeset;
|
||||||
int i;
|
int i, r;
|
||||||
|
|
||||||
if (argc != 1)
|
if (argc != 1)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@ -126,16 +180,18 @@ static int test_main(void *pctx, struct apk_database *db, int argc, char **argv)
|
||||||
|
|
||||||
/* run solver */
|
/* run solver */
|
||||||
apk_solver_sort(db);
|
apk_solver_sort(db);
|
||||||
if (apk_solver_solve(db, db->world, &solution) != 0)
|
r = apk_solver_solve(db, db->world, &solution, TRUE);
|
||||||
return 1;
|
if (r == 0) {
|
||||||
|
memset(&changeset, 0, sizeof(changeset));
|
||||||
memset(&changeset, 0, sizeof(changeset));
|
if (apk_solver_generate_changeset(db, solution, &changeset) == 0) {
|
||||||
if (apk_solver_generate_changeset(db, solution, &changeset) == 0) {
|
/* dump changeset */
|
||||||
/* dump changeset */
|
for (i = 0; i < changeset.changes->num; i++) {
|
||||||
for (i = 0; i < changeset.changes->num; i++) {
|
struct apk_change *c = &changeset.changes->item[i];
|
||||||
struct apk_change *c = &changeset.changes->item[i];
|
print_change(c->oldpkg, c->newpkg);
|
||||||
print_change(c->oldpkg, c->newpkg);
|
}
|
||||||
}
|
}
|
||||||
|
} else { /* r >= 1*/
|
||||||
|
print_errors_in_solution(db, r, solution);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository basic.repo a
|
--raw-repository basic.repo
|
||||||
|
a
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository basic.repo --installed basic.installed a
|
--raw-repository basic.repo --installed basic.installed
|
||||||
|
a
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository basic.repo --installed basic.installed -u a
|
--raw-repository basic.repo --installed basic.installed -u
|
||||||
|
a
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository basic.repo --installed basic.installed b
|
--raw-repository basic.repo --installed basic.installed
|
||||||
|
b
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository basic.repo --installed basic.installed2 -a a
|
--raw-repository basic.repo --installed basic.installed2 -a
|
||||||
|
a
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository basic.repo --installed basic.installed2 a
|
--raw-repository basic.repo --installed basic.installed2
|
||||||
|
a
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--no-network --raw-repository basic.repo --installed basic.installed -u a
|
--no-network --raw-repository basic.repo --installed basic.installed -u
|
||||||
|
a
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository complicated1.repo a
|
--raw-repository complicated1.repo
|
||||||
|
a
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository complicated1.repo b
|
--raw-repository complicated1.repo
|
||||||
|
b
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository complicated1.repo c
|
--raw-repository complicated1.repo
|
||||||
|
c
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
--raw-repository complicated1.repo --installed complicated1.installed a
|
--raw-repository complicated1.repo --installed complicated1.installed
|
||||||
|
a
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
3 unsatisfiable dependencies (solution with 3 names)
|
||||||
|
world: d>1.5
|
||||||
|
b-1: d<2.0
|
||||||
|
c-1: d>1.0
|
|
@ -0,0 +1,2 @@
|
||||||
|
--raw-repository complicated1.repo
|
||||||
|
a d>1.5
|
|
@ -0,0 +1,5 @@
|
||||||
|
4 unsatisfiable dependencies (solution with 3 names)
|
||||||
|
world: d<1.5
|
||||||
|
a-3: d>1.5
|
||||||
|
b-1: d<2.0
|
||||||
|
c-1: d>1.0
|
|
@ -0,0 +1,2 @@
|
||||||
|
--raw-repository complicated1.repo
|
||||||
|
a d<1.5
|
|
@ -0,0 +1,3 @@
|
||||||
|
1 unsatisfiable dependencies (solution with 3 names)
|
||||||
|
world: !b
|
||||||
|
a-3: b
|
|
@ -0,0 +1,2 @@
|
||||||
|
--raw-repository complicated1.repo
|
||||||
|
a !b
|
|
@ -0,0 +1,2 @@
|
||||||
|
1 unsatisfiable dependencies (solution with 4 names)
|
||||||
|
world: nonexistant
|
|
@ -0,0 +1,2 @@
|
||||||
|
--raw-repository complicated1.repo
|
||||||
|
a nonexistant
|
|
@ -0,0 +1,4 @@
|
||||||
|
3 unsatisfiable dependencies (solution with 3 names)
|
||||||
|
a-3: d>1.5
|
||||||
|
b-1: d<2.0
|
||||||
|
c-1: d>1.0
|
|
@ -0,0 +1,2 @@
|
||||||
|
--raw-repository complicated1.repo
|
||||||
|
a>2
|
|
@ -5,8 +5,12 @@ APK_TEST=../src/apk_test
|
||||||
fail=0
|
fail=0
|
||||||
for test in *.test; do
|
for test in *.test; do
|
||||||
bn=$(basename $test .test)
|
bn=$(basename $test .test)
|
||||||
$APK_TEST $(cat $test) &> $bn.got
|
(
|
||||||
if ! cmp $bn.expect $bn.got 2> /dev/null; then
|
read options
|
||||||
|
read world
|
||||||
|
$APK_TEST $options "$world" &> $bn.got
|
||||||
|
) < $bn.test
|
||||||
|
if ! cmp $bn.expect $bn.got &> /dev/null; then
|
||||||
fail=$((fail+1))
|
fail=$((fail+1))
|
||||||
echo "FAIL: $test"
|
echo "FAIL: $test"
|
||||||
diff -ru $bn.expect $bn.got
|
diff -ru $bn.expect $bn.got
|
||||||
|
|
Loading…
Reference in New Issue