From 15d883367ef4a616b8d50801bf3d460b20aabf44 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 29 Jul 2011 17:33:42 +0200 Subject: [PATCH] Add support for solutions. --- Makefile | 2 +- pakfire/satsolver.py | 105 +++++++++++++++++++++++----- po/POTFILES.in | 1 + po/pakfire.pot | 159 +++++++++++++++++++++++++++++++++++++------ src/_pakfiremodule.c | 13 ++++ src/problem.c | 14 ++++ src/problem.h | 2 + src/solution.c | 157 ++++++++++++++++++++++++++++++++++++++++++ src/solution.h | 24 +++++++ 9 files changed, 438 insertions(+), 39 deletions(-) create mode 100644 src/solution.c create mode 100644 src/solution.h diff --git a/Makefile b/Makefile index c32cea939..953957623 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ dist: python setup.py sdist .PHONY: install -install: +install: po python setup.py install --root $(DESTDIR) -mkdir -pv $(DESTDIR)/etc/pakfire.repos.d diff --git a/pakfire/satsolver.py b/pakfire/satsolver.py index d4a80a808..a795fe0a1 100644 --- a/pakfire/satsolver.py +++ b/pakfire/satsolver.py @@ -6,6 +6,7 @@ import _pakfire from _pakfire import * import transaction +import util from i18n import _ @@ -76,42 +77,114 @@ class Solver(object): self.pakfire = pakfire self.pool = pool - self._solver = _pakfire.Solver(self.pool) + def solve(self, request, update=False, uninstall=False, allow_downgrade=False, + interactive=False): - def solve(self, request, update=False, uninstall=False, allow_downgrade=False): - self._solver.set_allow_uninstall(uninstall) - self._solver.set_allow_downgrade(allow_downgrade) + # Create a new solver. + solver = _pakfire.Solver(self.pool) + + solver.set_allow_uninstall(uninstall) + solver.set_allow_downgrade(allow_downgrade) # Configure the solver for an update. if update: - self._solver.set_updatesystem(True) - self._solver.set_do_split_provides(True) + solver.set_updatesystem(True) + solver.set_do_split_provides(True) - res = self._solver.solve(request) + # Actually solve the request. + res = solver.solve(request) logging.debug("Solver status: %s" % res) # If the solver succeeded, we return the transaction and return. if res: # Return a resulting Transaction. - t = Transaction(self._solver) + t = Transaction(solver) return transaction.Transaction.from_solver(self.pakfire, self, t) # Do the problem handling... - problems = self._solver.get_problems(request) + problems = solver.get_problems(request) logging.info("") - logging.info(_("The solver returned %s problems:") % len(problems)) - logging.info("") + logging.info(_("The solver returned one problem:", "The solver returned %(num)s problems:", + len(problems)) % { "num" : len(problems) }) i = 0 - for p in self._solver.get_problems(request): + for problem in problems: i += 1 # Print information about the problem to the user. - logging.info(_(" Problem #%d:") % i) - logging.info(" %s" % p) - logging.info("") + logging.info(" #%d: %s" % (i, problem)) + + logging.info("") - return res + if not interactive: + return False + + # Ask the user if he or she want to modify the request. If not, just exit. + if not util.ask_user(_("Do you want to manually alter the request?")): + return False + + print _("You can now try to satisfy the solver by modifying your request.") + + altered = False + while True: + if len(problems) > 1: + print _("Which problem to you want to resolve?") + if altered: + print _("Press enter to try to re-solve the request.") + print "[1-%s]:" % len(problems), + + answer = raw_input() + + # If the user did not enter anything, we abort immediately. + if not answer: + break + + # If the user did type anything else than an integer, we ask + # again. + try: + answer = int(answer) + except ValueError: + continue + + # If the user entered an integer outside of range, we ask + # again. + try: + problem = problems[answer - 1] + except KeyError: + continue + + else: + problem = problem[0] + + # Get all solutions. + solutions = problem.get_solutions() + + if len(solutions) == 1: + solution = solutions[0] + print _(" Solution: %s") % solution + print + + if util.ask_user("Do you accept the solution above?"): + altered = True + print "XXX do something" + + continue + else: + print _(" Solutions:") + i = 0 + for solution in solutions: + i += 1 + print " #%d: %s" % (i, solution) + + print + + if not altered: + return False + + # If the request was altered by the user, we try to solve it again + # and see what happens. + return self.solve(request, update=update, uninstall=uninstall, + allow_downgrade=allow_downgrade, interactive=interactive) diff --git a/po/POTFILES.in b/po/POTFILES.in index d99bf2768..f81326557 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -44,6 +44,7 @@ src/problem.c src/relation.c src/repo.c src/request.c +src/solution.c src/solvable.c src/solver.c src/step.c diff --git a/po/pakfire.pot b/po/pakfire.pot index be74ce9a8..389d63ad3 100644 --- a/po/pakfire.pot +++ b/po/pakfire.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-07-29 15:04+0200\n" +"POT-Creation-Date: 2011-07-29 17:32+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -343,15 +343,40 @@ msgstr "" msgid "Loading installed packages" msgstr "" -#: ../pakfire/satsolver.py:105 -#, python-format -msgid "The solver returned %s problems:" +#: ../pakfire/satsolver.py:110 +msgid "The solver returned one problem:" msgstr "" #. Print information about the problem to the user. -#: ../pakfire/satsolver.py:113 +#: ../pakfire/satsolver.py:118 #, python-format -msgid " Problem #%d:" +msgid " #%d: %s" +msgstr "" + +#. Ask the user if he or she want to modify the request. If not, just exit. +#: ../pakfire/satsolver.py:126 +msgid "Do you want to manually alter the request?" +msgstr "" + +#: ../pakfire/satsolver.py:129 +msgid "You can now try to satisfy the solver by modifying your request." +msgstr "" + +#: ../pakfire/satsolver.py:134 +msgid "Which problem to you want to resolve?" +msgstr "" + +#: ../pakfire/satsolver.py:136 +msgid "Press enter to try to re-solve the request." +msgstr "" + +#: ../pakfire/satsolver.py:167 +#, python-format +msgid " Solution: %s" +msgstr "" + +#: ../pakfire/satsolver.py:176 +msgid " Solutions:" msgstr "" #: ../pakfire/transaction.py:89 @@ -409,82 +434,172 @@ msgstr "" msgid "%s [y/N]" msgstr "" -#: ../src/problem.c:126 +#: ../src/problem.c:140 #, c-format msgid "%s does not belong to a distupgrade repository" msgstr "" -#: ../src/problem.c:133 +#: ../src/problem.c:147 #, c-format msgid "%s has inferior architecture" msgstr "" -#: ../src/problem.c:140 +#: ../src/problem.c:154 #, c-format msgid "problem with installed package %s" msgstr "" -#: ../src/problem.c:146 +#: ../src/problem.c:160 #, c-format msgid "conflicting requests" msgstr "" -#: ../src/problem.c:151 +#: ../src/problem.c:165 #, c-format msgid "nothing provides requested %s" msgstr "" -#: ../src/problem.c:157 +#: ../src/problem.c:171 #, c-format msgid "some dependency problem" msgstr "" -#: ../src/problem.c:162 +#: ../src/problem.c:176 #, c-format msgid "package %s is not installable" msgstr "" -#: ../src/problem.c:169 +#: ../src/problem.c:183 #, c-format msgid "nothing provides %s needed by %s" msgstr "" -#: ../src/problem.c:176 +#: ../src/problem.c:190 #, c-format msgid "cannot install both %s and %s" msgstr "" -#: ../src/problem.c:183 +#: ../src/problem.c:197 #, c-format msgid "package %s conflicts with %s provided by %s" msgstr "" -#: ../src/problem.c:191 +#: ../src/problem.c:205 #, c-format msgid "package %s obsoletes %s provided by %s" msgstr "" -#: ../src/problem.c:199 +#: ../src/problem.c:213 #, c-format msgid "installed package %s obsoletes %s provided by %s" msgstr "" -#: ../src/problem.c:207 +#: ../src/problem.c:221 #, c-format msgid "package %s implicitely obsoletes %s provided by %s" msgstr "" -#: ../src/problem.c:215 +#: ../src/problem.c:229 #, c-format msgid "package %s requires %s, but none of the providers can be installed" msgstr "" -#: ../src/problem.c:222 +#: ../src/problem.c:236 #, c-format msgid "package %s conflicts with %s provided by itself" msgstr "" -#: ../src/problem.c:231 +#: ../src/problem.c:245 #, c-format msgid "bad rule type" msgstr "" + +#: ../src/solution.c:67 +#, c-format +msgid "do not keep %s installed" +msgstr "" + +#: ../src/solution.c:70 +#, c-format +msgid "do not install a solvable %s" +msgstr "" + +#: ../src/solution.c:73 +#, c-format +msgid "do not install %s" +msgstr "" + +#: ../src/solution.c:79 +#, c-format +msgid "do not forbid installation of %s" +msgstr "" + +#: ../src/solution.c:82 +#, c-format +msgid "do not deinstall all solvables %s" +msgstr "" + +#: ../src/solution.c:85 +#, c-format +msgid "do not deinstall %s" +msgstr "" + +#: ../src/solution.c:90 +#, c-format +msgid "do not install most recent version of %s" +msgstr "" + +#: ../src/solution.c:95 +#, c-format +msgid "do not lock %s" +msgstr "" + +#: ../src/solution.c:100 +#, c-format +msgid "do something different" +msgstr "" + +#: ../src/solution.c:107 +#, c-format +msgid "keep %s despite the inferior architecture" +msgstr "" + +#: ../src/solution.c:110 +#, c-format +msgid "install %s despite the inferior architecture" +msgstr "" + +#: ../src/solution.c:116 +#, c-format +msgid "keep obsolete %s" +msgstr "" + +#: ../src/solution.c:119 +#, c-format +msgid "install %s from excluded repository" +msgstr "" + +#: ../src/solution.c:131 +#, c-format +msgid "allow downgrade of %s to %s" +msgstr "" + +#: ../src/solution.c:135 +#, c-format +msgid "allow architecture change of %s to %s" +msgstr "" + +#: ../src/solution.c:140 +#, c-format +msgid "allow vendor change from '%s' (%s) to '%s' (%s)" +msgstr "" + +#: ../src/solution.c:144 +#, c-format +msgid "allow vendor change from '%s' (%s) to no vendor (%s)" +msgstr "" + +#: ../src/solution.c:150 +#, c-format +msgid "allow replacement of %s with %s" +msgstr "" diff --git a/src/_pakfiremodule.c b/src/_pakfiremodule.c index 0aaa2c811..39e1b55e2 100644 --- a/src/_pakfiremodule.c +++ b/src/_pakfiremodule.c @@ -7,6 +7,7 @@ #include "relation.h" #include "repo.h" #include "request.h" +#include "solution.h" #include "solvable.h" #include "solver.h" #include "step.h" @@ -30,6 +31,7 @@ static PyMethodDef Problem_methods[] = { {"get_source", (PyCFunction)Problem_get_source, METH_NOARGS, NULL}, {"get_target", (PyCFunction)Problem_get_target, METH_NOARGS, NULL}, {"get_dep", (PyCFunction)Problem_get_dep, METH_NOARGS, NULL}, + {"get_solutions", (PyCFunction)Problem_get_solutions, METH_NOARGS, NULL}, { NULL, NULL, 0, NULL } }; @@ -111,6 +113,10 @@ static PyMethodDef Solvable_methods[] = { { NULL, NULL, 0, NULL } }; +static PyMethodDef Solution_methods[] = { + { NULL, NULL, 0, NULL } +}; + static PyMethodDef Solver_methods[] = { {"solve", (PyCFunction)Solver_solve, METH_VARARGS, NULL}, {"get_allow_downgrade", (PyCFunction)Solver_get_allow_downgrade, METH_NOARGS, NULL}, @@ -193,6 +199,13 @@ void init_pakfire(void) { Py_INCREF(&RequestType); PyModule_AddObject(m, "Request", (PyObject *)&RequestType); + // Solution + SolutionType.tp_methods = Solution_methods; + if (PyType_Ready(&SolutionType) < 0) + return; + Py_INCREF(&SolutionType); + PyModule_AddObject(m, "Solution", (PyObject *)&SolutionType); + // Solver SolverType.tp_methods = Solver_methods; if (PyType_Ready(&SolverType) < 0) diff --git a/src/problem.c b/src/problem.c index d44ab2349..77c916b20 100644 --- a/src/problem.c +++ b/src/problem.c @@ -5,6 +5,7 @@ #include "problem.h" #include "relation.h" #include "request.h" +#include "solution.h" #include "solvable.h" #include "solver.h" @@ -111,8 +112,21 @@ PyObject *Problem_get_dep(ProblemObject *self) { } PyObject *Problem_get_solutions(ProblemObject *self) { + SolutionObject *solution; + Id s = 0; + PyObject *list = PyList_New(0); + while ((s = solver_next_solution(self->_solver, self->_id, s)) != 0) { + solution = PyObject_New(SolutionObject, &SolutionType); + + solution->_solver = self->_solver; + solution->problem_id = self->_id; + solution->id = s; + + PyList_Append(list, (PyObject *)solution); + } + return list; } diff --git a/src/problem.h b/src/problem.h index 73074fddf..15b3722ca 100644 --- a/src/problem.h +++ b/src/problem.h @@ -32,6 +32,8 @@ extern PyObject *Problem_get_source(ProblemObject *self); extern PyObject *Problem_get_target(ProblemObject *self); extern PyObject *Problem_get_dep(ProblemObject *self); +extern PyObject *Problem_get_solutions(ProblemObject *self); + extern PyTypeObject ProblemType; #endif diff --git a/src/solution.c b/src/solution.c new file mode 100644 index 000000000..469cb32f4 --- /dev/null +++ b/src/solution.c @@ -0,0 +1,157 @@ + +#include + +#include +#include + +#include "config.h" +#include "problem.h" +#include "solution.h" + +PyTypeObject SolutionType = { + PyObject_HEAD_INIT(NULL) + tp_name: "_pakfire.Solution", + tp_basicsize: sizeof(SolutionObject), + tp_flags: Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + tp_new: Solution_new, + tp_dealloc: (destructor)Solution_dealloc, + tp_doc: "Sat Solution objects", + tp_str: (reprfunc)Solution_string, +}; + +PyObject *Solution_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + SolutionObject *self; + ProblemObject *problem; + Id id; + + if (!PyArg_ParseTuple(args, "Oi", &problem, &id)) { + /* XXX raise exception */ + return NULL; + } + + self = (SolutionObject *)type->tp_alloc(type, 0); + if (self != NULL) { + self->_solver = problem->_solver; + self->problem_id = problem->_id; + self->id = id; + } + + return (PyObject *)self; +} + +PyObject *Solution_dealloc(SolutionObject *self) { + self->ob_type->tp_free((PyObject *)self); + + Py_RETURN_NONE; +} + +PyObject *Solution_string(SolutionObject *self) { + Pool *pool = self->_solver->pool; + Solver *solver = self->_solver; + char str[STRING_SIZE]; + + Solvable *s, *sd; + Id p, rp; + Id how, what, select; + + Id element = 0; + while ((element = solver_next_solutionelement(solver, self->problem_id, self->id, element, &p, &rp)) != 0) { + if (p == SOLVER_SOLUTION_JOB) { + how = solver->job.elements[rp - 1]; + what = solver->job.elements[rp]; + select = how & SOLVER_SELECTMASK; + + switch (how & SOLVER_JOBMASK) { + case SOLVER_INSTALL: + if (select == SOLVER_SOLVABLE && solver->installed && pool->solvables[what].repo == solver->installed) + snprintf(str, STRING_SIZE - 1, _("do not keep %s installed"), + pool_solvid2str(pool, what)); + else if (select == SOLVER_SOLVABLE_PROVIDES) + snprintf(str, STRING_SIZE - 1, _("do not install a solvable %s"), + solver_select2str(pool, select, what)); + else + snprintf(str, STRING_SIZE - 1, _("do not install %s"), + solver_select2str(pool, select, what)); + break; + + case SOLVER_ERASE: + if (select == SOLVER_SOLVABLE && !(solver->installed && pool->solvables[what].repo == solver->installed)) + snprintf(str, STRING_SIZE - 1, _("do not forbid installation of %s"), + pool_solvid2str(pool, what)); + else if (select == SOLVER_SOLVABLE_PROVIDES) + snprintf(str, STRING_SIZE - 1, _("do not deinstall all solvables %s"), + solver_select2str(pool, select, what)); + else + snprintf(str, STRING_SIZE - 1, _("do not deinstall %s"), + solver_select2str(pool, select, what)); + break; + + case SOLVER_UPDATE: + snprintf(str, STRING_SIZE - 1, _("do not install most recent version of %s"), + solver_select2str(pool, select, what)); + break; + + case SOLVER_LOCK: + snprintf(str, STRING_SIZE - 1, _("do not lock %s"), + solver_select2str(pool, select, what)); + break; + + default: + snprintf(str, STRING_SIZE - 1, _("do something different")); + break; + } + + } else if (p == SOLVER_SOLUTION_INFARCH) { + s = pool->solvables + rp; + if (solver->installed && s->repo == solver->installed) + snprintf(str, STRING_SIZE - 1, _("keep %s despite the inferior architecture"), + pool_solvable2str(pool, s)); + else + snprintf(str, STRING_SIZE - 1, _("install %s despite the inferior architecture"), + pool_solvable2str(pool, s)); + + } else if (p == SOLVER_SOLUTION_DISTUPGRADE) { + s = pool->solvables + rp; + if (solver->installed && s->repo == solver->installed) + snprintf(str, STRING_SIZE - 1, _("keep obsolete %s"), + pool_solvable2str(pool, s)); + else + snprintf(str, STRING_SIZE - 1, _("install %s from excluded repository"), + pool_solvable2str(pool, s)); + + } else { + s = pool->solvables + p; + sd = rp ? pool->solvables + rp : 0; + + if (sd) { + int illegal = policy_is_illegal(solver, s, sd, 0); + + // XXX multiple if clauses can match here + if ((illegal & POLICY_ILLEGAL_DOWNGRADE) != 0) + snprintf(str, STRING_SIZE - 1, _("allow downgrade of %s to %s"), + pool_solvable2str(pool, s), pool_solvable2str(pool, sd)); + + if ((illegal & POLICY_ILLEGAL_ARCHCHANGE) != 0) + snprintf(str, STRING_SIZE - 1, _("allow architecture change of %s to %s"), + pool_solvable2str(pool, s), pool_solvable2str(pool, sd)); + + if ((illegal & POLICY_ILLEGAL_VENDORCHANGE) != 0) { + if (sd->vendor) + snprintf(str, STRING_SIZE - 1, _("allow vendor change from '%s' (%s) to '%s' (%s)"), + pool_id2str(pool, s->vendor), pool_solvable2str(pool, s), + pool_id2str(pool, sd->vendor), pool_solvable2str(pool, sd)); + else + snprintf(str, STRING_SIZE - 1, _("allow vendor change from '%s' (%s) to no vendor (%s)"), + pool_id2str(pool, s->vendor), pool_solvable2str(pool, s), + pool_solvable2str(pool, sd)); + } + + if (!illegal) + snprintf(str, STRING_SIZE - 1, _("allow replacement of %s with %s"), + pool_solvable2str(pool, s), pool_solvable2str(pool, sd)); + } + } + } + + return Py_BuildValue("s", &str); +} diff --git a/src/solution.h b/src/solution.h new file mode 100644 index 000000000..fdf05c9b8 --- /dev/null +++ b/src/solution.h @@ -0,0 +1,24 @@ + +#ifndef PAKFIRE_SOLUTION_H +#define PAKFIRE_SOLUTION_H + +#include + +#include "solver.h" + +// Sat Solution object +typedef struct { + PyObject_HEAD + Solver *_solver; + Id problem_id; + Id id; +} SolutionObject; + +extern PyObject *Solution_new(PyTypeObject *type, PyObject *args, PyObject *kwds); +extern PyObject *Solution_dealloc(SolutionObject *self); + +extern PyObject *Solution_string(SolutionObject *self); + +extern PyTypeObject SolutionType; + +#endif -- 2.39.5