diff options
author | Timo Teräs <timo.teras@iki.fi> | 2013-06-11 14:06:06 +0300 |
---|---|---|
committer | Timo Teräs <timo.teras@iki.fi> | 2013-06-13 18:22:00 +0300 |
commit | b8c44536ca911418fee1c9ab4eecbb913a1ca852 (patch) | |
tree | a89e68b12f4d3daf089c475beeb71c53ffb8cc3a /src/commit.c | |
parent | f292a858677ae0e1af8910ffbd4b338f4b36c18b (diff) | |
download | apk-tools-b8c44536ca911418fee1c9ab4eecbb913a1ca852.tar.gz apk-tools-b8c44536ca911418fee1c9ab4eecbb913a1ca852.tar.bz2 apk-tools-b8c44536ca911418fee1c9ab4eecbb913a1ca852.tar.xz apk-tools-b8c44536ca911418fee1c9ab4eecbb913a1ca852.zip |
solver: rewrite as deductive solver -- core features
Implementing basic dependency handling, install_if and awareness
of pinning.
Diffstat (limited to 'src/commit.c')
-rw-r--r-- | src/commit.c | 547 |
1 files changed, 547 insertions, 0 deletions
diff --git a/src/commit.c b/src/commit.c new file mode 100644 index 0000000..660aab9 --- /dev/null +++ b/src/commit.c @@ -0,0 +1,547 @@ +/* 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."); + return -1; + } + + if (changeset->changes == NULL) + goto all_done; + + /* Count what needs to be done */ + memset(&prog, 0, sizeof(prog)); + prog.progress_fd = db->progress_fd; + for (i = 0; i < changeset->changes->num; i++) { + change = &changeset->changes->item[i]; + count_change(change, &prog.total); + if (change->new_pkg) + size_diff += change->new_pkg->installed_size; + if (change->old_pkg) + size_diff -= change->old_pkg->installed_size; + } + size_diff /= 1024; + size_unit = 'K'; + if (abs(size_diff) > 10000) { + size_diff /= 1024; + size_unit = 'M'; + } + + if (apk_verbosity > 1 || (apk_flags & APK_INTERACTIVE)) { + r = dump_packages(changeset, cmp_remove, + "The following packages will be REMOVED"); + r += dump_packages(changeset, cmp_downgrade, + "The following packages will be DOWNGRADED"); + if (r || (apk_flags & APK_INTERACTIVE) || apk_verbosity > 2) { + dump_packages(changeset, cmp_new, + "The following NEW packages will be installed"); + dump_packages(changeset, cmp_upgrade, + "The following packages will be upgraded"); + printf("After this operation, %d %ciB of %s\n", + abs(size_diff), size_unit, + (size_diff < 0) ? + "disk space will be freed." : + "additional disk space will be used."); + } + if (changeset->changes->num > 0 && + (apk_flags & APK_INTERACTIVE)) { + printf("Do you want to continue [Y/n]? "); + fflush(stdout); + r = fgetc(stdin); + if (r != 'y' && r != 'Y' && r != '\n') + return -1; + } + } + + /* Go through changes */ + r = 0; + for (i = 0; i < changeset->changes->num; i++) { + change = &changeset->changes->item[i]; + + if (print_change(db, change, prog.done.changes, prog.total.changes)) { + prog.pkg = change->new_pkg; + progress_cb(&prog, 0); + + if (!(apk_flags & APK_SIMULATE)) { + if (change->old_pkg != change->new_pkg || + (change->reinstall && pkg_available(db, change->new_pkg))) + r = apk_db_install_pkg(db, + change->old_pkg, change->new_pkg, + progress_cb, &prog); + if (r != 0) + break; + if (change->new_pkg) + change->new_pkg->ipkg->repository_tag = change->new_repository_tag; + } + } + + count_change(change, &prog.done); + } + update_progress(&prog, 100, 1); + + run_triggers(db, changeset); + +all_done: + apk_dependency_array_copy(&db->world, world); + apk_db_write_config(db); + + if (r == 0 && !db->performing_self_update) { + if (apk_verbosity > 1) { + apk_message("OK: %d packages, %d dirs, %d files, %zu MiB", + db->installed.stats.packages, + db->installed.stats.dirs, + db->installed.stats.files, + db->installed.stats.bytes / (1024 * 1024)); + } else { + apk_message("OK: %zu MiB in %d packages", + db->installed.stats.bytes / (1024 * 1024), + db->installed.stats.packages); + } + } + + return r; +} + +static void add_name_if_virtual(struct apk_name_array **names, struct apk_name *name) +{ + int i; + + if (name->providers->num == 0) + return; + + for (i = 0; i < name->providers->num; i++) + if (name->providers->item[i].pkg->name == name) + return; + + for (i = 0; i < (*names)->num; i++) + if ((*names)->item[i] == name) + return; + + *apk_name_array_add(names) = name; +} + +static void print_pinning_errors(struct apk_database *db, char *label, + struct apk_package *pkg, unsigned int tag) +{ + int i; + + if (pkg->ipkg != NULL) + return; + if (pkg->repos & apk_db_get_pinning_mask_repos(db, APK_DEFAULT_PINNING_MASK | BIT(tag))) + return; + + printf(" %s: but needs pinning:", label); + for (i = 0; i < db->num_repo_tags; i++) { + if (pkg->repos & db->repo_tags[i].allowed_repos) + printf(" @" BLOB_FMT, BLOB_PRINTF(*db->repo_tags[i].name)); + } + printf("\n"); +} + +static void print_dep_errors(struct apk_database *db, char *label, + struct apk_dependency_array *deps, + struct apk_name_array **names) +{ + int i, print_label = 1; + char buf[256]; + apk_blob_t p; + struct apk_indent indent; + + for (i = 0; i < deps->num; i++) { + struct apk_dependency *dep = &deps->item[i]; + struct apk_package *pkg; + + pkg = (struct apk_package*) (dep->name->state_int & ~1); + if (apk_dep_is_materialized_or_provided(dep, pkg)) + continue; + + if (pkg == NULL) + add_name_if_virtual(names, dep->name); + + if (print_label) { + print_label = 0; + indent.x = printf(" %s:", label); + indent.indent = indent.x + 1; + } + p = APK_BLOB_BUF(buf); + apk_blob_push_dep(&p, db, dep); + p = apk_blob_pushed(APK_BLOB_BUF(buf), p); + apk_print_indented(&indent, p); + } + if (!print_label) + printf("\n"); +} + +static void print_provides_errors(struct apk_database *db, char *label, + struct apk_package *pkg) +{ + int i; + + for (i = 0; i < pkg->provides->num; i++) { + struct apk_name *pn = pkg->provides->item[i].name; + struct apk_provider p = APK_PROVIDER_FROM_PROVIDES(pkg, &pkg->provides->item[i]); + struct apk_package *pkgC = (struct apk_package *) (pn->state_int & ~1); + + if (pkgC == NULL && p.version == &apk_null_blob) + continue; + + if ((pkgC == pkg) && (pn->state_int & 1) == 0) { + pn->state_int |= 1; + continue; + } + + printf(" %s provides " PROVIDER_FMT " conflicting with " PKG_VER_FMT "\n", + label, PROVIDER_PRINTF(pn, &p), PKG_VER_PRINTF(pkgC)); + } +} + +void apk_solver_print_errors(struct apk_database *db, + struct apk_changeset *changeset, + struct apk_dependency_array *world) +{ + struct apk_name_array *names; + char pkgtext[256]; + int i, j; + + apk_name_array_init(&names); + /* FIXME: Print more rational error messages. Group failed + * dependencies by package name. As in, + * pkg-1.2 selected, but: + * world: pkg>2 + */ + apk_error("unsatisfiable dependencies:"); + + for (i = 0; i < changeset->changes->num; i++) { + struct apk_package *pkg = changeset->changes->item[i].new_pkg; + if (pkg == NULL) + continue; + pkg->state_int = 1; + pkg->name->state_ptr = pkg; + for (j = 0; j < pkg->provides->num; j++) { + if (pkg->provides->item[j].version == &apk_null_blob) + continue; + if (pkg->provides->item[j].name->state_ptr == NULL) + pkg->provides->item[j].name->state_ptr = pkg; + } + } + + print_dep_errors(db, "world", world, &names); + for (i = 0; i < changeset->changes->num; i++) { + struct apk_change *change = &changeset->changes->item[i]; + struct apk_package *pkg = change->new_pkg; + if (pkg == NULL) + continue; + snprintf(pkgtext, sizeof(pkgtext), PKG_VER_FMT, PKG_VER_PRINTF(pkg)); + print_pinning_errors(db, pkgtext, pkg, change->new_repository_tag); + print_dep_errors(db, pkgtext, pkg->depends, &names); + print_provides_errors(db, pkgtext, pkg); + } + + for (i = 0; i < names->num; i++) { + struct apk_indent indent; + struct apk_name *name = names->item[i]; + + printf("\n %s is a virtual package provided by:\n ", name->name); + indent.x = 4; + indent.indent = 4; + + for (j = 0; j < name->providers->num; j++) { + struct apk_package *pkg = name->providers->item[j].pkg; + snprintf(pkgtext, sizeof(pkgtext), PKG_VER_FMT, PKG_VER_PRINTF(pkg)); + apk_print_indented(&indent, APK_BLOB_STR(pkgtext)); + } + printf("\n"); + } + apk_name_array_free(&names); +} + +int apk_solver_commit(struct apk_database *db, + unsigned short solver_flags, + struct apk_dependency_array *world) +{ + struct apk_changeset changeset = {}; + int r; + + if (apk_db_check_world(db, world) != 0) { + apk_error("Not committing changes due to missing repository tags. Use --force to override."); + return -1; + } + + r = apk_solver_solve(db, solver_flags, world, &changeset); + if (r < 0) + return r; + + if (r == 0 || (apk_flags & APK_FORCE)) { + /* Success -- or forced installation of bad graph */ + r = apk_solver_commit_changeset(db, &changeset, world); + } else { + /* Failure -- print errors */ + apk_solver_print_errors(db, &changeset, world); + } + apk_change_array_free(&changeset.changes); + + return r; +} |