]> git.ipfire.org Git - people/stevee/pakfire.git/blob - python/pakfire/base.py
Merge branch 'master' of ssh://git.ipfire.org/pub/git/oddments/pakfire
[people/stevee/pakfire.git] / python / pakfire / base.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 random
24 import string
25
26 import actions
27 import builder
28 import config
29 import distro
30 import filelist
31 import logger
32 import packages
33 import repository
34 import satsolver
35 import transaction
36 import util
37
38 import logging
39 log = logging.getLogger("pakfire")
40
41 from constants import *
42 from i18n import _
43
44 class Pakfire(object):
45 RELATIONS = (
46 (">=", satsolver.REL_GE,),
47 ("<=", satsolver.REL_LE,),
48 ("=" , satsolver.REL_EQ,),
49 ("<" , satsolver.REL_LT,),
50 (">" , satsolver.REL_GT,),
51 )
52
53 def __init__(self, mode=None, path="/", configs=[],
54 enable_repos=None, disable_repos=None,
55 distro_config=None, **kwargs):
56
57 # Set the mode.
58 assert mode in ("normal", "builder", "server",)
59 self.mode = mode
60
61 # Check if we are operating as the root user.
62 self.check_root_user()
63
64 # The path where we are operating in.
65 self.path = path
66
67 # Configure the instance of Pakfire we just started.
68 if mode == "builder":
69 self.path = os.path.join(BUILD_ROOT, util.random_string())
70
71 elif mode == "normal":
72 # check if we are actually running on an ipfire system.
73 if self.path == "/":
74 self.check_is_ipfire()
75
76 # Read configuration file(s)
77 self.config = config.Config(type=mode)
78 for filename in configs:
79 self.config.read(filename)
80 # Assume, that all other keyword arguments are configuration
81 # parameters.
82 self.config.update(kwargs)
83
84 # Setup the logger
85 logger.setup_logging(self.config)
86 self.config.dump()
87
88 # Get more information about the distribution we are running
89 # or building
90 self.distro = distro.Distribution(self, distro_config)
91 self.pool = satsolver.Pool(self.distro.arch)
92 self.repos = repository.Repositories(self,
93 enable_repos=enable_repos, disable_repos=disable_repos)
94
95 def __del__(self):
96 # Reset logging.
97 logger.setup_logging()
98
99 def create_solver(self):
100 return satsolver.Solver(self, self.pool)
101
102 def create_request(self, builder=False):
103 request = satsolver.Request(self.pool)
104
105 # Add multiinstall information.
106 for solv in PAKFIRE_MULTIINSTALL:
107 request.noobsoletes(solv)
108
109 return request
110
111 def create_relation(self, s):
112 assert s
113
114 if isinstance(s, filelist._File):
115 return satsolver.Relation(self.pool, s.name)
116
117 elif s.startswith("/"):
118 return satsolver.Relation(self.pool, s)
119
120 for pattern, type in self.RELATIONS:
121 if not pattern in s:
122 continue
123
124 name, version = s.split(pattern, 1)
125
126 # Trim spaces from strings.
127 name = name.strip()
128 version = version.strip()
129
130 return satsolver.Relation(self.pool, name, version, type)
131
132 return satsolver.Relation(self.pool, s)
133
134 def destroy(self):
135 if not self.path == "/":
136 util.rm(self.path)
137
138 @property
139 def environ(self):
140 env = {}
141
142 # Get distribution information.
143 env.update(self.distro.environ)
144
145 return env
146
147 @property
148 def supported_arches(self):
149 return self.config.supported_arches
150
151 @property
152 def offline(self):
153 """
154 A shortcut that indicates if the system is running in offline mode.
155 """
156 return self.config.get("offline", False)
157
158 def check_root_user(self):
159 if not os.getuid() == 0 or not os.getgid() == 0:
160 raise Exception, "You must run pakfire as the root user."
161
162 def check_build_mode(self):
163 """
164 Check if we are running in build mode.
165 Otherwise, raise an exception.
166 """
167 if not self.mode == "builder":
168 raise BuildError, "Cannot build when not in build mode."
169
170 def check_host_arch(self, arch):
171 """
172 Check if we can build for arch.
173 """
174 # If no arch was given on the command line we build for our
175 # own arch which should always work.
176 if not arch:
177 return True
178
179 if not self.config.host_supports_arch(arch):
180 raise BuildError, "Cannot build for the target architecture: %s" % arch
181
182 raise BuildError, arch
183
184 def check_is_ipfire(self):
185 return # XXX disabled for now
186
187 ret = os.path.exists("/etc/ipfire-release")
188
189 if not ret:
190 raise NotAnIPFireSystemError, "You can run pakfire only on an IPFire system"
191
192 @property
193 def builder(self):
194 # XXX just backwards compatibility
195 return self.mode == "builder"
196
197 def resolvdep(self, requires):
198 # Create a new request.
199 request = self.create_request()
200 for req in requires:
201 req = self.create_relation(req)
202 request.install(req)
203
204 # Do the solving.
205 solver = self.create_solver()
206 t = solver.solve(request)
207
208 if t:
209 t.dump()
210 else:
211 log.info(_("Nothing to do"))
212
213 def install(self, requires, interactive=True, logger=None, **kwargs):
214 if not logger:
215 logger = logging.getLogger("pakfire")
216
217 # Create a new request.
218 request = self.create_request()
219
220 # Expand all groups.
221 for req in requires:
222 if req.startswith("@"):
223 reqs = self.grouplist(req[1:])
224 else:
225 reqs = [req,]
226
227 for req in reqs:
228 if not isinstance(req, packages.BinaryPackage):
229 req = self.create_relation(req)
230
231 request.install(req)
232
233 # Do the solving.
234 solver = self.create_solver()
235 t = solver.solve(request, **kwargs)
236
237 if not t:
238 if not interactive:
239 raise DependencyError
240
241 log.info(_("Nothing to do"))
242 return
243
244 if interactive:
245 # Ask if the user acknowledges the transaction.
246 if not t.cli_yesno():
247 return
248
249 else:
250 t.dump(logger=logger)
251
252 # Run the transaction.
253 t.run()
254
255 def localinstall(self, files, yes=None, allow_uninstall=False):
256 repo_name = repo_desc = "localinstall"
257
258 # Create a new repository that holds all packages we passed on
259 # the commandline.
260 repo = repository.RepositoryDir(self, repo_name, repo_desc,
261 os.path.join(LOCAL_TMP_PATH, "repo_%s" % util.random_string()))
262
263 # Register the repository.
264 self.repos.add_repo(repo)
265
266 try:
267 # Add all packages to the repository index.
268 for file in files:
269 repo.collect_packages(file)
270
271 # Break if no packages were added at all.
272 if not len(repo):
273 log.critical(_("There are no packages to install."))
274 return
275
276 # Create a new request that installs all solvables from the
277 # repository.
278 request = self.create_request()
279 for solv in [p.solvable for p in repo]:
280 request.install(solv)
281
282 solver = self.create_solver()
283 t = solver.solve(request, uninstall=allow_uninstall)
284
285 # If solving was not possible, we exit here.
286 if not t:
287 log.info(_("Nothing to do"))
288 return
289
290 if yes is None:
291 # Ask the user if this is okay.
292 if not t.cli_yesno():
293 return
294 elif yes:
295 t.dump()
296 else:
297 return
298
299 # If okay, run the transcation.
300 t.run()
301
302 finally:
303 # Remove the temporary copy of the repository we have created earlier.
304 repo.remove()
305 self.repos.rem_repo(repo)
306
307 def reinstall(self, pkgs, strict=False):
308 """
309 Reinstall one or more packages.
310
311 If strict is True, only a package with excatly the same UUID
312 will replace the currently installed one.
313 """
314 # XXX it is possible to install packages without fulfulling
315 # all dependencies.
316
317 reinstall_pkgs = []
318 for pattern in pkgs:
319 _pkgs = []
320 for pkg in self.repos.whatprovides(pattern):
321 # Do not reinstall non-installed packages.
322 if not pkg.is_installed():
323 continue
324
325 _pkgs.append(pkg)
326
327 if not _pkgs:
328 log.warning(_("Could not find any installed package providing \"%s\".") \
329 % pattern)
330 elif len(_pkgs) == 1:
331 reinstall_pkgs.append(_pkgs[0])
332 #t.add("reinstall", _pkgs[0])
333 else:
334 log.warning(_("Multiple reinstall candidates for \"%s\": %s") \
335 % (pattern, ", ".join(p.friendly_name for p in sorted(_pkgs))))
336
337 if not reinstall_pkgs:
338 log.info(_("Nothing to do"))
339 return
340
341 # Packages we want to replace.
342 # Contains a tuple with the old and the new package.
343 pkgs = []
344
345 # Find the package that is installed in a remote repository to
346 # download it again and re-install it. We need that.
347 for pkg in reinstall_pkgs:
348 # Collect all candidates in here.
349 _pkgs = []
350
351 provides = "%s=%s" % (pkg.name, pkg.friendly_version)
352 for _pkg in self.repos.whatprovides(provides):
353 if _pkg.is_installed():
354 continue
355
356 if strict:
357 if pkg.uuid == _pkg.uuid:
358 _pkgs.append(_pkg)
359 else:
360 _pkgs.append(_pkg)
361
362 if not _pkgs:
363 log.warning(_("Could not find package %s in a remote repository.") % \
364 pkg.friendly_name)
365 else:
366 # Sort packages to reflect repository priorities, etc...
367 # and take the best (first) one.
368 _pkgs.sort()
369
370 # Re-install best package and cleanup the old one.
371 pkgs.append((pkg, _pkgs[0]))
372
373 # Eventually, create a request.
374 request = None
375
376 _pkgs = []
377 for old, new in pkgs:
378 if old.uuid == new.uuid:
379 _pkgs.append((old, new))
380 else:
381 if request is None:
382 # Create a new request.
383 request = self.create_request()
384
385 # Install the new package, the old will
386 # be cleaned up automatically.
387 request.install(new.solvable)
388
389 if request:
390 solver = self.create_solver()
391 t = solver.solve(request)
392 else:
393 # Create new transaction.
394 t = transaction.Transaction(self)
395
396 for old, new in _pkgs:
397 # Install the new package and remove the old one.
398 t.add(actions.ActionReinstall.type, new)
399 t.add(actions.ActionCleanup.type, old)
400
401 t.sort()
402
403 if not t:
404 log.info(_("Nothing to do"))
405 return
406
407 if not t.cli_yesno():
408 return
409
410 t.run()
411
412 def update(self, pkgs=None, check=False, excludes=None, interactive=True, logger=None, **kwargs):
413 """
414 check indicates, if the method should return after calculation
415 of the transaction.
416 """
417 request = self.create_request()
418
419 # If there are given any packets on the command line, we will
420 # only update them. Otherwise, we update the whole system.
421 if pkgs:
422 update = False
423 for pkg in pkgs:
424 pkg = self.create_relation(pkg)
425 request.update(pkg)
426 else:
427 update = True
428
429 # Exclude packages that should not be updated.
430 if excludes:
431 for exclude in excludes:
432 log.info(_("Excluding %s.") % exclude)
433
434 exclude = self.create_relation(exclude)
435 request.lock(exclude)
436
437 solver = self.create_solver()
438 t = solver.solve(request, update=update, **kwargs)
439
440 if not t:
441 log.info(_("Nothing to do"))
442
443 # If we are running in check mode, we return a non-zero value to
444 # indicate, that there are no updates.
445 if check:
446 return 1
447 else:
448 return
449
450 # Just exit here, because we won't do the transaction in this mode.
451 if check:
452 t.dump(logger=logger)
453 return
454
455 # Ask the user if the transaction is okay.
456 if interactive and not t.cli_yesno():
457 return
458
459 # Run the transaction.
460 t.run()
461
462 def downgrade(self, pkgs, allow_vendorchange=False, allow_archchange=False):
463 assert pkgs
464
465 # Create a new request.
466 request = self.create_request()
467
468 # Fill request.
469 for pattern in pkgs:
470 best = None
471 for pkg in self.repos.whatprovides(pattern):
472 # Only consider installed packages.
473 if not pkg.is_installed():
474 continue
475
476 if best and pkg > best:
477 best = pkg
478 elif best is None:
479 best = pkg
480
481 if best is None:
482 log.warning(_("\"%s\" package does not seem to be installed.") % pattern)
483 else:
484 rel = self.create_relation("%s<%s" % (best.name, best.friendly_version))
485 request.install(rel)
486
487 # Solve the request.
488 solver = self.create_solver()
489 t = solver.solve(request, allow_downgrade=True,
490 allow_vendorchange=allow_vendorchange,
491 allow_archchange=allow_archchange)
492
493 if not t:
494 log.info(_("Nothing to do"))
495 return
496
497 if not t.cli_yesno():
498 return
499
500 t.run()
501
502 def remove(self, pkgs):
503 # Create a new request.
504 request = self.create_request()
505 for pkg in pkgs:
506 pkg = self.create_relation(pkg)
507 request.remove(pkg)
508
509 # Solve the request.
510 solver = self.create_solver()
511 t = solver.solve(request, uninstall=True)
512
513 if not t:
514 log.info(_("Nothing to do"))
515 return
516
517 # Ask the user if okay.
518 if not t.cli_yesno():
519 return
520
521 # Process the transaction.
522 t.run()
523
524 def info(self, patterns):
525 pkgs = []
526
527 # For all patterns we run a single search which returns us a bunch
528 # of solvables which are transformed into Package objects.
529 for pattern in patterns:
530 if os.path.exists(pattern) and not os.path.isdir(pattern):
531 pkg = packages.open(self, self.repos.dummy, pattern)
532 if pkg:
533 pkgs.append(pkg)
534
535 else:
536 solvs = self.pool.search(pattern, satsolver.SEARCH_GLOB, "solvable:name")
537
538 for solv in solvs:
539 pkg = packages.SolvPackage(self, solv)
540 if pkg in pkgs:
541 continue
542
543 pkgs.append(pkg)
544
545 return sorted(pkgs)
546
547 def search(self, pattern):
548 # Do the search.
549 pkgs = {}
550 for solv in self.pool.search(pattern, satsolver.SEARCH_STRING|satsolver.SEARCH_FILES):
551 pkg = packages.SolvPackage(self, solv)
552
553 # Check, if a package with the name is already in the resultset
554 # and always replace older ones by more recent ones.
555 if pkgs.has_key(pkg.name):
556 if pkgs[pkg.name] < pkg:
557 pkgs[pkg.name] = pkg
558 else:
559 pkgs[pkg.name] = pkg
560
561 # Return a list of the packages, alphabetically sorted.
562 return sorted(pkgs.values())
563
564 def groupinstall(self, group, **kwargs):
565 self.install("@%s" % group, **kwargs)
566
567 def grouplist(self, group):
568 pkgs = []
569
570 for solv in self.pool.search(group, satsolver.SEARCH_SUBSTRING, "solvable:group"):
571 pkg = packages.SolvPackage(self, solv)
572
573 if group in pkg.groups and not pkg.name in pkgs:
574 pkgs.append(pkg.name)
575
576 return sorted(pkgs)
577
578 @staticmethod
579 def build(pkg, resultdirs=None, shell=False, install_test=True, after_shell=False, **kwargs):
580 if not resultdirs:
581 resultdirs = []
582
583 b = builder.BuildEnviron(pkg, **kwargs)
584 p = b.pakfire
585
586 # Always include local repository.
587 resultdirs.append(p.repos.local_build.path)
588
589 try:
590 # Start to prepare the build environment by mounting
591 # the filesystems and extracting files.
592 b.start()
593
594 try:
595 # Build the package.
596 b.build(install_test=install_test)
597 except BuildError:
598 # Raise the error, if the user does not want to
599 # have a shell.
600 if not shell:
601 raise
602
603 # Run a shell to debug the issue.
604 b.shell()
605
606 # If the user requests a shell after a successful build,
607 # we run it here.
608 if after_shell:
609 b.shell()
610
611 # Copy-out all resultfiles if the build was successful.
612 for resultdir in resultdirs:
613 if not resultdir:
614 continue
615
616 b.copy_result(resultdir)
617 finally:
618 b.stop()
619
620 def _build(self, pkg, resultdir, nodeps=False, **kwargs):
621 b = builder.Builder(self, pkg, resultdir, **kwargs)
622
623 try:
624 b.build()
625 except Error:
626 raise BuildError, _("Build command has failed.")
627
628 # If the build was successful, cleanup all temporary files.
629 b.cleanup()
630
631 @staticmethod
632 def shell(pkg, **kwargs):
633 b = builder.BuildEnviron(pkg, **kwargs)
634
635 try:
636 b.start()
637 b.shell()
638 finally:
639 b.stop()
640
641 def dist(self, pkgs, resultdirs=None):
642 assert resultdirs
643
644 for pkg in pkgs:
645 pkg = packages.Makefile(self, pkg)
646
647 pkg.dist(resultdirs)
648
649 def provides(self, patterns):
650 pkgs = []
651 for pattern in patterns:
652 for pkg in self.repos.whatprovides(pattern):
653 if pkg in pkgs:
654 continue
655
656 pkgs.append(pkg)
657
658 return sorted(pkgs)
659
660 def repo_create(self, path, input_paths, type="binary"):
661 assert type in ("binary", "source",)
662
663 repo = repository.RepositoryDir(
664 self,
665 name="new",
666 description="New repository.",
667 path=path,
668 type=type,
669 )
670
671 for input_path in input_paths:
672 repo.collect_packages(input_path)
673
674 repo.save()
675
676 return repo
677
678 def repo_list(self):
679 return [r for r in self.repos]
680
681 def clean_all(self):
682 log.debug("Cleaning up everything...")
683
684 # Clean up repository caches.
685 self.repos.clean()
686
687 def check(self, downgrade=True, uninstall=True):
688 """
689 Try to fix any errors in the system.
690 """
691 # Detect any errors in the dependency tree.
692 # For that we create an empty request and solver and try to solve
693 # something.
694 request = self.create_request()
695 solver = self.create_solver()
696
697 # XXX the solver does crash if we call it with fix_system=1,
698 # allow_downgrade=1 and uninstall=1. Need to fix this.
699 allow_downgrade = False
700 uninstall = False
701
702 t = solver.solve(request, fix_system=True, allow_downgrade=downgrade,
703 uninstall=uninstall)
704
705 if not t:
706 log.info(_("Everything is fine."))
707 return
708
709 # Ask the user if okay.
710 if not t.cli_yesno():
711 return
712
713 # Process the transaction.
714 t.run()
715
716 @staticmethod
717 def cache_create(**kwargs):
718 # Create a build environment that we are going to pack into
719 # a shiny tarball.
720 b = builder.BuildEnviron(**kwargs)
721 p = b.pakfire
722
723 # Get filename of the file from builder instance.
724 filename = b.cache_file
725
726 try:
727 b.start()
728
729 # Create directory if not existant.
730 dirname = os.path.dirname(filename)
731 if not os.path.exists(dirname):
732 os.makedirs(dirname)
733
734 b.cache_export(filename)
735 finally:
736 b.stop()