]>
git.ipfire.org Git - pakfire.git/blob - python/pakfire/base.py
2 ###############################################################################
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2011 Pakfire development team #
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. #
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. #
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/>. #
20 ###############################################################################
39 log
= logging
.getLogger("pakfire")
41 from constants
import *
44 class Pakfire(object):
46 (">=", satsolver
.REL_GE
,),
47 ("<=", satsolver
.REL_LE
,),
48 ("=" , satsolver
.REL_EQ
,),
49 ("<" , satsolver
.REL_LT
,),
50 (">" , satsolver
.REL_GT
,),
53 def __init__(self
, mode
=None, path
="/", configs
=[],
54 enable_repos
=None, disable_repos
=None,
55 distro_config
=None, **kwargs
):
58 assert mode
in ("normal", "builder", "server",)
61 # Check if we are operating as the root user.
62 self
.check_root_user()
64 # The path where we are operating in.
67 # Configure the instance of Pakfire we just started.
69 self
.path
= os
.path
.join(BUILD_ROOT
, util
.random_string())
71 elif mode
== "normal":
72 # check if we are actually running on an ipfire system.
74 self
.check_is_ipfire()
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
82 self
.config
.update(kwargs
)
85 logger
.setup_logging(self
.config
)
88 # Get more information about the distribution we are running
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
)
97 logger
.setup_logging()
99 def create_solver(self
):
100 return satsolver
.Solver(self
, self
.pool
)
102 def create_request(self
, builder
=False):
103 request
= satsolver
.Request(self
.pool
)
105 # Add multiinstall information.
106 for solv
in PAKFIRE_MULTIINSTALL
:
107 request
.noobsoletes(solv
)
111 def create_relation(self
, s
):
114 if isinstance(s
, filelist
._File
):
115 return satsolver
.Relation(self
.pool
, s
.name
)
117 elif s
.startswith("/"):
118 return satsolver
.Relation(self
.pool
, s
)
120 for pattern
, type in self
.RELATIONS
:
124 name
, version
= s
.split(pattern
, 1)
126 return satsolver
.Relation(self
.pool
, name
, version
, type)
128 return satsolver
.Relation(self
.pool
, s
)
131 if not self
.path
== "/":
138 # Get distribution information.
139 env
.update(self
.distro
.environ
)
144 def supported_arches(self
):
145 return self
.config
.supported_arches
150 A shortcut that indicates if the system is running in offline mode.
152 return self
.config
.get("offline", False)
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."
158 def check_build_mode(self
):
160 Check if we are running in build mode.
161 Otherwise, raise an exception.
163 if not self
.mode
== "builder":
164 raise BuildError
, "Cannot build when not in build mode."
166 def check_host_arch(self
, arch
):
168 Check if we can build for arch.
170 # If no arch was given on the command line we build for our
171 # own arch which should always work.
175 if not self
.config
.host_supports_arch(arch
):
176 raise BuildError
, "Cannot build for the target architecture: %s" % arch
178 raise BuildError
, arch
180 def check_is_ipfire(self
):
181 return # XXX disabled for now
183 ret
= os
.path
.exists("/etc/ipfire-release")
186 raise NotAnIPFireSystemError
, "You can run pakfire only on an IPFire system"
190 # XXX just backwards compatibility
191 return self
.mode
== "builder"
193 def resolvdep(self
, requires
):
194 # Create a new request.
195 request
= self
.create_request()
197 req
= self
.create_relation(req
)
201 solver
= self
.create_solver()
202 t
= solver
.solve(request
)
207 log
.info(_("Nothing to do"))
209 def install(self
, requires
, interactive
=True, logger
=None, **kwargs
):
211 logger
= logging
.getLogger("pakfire")
213 # Create a new request.
214 request
= self
.create_request()
218 if req
.startswith("@"):
219 reqs
= self
.grouplist(req
[1:])
224 if not isinstance(req
, packages
.BinaryPackage
):
225 req
= self
.create_relation(req
)
230 solver
= self
.create_solver()
231 t
= solver
.solve(request
, **kwargs
)
235 raise DependencyError
237 log
.info(_("Nothing to do"))
241 # Ask if the user acknowledges the transaction.
242 if not t
.cli_yesno():
246 t
.dump(logger
=logger
)
248 # Run the transaction.
251 def localinstall(self
, files
, yes
=None, allow_uninstall
=False):
252 repo_name
= repo_desc
= "localinstall"
254 # Create a new repository that holds all packages we passed on
256 repo
= repository
.RepositoryDir(self
, repo_name
, repo_desc
,
257 os
.path
.join(LOCAL_TMP_PATH
, "repo_%s" % util
.random_string()))
259 # Register the repository.
260 self
.repos
.add_repo(repo
)
263 # Add all packages to the repository index.
265 repo
.collect_packages(file)
267 # Break if no packages were added at all.
269 log
.critical(_("There are no packages to install."))
272 # Create a new request that installs all solvables from the
274 request
= self
.create_request()
275 for solv
in [p
.solvable
for p
in repo
]:
276 request
.install(solv
)
278 solver
= self
.create_solver()
279 t
= solver
.solve(request
, uninstall
=allow_uninstall
)
281 # If solving was not possible, we exit here.
283 log
.info(_("Nothing to do"))
287 # Ask the user if this is okay.
288 if not t
.cli_yesno():
295 # If okay, run the transcation.
299 # Remove the temporary copy of the repository we have created earlier.
301 self
.repos
.rem_repo(repo
)
303 def reinstall(self
, pkgs
, strict
=False):
305 Reinstall one or more packages.
307 If strict is True, only a package with excatly the same UUID
308 will replace the currently installed one.
310 # XXX it is possible to install packages without fulfulling
316 for pkg
in self
.repos
.whatprovides(pattern
):
317 # Do not reinstall non-installed packages.
318 if not pkg
.is_installed():
324 log
.warning(_("Could not find any installed package providing \"%s\".") \
326 elif len(_pkgs
) == 1:
327 reinstall_pkgs
.append(_pkgs
[0])
328 #t.add("reinstall", _pkgs[0])
330 log
.warning(_("Multiple reinstall candidates for \"%s\": %s") \
331 % (pattern
, ", ".join(p
.friendly_name
for p
in sorted(_pkgs
))))
333 if not reinstall_pkgs
:
334 log
.info(_("Nothing to do"))
337 # Packages we want to replace.
338 # Contains a tuple with the old and the new package.
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.
347 provides
= "%s=%s" % (pkg
.name
, pkg
.friendly_version
)
348 for _pkg
in self
.repos
.whatprovides(provides
):
349 if _pkg
.is_installed():
353 if pkg
.uuid
== _pkg
.uuid
:
359 log
.warning(_("Could not find package %s in a remote repository.") % \
362 # Sort packages to reflect repository priorities, etc...
363 # and take the best (first) one.
366 # Re-install best package and cleanup the old one.
367 pkgs
.append((pkg
, _pkgs
[0]))
369 # Eventually, create a request.
373 for old
, new
in pkgs
:
374 if old
.uuid
== new
.uuid
:
375 _pkgs
.append((old
, new
))
378 # Create a new request.
379 request
= self
.create_request()
381 # Install the new package, the old will
382 # be cleaned up automatically.
383 request
.install(new
.solvable
)
386 solver
= self
.create_solver()
387 t
= solver
.solve(request
)
389 # Create new transaction.
390 t
= transaction
.Transaction(self
)
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
)
400 log
.info(_("Nothing to do"))
403 if not t
.cli_yesno():
408 def update(self
, pkgs
=None, check
=False, excludes
=None, interactive
=True, logger
=None, **kwargs
):
410 check indicates, if the method should return after calculation
413 request
= self
.create_request()
415 # If there are given any packets on the command line, we will
416 # only update them. Otherwise, we update the whole system.
420 pkg
= self
.create_relation(pkg
)
425 # Exclude packages that should not be updated.
427 for exclude
in excludes
:
428 log
.info(_("Excluding %s.") % exclude
)
430 exclude
= self
.create_relation(exclude
)
431 request
.lock(exclude
)
433 solver
= self
.create_solver()
434 t
= solver
.solve(request
, update
=update
, **kwargs
)
437 log
.info(_("Nothing to do"))
439 # If we are running in check mode, we return a non-zero value to
440 # indicate, that there are no updates.
446 # Just exit here, because we won't do the transaction in this mode.
448 t
.dump(logger
=logger
)
451 # Ask the user if the transaction is okay.
452 if interactive
and not t
.cli_yesno():
455 # Run the transaction.
458 def downgrade(self
, pkgs
, allow_vendorchange
=False, allow_archchange
=False):
461 # Create a new request.
462 request
= self
.create_request()
467 for pkg
in self
.repos
.whatprovides(pattern
):
468 # Only consider installed packages.
469 if not pkg
.is_installed():
472 if best
and pkg
> best
:
478 log
.warning(_("\"%s\" package does not seem to be installed.") % pattern
)
480 rel
= self
.create_relation("%s<%s" % (best
.name
, best
.friendly_version
))
484 solver
= self
.create_solver()
485 t
= solver
.solve(request
, allow_downgrade
=True,
486 allow_vendorchange
=allow_vendorchange
,
487 allow_archchange
=allow_archchange
)
490 log
.info(_("Nothing to do"))
493 if not t
.cli_yesno():
498 def remove(self
, pkgs
):
499 # Create a new request.
500 request
= self
.create_request()
502 pkg
= self
.create_relation(pkg
)
506 solver
= self
.create_solver()
507 t
= solver
.solve(request
, uninstall
=True)
510 log
.info(_("Nothing to do"))
513 # Ask the user if okay.
514 if not t
.cli_yesno():
517 # Process the transaction.
520 def info(self
, patterns
):
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
)
532 solvs
= self
.pool
.search(pattern
, satsolver
.SEARCH_GLOB
, "solvable:name")
535 pkg
= packages
.SolvPackage(self
, solv
)
543 def search(self
, pattern
):
546 for solv
in self
.pool
.search(pattern
, satsolver
.SEARCH_STRING|satsolver
.SEARCH_FILES
):
547 pkg
= packages
.SolvPackage(self
, solv
)
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
:
557 # Return a list of the packages, alphabetically sorted.
558 return sorted(pkgs
.values())
560 def groupinstall(self
, group
, **kwargs
):
561 self
.install("@%s" % group
, **kwargs
)
563 def grouplist(self
, group
):
566 for solv
in self
.pool
.search(group
, satsolver
.SEARCH_SUBSTRING
, "solvable:group"):
567 pkg
= packages
.SolvPackage(self
, solv
)
569 if group
in pkg
.groups
and not pkg
.name
in pkgs
:
570 pkgs
.append(pkg
.name
)
575 def build(pkg
, resultdirs
=None, shell
=False, install_test
=True, after_shell
=False, **kwargs
):
579 b
= builder
.BuildEnviron(pkg
, **kwargs
)
582 # Always include local repository.
583 resultdirs
.append(p
.repos
.local_build
.path
)
586 # Start to prepare the build environment by mounting
587 # the filesystems and extracting files.
592 b
.build(install_test
=install_test
)
594 # Raise the error, if the user does not want to
599 # Run a shell to debug the issue.
602 # If the user requests a shell after a successful build,
607 # Copy-out all resultfiles if the build was successful.
608 for resultdir
in resultdirs
:
612 b
.copy_result(resultdir
)
616 def _build(self
, pkg
, resultdir
, nodeps
=False, **kwargs
):
617 b
= builder
.Builder(self
, pkg
, resultdir
, **kwargs
)
622 raise BuildError
, _("Build command has failed.")
624 # If the build was successful, cleanup all temporary files.
628 def shell(pkg
, **kwargs
):
629 b
= builder
.BuildEnviron(pkg
, **kwargs
)
637 def dist(self
, pkgs
, resultdirs
=None):
641 pkg
= packages
.Makefile(self
, pkg
)
645 def provides(self
, patterns
):
647 for pattern
in patterns
:
648 for pkg
in self
.repos
.whatprovides(pattern
):
656 def repo_create(self
, path
, input_paths
, type="binary"):
657 assert type in ("binary", "source",)
659 repo
= repository
.RepositoryDir(
662 description
="New repository.",
667 for input_path
in input_paths
:
668 repo
.collect_packages(input_path
)
675 return [r
for r
in self
.repos
]
678 log
.debug("Cleaning up everything...")
680 # Clean up repository caches.
683 def check(self
, downgrade
=True, uninstall
=True):
685 Try to fix any errors in the system.
687 # Detect any errors in the dependency tree.
688 # For that we create an empty request and solver and try to solve
690 request
= self
.create_request()
691 solver
= self
.create_solver()
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
698 t
= solver
.solve(request
, fix_system
=True, allow_downgrade
=downgrade
,
702 log
.info(_("Everything is fine."))
705 # Ask the user if okay.
706 if not t
.cli_yesno():
709 # Process the transaction.
713 def cache_create(**kwargs
):
714 # Create a build environment that we are going to pack into
716 b
= builder
.BuildEnviron(**kwargs
)
719 # Get filename of the file from builder instance.
720 filename
= b
.cache_file
725 # Create directory if not existant.
726 dirname
= os
.path
.dirname(filename
)
727 if not os
.path
.exists(dirname
):
730 b
.cache_export(filename
)