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