Commit b8c44536 authored by Timo Teräs's avatar Timo Teräs

solver: rewrite as deductive solver -- core features

Implementing basic dependency handling, install_if and awareness
of pinning.
parent f292a858
...@@ -25,7 +25,7 @@ apk-objs := apk.o add.o del.o fix.o update.o info.o \ ...@@ -25,7 +25,7 @@ apk-objs := apk.o add.o del.o fix.o update.o info.o \
libapk.so-objs := common.o database.o package.o archive.o \ libapk.so-objs := common.o database.o package.o archive.o \
version.o io.o url.o gunzip.o blob.o hash.o print.o \ version.o io.o url.o gunzip.o blob.o hash.o print.o \
solver.o commit.o solver.o
ifeq ($(TEST),y) ifeq ($(TEST),y)
progs-y += apk-test progs-y += apk-test
......
...@@ -138,6 +138,7 @@ struct apk_database { ...@@ -138,6 +138,7 @@ struct apk_database {
char *cache_remount_dir; char *cache_remount_dir;
apk_blob_t *arch; apk_blob_t *arch;
unsigned int local_repos, available_repos; unsigned int local_repos, available_repos;
unsigned int pending_triggers;
int performing_self_update : 1; int performing_self_update : 1;
int permanent : 1; int permanent : 1;
int compat_newfeatures : 1; int compat_newfeatures : 1;
...@@ -206,7 +207,7 @@ void apk_db_close(struct apk_database *db); ...@@ -206,7 +207,7 @@ void apk_db_close(struct apk_database *db);
int apk_db_write_config(struct apk_database *db); int apk_db_write_config(struct apk_database *db);
int apk_db_permanent(struct apk_database *db); int apk_db_permanent(struct apk_database *db);
int apk_db_check_world(struct apk_database *db, struct apk_dependency_array *world); int apk_db_check_world(struct apk_database *db, struct apk_dependency_array *world);
struct apk_package_array *apk_db_get_pending_triggers(struct apk_database *db); int apk_db_fire_triggers(struct apk_database *db);
struct apk_package *apk_db_pkg_add(struct apk_database *db, struct apk_package *pkg); struct apk_package *apk_db_pkg_add(struct apk_database *db, struct apk_package *pkg);
struct apk_package *apk_db_get_pkg(struct apk_database *db, struct apk_checksum *csum); struct apk_package *apk_db_get_pkg(struct apk_database *db, struct apk_checksum *csum);
...@@ -222,6 +223,7 @@ struct apk_repository *apk_db_select_repo(struct apk_database *db, ...@@ -222,6 +223,7 @@ struct apk_repository *apk_db_select_repo(struct apk_database *db,
int apk_repo_format_filename(char *buf, size_t len, int apk_repo_format_filename(char *buf, size_t len,
const char *repourl, apk_blob_t *arch, const char *repourl, apk_blob_t *arch,
const char *pkgfile); const char *pkgfile);
unsigned int apk_db_get_pinning_mask_repos(struct apk_database *db, unsigned short pinning_mask);
int apk_db_cache_active(struct apk_database *db); int apk_db_cache_active(struct apk_database *db);
void apk_cache_format_index(apk_blob_t to, struct apk_repository *repo); void apk_cache_format_index(apk_blob_t to, struct apk_repository *repo);
......
...@@ -14,8 +14,9 @@ ...@@ -14,8 +14,9 @@
#include <string.h> #include <string.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define BIT(x) (1 << (x)) #define BIT(x) (1 << (x))
#define max(a, b) ((a) > (b) ? (a) : (b))
#ifndef TRUE #ifndef TRUE
#define TRUE 1 #define TRUE 1
...@@ -259,7 +260,7 @@ static inline void list_add_tail(struct list_head *new, struct list_head *head) ...@@ -259,7 +260,7 @@ static inline void list_add_tail(struct list_head *new, struct list_head *head)
__list_add(new, head->prev, head); __list_add(new, head->prev, head);
} }
static inline void __list_del(struct list_head * prev, struct list_head * next) static inline void __list_del(struct list_head *prev, struct list_head *next)
{ {
next->prev = prev; next->prev = prev;
prev->next = next; prev->next = next;
...@@ -272,6 +273,13 @@ static inline void list_del(struct list_head *entry) ...@@ -272,6 +273,13 @@ static inline void list_del(struct list_head *entry)
entry->prev = LIST_POISON2; entry->prev = LIST_POISON2;
} }
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = NULL;
entry->prev = NULL;
}
static inline int list_hashed(const struct list_head *n) static inline int list_hashed(const struct list_head *n)
{ {
return n->next != n && n->next != NULL; return n->next != n && n->next != NULL;
...@@ -282,8 +290,17 @@ static inline int list_empty(const struct list_head *n) ...@@ -282,8 +290,17 @@ static inline int list_empty(const struct list_head *n)
return n->next == n; return n->next == n;
} }
static inline struct list_head *__list_pop(struct list_head *head)
{
struct list_head *n = head->next;
list_del_init(n);
return n;
}
#define list_entry(ptr, type, member) container_of(ptr,type,member) #define list_entry(ptr, type, member) container_of(ptr,type,member)
#define list_pop(head, type, member) container_of(__list_pop(head),type,member)
#define list_for_each(pos, head) \ #define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next) for (pos = (head)->next; pos != (head); pos = pos->next)
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "apk_version.h" #include "apk_version.h"
#include "apk_hash.h" #include "apk_hash.h"
#include "apk_io.h" #include "apk_io.h"
#include "apk_solver_data.h"
struct apk_database; struct apk_database;
struct apk_name; struct apk_name;
...@@ -60,7 +61,6 @@ struct apk_sign_ctx { ...@@ -60,7 +61,6 @@ struct apk_sign_ctx {
struct apk_dependency { struct apk_dependency {
struct apk_name *name; struct apk_name *name;
apk_blob_t *version; apk_blob_t *version;
unsigned solver_state : 22;
unsigned repository_tag : 6; unsigned repository_tag : 6;
unsigned conflict : 1; unsigned conflict : 1;
unsigned result_mask : 3; unsigned result_mask : 3;
...@@ -86,6 +86,7 @@ struct apk_installed_package { ...@@ -86,6 +86,7 @@ struct apk_installed_package {
struct apk_package { struct apk_package {
apk_hash_node hash_node; apk_hash_node hash_node;
union { union {
struct apk_solver_package_state ss;
int state_int; int state_int;
void *state_ptr; void *state_ptr;
}; };
...@@ -98,7 +99,6 @@ struct apk_package { ...@@ -98,7 +99,6 @@ struct apk_package {
struct apk_dependency_array *depends, *install_if, *provides; struct apk_dependency_array *depends, *install_if, *provides;
size_t installed_size, size; size_t installed_size, size;
time_t build_time; time_t build_time;
unsigned int topology_hard;
unsigned repos : APK_MAX_REPOS; unsigned repos : APK_MAX_REPOS;
struct apk_checksum csum; struct apk_checksum csum;
}; };
......
/* apk_solver.h - Alpine Package Keeper (APK) /* apk_solver.h - Alpine Package Keeper (APK)
* *
* Copyright (C) 2005-2008 Natanael Copa <n@tanael.org> * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
* Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi> * Copyright (C) 2008-2013 Timo Teräs <timo.teras@iki.fi>
* All rights reserved. * All rights reserved.
* *
* This program is free software; you can redistribute it and/or modify it * This program is free software; you can redistribute it and/or modify it
...@@ -15,22 +15,18 @@ ...@@ -15,22 +15,18 @@
struct apk_name; struct apk_name;
struct apk_package; struct apk_package;
struct apk_solution_entry {
struct apk_package *pkg;
unsigned short repository_tag : 15;
unsigned reinstall : 1;
};
APK_ARRAY(apk_solution_array, struct apk_solution_entry);
struct apk_change { struct apk_change {
struct apk_package *oldpkg; struct apk_package *old_pkg;
struct apk_package *newpkg; struct apk_package *new_pkg;
unsigned short repository_tag : 15; unsigned old_repository_tag : 15;
unsigned new_repository_tag : 15;
unsigned reinstall : 1; unsigned reinstall : 1;
}; };
APK_ARRAY(apk_change_array, struct apk_change); APK_ARRAY(apk_change_array, struct apk_change);
struct apk_changeset { struct apk_changeset {
int num_install, num_remove, num_adjust;
int num_total_changes;
struct apk_change_array *changes; struct apk_change_array *changes;
}; };
...@@ -44,17 +40,16 @@ void apk_solver_set_name_flags(struct apk_name *name, ...@@ -44,17 +40,16 @@ void apk_solver_set_name_flags(struct apk_name *name,
int apk_solver_solve(struct apk_database *db, int apk_solver_solve(struct apk_database *db,
unsigned short solver_flags, unsigned short solver_flags,
struct apk_dependency_array *world, struct apk_dependency_array *world,
struct apk_solution_array **solution,
struct apk_changeset *changeset); struct apk_changeset *changeset);
int apk_solver_commit_changeset(struct apk_database *db, int apk_solver_commit_changeset(struct apk_database *db,
struct apk_changeset *changeset, struct apk_changeset *changeset,
struct apk_dependency_array *world); struct apk_dependency_array *world);
void apk_solver_print_errors(struct apk_database *db, void apk_solver_print_errors(struct apk_database *db,
struct apk_solution_array *solution, struct apk_changeset *changeset,
struct apk_dependency_array *world, struct apk_dependency_array *world);
int unsatisfiable);
int apk_solver_commit(struct apk_database *db, int apk_solver_commit(struct apk_database *db, unsigned short solver_flags,
unsigned short solver_flags,
struct apk_dependency_array *world); struct apk_dependency_array *world);
#endif #endif
......
...@@ -16,39 +16,33 @@ ...@@ -16,39 +16,33 @@
#include "apk_defines.h" #include "apk_defines.h"
#include "apk_provider_data.h" #include "apk_provider_data.h"
struct apk_score {
uint32_t unsatisfied;
uint32_t non_preferred_actions;
uint32_t non_preferred_pinnings;
uint32_t preference;
};
struct apk_solver_name_state { struct apk_solver_name_state {
/* dynamic */ struct list_head dirty_list;
struct list_head unsolved_list; struct list_head unresolved_list;
struct apk_score minimum_penalty;
struct apk_provider chosen; struct apk_provider chosen;
unsigned int last_touched_decision; unsigned int preferred_pinning;
unsigned short requirers;
unsigned short install_ifs;
unsigned short preferred_pinning;
unsigned short locked;
/* one time prepare/finish flags */ unsigned short requirers;
unsigned solver_flags_local : 4; unsigned short merge_index;
unsigned solver_flags_inheritable : 4; unsigned short max_dep_chain;
unsigned seen : 1;
unsigned decision_counted : 1;
unsigned originally_installed : 1;
unsigned has_available_pkgs : 1;
unsigned in_changeset : 1;
unsigned in_world_dependency : 1; unsigned in_world_dependency : 1;
unsigned in_changeset : 1;
unsigned reevaluate_deps : 1;
unsigned reevaluate_iif : 1;
unsigned locked : 1;
unsigned has_iif : 1;
unsigned has_options : 1;
};
/* dynamic state flags */ struct apk_solver_package_state {
unsigned none_excluded : 1; unsigned short conflicts;
unsigned name_touched : 1; unsigned seen : 1;
unsigned preferred_chosen : 1; unsigned available : 1;
unsigned in_changeset : 1;
unsigned iif_triggered : 1;
unsigned max_dep_chain : 10;
}; };
#endif #endif
...@@ -34,7 +34,7 @@ static int cache_download(struct apk_database *db) ...@@ -34,7 +34,7 @@ static int cache_download(struct apk_database *db)
char item[PATH_MAX], cacheitem[PATH_MAX]; char item[PATH_MAX], cacheitem[PATH_MAX];
int i, r, ret = 0; int i, r, ret = 0;
r = apk_solver_solve(db, 0, db->world, NULL, &changeset); r = apk_solver_solve(db, 0, db->world, &changeset);
if (r < 0) { if (r < 0) {
apk_error("Unable to select packages. Run apk fix."); apk_error("Unable to select packages. Run apk fix.");
return r; return r;
...@@ -42,9 +42,8 @@ static int cache_download(struct apk_database *db) ...@@ -42,9 +42,8 @@ static int cache_download(struct apk_database *db)
for (i = 0; i < changeset.changes->num; i++) { for (i = 0; i < changeset.changes->num; i++) {
change = &changeset.changes->item[i]; change = &changeset.changes->item[i];
pkg = change->newpkg; pkg = change->new_pkg;
if ((pkg == NULL) || (pkg->repos & db->local_repos))
if (pkg->repos & db->local_repos)
continue; continue;
repo = apk_db_select_repo(db, pkg); repo = apk_db_select_repo(db, pkg);
......
/* commit.c - Alpine Package Keeper (APK)
* Apply solver calculated changes to database.
*
* Copyright (C) 2008-2013 Timo Teräs <timo.teras@iki.fi>
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation. See http://www.gnu.org/ for details.
*/
#include <stdint.h>
#include <unistd.h>
#include "apk_defines.h"
#include "apk_database.h"
#include "apk_package.h"
#include "apk_solver.h"
#include "apk_print.h"
static inline int pkg_available(struct apk_database *db, struct apk_package *pkg)
{
if (pkg->repos & db->available_repos)
return TRUE;
return FALSE;
}
static int print_change(struct apk_database *db, struct apk_change *change,
int cur, int total)
{
struct apk_name *name;
struct apk_package *oldpkg = change->old_pkg;
struct apk_package *newpkg = change->new_pkg;
const char *msg = NULL;
char status[32], n[512], *nameptr;
apk_blob_t *oneversion = NULL;
int r;
snprintf(status, sizeof(status), "(%i/%i)", cur+1, total);
status[sizeof(status) - 1] = 0;
name = newpkg ? newpkg->name : oldpkg->name;
if (change->new_repository_tag > 0) {
snprintf(n, sizeof(n), "%s@" BLOB_FMT,
name->name,
BLOB_PRINTF(*db->repo_tags[change->new_repository_tag].name));
n[sizeof(n) - 1] = 0;
nameptr = n;
} else {
nameptr = name->name;
}
if (oldpkg == NULL) {
msg = "Installing";
oneversion = newpkg->version;
} else if (newpkg == NULL) {
msg = "Purging";
oneversion = oldpkg->version;
} else if (newpkg == oldpkg) {
if (change->reinstall) {
if (pkg_available(db, newpkg))
msg = "Re-installing";
else
msg = "[APK unavailable, skipped] Re-installing";
} else if (change->old_repository_tag != change->new_repository_tag) {
msg = "Updating pinning";
}
oneversion = newpkg->version;
} else {
r = apk_pkg_version_compare(newpkg, oldpkg);
switch (r) {
case APK_VERSION_LESS:
msg = "Downgrading";
break;
case APK_VERSION_EQUAL:
msg = "Replacing";
break;
case APK_VERSION_GREATER:
msg = "Upgrading";
break;
}
}
if (msg == NULL)
return FALSE;
if (oneversion) {
apk_message("%s %s %s (" BLOB_FMT ")",
status, msg, nameptr,
BLOB_PRINTF(*oneversion));
} else {
apk_message("%s %s %s (" BLOB_FMT " -> " BLOB_FMT ")",
status, msg, nameptr,
BLOB_PRINTF(*oldpkg->version),
BLOB_PRINTF(*newpkg->version));
}
return TRUE;
}
struct apk_stats {
unsigned int changes;
unsigned int bytes;
unsigned int packages;
};
static void count_change(struct apk_change *change, struct apk_stats *stats)
{
if (change->new_pkg != change->old_pkg) {
if (change->new_pkg != NULL) {
stats->bytes += change->new_pkg->installed_size;
stats->packages ++;
}
if (change->old_pkg != NULL)
stats->packages ++;
stats->changes++;
} else if (change->reinstall || change->new_repository_tag != change->old_repository_tag) {
stats->changes++;
}
}
static void draw_progress(int percent)
{
const int bar_width = apk_get_screen_width() - 7;
int i;
fprintf(stderr, "\e7%3i%% [", percent);
for (i = 0; i < bar_width * percent / 100; i++)
fputc('#', stderr);
for (; i < bar_width; i++)
fputc(' ', stderr);
fputc(']', stderr);
fflush(stderr);
fputs("\e8\e[0K", stderr);
}
struct progress {
struct apk_stats done;
struct apk_stats total;
struct apk_package *pkg;
size_t percent;
int progress_fd;
};
static void update_progress(struct progress *prog, size_t percent, int force)
{
if (prog->percent == percent && !force)
return;
prog->percent = percent;
if (apk_flags & APK_PROGRESS)
draw_progress(percent);
if (prog->progress_fd != 0) {
char buf[8];
size_t n = snprintf(buf, sizeof(buf), "%zu\n", percent);
write(prog->progress_fd, buf, n);
}
}
static void progress_cb(void *ctx, size_t pkg_percent)
{
struct progress *prog = (struct progress *) ctx;
size_t partial = 0, percent;
if (prog->pkg != NULL)
partial = muldiv(pkg_percent, prog->pkg->installed_size, APK_PROGRESS_SCALE);
percent = muldiv(100, prog->done.bytes + prog->done.packages + partial,
prog->total.bytes + prog->total.packages);
update_progress(prog, percent, pkg_percent == 0);
}
static int dump_packages(struct apk_changeset *changeset,
int (*cmp)(struct apk_change *change),
const char *msg)
{
struct apk_change *change;
struct apk_name *name;
struct apk_indent indent = { .indent = 2 };
int match = 0, i;
for (i = 0; i < changeset->changes->num; i++) {
change = &changeset->changes->item[i];
if (!cmp(change))
continue;
if (match == 0)
printf("%s:\n", msg);
if (change->new_pkg != NULL)
name = change->new_pkg->name;
else
name = change->old_pkg->name;
apk_print_indented(&indent, APK_BLOB_STR(name->name));
match++;
}
if (match)
printf("\n");
return match;
}
static int cmp_remove(struct apk_change *change)
{
return change->new_pkg == NULL;
}
static int cmp_new(struct apk_change *change)
{
return change->old_pkg == NULL;
}
static int cmp_downgrade(struct apk_change *change)
{
if (change->new_pkg == NULL || change->old_pkg == NULL)
return 0;
if (apk_pkg_version_compare(change->new_pkg, change->old_pkg)
& APK_VERSION_LESS)
return 1;
return 0;
}
static int cmp_upgrade(struct apk_change *change)
{
if (change->new_pkg == NULL || change->old_pkg == NULL)
return 0;
/* Count swapping package as upgrade too - this can happen if
* same package version is used after it was rebuilt against
* newer libraries. Basically, different (and probably newer)
* package, but equal version number. */
if ((apk_pkg_version_compare(change->new_pkg, change->old_pkg) &
(APK_VERSION_GREATER | APK_VERSION_EQUAL)) &&
(change->new_pkg != change->old_pkg))
return 1;
return 0;
}
static void run_triggers(struct apk_database *db, struct apk_changeset *changeset)
{
int i;
if (apk_db_fire_triggers(db) == 0)
return;
for (i = 0; i < changeset->changes->num; i++) {
struct apk_package *pkg = changeset->changes->item[i].new_pkg;
struct apk_installed_package *ipkg;
if (pkg == NULL)
continue;
ipkg = pkg->ipkg;
if (ipkg->pending_triggers->num == 0)
continue;
*apk_string_array_add(&ipkg->pending_triggers) = NULL;
apk_ipkg_run_script(ipkg, db, APK_SCRIPT_TRIGGER,
ipkg->pending_triggers->item);
apk_string_array_free(&ipkg->pending_triggers);
}
}
int apk_solver_commit_changeset(struct apk_database *db,
struct apk_changeset *changeset,
struct apk_dependency_array *world)
{
struct progress prog;
struct apk_change *change;
int i, r = 0, size_diff = 0, size_unit;
if (apk_db_check_world(db, world) != 0) {
apk_error("Not committing changes due to missing repository tags. Use --force to override.");