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