]> git.ipfire.org Git - pakfire.git/blame_incremental - python/pakfire/base.py
Fix inner builder.
[pakfire.git] / python / pakfire / base.py
... / ...
CommitLineData
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
22import os
23import random
24import string
25
26import actions
27import builder
28import config
29import distro
30import filelist
31import keyring
32import logger
33import packages
34import repository
35import satsolver
36import transaction
37import util
38
39import logging
40log = logging.getLogger("pakfire")
41
42from config import Config
43from constants import *
44from i18n import _
45
46class Pakfire(object):
47 mode = None
48
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
53 # Check if we are operating as the root user.
54 self.check_root_user()
55
56 # The path where we are operating in.
57 self.path = path
58
59 # check if we are actually running on an ipfire system.
60 if not self.mode and self.path == "/":
61 self.check_is_ipfire()
62
63 # Get the configuration.
64 if config:
65 self.config = config
66 else:
67 self.config = self._load_config(configs)
68
69 # Update configuration with additional arguments.
70 for section, settings in kwargs.items():
71 self.config.update(section, settings)
72
73 # Dump the configuration.
74 self.config.dump()
75
76 # Initialize the keyring.
77 self.keyring = keyring.Keyring(self)
78
79 # Get more information about the distribution we are running
80 # or building
81 self.distro = distro.Distribution(self.config.get_distro_conf())
82 if arch:
83 self.distro.arch = arch
84
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)
105
106 def __del__(self):
107 # Reset logging.
108 logger.setup_logging()
109
110 def destroy(self):
111 self.repos.shutdown()
112
113 self.initialized = False
114
115 @property
116 def environ(self):
117 env = {}
118
119 # Get distribution information.
120 env.update(self.distro.environ)
121
122 return env
123
124 @property
125 def supported_arches(self):
126 return system.supported_arches
127
128 @property
129 def offline(self):
130 """
131 A shortcut that indicates if the system is running in offline mode.
132 """
133 return self.config.get("downloader", "offline", False)
134
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
139 def check_host_arch(self, arch):
140 """
141 Check if we can build for arch.
142 """
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
148 if not system.host_supports_arch(arch):
149 raise BuildError, "Cannot build for the target architecture: %s" % arch
150
151 raise BuildError, arch
152
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
159 @property
160 def builder(self):
161 # XXX just backwards compatibility
162 return self.mode == "builder"
163
164 def install(self, requires, interactive=True, logger=None, signatures_mode=None, **kwargs):
165 # Initialize this pakfire instance.
166 self.initialize()
167
168 if not logger:
169 logger = logging.getLogger("pakfire")
170
171 # Pointer to temporary repository.
172 repo = None
173
174 # Sort out what we got...
175 files = []
176 relations = []
177
178 for req in requires:
179 if isinstance(req, packages.Package):
180 relations.append(req)
181 continue
182
183 # This looks like a file.
184 if req.endswith(".%s" % PACKAGE_EXTENSION) and os.path.exists(req):
185 files.append(req)
186 continue
187
188 # We treat the rest as relations. The solver will return any errors.
189 relations.append(req)
190
191 # Redefine requires, which will be the list that will be passed to the
192 # solver.
193 requires = relations
194
195 try:
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()))
202
203 # Register the repository.
204 self.repos.add_repo(repo)
205
206 # Add all packages to the repository index.
207 repo.add_packages(*files)
208
209 # Add all packages to the requires.
210 requires += repo
211
212 # Do the solving.
213 request = self.pool.create_request(install=requires)
214 solver = self.pool.solve(request, logger=logger, interactive=interactive, **kwargs)
215
216 # Create the transaction.
217 t = transaction.Transaction.from_solver(self, solver)
218 t.dump(logger=logger)
219
220 # Ask if the user acknowledges the transaction.
221 if interactive and not t.cli_yesno():
222 return
223
224 # Run the transaction.
225 t.run(logger=logger, signatures_mode=signatures_mode)
226
227 finally:
228 if repo:
229 # Remove the temporary repository we have created earlier.
230 repo.remove()
231 self.repos.rem_repo(repo)
232
233 def reinstall(self, pkgs, strict=False, logger=None):
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 """
240 # Initialize this pakfire instance.
241 self.initialize()
242
243 if logger is None:
244 logger = logging.getLogger("pakfire")
245
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:
260 logger.warning(_("Could not find any installed package providing \"%s\".") \
261 % pattern)
262 elif len(_pkgs) == 1:
263 reinstall_pkgs.append(_pkgs[0])
264 #t.add("reinstall", _pkgs[0])
265 else:
266 logger.warning(_("Multiple reinstall candidates for \"%(pattern)s\": %(pkgs)s") \
267 % { "pattern" : pattern, "pkgs" : ", ".join(p.friendly_name for p in sorted(_pkgs)) })
268
269 if not reinstall_pkgs:
270 logger.info(_("Nothing to do"))
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:
295 logger.warning(_("Could not find package %s in a remote repository.") % \
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.
315 request = self.pool.create_request()
316
317 # Install the new package, the old will
318 # be cleaned up automatically.
319 request.install(new.solvable)
320
321 if request:
322 solver = self.pool.solve(request)
323 t = solver.transaction
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:
336 logger.info(_("Nothing to do"))
337 return
338
339 t.dump(logger=logger)
340
341 if not t.cli_yesno():
342 return
343
344 t.run(logger=logger)
345
346 def update(self, pkgs=None, check=False, excludes=None, interactive=True, logger=None, **kwargs):
347 """
348 check indicates, if the method should return after calculation
349 of the transaction.
350 """
351 # Initialize this pakfire instance.
352 self.initialize()
353
354 if logger is None:
355 logger = logging.getLogger("pakfire")
356
357 # If there are given any packets on the command line, we will
358 # only update them. Otherwise, we update the whole system.
359 updateall = True
360 if pkgs:
361 updateall = False
362
363 request = self.pool.create_request(update=pkgs, updateall=updateall)
364
365 # Exclude packages that should not be updated.
366 for exclude in excludes or []:
367 logger.info(_("Excluding %s.") % exclude)
368
369 exclude = self.pool.create_relation(exclude)
370 request.lock(exclude)
371
372 solver = self.pool.solve(request, logger=logger, **kwargs)
373
374 if not solver.status:
375 logger.info(_("Nothing to do"))
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
384 # Create the transaction.
385 t = solver.transaction
386 t.dump(logger=logger)
387
388 # Just exit here, because we won't do the transaction in this mode.
389 if check:
390 return
391
392 # Ask the user if the transaction is okay.
393 if interactive and not t.cli_yesno():
394 return
395
396 # Run the transaction.
397 t.run(logger=logger)
398
399 def downgrade(self, pkgs, allow_vendorchange=False, allow_archchange=False):
400 assert pkgs
401
402 # Initialize this pakfire instance.
403 self.initialize()
404
405 # Create a new request.
406 request = self.pool.create_request()
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:
422 log.warning(_("\"%s\" package does not seem to be installed.") % pattern)
423 else:
424 rel = self.pool.create_relation("%s < %s" % (best.name, best.friendly_version))
425 request.install(rel)
426
427 # Solve the request.
428 solver = self.pool.solve(request,
429 allow_downgrade=True,
430 allow_vendorchange=allow_vendorchange,
431 allow_archchange=allow_archchange,
432 )
433 assert solver.status is True
434
435 # Create the transaction.
436 t = solver.transaction
437 t.dump(logger=logger)
438
439 if not t:
440 log.info(_("Nothing to do"))
441 return
442
443 if not t.cli_yesno():
444 return
445
446 t.run()
447
448 def remove(self, pkgs):
449 # Initialize this pakfire instance.
450 self.initialize()
451
452 # Create a new request.
453 request = self.pool.create_request(remove=pkgs)
454
455 # Solve the request.
456 solver = self.pool.solve(request, allow_uninstall=True)
457 assert solver.status is True
458
459 # Create the transaction.
460 t = solver.transaction
461 t.dump()
462
463 if not t:
464 log.info(_("Nothing to do"))
465 return
466
467 # Ask the user if okay.
468 if not t.cli_yesno():
469 return
470
471 # Process the transaction.
472 t.run()
473
474 def info(self, patterns):
475 # Initialize this pakfire instance.
476 self.initialize()
477
478 pkgs = []
479
480 # For all patterns we run a single search which returns us a bunch
481 # of solvables which are transformed into Package objects.
482 for pattern in patterns:
483 if os.path.exists(pattern) and not os.path.isdir(pattern):
484 pkg = packages.open(self, self.repos.dummy, pattern)
485 if pkg:
486 pkgs.append(pkg)
487
488 else:
489 solvs = self.pool.search(pattern, satsolver.SEARCH_GLOB, "solvable:name")
490
491 for solv in solvs:
492 pkg = packages.SolvPackage(self, solv)
493 if pkg in pkgs:
494 continue
495
496 pkgs.append(pkg)
497
498 return sorted(pkgs)
499
500 def search(self, pattern):
501 # Initialize this pakfire instance.
502 self.initialize()
503
504 # Do the search.
505 pkgs = {}
506 for solv in self.pool.search(pattern, satsolver.SEARCH_STRING|satsolver.SEARCH_FILES):
507 pkg = packages.SolvPackage(self, solv)
508
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())
519
520 def groupinstall(self, group, **kwargs):
521 self.install("@%s" % group, **kwargs)
522
523 def grouplist(self, group):
524 # Initialize this pakfire instance.
525 self.initialize()
526
527 return self.pool.grouplist(group)
528
529 def provides(self, patterns):
530 # Initialize this pakfire instance.
531 self.initialize()
532
533 pkgs = []
534 for pattern in patterns:
535 for pkg in self.repos.whatprovides(pattern):
536 if pkg in pkgs:
537 continue
538
539 pkgs.append(pkg)
540
541 return sorted(pkgs)
542
543 def repo_list(self):
544 # Initialize this pakfire instance.
545 self.initialize()
546
547 return [r for r in self.repos]
548
549 def clean_all(self):
550 # Initialize this pakfire instance.
551 self.initialize()
552
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 """
562 # Initialize this pakfire instance.
563 self.initialize()
564
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
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
606
607class PakfireBuilder(Pakfire):
608 mode = "builder"
609
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
638 def dist(self, pkg, resultdir):
639 pkg = packages.Makefile(self, pkg)
640
641 return pkg.dist(resultdir=resultdir)
642
643 def build(self, pkg, resultdirs=None, shell=False, install_test=True, after_shell=False, **kwargs):
644
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)
648
649 b = builder.BuildEnviron(self, pkg, **kwargs)
650
651 try:
652 # Start to prepare the build environment by mounting
653 # the filesystems and extracting files.
654 b.start()
655
656 try:
657 # Build the package.
658 b.build(install_test=install_test)
659
660 except BuildError:
661 # Raise the error, if the user does not want to
662 # have a shell.
663 if not shell:
664 raise
665
666 # Run a shell to debug the issue.
667 b.shell()
668
669 # Copy-out all resultfiles if the build was successful.
670 if not resultdirs:
671 resultdirs = []
672
673 # Always include local repository.
674 resultdirs.append(self.repos.local_build.path)
675
676 for resultdir in resultdirs:
677 if not resultdir:
678 continue
679
680 b.copy_result(resultdir)
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
687 finally:
688 b.stop()
689
690 def shell(self, pkg, **kwargs):
691 b = builder.BuildEnviron(self, pkg, **kwargs)
692
693 try:
694 b.start()
695
696 try:
697 b.build(prepare=True)
698 except BuildError:
699 pass
700
701 b.shell()
702 finally:
703 b.stop()
704
705
706class PakfireServer(Pakfire):
707 mode = "server"
708
709 def repo_create(self, path, input_paths, name=None, key_id=None, type="binary"):
710 assert type in ("binary", "source",)
711
712 if not name:
713 name = _("New repository")
714
715 # Create new repository.
716 repo = repository.RepositoryDir(self, name=name, description="New repository.",
717 path=path, type=type, key_id=key_id)
718
719 # Add all packages.
720 repo.add_packages(*input_paths)
721
722 # Write metadata to disk.
723 repo.save()
724
725 # Return the new repository.
726 return repo