]> git.ipfire.org Git - people/stevee/pakfire.git/blob - python/pakfire/satsolver.py
Cleanup database and add indexes.
[people/stevee/pakfire.git] / python / pakfire / satsolver.py
1 #!/usr/bin/python
2 ###############################################################################
3 # #
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2011 Pakfire development team #
6 # #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 import os
23 import time
24
25 import logging
26 log = logging.getLogger("pakfire")
27
28 import filelist
29 import packages
30 import transaction
31 import util
32 import _pakfire
33
34 from constants import *
35 from i18n import _
36
37 # Put some variables into our own namespace, to make them easily accessible
38 # for code, that imports the satsolver module.
39 SEARCH_STRING = _pakfire.SEARCH_STRING
40 SEARCH_FILES = _pakfire.SEARCH_FILES
41 SEARCH_GLOB = _pakfire.SEARCH_GLOB
42
43 Repo = _pakfire.Repo
44 Solvable = _pakfire.Solvable
45 Relation = _pakfire.Relation
46
47 class Pool(_pakfire.Pool):
48 RELATIONS = (
49 (">=", _pakfire.REL_GE,),
50 ("<=", _pakfire.REL_LE,),
51 ("=" , _pakfire.REL_EQ,),
52 ("<" , _pakfire.REL_LT,),
53 (">" , _pakfire.REL_GT,),
54 )
55
56 def create_relation(self, s):
57 assert s
58
59 if isinstance(s, filelist._File):
60 return Relation(self, s.name)
61
62 elif s.startswith("/"):
63 return Relation(self, s)
64
65 for pattern, type in self.RELATIONS:
66 if not pattern in s:
67 continue
68
69 name, version = s.split(pattern, 1)
70 return Relation(self, name.strip(), version.strip(), type)
71
72 return Relation(self, s)
73
74 def create_request(self, builder=False, install=None, remove=None, update=None, updateall=False):
75 request = Request(self)
76
77 # Add multiinstall information.
78 for solv in PAKFIRE_MULTIINSTALL:
79 request.noobsoletes(solv)
80
81 # Apply all installs.
82 for req in self.expand_requires(install):
83 request.install(req)
84
85 # Apply all removes.
86 for req in self.expand_requires(remove):
87 request.remove(req)
88
89 # Apply all updates.
90 for req in self.expand_requires(update):
91 request.update(req)
92
93 # Configure the request to update all packages
94 # if requested.
95 if updateall:
96 request.updateall()
97
98 # Return the request.
99 return request
100
101 def grouplist(self, group):
102 pkgs = []
103
104 for solv in self.search(group, _pakfire.SEARCH_SUBSTRING, "solvable:group"):
105 pkg = packages.SolvPackage(self, solv)
106
107 if group in pkg.groups and not pkg.name in pkgs:
108 pkgs.append(pkg.name)
109
110 return sorted(pkgs)
111
112 def expand_requires(self, requires):
113 if requires is None:
114 return []
115
116 ret = []
117 for req in requires:
118 if isinstance(req, packages.BinaryPackage):
119 ret.append(req)
120 continue
121
122 if isinstance(req, packages.SolvPackage):
123 ret.append(req.solvable)
124 continue
125
126 assert type(req) == type("a"), req
127
128 # Expand all groups.
129 if req.startswith("@"):
130 reqs = self.grouplist(req[1:])
131 else:
132 reqs = [req,]
133
134 for req in reqs:
135 req = self.create_relation(req)
136 ret.append(req)
137
138 return ret
139
140 def resolvdep(self, pakfire, pkg, logger=None):
141 assert os.path.exists(pkg)
142
143 # Open the package file.
144 pkg = packages.open(pakfire, None, pkg)
145
146 # Create a new request.
147 request = self.create_request(install=pkg.requires)
148
149 # Add build dependencies if needed.
150 if isinstance(pkg, packages.Makefile) or isinstance(pkg, packages.SourcePackage):
151 for req in self.expand_requires(BUILD_PACKAGES):
152 request.install(req)
153
154 # Solv the request.
155 solver = self.solve(request, logger=logger)
156
157 if solver.status:
158 return solver
159
160 raise DependencyError, solver.get_problem_string()
161
162 def solve(self, request, interactive=False, logger=None, force_best=False, **kwargs):
163 # XXX implement interactive
164
165 if not logger:
166 logger = logging.getLogger("pakfire")
167
168 # Create a solver.
169 solver = Solver(self, request, logger=logger)
170
171 # Apply configuration to solver.
172 for key, val in kwargs.items():
173 solver.set(key, val)
174
175 # Do the solving.
176 solver.solve(force_best=force_best)
177
178 # Return the solver so one can do stuff with it...
179 return solver
180
181 def whatprovides(self, pakfire, what):
182 pkgs = []
183 for solv in self.providers(what):
184 pkg = packages.SolvPackage(pakfire, solv)
185 pkgs.append(pkg)
186
187 return pkgs
188
189
190 class Request(_pakfire.Request):
191 def install(self, what):
192 if isinstance(what, Solvable):
193 self.install_solvable(what)
194 return
195
196 elif isinstance(what, Relation):
197 self.install_relation(what)
198 return
199
200 elif type(what) == type("string"):
201 self.install_name(what)
202 return
203
204 raise Exception, "Unknown type"
205
206 def remove(self, what):
207 if isinstance(what, Solvable):
208 self.remove_solvable(what)
209 return
210
211 elif isinstance(what, Relation):
212 self.remove_relation(what)
213 return
214
215 elif type(what) == type("string"):
216 self.remove_name(what)
217 return
218
219 raise Exception, "Unknown type"
220
221 def update(self, what):
222 if isinstance(what, Solvable):
223 self.update_solvable(what)
224 return
225
226 elif isinstance(what, Relation):
227 self.update_relation(what)
228 return
229
230 elif type(what) == type("string"):
231 self.update_name(what)
232 return
233
234 raise Exception, "Unknown type"
235
236 def lock(self, what):
237 if isinstance(what, Solvable):
238 self.lock_solvable(what)
239 return
240
241 elif isinstance(what, Relation):
242 self.lock_relation(what)
243 return
244
245 elif type(what) == type("string"):
246 self.lock_name(what)
247 return
248
249 raise Exception, "Unknown type"
250
251 def noobsoletes(self, what):
252 if isinstance(what, Solvable):
253 self.noobsoletes_solvable(what)
254 return
255
256 elif isinstance(what, Relation):
257 self.noobsoletes_relation(what)
258 return
259
260 elif type(what) == type("string"):
261 self.noobsoletes_name(what)
262 return
263
264 raise Exception, "Unknown type"
265
266
267 class Solver(object):
268 option2flag = {
269 "allow_archchange" : _pakfire.SOLVER_FLAG_ALLOW_ARCHCHANGE,
270 "allow_downgrade" : _pakfire.SOLVER_FLAG_ALLOW_DOWNGRADE,
271 "allow_uninstall" : _pakfire.SOLVER_FLAG_ALLOW_UNINSTALL,
272 "allow_vendorchange" : _pakfire.SOLVER_FLAG_ALLOW_VENDORCHANGE,
273 "ignore_recommended" : _pakfire.SOLVER_FLAG_IGNORE_RECOMMENDED,
274 }
275
276 def __init__(self, pool, request, logger=None):
277 if logger is None:
278 logger = logging.getLogger("pakfire")
279 self.logger = logger
280
281 self.pool = pool
282 self.request = request
283 assert self.request, "Empty request?"
284
285 # Create a new solver.
286 self.solver = _pakfire.Solver(self.pool)
287
288 # The status of the solver.
289 # None when the solving was not done, yet.
290 # True when the request could be solved.
291 # False when the request could not be solved.
292 self.status = None
293
294 # Time that was needed to solve the request.
295 self.time = None
296
297 # Cache the transaction and problems.
298 self.__problems = None
299 self.__transaction = None
300
301 # Create some sane settings for the most common use cases.
302 self.set("allow_archchange", True)
303
304 def set(self, option, value):
305 try:
306 flag = self.option2flag[option]
307 except KeyError:
308 raise Exception, "Unknown configuration setting: %s" % option
309 self.solver.set_flag(flag, value)
310
311 def get(self, option):
312 try:
313 flag = self.option2flag[option]
314 except KeyError:
315 raise Exception, "Unknown configuration setting: %s" % option
316 return self.solver.get_flag(flag)
317
318 def solve(self, force_best=False):
319 assert self.status is None, "Solver did already solve something."
320
321 # Actually solve the request.
322 start_time = time.time()
323 self.status = self.solver.solve(self.request, force_best)
324
325 # Save the amount of time that was needed to solve the request.
326 self.time = time.time() - start_time
327
328 if self.status:
329 self.logger.info(_("Dependency solving finished in %.2f ms") % (self.time * 1000))
330 else:
331 raise DependencyError, self.get_problem_string()
332
333 @property
334 def problems(self):
335 if self.__problems is None:
336 self.__problems = self.solver.get_problems(self.request)
337
338 return self.__problems
339
340 def get_problem_string(self):
341 assert self.status is False
342
343 lines = [
344 _("The solver returned one problem:", "The solver returned %(num)s problems:",
345 len(self.problems)) % { "num" : len(self.problems) },
346 ]
347
348 i = 0
349 for problem in self.problems:
350 i += 1
351
352 # Print information about the problem.
353 lines.append(" #%d: %s" % (i, problem))
354
355 return "\n".join(lines)
356
357
358 def DEADCODE(self):
359 # If the solver succeeded, we return the transaction and return.
360 if res:
361 # Return a resulting Transaction.
362 t = Transaction(solver)
363
364 return transaction.Transaction.from_solver(self.pakfire, self, t)
365
366 # Do the problem handling...
367 problems = solver.get_problems(request)
368
369 logger.info("")
370 logger.info(_("The solver returned one problem:", "The solver returned %(num)s problems:",
371 len(problems)) % { "num" : len(problems) })
372
373 i = 0
374 for problem in problems:
375 i += 1
376
377 # Print information about the problem to the user.
378 logger.info(" #%d: %s" % (i, problem))
379
380 logger.info("")
381
382 if not interactive:
383 return False
384
385 # Ask the user if he or she want to modify the request. If not, just exit.
386 if not util.ask_user(_("Do you want to manually alter the request?")):
387 return False
388
389 print _("You can now try to satisfy the solver by modifying your request.")
390
391 altered = False
392 while True:
393 if len(problems) > 1:
394 print _("Which problem to you want to resolve?")
395 if altered:
396 print _("Press enter to try to re-solve the request.")
397 print "[1-%s]:" % len(problems),
398
399 answer = raw_input()
400
401 # If the user did not enter anything, we abort immediately.
402 if not answer:
403 break
404
405 # If the user did type anything else than an integer, we ask
406 # again.
407 try:
408 answer = int(answer)
409 except ValueError:
410 continue
411
412 # If the user entered an integer outside of range, we ask
413 # again.
414 try:
415 problem = problems[answer - 1]
416 except KeyError:
417 continue
418
419 else:
420 problem = problem[0]
421
422 # Get all solutions.
423 solutions = problem.get_solutions()
424
425 if len(solutions) == 1:
426 solution = solutions[0]
427 print _(" Solution: %s") % solution
428 print
429
430 if util.ask_user("Do you accept the solution above?"):
431 altered = True
432 print "XXX do something"
433
434 continue
435 else:
436 print _(" Solutions:")
437 i = 0
438 for solution in solutions:
439 i += 1
440 print " #%d: %s" % (i, solution)
441
442 print
443
444 if not altered:
445 return False
446
447 # If the request was altered by the user, we try to solve it again
448 # and see what happens.
449 return self.solve(request, update=update, uninstall=uninstall,
450 allow_downgrade=allow_downgrade, interactive=interactive)