]>
git.ipfire.org Git - people/stevee/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 # Trim spaces from strings.
128 version
= version
.strip()
130 return satsolver
.Relation(self
.pool
, name
, version
, type)
132 return satsolver
.Relation(self
.pool
, s
)
135 if not self
.path
== "/":
142 # Get distribution information.
143 env
.update(self
.distro
.environ
)
148 def supported_arches(self
):
149 return self
.config
.supported_arches
154 A shortcut that indicates if the system is running in offline mode.
156 return self
.config
.get("offline", False)
158 def check_root_user(self
):
159 if not os
.getuid() == 0 or not os
.getgid() == 0:
160 raise Exception, "You must run pakfire as the root user."
162 def check_build_mode(self
):
164 Check if we are running in build mode.
165 Otherwise, raise an exception.
167 if not self
.mode
== "builder":
168 raise BuildError
, "Cannot build when not in build mode."
170 def check_host_arch(self
, arch
):
172 Check if we can build for arch.
174 # If no arch was given on the command line we build for our
175 # own arch which should always work.
179 if not self
.config
.host_supports_arch(arch
):
180 raise BuildError
, "Cannot build for the target architecture: %s" % arch
182 raise BuildError
, arch
184 def check_is_ipfire(self
):
185 return # XXX disabled for now
187 ret
= os
.path
.exists("/etc/ipfire-release")
190 raise NotAnIPFireSystemError
, "You can run pakfire only on an IPFire system"
194 # XXX just backwards compatibility
195 return self
.mode
== "builder"
197 def resolvdep(self
, requires
):
198 # Create a new request.
199 request
= self
.create_request()
201 req
= self
.create_relation(req
)
205 solver
= self
.create_solver()
206 t
= solver
.solve(request
)
211 log
.info(_("Nothing to do"))
213 def install(self
, requires
, interactive
=True, logger
=None, **kwargs
):
215 logger
= logging
.getLogger("pakfire")
217 # Create a new request.
218 request
= self
.create_request()
222 if req
.startswith("@"):
223 reqs
= self
.grouplist(req
[1:])
228 if not isinstance(req
, packages
.BinaryPackage
):
229 req
= self
.create_relation(req
)
234 solver
= self
.create_solver()
235 t
= solver
.solve(request
, **kwargs
)
239 raise DependencyError
241 log
.info(_("Nothing to do"))
245 # Ask if the user acknowledges the transaction.
246 if not t
.cli_yesno():
250 t
.dump(logger
=logger
)
252 # Run the transaction.
255 def localinstall(self
, files
, yes
=None, allow_uninstall
=False):
256 repo_name
= repo_desc
= "localinstall"
258 # Create a new repository that holds all packages we passed on
260 repo
= repository
.RepositoryDir(self
, repo_name
, repo_desc
,
261 os
.path
.join(LOCAL_TMP_PATH
, "repo_%s" % util
.random_string()))
263 # Register the repository.
264 self
.repos
.add_repo(repo
)
267 # Add all packages to the repository index.
269 repo
.collect_packages(file)
271 # Break if no packages were added at all.
273 log
.critical(_("There are no packages to install."))
276 # Create a new request that installs all solvables from the
278 request
= self
.create_request()
279 for solv
in [p
.solvable
for p
in repo
]:
280 request
.install(solv
)
282 solver
= self
.create_solver()
283 t
= solver
.solve(request
, uninstall
=allow_uninstall
)
285 # If solving was not possible, we exit here.
287 log
.info(_("Nothing to do"))
291 # Ask the user if this is okay.
292 if not t
.cli_yesno():
299 # If okay, run the transcation.
303 # Remove the temporary copy of the repository we have created earlier.
305 self
.repos
.rem_repo(repo
)
307 def reinstall(self
, pkgs
, strict
=False):
309 Reinstall one or more packages.
311 If strict is True, only a package with excatly the same UUID
312 will replace the currently installed one.
314 # XXX it is possible to install packages without fulfulling
320 for pkg
in self
.repos
.whatprovides(pattern
):
321 # Do not reinstall non-installed packages.
322 if not pkg
.is_installed():
328 log
.warning(_("Could not find any installed package providing \"%s\".") \
330 elif len(_pkgs
) == 1:
331 reinstall_pkgs
.append(_pkgs
[0])
332 #t.add("reinstall", _pkgs[0])
334 log
.warning(_("Multiple reinstall candidates for \"%s\": %s") \
335 % (pattern
, ", ".join(p
.friendly_name
for p
in sorted(_pkgs
))))
337 if not reinstall_pkgs
:
338 log
.info(_("Nothing to do"))
341 # Packages we want to replace.
342 # Contains a tuple with the old and the new package.
345 # Find the package that is installed in a remote repository to
346 # download it again and re-install it. We need that.
347 for pkg
in reinstall_pkgs
:
348 # Collect all candidates in here.
351 provides
= "%s=%s" % (pkg
.name
, pkg
.friendly_version
)
352 for _pkg
in self
.repos
.whatprovides(provides
):
353 if _pkg
.is_installed():
357 if pkg
.uuid
== _pkg
.uuid
:
363 log
.warning(_("Could not find package %s in a remote repository.") % \
366 # Sort packages to reflect repository priorities, etc...
367 # and take the best (first) one.
370 # Re-install best package and cleanup the old one.
371 pkgs
.append((pkg
, _pkgs
[0]))
373 # Eventually, create a request.
377 for old
, new
in pkgs
:
378 if old
.uuid
== new
.uuid
:
379 _pkgs
.append((old
, new
))
382 # Create a new request.
383 request
= self
.create_request()
385 # Install the new package, the old will
386 # be cleaned up automatically.
387 request
.install(new
.solvable
)
390 solver
= self
.create_solver()
391 t
= solver
.solve(request
)
393 # Create new transaction.
394 t
= transaction
.Transaction(self
)
396 for old
, new
in _pkgs
:
397 # Install the new package and remove the old one.
398 t
.add(actions
.ActionReinstall
.type, new
)
399 t
.add(actions
.ActionCleanup
.type, old
)
404 log
.info(_("Nothing to do"))
407 if not t
.cli_yesno():
412 def update(self
, pkgs
=None, check
=False, excludes
=None, interactive
=True, logger
=None, **kwargs
):
414 check indicates, if the method should return after calculation
417 request
= self
.create_request()
419 # If there are given any packets on the command line, we will
420 # only update them. Otherwise, we update the whole system.
424 pkg
= self
.create_relation(pkg
)
429 # Exclude packages that should not be updated.
431 for exclude
in excludes
:
432 log
.info(_("Excluding %s.") % exclude
)
434 exclude
= self
.create_relation(exclude
)
435 request
.lock(exclude
)
437 solver
= self
.create_solver()
438 t
= solver
.solve(request
, update
=update
, **kwargs
)
441 log
.info(_("Nothing to do"))
443 # If we are running in check mode, we return a non-zero value to
444 # indicate, that there are no updates.
450 # Just exit here, because we won't do the transaction in this mode.
452 t
.dump(logger
=logger
)
455 # Ask the user if the transaction is okay.
456 if interactive
and not t
.cli_yesno():
459 # Run the transaction.
462 def downgrade(self
, pkgs
, allow_vendorchange
=False, allow_archchange
=False):
465 # Create a new request.
466 request
= self
.create_request()
471 for pkg
in self
.repos
.whatprovides(pattern
):
472 # Only consider installed packages.
473 if not pkg
.is_installed():
476 if best
and pkg
> best
:
482 log
.warning(_("\"%s\" package does not seem to be installed.") % pattern
)
484 rel
= self
.create_relation("%s<%s" % (best
.name
, best
.friendly_version
))
488 solver
= self
.create_solver()
489 t
= solver
.solve(request
, allow_downgrade
=True,
490 allow_vendorchange
=allow_vendorchange
,
491 allow_archchange
=allow_archchange
)
494 log
.info(_("Nothing to do"))
497 if not t
.cli_yesno():
502 def remove(self
, pkgs
):
503 # Create a new request.
504 request
= self
.create_request()
506 pkg
= self
.create_relation(pkg
)
510 solver
= self
.create_solver()
511 t
= solver
.solve(request
, uninstall
=True)
514 log
.info(_("Nothing to do"))
517 # Ask the user if okay.
518 if not t
.cli_yesno():
521 # Process the transaction.
524 def info(self
, patterns
):
527 # For all patterns we run a single search which returns us a bunch
528 # of solvables which are transformed into Package objects.
529 for pattern
in patterns
:
530 if os
.path
.exists(pattern
) and not os
.path
.isdir(pattern
):
531 pkg
= packages
.open(self
, self
.repos
.dummy
, pattern
)
536 solvs
= self
.pool
.search(pattern
, satsolver
.SEARCH_GLOB
, "solvable:name")
539 pkg
= packages
.SolvPackage(self
, solv
)
547 def search(self
, pattern
):
550 for solv
in self
.pool
.search(pattern
, satsolver
.SEARCH_STRING|satsolver
.SEARCH_FILES
):
551 pkg
= packages
.SolvPackage(self
, solv
)
553 # Check, if a package with the name is already in the resultset
554 # and always replace older ones by more recent ones.
555 if pkgs
.has_key(pkg
.name
):
556 if pkgs
[pkg
.name
] < pkg
:
561 # Return a list of the packages, alphabetically sorted.
562 return sorted(pkgs
.values())
564 def groupinstall(self
, group
, **kwargs
):
565 self
.install("@%s" % group
, **kwargs
)
567 def grouplist(self
, group
):
570 for solv
in self
.pool
.search(group
, satsolver
.SEARCH_SUBSTRING
, "solvable:group"):
571 pkg
= packages
.SolvPackage(self
, solv
)
573 if group
in pkg
.groups
and not pkg
.name
in pkgs
:
574 pkgs
.append(pkg
.name
)
579 def build(pkg
, resultdirs
=None, shell
=False, install_test
=True, after_shell
=False, **kwargs
):
583 b
= builder
.BuildEnviron(pkg
, **kwargs
)
586 # Always include local repository.
587 resultdirs
.append(p
.repos
.local_build
.path
)
590 # Start to prepare the build environment by mounting
591 # the filesystems and extracting files.
596 b
.build(install_test
=install_test
)
598 # Raise the error, if the user does not want to
603 # Run a shell to debug the issue.
606 # If the user requests a shell after a successful build,
611 # Copy-out all resultfiles if the build was successful.
612 for resultdir
in resultdirs
:
616 b
.copy_result(resultdir
)
620 def _build(self
, pkg
, resultdir
, nodeps
=False, **kwargs
):
621 b
= builder
.Builder(self
, pkg
, resultdir
, **kwargs
)
626 raise BuildError
, _("Build command has failed.")
628 # If the build was successful, cleanup all temporary files.
632 def shell(pkg
, **kwargs
):
633 b
= builder
.BuildEnviron(pkg
, **kwargs
)
641 def dist(self
, pkgs
, resultdirs
=None):
645 pkg
= packages
.Makefile(self
, pkg
)
649 def provides(self
, patterns
):
651 for pattern
in patterns
:
652 for pkg
in self
.repos
.whatprovides(pattern
):
660 def repo_create(self
, path
, input_paths
, type="binary"):
661 assert type in ("binary", "source",)
663 repo
= repository
.RepositoryDir(
666 description
="New repository.",
671 for input_path
in input_paths
:
672 repo
.collect_packages(input_path
)
679 return [r
for r
in self
.repos
]
682 log
.debug("Cleaning up everything...")
684 # Clean up repository caches.
687 def check(self
, downgrade
=True, uninstall
=True):
689 Try to fix any errors in the system.
691 # Detect any errors in the dependency tree.
692 # For that we create an empty request and solver and try to solve
694 request
= self
.create_request()
695 solver
= self
.create_solver()
697 # XXX the solver does crash if we call it with fix_system=1,
698 # allow_downgrade=1 and uninstall=1. Need to fix this.
699 allow_downgrade
= False
702 t
= solver
.solve(request
, fix_system
=True, allow_downgrade
=downgrade
,
706 log
.info(_("Everything is fine."))
709 # Ask the user if okay.
710 if not t
.cli_yesno():
713 # Process the transaction.
717 def cache_create(**kwargs
):
718 # Create a build environment that we are going to pack into
720 b
= builder
.BuildEnviron(**kwargs
)
723 # Get filename of the file from builder instance.
724 filename
= b
.cache_file
729 # Create directory if not existant.
730 dirname
= os
.path
.dirname(filename
)
731 if not os
.path
.exists(dirname
):
734 b
.cache_export(filename
)