]> git.ipfire.org Git - people/stevee/pakfire.git/blame - python/pakfire/base.py
Only load files with ".repo" extension from configuration dir.
[people/stevee/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
53bb7960 28import config
7c8f2953 29import distro
862bea4d 30import filelist
7c8f2953 31import logger
ae20b05f 32import packages
0ca71090 33import repository
c605d735 34import satsolver
0ca71090 35import transaction
7c8f2953
MT
36import util
37
8b6bc023
MT
38import logging
39log = logging.getLogger("pakfire")
40
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
eb34496a 53 def __init__(self, mode=None, path="/", configs=[],
715d7009 54 enable_repos=None, disable_repos=None,
6a509182 55 distro_config=None, **kwargs):
6557ff4c
MT
56
57 # Set the mode.
6a509182 58 assert mode in ("normal", "builder", "server",)
6557ff4c
MT
59 self.mode = mode
60
7c8f2953
MT
61 # Check if we are operating as the root user.
62 self.check_root_user()
63
64 # The path where we are operating in.
6557ff4c
MT
65 self.path = path
66
67 # Configure the instance of Pakfire we just started.
68 if mode == "builder":
7c8f2953 69 self.path = os.path.join(BUILD_ROOT, util.random_string())
7c8f2953 70
6557ff4c 71 elif mode == "normal":
7b1e25fd 72 # check if we are actually running on an ipfire system.
0891edd2
MT
73 if self.path == "/":
74 self.check_is_ipfire()
7c8f2953
MT
75
76 # Read configuration file(s)
6557ff4c 77 self.config = config.Config(type=mode)
7c8f2953
MT
78 for filename in configs:
79 self.config.read(filename)
6a509182
MT
80 # Assume, that all other keyword arguments are configuration
81 # parameters.
82 self.config.update(kwargs)
7c8f2953
MT
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
53bb7960 90 self.distro = distro.Distribution(self, distro_config)
c605d735 91 self.pool = satsolver.Pool(self.distro.arch)
ae20b05f
MT
92 self.repos = repository.Repositories(self,
93 enable_repos=enable_repos, disable_repos=disable_repos)
7c8f2953 94
60845a36
MT
95 def __del__(self):
96 # Reset logging.
97 logger.setup_logging()
98
c605d735
MT
99 def create_solver(self):
100 return satsolver.Solver(self, self.pool)
101
063606f6
MT
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
7c8f2953 110
018127aa
MT
111 def create_relation(self, s):
112 assert s
113
862bea4d
MT
114 if isinstance(s, filelist._File):
115 return satsolver.Relation(self.pool, s.name)
116
117 elif s.startswith("/"):
018127aa
MT
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
7c8f2953
MT
130 def destroy(self):
131 if not self.path == "/":
132 util.rm(self.path)
133
c07a3ca7
MT
134 @property
135 def environ(self):
136 env = {}
137
138 # Get distribution information.
139 env.update(self.distro.environ)
140
141 return env
142
7c8f2953
MT
143 @property
144 def supported_arches(self):
3ad4bb5a 145 return self.config.supported_arches
7c8f2953 146
6a509182
MT
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
7c8f2953
MT
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 """
6557ff4c 163 if not self.mode == "builder":
7c8f2953
MT
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 """
7c8f2953
MT
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
3ad4bb5a 175 if not self.config.host_supports_arch(arch):
7c8f2953
MT
176 raise BuildError, "Cannot build for the target architecture: %s" % arch
177
178 raise BuildError, arch
18edfe75 179
7b1e25fd 180 def check_is_ipfire(self):
c07a3ca7
MT
181 return # XXX disabled for now
182
7b1e25fd
MT
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
6557ff4c
MT
188 @property
189 def builder(self):
190 # XXX just backwards compatibility
191 return self.mode == "builder"
192
b25a3d84
MT
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:
8b6bc023 207 log.info(_("Nothing to do"))
b25a3d84 208
c07a3ca7
MT
209 def install(self, requires, interactive=True, logger=None, **kwargs):
210 if not logger:
8b6bc023 211 logger = logging.getLogger("pakfire")
c07a3ca7 212
ae20b05f 213 # Create a new request.
84680c15 214 request = self.create_request()
c07a3ca7
MT
215
216 # Expand all groups.
18edfe75 217 for req in requires:
c07a3ca7
MT
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)
18edfe75 228
ae20b05f 229 # Do the solving.
84680c15 230 solver = self.create_solver()
c07a3ca7 231 t = solver.solve(request, **kwargs)
18edfe75 232
ae20b05f 233 if not t:
c07a3ca7
MT
234 if not interactive:
235 raise DependencyError
236
8b6bc023 237 log.info(_("Nothing to do"))
18edfe75
MT
238 return
239
c07a3ca7
MT
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)
c0fd807c
MT
247
248 # Run the transaction.
ae20b05f 249 t.run()
18edfe75 250
325e6065 251 def localinstall(self, files, yes=None, allow_uninstall=False):
de7fba8f 252 repo_name = repo_desc = "localinstall"
e0b99370
MT
253
254 # Create a new repository that holds all packages we passed on
255 # the commandline.
022c792a
MT
256 repo = repository.RepositoryDir(self, repo_name, repo_desc,
257 os.path.join(LOCAL_TMP_PATH, "repo_%s" % util.random_string()))
e0b99370 258
022c792a
MT
259 # Register the repository.
260 self.repos.add_repo(repo)
e0b99370 261
022c792a
MT
262 try:
263 # Add all packages to the repository index.
264 for file in files:
265 repo.collect_packages(file)
e0b99370 266
022c792a
MT
267 # Break if no packages were added at all.
268 if not len(repo):
8b6bc023 269 log.critical(_("There are no packages to install."))
022c792a 270 return
e0b99370 271
022c792a
MT
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)
e0b99370 277
022c792a 278 solver = self.create_solver()
325e6065 279 t = solver.solve(request, uninstall=allow_uninstall)
e0b99370 280
022c792a
MT
281 # If solving was not possible, we exit here.
282 if not t:
8b6bc023 283 log.info(_("Nothing to do"))
022c792a 284 return
c0fd807c 285
08d60af8
MT
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:
022c792a
MT
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()
75bb74a7 301 self.repos.rem_repo(repo)
e0b99370 302
0ca71090
MT
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:
8b6bc023 324 log.warning(_("Could not find any installed package providing \"%s\".") \
0ca71090
MT
325 % pattern)
326 elif len(_pkgs) == 1:
327 reinstall_pkgs.append(_pkgs[0])
328 #t.add("reinstall", _pkgs[0])
329 else:
8b6bc023 330 log.warning(_("Multiple reinstall candidates for \"%s\": %s") \
0ca71090
MT
331 % (pattern, ", ".join(p.friendly_name for p in sorted(_pkgs))))
332
333 if not reinstall_pkgs:
8b6bc023 334 log.info(_("Nothing to do"))
0ca71090
MT
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:
8b6bc023 359 log.warning(_("Could not find package %s in a remote repository.") % \
0ca71090
MT
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:
8b6bc023 400 log.info(_("Nothing to do"))
0ca71090
MT
401 return
402
403 if not t.cli_yesno():
404 return
405
406 t.run()
407
05fb1da0 408 def update(self, pkgs, check=False, excludes=None, allow_vendorchange=False, allow_archchange=False):
e38914be
MT
409 """
410 check indicates, if the method should return after calculation
411 of the transaction.
412 """
84680c15 413 request = self.create_request()
0c1a80b8 414
6c395339
MT
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:
b34c426d 420 pkg = self.create_relation(pkg)
6c395339
MT
421 request.update(pkg)
422 else:
423 update = True
0c1a80b8 424
8834f7c9
MT
425 # Exclude packages that should not be updated.
426 if excludes:
427 for exclude in excludes:
8b6bc023 428 log.info(_("Excluding %s.") % exclude)
8834f7c9
MT
429
430 exclude = self.create_relation(exclude)
431 request.lock(exclude)
432
84680c15 433 solver = self.create_solver()
05fb1da0
MT
434 t = solver.solve(request, update=update,
435 allow_vendorchange=allow_vendorchange,
436 allow_archchange=allow_archchange)
0c1a80b8
MT
437
438 if not t:
8b6bc023 439 log.info(_("Nothing to do"))
e38914be
MT
440
441 # If we are running in check mode, we return a non-zero value to
442 # indicate, that there are no updates.
443 if check:
444 return 1
445 else:
446 return
447
448 # Just exit here, because we won't do the transaction in this mode.
449 if check:
450 t.dump()
0c1a80b8
MT
451 return
452
c0fd807c
MT
453 # Ask the user if the transaction is okay.
454 if not t.cli_yesno():
455 return
0c1a80b8 456
c0fd807c 457 # Run the transaction.
0c1a80b8 458 t.run()
18edfe75 459
67d1ddbd
MT
460 def downgrade(self, pkgs, allow_vendorchange=False, allow_archchange=False):
461 assert pkgs
462
463 # Create a new request.
464 request = self.create_request()
465
466 # Fill request.
467 for pattern in pkgs:
468 best = None
469 for pkg in self.repos.whatprovides(pattern):
470 # Only consider installed packages.
471 if not pkg.is_installed():
472 continue
473
474 if best and pkg > best:
475 best = pkg
476 elif best is None:
477 best = pkg
478
479 if best is None:
8b6bc023 480 log.warning(_("\"%s\" package does not seem to be installed.") % pattern)
67d1ddbd
MT
481 else:
482 rel = self.create_relation("%s<%s" % (best.name, best.friendly_version))
483 request.install(rel)
484
485 # Solve the request.
486 solver = self.create_solver()
487 t = solver.solve(request, allow_downgrade=True,
488 allow_vendorchange=allow_vendorchange,
489 allow_archchange=allow_archchange)
490
491 if not t:
8b6bc023 492 log.info(_("Nothing to do"))
67d1ddbd
MT
493 return
494
495 if not t.cli_yesno():
496 return
497
498 t.run()
499
a39fd08b
MT
500 def remove(self, pkgs):
501 # Create a new request.
84680c15 502 request = self.create_request()
a39fd08b 503 for pkg in pkgs:
b34c426d 504 pkg = self.create_relation(pkg)
a39fd08b
MT
505 request.remove(pkg)
506
507 # Solve the request.
84680c15 508 solver = self.create_solver()
6eb8f338 509 t = solver.solve(request, uninstall=True)
a39fd08b
MT
510
511 if not t:
8b6bc023 512 log.info(_("Nothing to do"))
a39fd08b
MT
513 return
514
c0fd807c
MT
515 # Ask the user if okay.
516 if not t.cli_yesno():
517 return
518
519 # Process the transaction.
a39fd08b
MT
520 t.run()
521
c1e7f927 522 def info(self, patterns):
18edfe75
MT
523 pkgs = []
524
1f27e8fe
MT
525 # For all patterns we run a single search which returns us a bunch
526 # of solvables which are transformed into Package objects.
18edfe75 527 for pattern in patterns:
5dda5744 528 if os.path.exists(pattern) and not os.path.isdir(pattern):
c07a3ca7
MT
529 pkg = packages.open(self, self.repos.dummy, pattern)
530 if pkg:
531 pkgs.append(pkg)
1f27e8fe 532
c07a3ca7
MT
533 else:
534 solvs = self.pool.search(pattern, satsolver.SEARCH_GLOB, "solvable:name")
e1972777 535
c07a3ca7
MT
536 for solv in solvs:
537 pkg = packages.SolvPackage(self, solv)
538 if pkg in pkgs:
539 continue
540
541 pkgs.append(pkg)
18edfe75 542
e1972777 543 return sorted(pkgs)
18edfe75
MT
544
545 def search(self, pattern):
546 # Do the search.
c2e67297 547 pkgs = {}
884cd2fb 548 for solv in self.pool.search(pattern, satsolver.SEARCH_STRING|satsolver.SEARCH_FILES):
c2e67297 549 pkg = packages.SolvPackage(self, solv)
18edfe75 550
c2e67297
MT
551 # Check, if a package with the name is already in the resultset
552 # and always replace older ones by more recent ones.
553 if pkgs.has_key(pkg.name):
554 if pkgs[pkg.name] < pkg:
555 pkgs[pkg.name] = pkg
556 else:
557 pkgs[pkg.name] = pkg
558
559 # Return a list of the packages, alphabetically sorted.
560 return sorted(pkgs.values())
18edfe75 561
c07a3ca7
MT
562 def groupinstall(self, group, **kwargs):
563 self.install("@%s" % group, **kwargs)
18edfe75
MT
564
565 def grouplist(self, group):
fec9a917
MT
566 pkgs = []
567
568 for solv in self.pool.search(group, satsolver.SEARCH_SUBSTRING, "solvable:group"):
569 pkg = packages.SolvPackage(self, solv)
18edfe75 570
fec9a917
MT
571 if group in pkg.groups and not pkg.name in pkgs:
572 pkgs.append(pkg.name)
18edfe75 573
fec9a917 574 return sorted(pkgs)
18edfe75
MT
575
576 @staticmethod
1710ccec 577 def build(pkg, resultdirs=None, shell=False, install_test=True, after_shell=False, **kwargs):
18edfe75
MT
578 if not resultdirs:
579 resultdirs = []
580
ff9299d0 581 b = builder.BuildEnviron(pkg, **kwargs)
18edfe75
MT
582 p = b.pakfire
583
584 # Always include local repository.
585 resultdirs.append(p.repos.local_build.path)
586
587 try:
8930b228
MT
588 # Start to prepare the build environment by mounting
589 # the filesystems and extracting files.
590 b.start()
591
1710ccec
MT
592 try:
593 # Build the package.
594 b.build(install_test=install_test)
595 except BuildError:
596 # Raise the error, if the user does not want to
597 # have a shell.
598 if not shell:
599 raise
18edfe75 600
1710ccec
MT
601 # Run a shell to debug the issue.
602 b.shell()
603
604 # If the user requests a shell after a successful build,
605 # we run it here.
606 if after_shell:
607 b.shell()
608
609 # Copy-out all resultfiles if the build was successful.
18edfe75
MT
610 for resultdir in resultdirs:
611 if not resultdir:
612 continue
613
614 b.copy_result(resultdir)
18edfe75 615 finally:
8930b228 616 b.stop()
18edfe75 617
c07a3ca7 618 def _build(self, pkg, resultdir, nodeps=False, **kwargs):
ff9299d0 619 b = builder.Builder(self, pkg, resultdir, **kwargs)
c07a3ca7
MT
620
621 try:
622 b.build()
623 except Error:
624 raise BuildError, _("Build command has failed.")
0d96815e
MT
625
626 # If the build was successful, cleanup all temporary files.
627 b.cleanup()
c07a3ca7 628
18edfe75
MT
629 @staticmethod
630 def shell(pkg, **kwargs):
ff9299d0 631 b = builder.BuildEnviron(pkg, **kwargs)
18edfe75
MT
632
633 try:
8930b228 634 b.start()
18edfe75
MT
635 b.shell()
636 finally:
8930b228 637 b.stop()
18edfe75 638
c07a3ca7 639 def dist(self, pkgs, resultdirs=None):
269c59f3 640 assert resultdirs
18edfe75 641
c07a3ca7
MT
642 for pkg in pkgs:
643 pkg = packages.Makefile(self, pkg)
50f96897 644
c07a3ca7 645 pkg.dist(resultdirs)
18edfe75
MT
646
647 def provides(self, patterns):
648 pkgs = []
649 for pattern in patterns:
e1972777
MT
650 for pkg in self.repos.whatprovides(pattern):
651 if pkg in pkgs:
652 continue
18edfe75 653
e1972777 654 pkgs.append(pkg)
18edfe75 655
e1972777 656 return sorted(pkgs)
18edfe75 657
8276111d
MT
658 def repo_create(self, path, input_paths, type="binary"):
659 assert type in ("binary", "source",)
660
c605d735 661 repo = repository.RepositoryDir(
18edfe75
MT
662 self,
663 name="new",
664 description="New repository.",
665 path=path,
8276111d 666 type=type,
18edfe75
MT
667 )
668
669 for input_path in input_paths:
c605d735 670 repo.collect_packages(input_path)
18edfe75
MT
671
672 repo.save()
673
8276111d
MT
674 return repo
675
18edfe75 676 def repo_list(self):
c605d735 677 return [r for r in self.repos]
31267a64
MT
678
679 def clean_all(self):
8b6bc023 680 log.debug("Cleaning up everything...")
31267a64
MT
681
682 # Clean up repository caches.
683 self.repos.clean()
35d89fd7
MT
684
685 def check(self, downgrade=True, uninstall=True):
686 """
687 Try to fix any errors in the system.
688 """
689 # Detect any errors in the dependency tree.
690 # For that we create an empty request and solver and try to solve
691 # something.
692 request = self.create_request()
693 solver = self.create_solver()
694
695 # XXX the solver does crash if we call it with fix_system=1,
696 # allow_downgrade=1 and uninstall=1. Need to fix this.
697 allow_downgrade = False
698 uninstall = False
699
700 t = solver.solve(request, fix_system=True, allow_downgrade=downgrade,
701 uninstall=uninstall)
702
703 if not t:
8b6bc023 704 log.info(_("Everything is fine."))
35d89fd7
MT
705 return
706
707 # Ask the user if okay.
708 if not t.cli_yesno():
709 return
710
711 # Process the transaction.
712 t.run()