diff options
Diffstat (limited to 'apt/debfile.py')
| -rw-r--r-- | apt/debfile.py | 247 |
1 files changed, 154 insertions, 93 deletions
diff --git a/apt/debfile.py b/apt/debfile.py index adf9b348..3f4bca4b 100644 --- a/apt/debfile.py +++ b/apt/debfile.py @@ -17,6 +17,8 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA """Classes for working with locally available Debian packages.""" +from __future__ import print_function + import apt import apt_inst import apt_pkg @@ -25,19 +27,21 @@ import os import sys from apt_pkg import gettext as _ -from StringIO import StringIO +from io import BytesIO + 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_NONE, + VERSION_OUTDATED, + VERSION_SAME, VERSION_NEWER) = range(4) debug = 0 @@ -68,7 +72,7 @@ class DebPackage(object): self._sections = apt_pkg.TagSection(control) self.pkgname = self._sections["Package"] self._check_was_run = False - + def __getitem__(self, key): return self._sections[key] @@ -91,29 +95,31 @@ class DebPackage(object): """ return the list of files in control.tar.gt """ control = [] try: - self._debfile.control.go(lambda item, data: control.append(item.name)) + self._debfile.control.go( + lambda item, data: control.append(item.name)) except SystemError: return [_("List of control files for '%s' could not be read") % self.filename] return sorted(control) - # helper that will return a pkgname with a multiarch suffix if needed - def _maybe_append_multiarch_suffix(self, pkgname, + def _maybe_append_multiarch_suffix(self, pkgname, in_conflict_checking=False): # trivial cases + if ":" in pkgname: + return pkgname if not self._multiarch: return pkgname elif self._cache.is_virtual_package(pkgname): return pkgname - elif (pkgname in self._cache and + elif (pkgname in self._cache and self._cache[pkgname].candidate and self._cache[pkgname].candidate.architecture == "all"): return pkgname # now do the real multiarch checking multiarch_pkgname = "%s:%s" % (pkgname, self._multiarch) # the upper layers will handle this - if not multiarch_pkgname in self._cache: + if multiarch_pkgname not in self._cache: return multiarch_pkgname # now check the multiarch state cand = self._cache[multiarch_pkgname].candidate._cand @@ -124,8 +130,8 @@ class DebPackage(object): return pkgname # for conflicts we need a special case here, any not multiarch enabled # package has a implicit conflict - if (in_conflict_checking and - not (cand.multi_arch & cand.MULTI_ARCH_SAME)): + if (in_conflict_checking and + not (cand.multi_arch & cand.MULTI_ARCH_SAME)): return pkgname return multiarch_pkgname @@ -146,9 +152,11 @@ class DebPackage(object): depname = self._maybe_append_multiarch_suffix(depname) # check for virtual pkgs - if not depname in self._cache: + if depname not in self._cache: if self._cache.is_virtual_package(depname): - self._dbg(3, "_is_or_group_satisfied(): %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 @@ -161,13 +169,15 @@ class DebPackage(object): # 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 + # 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_providing_packages( depname, include_nonvirtual=True): if ppkg.is_installed: - self._dbg(3, "found installed '%s' that provides '%s'" % (ppkg.name, depname)) + self._dbg( + 3, "found installed '%s' that provides '%s'" % ( + ppkg.name, depname)) return True return False @@ -180,7 +190,7 @@ class DebPackage(object): depname = self._maybe_append_multiarch_suffix(depname) # if we don't have it in the cache, it may be virtual - if not depname in self._cache: + if depname not in self._cache: if not self._cache.is_virtual_package(depname): continue providers = self._cache.get_providing_packages(depname) @@ -210,16 +220,19 @@ class DebPackage(object): or_str += dep[0] if ver and oper: or_str += " (%s %s)" % (dep[2], dep[1]) - if dep != or_group[len(or_group)-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, "_check_single_pkg_conflict() 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 @@ -232,7 +245,7 @@ class DebPackage(object): #print "pkgver: %s " % pkgver #print "oper: %s " % oper if (apt_pkg.check_dep(pkgver, oper, ver) and not - self.replaces_real_pkg(pkgname, oper, ver)): + self.replaces_real_pkg(pkgname, oper, ver)): self._failure_string += _("Conflicts with the installed package " "'%s'") % pkg.name self._dbg(3, "conflicts with installed pkg '%s'" % pkg.name) @@ -253,7 +266,7 @@ class DebPackage(object): depname, in_conflict_checking=True) # check conflicts with virtual pkgs - if not depname in self._cache: + if depname not in self._cache: # FIXME: we have to check for virtual replaces here as # well (to pass tests/gdebi-test8.deb) if self._cache.is_virtual_package(depname): @@ -263,8 +276,8 @@ 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): @@ -276,7 +289,7 @@ class DebPackage(object): """List of package names conflicting with this package.""" key = "Conflicts" try: - return apt_pkg.parse_depends(self._sections[key]) + return apt_pkg.parse_depends(self._sections[key], False) except KeyError: return [] @@ -287,7 +300,8 @@ class DebPackage(object): # find depends for key in "Depends", "Pre-Depends": try: - depends.extend(apt_pkg.parse_depends(self._sections[key])) + depends.extend( + apt_pkg.parse_depends(self._sections[key], False)) except KeyError: pass return depends @@ -297,7 +311,7 @@ class DebPackage(object): """List of virtual packages which are provided by this package.""" key = "Provides" try: - return apt_pkg.parse_depends(self._sections[key]) + return apt_pkg.parse_depends(self._sections[key], False) except KeyError: return [] @@ -306,7 +320,7 @@ class DebPackage(object): """List of packages which are replaced by this package.""" key = "Replaces" try: - return apt_pkg.parse_depends(self._sections[key]) + return apt_pkg.parse_depends(self._sections[key], False) except KeyError: return [] @@ -347,22 +361,22 @@ class DebPackage(object): return res def check_breaks_existing_packages(self): - """ - check if installing the package would break exsisting + """ + 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 = max(int(size/50), 1) + steps = max(int(size / 50), 1) debver = self._sections["Version"] debarch = self._sections["Architecture"] # store what we provide so that we can later check against that - provides = [ x[0][0] for x in self.provides] + provides = [x[0][0] for x in self.provides] for (i, pkg) in enumerate(self._cache): - if i%steps == 0: - self._cache.op_progress.update(float(i)/size*100.0) + 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 @@ -371,14 +385,21 @@ class DebPackage(object): 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): + 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} + # 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 @@ -386,25 +407,41 @@ class DebPackage(object): 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 and c_or.target_pkg.architecture == debarch: - 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._failure_string += _("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 } + if (c_or.target_pkg.name == self.pkgname and + c_or.target_pkg.architecture == debarch): + 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._failure_string += _( + "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 if (c_or.target_pkg.name in provides and - self.pkgname != pkg.name): - self._dbg(2, "would break (conflicts) %s" % provides) - self._failure_string += _("Breaks existing package '%(pkgname)s' that conflict: '%(targetpkg)s'. But the '%(debfile)s' provides it via: '%(provides)s'") % { - 'provides' : ",".join(provides), - 'debfile' : self.filename, - 'targetpkg' : c_or.target_pkg.name, - 'pkgname' : pkg.name } + self.pkgname != pkg.name): + self._dbg( + 2, "would break (conflicts) %s" % provides) + self._failure_string += _( + "Breaks existing package '%(pkgname)s' " + "that conflict: '%(targetpkg)s'. But the " + "'%(debfile)s' provides it via: " + "'%(provides)s'") % { + 'provides': ",".join(provides), + 'debfile': self.filename, + 'targetpkg': c_or.target_pkg.name, + 'pkgname': pkg.name} self._cache.op_progress.done() return False self._cache.op_progress.done() @@ -419,6 +456,11 @@ class DebPackage(object): """ self._dbg(3, "compare_to_version_in_cache") pkgname = self._sections["Package"] + architecture = self._sections["Architecture"] + + # Arch qualify the package name + pkgname = ":".join([pkgname, architecture]) + debver = self._sections["Version"] self._dbg(1, "debver: %s" % debver) if pkgname in self._cache: @@ -439,19 +481,19 @@ class DebPackage(object): return self.VERSION_OUTDATED return self.VERSION_NONE - def check(self): + def check(self, allow_downgrade=False): """Check if the package is installable.""" self._dbg(3, "check") self._check_was_run = True # check arch - if not "Architecture" in self._sections: + if "Architecture" not 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"): + if arch != "all" and arch != apt_pkg.config.find("APT::Architecture"): if arch in apt_pkg.get_architectures(): self._multiarch = arch self.pkgname = "%s:%s" % (self.pkgname, self._multiarch) @@ -462,10 +504,12 @@ class DebPackage(object): return False # check version - if self.compare_to_version_in_cache() == self.VERSION_OUTDATED: + if (not allow_downgrade and + 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") + self._failure_string = _( + "A later version is already installed") return False # FIXME: this sort of error handling sux @@ -475,7 +519,7 @@ class DebPackage(object): if not self.check_conflicts(): return False - # check if installing it would break anything on the + # check if installing it would break anything on the # current system if not self.check_breaks_existing_packages(): return False @@ -499,19 +543,18 @@ class DebPackage(object): def satisfy_depends_str(self, dependsstr): """Satisfy the dependencies in the given string.""" - return self._satisfy_depends(apt_pkg.parse_depends(dependsstr)) + return self._satisfy_depends(apt_pkg.parse_depends(dependsstr, False)) def _satisfy_depends(self, depends): """Satisfy the dependencies.""" # turn off MarkAndSweep via a action group (if available) try: _actiongroup = apt_pkg.ActionGroup(self._cache._depcache) + _actiongroup # pyflakes except AttributeError: pass # 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) if not self._is_or_group_satisfied(or_group): if not self._satisfy_or_group(or_group): return False @@ -530,7 +573,8 @@ class DebPackage(object): """Return missing dependencies.""" self._dbg(1, "Installing: %s" % self._need_pkgs) if not self._check_was_run: - raise AttributeError("property only available after check() was run") + raise AttributeError( + "property only available after check() was run") return self._need_pkgs @property @@ -543,7 +587,8 @@ class DebPackage(object): remove = [] unauthenticated = [] if not self._check_was_run: - raise AttributeError("property only available after check() was run") + raise AttributeError( + "property only available after check() was run") for pkg in self._cache: if pkg.marked_install or pkg.marked_upgrade: install.append(pkg.name) @@ -562,7 +607,7 @@ class DebPackage(object): def to_hex(in_data): hex = "" for (i, c) in enumerate(in_data): - if i%80 == 0: + if i % 80 == 0: hex += "\n" hex += "%2.2x " % ord(c) return hex @@ -585,20 +630,20 @@ class DebPackage(object): else: s += chr(b) return s - + def _get_content(self, part, name, auto_decompress=True, auto_hex=True): if name.startswith("./"): name = name[2:] data = part.extractdata(name) # check for zip content if name.endswith(".gz") and auto_decompress: - io = StringIO(data) + io = BytesIO(data) gz = gzip.GzipFile(fileobj=io) - data = _("Automatically decompressed:\n\n") + data = _("Automatically decompressed:\n\n").encode("utf-8") data += gz.read() # auto-convert to hex try: - data = unicode(data, "utf-8") + data = data.decode("utf-8") except Exception: new_data = _("Automatically converted to printable ascii:\n") new_data += self.to_strish(data) @@ -622,7 +667,7 @@ class DebPackage(object): def _dbg(self, level, msg): """Write debugging output to sys.stderr.""" if level <= self.debug: - print >> sys.stderr, msg + print(msg, file=sys.stderr) def install(self, install_progress=None): """Install the package.""" @@ -640,6 +685,7 @@ class DebPackage(object): install_progress.finishUpdate() return res + class DscSrcPackage(DebPackage): """A locally available source package.""" @@ -664,28 +710,42 @@ class DscSrcPackage(DebPackage): """Return the dependencies of the package""" return self._conflicts + @property + def filelist(self): + """Return the list of files associated with this dsc file""" + # Files stanza looks like (hash, size, filename, ...) + return self._sections['Files'].split()[2::3] + def open(self, file): """Open the package.""" depends_tags = ["Build-Depends", "Build-Depends-Indep"] conflicts_tags = ["Build-Conflicts", "Build-Conflicts-Indep"] - fobj = open(file) + fd = apt_pkg.open_maybe_clear_signed_file(file) + fobj = os.fdopen(fd) tagfile = apt_pkg.TagFile(fobj) try: for sec in tagfile: + # we only care about the stanza with the "Format:" tag, the + # rest is gpg signature noise. we should probably have + # bindings for apts OpenMaybeClearsignedFile() + if "Format" not in sec: + continue for tag in depends_tags: - if not tag in sec: + if tag not in sec: continue self._depends.extend(apt_pkg.parse_src_depends(sec[tag])) for tag in conflicts_tags: - if not tag in sec: + if tag not in sec: continue self._conflicts.extend(apt_pkg.parse_src_depends(sec[tag])) if 'Source' in sec: self.pkgname = sec['Source'] if 'Binary' in sec: - self.binaries = sec['Binary'].split(', ') - if 'Version' in sec: - self._sections['Version'] = sec['Version'] + self.binaries = [b.strip() for b in + sec['Binary'].split(',')] + for tag in sec.keys(): + if tag in sec: + self._sections[tag] = sec[tag] finally: del tagfile fobj.close() @@ -695,7 +755,7 @@ class DscSrcPackage(DebPackage): " ".join(self.binaries)) self._sections["Description"] = s self._check_was_run = False - + def check(self): """Check if the package is installable..""" if not self.check_conflicts(): @@ -709,6 +769,7 @@ class DscSrcPackage(DebPackage): # after _satisfy_depends() should probably be done return self._satisfy_depends(self.depends) + def _test(): """Test function""" from apt.cache import Cache @@ -717,25 +778,25 @@ def _test(): cache = Cache() vp = "www-browser" - print "%s virtual: %s" % (vp, cache.is_virtual_package(vp)) + print("%s virtual: %s" % (vp, cache.is_virtual_package(vp))) providers = cache.get_providing_packages(vp) - print "Providers for %s :" % vp + print("Providers for %s :" % vp) for pkg in providers: - print " %s" % pkg.name + print(" %s" % pkg.name) d = DebPackage(sys.argv[1], cache) - print "Deb: %s" % d.pkgname + print("Deb: %s" % d.pkgname) if not d.check(): - print "can't be satified" - print d._failure_string - print "missing deps: %s" % d.missing_deps - print d.required_changes + print("can't be satified") + print(d._failure_string) + print("missing deps: %s" % d.missing_deps) + print(d.required_changes) - print d.filelist + print(d.filelist) - print "Installing ..." + print("Installing ...") ret = d.install(InstallProgress()) - print ret + print(ret) #s = DscSrcPackage(cache, "../tests/3ddesktop_0.2.9-6.dsc") #s.check_dep() @@ -744,7 +805,7 @@ def _test(): s = DscSrcPackage(cache=cache) d = "libc6 (>= 2.3.2), libaio (>= 0.3.96) | libaio1 (>= 0.3.96)" - print s._satisfy_depends(apt_pkg.parse_depends(d)) + print(s._satisfy_depends(apt_pkg.parse_depends(d, False))) if __name__ == "__main__": _test() |
