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