diff options
| author | Kiwinote <kiwinote@gmail.com> | 2010-06-25 17:46:47 +0100 |
|---|---|---|
| committer | Kiwinote <kiwinote@gmail.com> | 2010-06-25 17:46:47 +0100 |
| commit | 0be0102d652ff86a2066088535bfe58cf2d713a4 (patch) | |
| tree | a9b7e91b400bd850ef299e19fa6fac7cc3fb0b91 | |
| parent | d6cc940087ee8897bad4e6a25d04403d28c5dc0a (diff) | |
| download | python-apt-0be0102d652ff86a2066088535bfe58cf2d713a4.tar.gz | |
Merge gdebi changes
| -rw-r--r-- | apt/cache.py | 68 | ||||
| -rw-r--r-- | apt/debfile.py | 351 |
2 files changed, 310 insertions, 109 deletions
diff --git a/apt/cache.py b/apt/cache.py index 8e07e4d0..f7a8bbaa 100644 --- a/apt/cache.py +++ b/apt/cache.py @@ -1,23 +1,23 @@ # cache.py - apt cache abstraction # -# Copyright (c) 2005-2009 Canonical +# Copyright (c) 2005-2010 Canonical, Ltd. # -# Author: Michael Vogt <michael.vogt@ubuntu.com> +# Author: Michael Vogt <mvo@ubuntu.com> # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA import os import weakref @@ -84,6 +84,11 @@ class Cache(object): # recognized (LP: #320665) apt_pkg.init_system() self.open(progress) + if progress: + self.op_progress = progress + else: + self.op_progress = apt.progress.base.OpProgress() + def _check_and_create_required_dirs(self, rootdir): """ @@ -198,6 +203,16 @@ class Cache(object): self._depcache.upgrade(dist_upgrade) self.cache_post_change() + def downloadable(self, pkg, use_candidate=True): + """Check if the given package can be downloaded.""" + if use_candidate: + ver = self._depcache.get_candidate_ver(pkg._pkg) + else: + ver = pkg._pkg.current_ver + if ver == None: + return False + return ver.downloadable + @property def required_download(self): """Get the size of the packages that are required to download.""" @@ -306,6 +321,30 @@ class Cache(object): providers.add(package) return list(providers) + def get_providers_for(self, pkgname): + """Return a list of all packages providing a non-virtual package.""" + providers = [] + for pkg in self: + v = self._depcache.get_candidate_ver(pkg._pkg) + if v == None: + continue + for p in v.provides_list: + #print virtual_pkg + #print p[0] + if pkgname == p[0]: + # we found a pkg that provides this virtual + # pkg, check if the proivdes is any good + providers.append(pkg) + #cand = self._cache[pkg.name] + #candver = self._cache._depcache.GetCandidateVer(cand._pkg) + #instver = cand._pkg.CurrentVer + #res = apt_pkg.CheckDep(candver.VerStr,oper,ver) + #if res == True: + # self._dbg(1,"we can use %s" % pkg.name) + # or_found = True + # break + return providers + @deprecated_args def update(self, fetch_progress=None, pulse_interval=0, raise_on_error=True): @@ -474,6 +513,7 @@ class Cache(object): _fetchArchives = function_deprecated_by(_fetch_archives) isVirtualPackage = function_deprecated_by(is_virtual_package) getProvidingPackages = function_deprecated_by(get_providing_packages) + getProvidersFor = function_deprecated_by(get_providers_for) installArchives = function_deprecated_by(install_archives) cachePostChange = function_deprecated_by(cache_post_change) cachePreChange = function_deprecated_by(cache_pre_change) diff --git a/apt/debfile.py b/apt/debfile.py index ccaa25e4..494dd14e 100644 --- a/apt/debfile.py +++ b/apt/debfile.py @@ -1,79 +1,65 @@ -# Copyright (c) 2005-2009 Canonical +# Copyright (c) 2005-2010 Canonical, Ltd. # -# Author: Michael Vogt <michael.vogt@ubuntu.com> +# Author: Michael Vogt <mvo@ubuntu.com> # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA """Classes for working with locally available Debian packages.""" +import apt_inst +import apt_pkg +import gzip import os import sys -import apt_inst -import apt_pkg from apt_pkg import gettext as _ - - -# Constants for comparing the local package file with the version in the cache -(VERSION_NONE, VERSION_OUTDATED, VERSION_SAME, VERSION_NEWER) = range(4) - +from debian.debfile import DebFile +from StringIO import StringIO class NoDebArchiveException(IOError): """Exception which is raised if a file is no Debian archive.""" - class DebPackage(object): """A Debian Package (.deb file).""" + # Constants for comparing the local package file with the version in the cache + (VERSION_NONE, VERSION_OUTDATED, VERSION_SAME, VERSION_NEWER) = range(4) + _supported_data_members = ("data.tar.gz", "data.tar.bz2", "data.tar.lzma") debug = 0 def __init__(self, filename=None, cache=None): + if cache: + cache.clear() self._cache = cache + self.file = filename self._need_pkgs = [] + self._sections = {} self._debfile = None self.pkgname = "" - self.filename = filename - self._sections = {} self._installed_conflicts = set() self._failure_string = "" - if filename: - self.open(filename) + if self.file: + self.open(self.file) - def open(self, filename): + def open(self, file): " open given debfile " - self.filename = filename - self._debfile = apt_inst.DebFile(self.filename) - control = self._debfile.control.extractdata("control") + control = apt_inst.debExtractControl(open(file)) self._sections = apt_pkg.TagSection(control) self.pkgname = self._sections["Package"] - def __getitem__(self, key): - return self._sections[key] - - @property - def filelist(self): - """return the list of files in the deb.""" - files = [] - try: - self._debfile.data.go(lambda item, data: files.append(item.name)) - except SystemError: - return [_("List of files for '%s' could not be read" % - self.filename)] - return files - def _is_or_group_satisfied(self, or_group): """Return True if at least one dependency of the or-group is satisfied. @@ -90,20 +76,34 @@ class DebPackage(object): # check for virtual pkgs if not depname in self._cache: if self._cache.is_virtual_package(depname): - self._dbg(3, "_isOrGroupSatisfied(): %s is virtual dep" % - depname) + self._dbg(3, "_is_or_group_satisfied(): %s is virtual dep" % depname) for pkg in self._cache.get_providing_packages(depname): if pkg.is_installed: return True continue - + # check real dependency inst = self._cache[depname].installed if inst is not None and apt_pkg.check_dep(inst.version, oper, ver): return True + + # if no real dependency is installed, check if there is + # a package installed that provides this dependency + # (e.g. scrollkeeper dependecies are provided by rarian-compat) + # but only do that if there is no version required in the + # dependency (we do not supprot versionized dependencies) + if not oper: + for ppkg in self._cache.get_providers_for(depname): + if ppkg.is_installed: + self._dbg(3, "found installed '%s' that provides '%s'" % (ppkg.name, depname)) + return True return False def _satisfy_or_group(self, or_group): """Try to satisfy the or_group.""" + + or_found = False + virtual_pkg = None + for dep in or_group: depname, ver, oper = dep @@ -136,19 +136,18 @@ class DebPackage(object): or_str = "" for dep in or_group: or_str += dep[0] - if dep != or_group[-1]: + if ver and oper: + or_str += " (%s %s)" % (dep[2], dep[1]) + if dep != or_group[len(or_group)-1]: or_str += "|" - self._failure_string += _("Dependency is not satisfiable: %s\n" % - or_str) + self._failure_string += _("Dependency is not satisfiable: %s\n") % or_str return False def _check_single_pkg_conflict(self, pkgname, ver, oper): """Return True if a pkg conflicts with a real installed/marked pkg.""" # FIXME: deal with conflicts against its own provides # (e.g. Provides: ftp-server, Conflicts: ftp-server) - self._dbg(3, "_checkSinglePkgConflict() pkg='%s' ver='%s' oper='%s'" % - (pkgname, ver, oper)) - + self._dbg(3, "_check_single_pkg_conflict() pkg='%s' ver='%s' oper='%s'" % (pkgname, ver, oper)) pkg = self._cache[pkgname] if pkg.is_installed: pkgver = pkg.installed.version @@ -162,8 +161,7 @@ class DebPackage(object): #print "oper: %s " % oper if (apt_pkg.check_dep(pkgver, oper, ver) and not self.replaces_real_pkg(pkgname, oper, ver)): - self._failure_string += _("Conflicts with the installed package " - "'%s'" % pkg.name) + self._failure_string += _("Conflicts with the installed package '%s'") % pkg.name return True return False @@ -171,6 +169,9 @@ class DebPackage(object): """Check the or-group for conflicts with installed pkgs.""" self._dbg(2, "_check_conflicts_or_group(): %s " % (or_group)) + or_found = False + virtual_pkg = None + for dep in or_group: depname = dep[0] ver = dep[1] @@ -187,8 +188,7 @@ class DebPackage(object): if self.pkgname == pkg.name: self._dbg(3, "conflict on self, ignoring") continue - if self._check_single_pkg_conflict(pkg.name, ver, - oper): + if self._check_single_pkg_conflict(pkg.name, ver, oper): self._installed_conflicts.add(pkg.name) continue if self._check_single_pkg_conflict(depname, ver, oper): @@ -209,7 +209,7 @@ class DebPackage(object): """List of package names on which this package depends on.""" depends = [] # find depends - for key in "Depends", "PreDepends": + for key in "Depends", "Pre-Depends": try: depends.extend(apt_pkg.parse_depends(self._sections[key])) except KeyError: @@ -240,7 +240,7 @@ class DebPackage(object): Return True if the deb packages replaces a real (not virtual) packages named (pkgname, oper, ver). """ - self._dbg(3, "replacesPkg() %s %s %s" % (pkgname, oper, ver)) + self._dbg(3, "replaces_real_pkg() %s %s %s" % (pkgname, oper, ver)) pkg = self._cache[pkgname] if pkg.is_installed: pkgver = pkg.installed.version @@ -262,6 +262,19 @@ class DebPackage(object): Check if the package conflicts with a existing or to be installed package. Return True if the pkg is OK. """ + for con in self.conflicts: + for pro in self.provides: + if con[0][0] == pro[0][0]: + if con[0][2]: + pro_ver = self._sections["Version"] + if apt_pkg.check_dep(pro_ver, con[0][1], con[0][2]): + #print "Conflicts with provided pkg!" + self._failure_string = "Conflicts with a provided package" + return False + else: + #print "Conflicts with provided pkg!" + self._failure_string = "Conflicts with a provided package" + return False res = True for or_group in self.conflicts: if self._check_conflicts_or_group(or_group): @@ -270,6 +283,57 @@ class DebPackage(object): res = False return res + def check_breaks_existing_packages(self): + """ + check if installing the package would break exsisting + package on the system, e.g. system has: + smc depends on smc-data (= 1.4) + and user tries to installs smc-data 1.6 + """ + # show progress information as this step may take some time + size = float(len(self._cache)) + steps = int(size/50) + debver = self._sections["Version"] + for (i, pkg) in enumerate(self._cache): + if i%steps == 0: + self._cache.op_progress.update(float(i)/size*100.0) + if not pkg.is_installed: + continue + # check if the exising dependencies are still satisfied + # with the package + ver = pkg._pkg.current_ver + for dep_or in pkg.installed.dependencies: + for dep in dep_or.or_dependencies: + if dep.name == self.pkgname: + if not apt_pkg.check_dep(debver, dep.relation, dep.version): + self._dbg(2, "would break (depends) %s" % pkg.name) + # TRANSLATORS: the first '%s' is the package that breaks, the second the dependency that makes it break, the third the relation (e.g. >=) and the latest the version for the releation + self._failure_string += _("Breaks existing package '%(pkgname)s' dependency %(depname)s (%(deprelation)s %(depversion)s)") % { + 'pkgname' : pkg.name, + 'depname' : dep.name, + 'deprelation' : dep.relation, + 'depversion' : dep.version} + self._cache.op_progress.done() + return False + # now check if there are conflicts against this package on + # the existing system + if "Conflicts" in ver.depends_list: + for conflicts_ver_list in ver.depends_list["Conflicts"]: + for c_or in conflicts_ver_list: + if c_or.target_pkg.name == self.pkgname: + if apt_pkg.check_dep(debver, c_or.comp_type, c_or.target_ver): + self._dbg(2, "would break (conflicts) %s" % pkg.name) + # TRANSLATORS: the first '%s' is the package that conflicts, the second the packagename that it conflicts with (so the name of the deb the user tries to install), the third is the relation (e.g. >=) and the last is the version for the relation + self._failureString += _("Breaks existing package '%(pkgname)s' conflict: %(targetpkg)s (%(comptype)s %(targetver)s)") % { + 'pkgname' : pkg.name, + 'targetpkg' : c_or.target_pkg.name, + 'comptype' : c_or.comp_type, + 'targetver' : c_or.target_ver } + self._cache.op_progress.done() + return False + self._cache.op_progress.done() + return True + def compare_to_version_in_cache(self, use_installed=True): """Compare the package to the version available in the cache. @@ -277,7 +341,7 @@ class DebPackage(object): and if so in what version, returns one of (VERSION_NONE, VERSION_OUTDATED, VERSION_SAME, VERSION_NEWER). """ - self._dbg(3, "compareToVersionInCache") + self._dbg(3, "compare_to_version_in_cache") pkgname = self._sections["Package"] debver = self._sections["Version"] self._dbg(1, "debver: %s" % debver) @@ -287,32 +351,37 @@ class DebPackage(object): else: cachever = self._cache[pkgname].candidate.version if cachever is not None: - cmpres = apt_pkg.version_compare(cachever, debver) - self._dbg(1, "CompareVersion(debver,instver): %s" % cmpres) - if cmpres == 0: - return VERSION_SAME - elif cmpres < 0: - return VERSION_NEWER - elif cmpres > 0: - return VERSION_OUTDATED - return VERSION_NONE + cmp = apt_pkg.version_compare(cachever, debver) + self._dbg(1, "CompareVersion(debver,instver): %s" % cmp) + if cmp == 0: + return self.VERSION_SAME + elif cmp < 0: + return self.VERSION_NEWER + elif cmp > 0: + return self.VERSION_OUTDATED + return self.VERSION_NONE def check(self): """Check if the package is installable.""" self._dbg(3, "check_depends") # check arch + if not "Architecture" in self._sections: + self._dbg(1, "ERROR: no architecture field") + self._failure_string = _("No Architecture field in the package") + return False arch = self._sections["Architecture"] if arch != "all" and arch != apt_pkg.config.find("APT::Architecture"): self._dbg(1, "ERROR: Wrong architecture dude!") - self._failure_string = _("Wrong architecture '%s'" % arch) + self._failure_string = _("Wrong architecture '%s'") % arch return False # check version - if self.compare_to_version_in_cache() == VERSION_OUTDATED: - # the deb is older than the installed - self._failure_string = _("A later version is already installed") - return False + if self.compare_to_version_in_cache() == self.VERSION_OUTDATED: + if self._cache[self.pkgname].installed: + # the deb is older than the installed + self._failure_string = _("A later version is already installed") + return False # FIXME: this sort of error handling sux self._failure_string = "" @@ -321,6 +390,11 @@ class DebPackage(object): if not self.check_conflicts(): return False + # check if installing it would break anything on the + # current system + if not self.check_breaks_existing_packages(): + return False + # try to satisfy the dependencies if not self._satisfy_depends(self.depends): return False @@ -331,8 +405,7 @@ class DebPackage(object): return False if self._cache._depcache.broken_count > 0: - self._failure_string = _("Failed to satisfy all dependencies " - "(broken cache)") + self._failure_string = _("Failed to satisfy all dependencies (broken cache)") # clean the cache again self._cache.clear() return False @@ -352,19 +425,18 @@ class DebPackage(object): # check depends for or_group in depends: #print "or_group: %s" % or_group - #print "or_group satified: %s" % self._is_or_group_satisfied( - # or_group) + #print "or_group satified: %s" % self._is_or_group_satisfied(or_group) if not self._is_or_group_satisfied(or_group): if not self._satisfy_or_group(or_group): return False # now try it out in the cache - for pkg in self._need_pkgs: - try: - self._cache[pkg].mark_install(fromUser=False) - except SystemError: - self._failure_string = _("Cannot install '%s'" % pkg) - self._cache.clear() - return False + for pkg in self._need_pkgs: + try: + self._cache[pkg].mark_install(from_user=False) + except SystemError, e: + self._failure_string = _("Cannot install '%s'") % pkg + self._cache.clear() + return False return True @property @@ -398,6 +470,95 @@ class DebPackage(object): remove.append(pkg.name) return (install, remove, unauthenticated) + def control_filelist(self): + """ return the list of files in control.tar.gt """ + content = [] + for name in DebFile(self.file).control: + if name and name != ".": + content.append(name) + return sorted(content) + control_filelist = property(control_filelist) + + def to_hex(self, in_data): + hex = "" + for (i, c) in enumerate(in_data): + if i%80 == 0: + hex += "\n" + hex += "%2.2x " % ord(c) + return hex + + def to_strish(self, in_data): + s = "" + for c in in_data: + if ord(c) < 10 or ord(c) > 127: + s += " " + else: + s += c + return s + + def _get_content(self, part, name, auto_decompress=True, auto_hex=True): + data = part.get_content(name) + # check for zip content + if name.endswith(".gz") and auto_decompress: + io = StringIO(data) + gz = gzip.GzipFile(fileobj=io) + data = _("Automatically decompressed:\n\n") + data += gz.read() + # auto-convert to hex + try: + data = unicode(data, "utf-8") + except Exception, e: + new_data = _("Automatically converted to printable ascii:\n") + new_data += self.to_strish(data) + return new_data + return data + + def control_content(self, name): + """ return the content of a specific control.tar.gz file """ + control = DebFile(self.file).control + if name in control: + return self._get_content(control, name) + return "" + + def data_content(self, name): + """ return the content of a specific control.tar.gz file """ + data = DebFile(self.file).data + if name in data: + return self._get_content(data, name) + return "" + + @property + def filelist(self): + """ return the list of files in the deb. """ + files = [] + def extract_cb(What,Name,Link,Mode,UID,GID,Size,MTime,Major,Minor): + #print "%s '%s','%s',%u,%u,%u,%u,%u,%u,%u"\ + # % (What,Name,Link,Mode,UID,GID,Size, MTime, Major, Minor) + if Name != "./": + files.append(Name) + try: + try: + apt_inst.debExtract(open(self.file), extract_cb, "data.tar.gz") + except SystemError, e: + try: + apt_inst.debExtract(open(self.file), extract_cb, "data.tar.bz2") + except SystemError, e: + try: + apt_inst.debExtract(open(self.file), extract_cb, "data.tar.lzma") + except SystemError, e: + return [_("List of files could not be read, please report this as a bug")] + # IOError may happen because of gvfs madness (LP: #211822) + except IOError, e: + return [_("IOError during filelist read: %s") % e] + return files + + def __getitem__(self,item): + if not item in self._sections: + # Translators: it's for missing entries in the deb package, + # e.g. a missing "Maintainer" field + return _("%s is not available") % item + return self._sections[item] + def _dbg(self, level, msg): """Write debugging output to sys.stderr.""" if level <= self.debug: @@ -406,31 +567,31 @@ class DebPackage(object): def install(self, install_progress=None): """Install the package.""" if install_progress is None: - return os.spawnlp(os.P_WAIT, "dpkg", "dpkg", "-i", self.filename) + return os.spawnlp(os.P_WAIT, "dpkg", "dpkg", "-i", self.file) else: try: install_progress.start_update() except AttributeError: install_progress.startUpdate() - res = install_progress.run(self.filename) + res = install_progress.run(self.file) try: install_progress.finish_update() except AttributeError: install_progress.finishUpdate() return res - class DscSrcPackage(DebPackage): """A locally available source package.""" def __init__(self, filename=None, cache=None): DebPackage.__init__(self, None, cache) + self.file = filename self._depends = [] self._conflicts = [] self.pkgname = "" self.binaries = [] - if filename is not None: - self.open(filename) + if self.file is not None: + self.open(self.file) @property def depends(self): @@ -446,7 +607,6 @@ class DscSrcPackage(DebPackage): """Open the package.""" depends_tags = ["Build-Depends", "Build-Depends-Indep"] conflicts_tags = ["Build-Conflicts", "Build-Conflicts-Indep"] - fobj = open(file) tagfile = apt_pkg.TagFile(fobj) try: @@ -481,11 +641,10 @@ class DscSrcPackage(DebPackage): if self._cache[pkgname]._pkg.essential: raise Exception(_("An essential package would be removed")) self._cache[pkgname].mark_delete() - # FIXME: a additional run of the checkConflicts() - # after _satisfyDepends() should probably be done + # FIXME: a additional run of the check_conflicts() + # after _satisfy_depends() should probably be done return self._satisfy_depends(self.depends) - def _test(): """Test function""" from apt.cache import Cache @@ -494,7 +653,7 @@ def _test(): cache = Cache() vp = "www-browser" - #print "%s virtual: %s" % (vp, cache.isVirtualPackage(vp)) + print "%s virtual: %s" % (vp, cache.is_virtual_pkg(vp)) providers = cache.get_providing_packages(vp) print "Providers for %s :" % vp for pkg in providers: @@ -508,6 +667,8 @@ def _test(): print "missing deps: %s" % d.missing_deps print d.required_changes + print d.filelist + print "Installing ..." ret = d.install(DpkgInstallProgress()) print ret |
