diff options
| author | Michael Vogt <michael.vogt@ubuntu.com> | 2012-08-14 14:58:25 +0200 |
|---|---|---|
| committer | Michael Vogt <michael.vogt@ubuntu.com> | 2012-08-14 14:58:25 +0200 |
| commit | c1ad8a0fbc0adfebdb847f28da92b3d5a1f90582 (patch) | |
| tree | 80225148a1c1d0d4283fb76e827c6ccc9ca206bf /apt | |
| parent | c736d5c9290a2ffdb8802a4ac62e521cf1218b54 (diff) | |
| parent | dfd6bacface878eecf3fec4876bdae2f0491a646 (diff) | |
| download | python-apt-c1ad8a0fbc0adfebdb847f28da92b3d5a1f90582.tar.gz | |
merged from the debian-sid tree
Diffstat (limited to 'apt')
| -rw-r--r-- | apt/auth.py | 180 | ||||
| -rw-r--r-- | apt/cache.py | 22 | ||||
| -rw-r--r-- | apt/debfile.py | 140 | ||||
| -rw-r--r-- | apt/package.py | 47 | ||||
| -rw-r--r-- | apt/progress/base.py | 5 | ||||
| -rw-r--r-- | apt/progress/gtk2.py | 12 | ||||
| -rw-r--r-- | apt/utils.py | 8 |
7 files changed, 341 insertions, 73 deletions
diff --git a/apt/auth.py b/apt/auth.py new file mode 100644 index 00000000..5d4b1cd6 --- /dev/null +++ b/apt/auth.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# auth - authentication key management +# +# Copyright (c) 2004 Canonical +# Copyright (c) 2012 Sebastian Heinlein +# +# Author: Michael Vogt <mvo@debian.org> +# Sebastian Heinlein <devel@glatzor.de> +# +# 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. +# +# 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 +"""Handle GnuPG keys used to trust signed repositories.""" + +import atexit +import os +import os.path +import subprocess +import tempfile + +import apt_pkg +from apt_pkg import gettext as _ + + +class TrustedKey(object): + + """Represents a trusted key.""" + + def __init__(self, name, keyid, date): + self.raw_name = name + # Allow to translated some known keys + self.name = _(name) + self.keyid = keyid + self.date = date + + def __str__(self): + return "%s\n%s %s" % (self.name, self.keyid, self.date) + + +def _call_apt_key_script(*args, **kwargs): + """Run the apt-key script with the given arguments.""" + conf = None + cmd = [apt_pkg.config.find_file("Dir::Bin::Apt-Key", "/usr/bin/apt-key")] + cmd.extend(args) + env = os.environ.copy() + env["LANG"] = "C" + try: + if apt_pkg.config.find_dir("Dir") != "/": + # If the key is to be installed into a chroot we have to export the + # configuration from the chroot to the apt-key script by using + # a temporary APT_CONFIG file. The apt-key script uses apt-config + # shell internally + conf = tempfile.NamedTemporaryFile(prefix="apt-key", suffix=".conf") + conf.write(apt_pkg.config.dump().encode("UTF-8")) + conf.flush() + env["APT_CONFIG"] = conf.name + proc = subprocess.Popen(cmd, env=env, universal_newlines=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + content = kwargs.get("stdin", None) + if isinstance(content, unicode): + content = content.encode("utf-8") + + output, stderr = proc.communicate(content) + + assert stderr == None + + if proc.returncode: + raise SystemError("The apt-key script failed with return code %s:\n" + "%s\n%s" % (proc.returncode, " ".join(cmd), + output)) + return output.strip() + finally: + if conf is not None: + conf.close() + +def add_key_from_file(filename): + """Import a GnuPG key file to trust repositores signed by it. + + Keyword arguments: + filename -- the absolute path to the public GnuPG key file + """ + if not os.path.abspath(filename): + raise SystemError("An absolute path is required: %s" % filename) + if not os.access(filename, os.R_OK): + raise SystemError("Key file cannot be accessed: %s" % filename) + _call_apt_key_script("add", filename) + +def add_key_from_keyserver(keyid, keyserver): + """Import a GnuPG key file to trust repositores signed by it. + + Keyword arguments: + keyid -- the identifier of the key, e.g. 0x0EB12DSA + keyserver -- the URL or hostname of the key server + """ + _call_apt_key_script("adv", "--quiet", "--keyserver", keyserver, + "--recv", keyid) + +def add_key(content): + """Import a GnuPG key to trust repositores signed by it. + + Keyword arguments: + content -- the content of the GnuPG public key + """ + _call_apt_key_script("adv", "--quiet", "--batch", + "--import", "-", stdin=content) + +def remove_key(fingerprint): + """Remove a GnuPG key to no longer trust repositores signed by it. + + Keyword arguments: + fingerprint -- the fingerprint identifying the key + """ + _call_apt_key_script("rm", fingerprint) + +def export_key(fingerprint): + """Return the GnuPG key in text format. + + Keyword arguments: + fingerprint -- the fingerprint identifying the key + """ + return _call_apt_key_script("export", fingerprint) + +def update(): + """Update the local keyring with the archive keyring and remove from + the local keyring the archive keys which are no longer valid. The + archive keyring is shipped in the archive-keyring package of your + distribution, e.g. the debian-archive-keyring package in Debian. + """ + return _call_apt_key_script("update") + +def net_update(): + """Work similar to the update command above, but get the archive + keyring from an URI instead and validate it against a master key. + This requires an installed wget(1) and an APT build configured to + have a server to fetch from and a master keyring to validate. APT + in Debian does not support this command and relies on update + instead, but Ubuntu's APT does. + """ + return _call_apt_key_script("net-update") + +def list_keys(): + """Returns a list of TrustedKey instances for each key which is + used to trust repositories. + """ + # The output of `apt-key list` is difficult to parse since the + # --with-colons parameter isn't user + output = _call_apt_key_script("adv", "--with-colons", "--batch", + "--list-keys") + res = [] + for line in output.split("\n"): + fields = line.split(":") + if fields[0] == "pub": + key = TrustedKey(fields[9], fields[4][-8:], fields[5]) + res.append(key) + return res + +if __name__ == "__main__": + # Add some known keys we would like to see translated so that they get + # picked up by gettext + lambda: _("Ubuntu Archive Automatic Signing Key <ftpmaster@ubuntu.com>") + lambda: _("Ubuntu CD Image Automatic Signing Key <cdimage@ubuntu.com>") + + apt_pkg.init() + for trusted_key in list_keys(): + print(trusted_key) diff --git a/apt/cache.py b/apt/cache.py index be137b76..86a788bb 100644 --- a/apt/cache.py +++ b/apt/cache.py @@ -46,8 +46,13 @@ class LockFailedException(IOError): class Cache(object): """Dictionary-like package cache. - This class has all the packages that are available in it's - dictionary. + The APT cache file contains a hash table mapping names of binary + packages to their metadata. A Cache object is the in-core + representation of the same. It provides access to APTs idea of the + list of available packages. + + The cache can be used like a mapping from package names to Package + objects (although only getting items is supported). Keyword arguments: progress -- a OpProgress object @@ -84,6 +89,10 @@ class Cache(object): apt_pkg.config.set("Dir", rootdir) apt_pkg.config.set("Dir::State::status", rootdir + "/var/lib/dpkg/status") + # also set dpkg to the rootdir path so that its called for the + # --print-foreign-architectures call + apt_pkg.config.set("Dir::bin::dpkg", + os.path.join(rootdir, "usr", "bin", "dpkg")) # create required dirs/files when run with special rootdir # automatically self._check_and_create_required_dirs(rootdir) @@ -112,7 +121,7 @@ class Cache(object): ] for d in dirs: if not os.path.exists(rootdir + d): - print "creating: ", rootdir + d + #print "creating: ", rootdir + d os.makedirs(rootdir + d) for f in files: if not os.path.exists(rootdir + f): @@ -143,8 +152,7 @@ class Cache(object): self._sorted_set = None self._weakref.clear() - self._have_multi_arch = bool(apt_pkg.config.value_list("APT::" + - "Architectures")) + self._have_multi_arch = len(apt_pkg.get_architectures()) > 1 progress.op = _("Building data structures") i = last = 0 @@ -507,7 +515,7 @@ class Cache(object): self._callbacks[name].append(callback) def actiongroup(self): - """Return an ActionGroup() object for the current cache. + """Return an `ActionGroup` object for the current cache. Action groups can be used to speedup actions. The action group is active as soon as it is created, and disabled when the object is @@ -520,7 +528,7 @@ class Cache(object): for package in my_selected_packages: package.mark_install() - This way, the ActionGroup is automatically released as soon as the + This way, the action group is automatically released as soon as the with statement block is left. It also has the benefit of making it clear which parts of the code run with a action group and which don't. diff --git a/apt/debfile.py b/apt/debfile.py index d0f41def..2eb807b8 100644 --- a/apt/debfile.py +++ b/apt/debfile.py @@ -40,9 +40,6 @@ class DebPackage(object): VERSION_SAME, VERSION_NEWER) = range(4) - _supported_data_members = ("data.tar.gz", "data.tar.bz2", "data.tar.lzma", - "data.tar.xz") - debug = 0 def __init__(self, filename=None, cache=None): @@ -53,7 +50,9 @@ class DebPackage(object): self.pkgname = "" self._sections = {} self._need_pkgs = [] + self._check_was_run = False self._failure_string = "" + self._multiarch = None if filename: self.open(filename) @@ -68,7 +67,8 @@ class DebPackage(object): control = self._debfile.control.extractdata("control") self._sections = apt_pkg.TagSection(control) self.pkgname = self._sections["Package"] - + self._check_was_run = False + def __getitem__(self, key): return self._sections[key] @@ -79,10 +79,52 @@ class DebPackage(object): 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 [_("List of files for '%s' could not be read") % + self.filename] return files + @property + def control_filelist(self): + """ return the list of files in control.tar.gt """ + control = [] + try: + 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, + in_conflict_checking=False): + # trivial cases + if not self._multiarch: + return pkgname + elif self._cache.is_virtual_package(pkgname): + return pkgname + elif (pkgname in self._cache 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: + return multiarch_pkgname + # now check the multiarch state + cand = self._cache[multiarch_pkgname].candidate._cand + #print pkgname, multiarch_pkgname, cand.multi_arch + # the default is to add the suffix, unless its a pkg that can satify + # foreign dependencies + if cand.multi_arch & cand.MULTI_ARCH_FOREIGN: + 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)): + return pkgname + return multiarch_pkgname + def _is_or_group_satisfied(self, or_group): """Return True if at least one dependency of the or-group is satisfied. @@ -96,6 +138,9 @@ class DebPackage(object): ver = dep[1] oper = dep[2] + # multiarch + depname = self._maybe_append_multiarch_suffix(depname) + # check for virtual pkgs if not depname in self._cache: if self._cache.is_virtual_package(depname): @@ -124,13 +169,12 @@ class DebPackage(object): 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 + # multiarch + 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 not self._cache.is_virtual_package(depname): @@ -194,15 +238,16 @@ class DebPackage(object): def _check_conflicts_or_group(self, or_group): """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] oper = dep[2] + # FIXME: is this good enough? i.e. will apt always populate + # the cache with conflicting pkgnames for our arch? + depname = self._maybe_append_multiarch_suffix( + depname, in_conflict_checking=True) + # check conflicts with virtual pkgs if not depname in self._cache: # FIXME: we have to check for virtual replaces here as @@ -308,6 +353,7 @@ class DebPackage(object): size = float(len(self._cache)) 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] for (i, pkg) in enumerate(self._cache): @@ -336,7 +382,7 @@ 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: + 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 @@ -393,6 +439,8 @@ class DebPackage(object): """Check if the package is installable.""" self._dbg(3, "check") + self._check_was_run = True + # check arch if not "Architecture" in self._sections: self._dbg(1, "ERROR: no architecture field") @@ -400,9 +448,14 @@ class DebPackage(object): 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 - return False + if arch in apt_pkg.get_architectures(): + self._multiarch = arch + self.pkgname = "%s:%s" % (self.pkgname, self._multiarch) + self._dbg(1, "Found multiarch arch: '%s'" % arch) + else: + self._dbg(1, "ERROR: Wrong architecture dude!") + self._failure_string = _("Wrong architecture '%s'") % arch + return False # check version if self.compare_to_version_in_cache() == self.VERSION_OUTDATED: @@ -462,7 +515,7 @@ class DebPackage(object): for pkg in self._need_pkgs: try: self._cache[pkg].mark_install(from_user=False) - except SystemError as e: + except SystemError: self._failure_string = _("Cannot install '%s'") % pkg self._cache.clear() return False @@ -472,8 +525,8 @@ class DebPackage(object): def missing_deps(self): """Return missing dependencies.""" self._dbg(1, "Installing: %s" % self._need_pkgs) - if self._need_pkgs is None: - self.check() + if not self._check_was_run: + raise AttributeError("property only available after check() was run") return self._need_pkgs @property @@ -485,6 +538,8 @@ class DebPackage(object): install = [] remove = [] unauthenticated = [] + if not self._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) @@ -499,19 +554,6 @@ class DebPackage(object): remove.append(pkg.name) return (install, remove, unauthenticated) - @property - def control_filelist(self): - """ return the list of files in control.tar.gt """ - try: - from debian.debfile import DebFile - except: - raise Exception(_("Python-debian module not available")) - content = [] - for name in DebFile(self.filename).control: - if name and name != ".": - content.append(name) - return sorted(content) - @staticmethod def to_hex(in_data): hex = "" @@ -524,11 +566,20 @@ class DebPackage(object): @staticmethod def to_strish(in_data): s = "" - for c in in_data: - if ord(c) < 10 or ord(c) > 127: - s += " " - else: - s += c + # py2 compat, in_data is type string + if type(in_data) == str: + for c in in_data: + if ord(c) < 10 or ord(c) > 127: + s += " " + else: + s += c + # py3 compat, in_data is type bytes + else: + for b in in_data: + if b < 10 or b > 127: + s += " " + else: + s += chr(b) return s def _get_content(self, part, name, auto_decompress=True, auto_hex=True): @@ -544,7 +595,7 @@ class DebPackage(object): # auto-convert to hex try: data = unicode(data, "utf-8") - except Exception as e: + except Exception: new_data = _("Automatically converted to printable ascii:\n") new_data += self.to_strish(data) return new_data @@ -639,7 +690,8 @@ class DscSrcPackage(DebPackage): "source package '%s' that builds %s\n") % (self.pkgname, " ".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(): @@ -647,6 +699,8 @@ class DscSrcPackage(DebPackage): if self._cache[pkgname]._pkg.essential: raise Exception(_("An essential package would be removed")) self._cache[pkgname].mark_delete() + # properties are ok now + self._check_was_run = True # FIXME: a additional run of the check_conflicts() # after _satisfy_depends() should probably be done return self._satisfy_depends(self.depends) @@ -654,7 +708,7 @@ class DscSrcPackage(DebPackage): def _test(): """Test function""" from apt.cache import Cache - from apt.progress import DpkgInstallProgress + from apt.progress.base import InstallProgress cache = Cache() @@ -676,7 +730,7 @@ def _test(): print d.filelist print "Installing ..." - ret = d.install(DpkgInstallProgress()) + ret = d.install(InstallProgress()) print ret #s = DscSrcPackage(cache, "../tests/3ddesktop_0.2.9-6.dsc") diff --git a/apt/package.py b/apt/package.py index 9223ed22..29eec909 100644 --- a/apt/package.py +++ b/apt/package.py @@ -94,7 +94,7 @@ class BaseDependency(object): preDepend = AttributeDeprecatedBy('pre_depend') -class Dependency(object): +class Dependency(list): """Represent an Or-group of dependencies. Attributes defined here: @@ -102,11 +102,12 @@ class Dependency(object): """ def __init__(self, alternatives): - self.or_dependencies = alternatives - - def __repr__(self): - return repr(self.or_dependencies) + super(Dependency, self).__init__() + self.extend(alternatives) + @property + def or_dependencies(self): + return self class DeprecatedProperty(property): """A property which gives DeprecationWarning on access. @@ -408,6 +409,17 @@ class Version(object): return self._cand.priority_str @property + def policy_priority(self): + """Return the internal policy priority as a number. + See apt_preferences(5) for more information about what it means. + """ + priority = 0 + policy = self.package._pcache._depcache.policy + for (packagefile, _) in self._cand.file_list: + priority = max(priority, policy.get_priority(packagefile)) + return priority + + @property def record(self): """Return a Record() object for this version. @@ -455,6 +467,11 @@ class Version(object): return self.get_dependencies("Recommends") @property + def suggests(self): + """Return the suggests of the package version.""" + return self.get_dependencies("Suggests") + + @property def origins(self): """Return a list of origins for the package version.""" origins = [] @@ -528,7 +545,10 @@ class Version(object): .. versionadded:: 0.7.10 """ - return iter(self._uris()).next() + try: + return iter(self._uris()).next() + except StopIteration: + return None def fetch_binary(self, destdir='', progress=None): """Fetch the binary version of the package. @@ -545,8 +565,8 @@ class Version(object): base = os.path.basename(self._records.filename) destfile = os.path.join(destdir, base) if _file_is_same(destfile, self.size, self._records.md5_hash): - print 'Ignoring already existing file:', destfile - return + print('Ignoring already existing file: %s' % destfile) + return os.path.abspath(destfile) acq = apt_pkg.Acquire(progress or apt.progress.text.AcquireProgress()) acqfile = apt_pkg.AcquireFile(acq, self.uri, self._records.md5_hash, self.size, base, destfile=destfile) @@ -555,7 +575,7 @@ class Version(object): if acqfile.status != acqfile.STAT_DONE: raise FetchError("The item %r could not be fetched: %s" % (acqfile.destfile, acqfile.error_text)) - print self._records.filename + return os.path.abspath(destfile) def fetch_source(self, destdir="", progress=None, unpack=True): @@ -594,7 +614,7 @@ class Version(object): if type_ == 'dsc': dsc = destfile if _file_is_same(destfile, size, md5): - print 'Ignoring already existing file:', destfile + print('Ignoring already existing file: %s' % destfile) continue files.append(apt_pkg.AcquireFile(acq, src.index.archive_uri(path), md5, size, base, destfile=destfile)) @@ -707,6 +727,9 @@ class Package(object): return '<Package: name:%r architecture=%r id:%r>' % (self._pkg.name, self._pkg.architecture, self._pkg.id) + def __lt__(self, other): + return self.name < other.name + def candidate(self): """Return the candidate version of the package. @@ -973,8 +996,8 @@ class Package(object): another package, and if no packages depend on it anymore, the package is no longer required. """ - return self.is_installed and \ - self._pcache._depcache.is_garbage(self._pkg) + return ((self.is_installed or self.marked_install) and + self._pcache._depcache.is_garbage(self._pkg)) @property def is_auto_installed(self): diff --git a/apt/progress/base.py b/apt/progress/base.py index 97375431..ab57dd82 100644 --- a/apt/progress/base.py +++ b/apt/progress/base.py @@ -27,6 +27,7 @@ import fcntl import os import re import select +import sys import apt_pkg @@ -141,6 +142,7 @@ class InstallProgress(object): def __init__(self): (self.statusfd, self.writefd) = os.pipe() + # These will leak fds, but fixing this safely requires API changes. self.write_stream = os.fdopen(self.writefd, "w") self.status_stream = os.fdopen(self.statusfd, "r") fcntl.fcntl(self.statusfd, fcntl.F_SETFL, os.O_NONBLOCK) @@ -196,7 +198,8 @@ class InstallProgress(object): os._exit(os.spawnlp(os.P_WAIT, "dpkg", "dpkg", "--status-fd", str(self.write_stream.fileno()), "-i", obj)) - except Exception: + except Exception as e: + sys.stderr.write("%s\n" % e) os._exit(apt_pkg.PackageManager.RESULT_FAILED) self.child_pid = pid diff --git a/apt/progress/gtk2.py b/apt/progress/gtk2.py index 9137ef76..c2635ca0 100644 --- a/apt/progress/gtk2.py +++ b/apt/progress/gtk2.py @@ -22,9 +22,6 @@ # USA """GObject-powered progress classes and a GTK+ status widget.""" -import os -import time - import pygtk pygtk.require('2.0') import gtk @@ -34,6 +31,7 @@ except ImportError: import gobject as glib import gobject import pango +import time import vte import apt_pkg @@ -127,16 +125,15 @@ class GInstallProgress(gobject.GObject, base.InstallProgress): self.apt_status = -1 self.time_last_update = time.time() self.term = term - reaper = vte.reaper_get() - reaper.connect("child-exited", self.child_exited) + self.term.connect("child-exited", self.child_exited) self.env = ["VTE_PTY_KEEP_FD=%s" % self.writefd, "DEBIAN_FRONTEND=gnome", "APT_LISTCHANGES_FRONTEND=gtk"] self._context = glib.main_context_default() - def child_exited(self, term, pid, status): + def child_exited(self, term): """Called when a child process exits""" - self.apt_status = os.WEXITSTATUS(status) + self.apt_status = term.get_child_exit_status() self.finished = True def error(self, pkg, errormsg): @@ -204,6 +201,7 @@ class GInstallProgress(gobject.GObject, base.InstallProgress): """Wait for the child process to exit.""" while not self.finished: self.update_interface() + time.sleep(0.02) return self.apt_status if apt_pkg._COMPAT_0_7: diff --git a/apt/utils.py b/apt/utils.py index 4b3da39f..08140258 100644 --- a/apt/utils.py +++ b/apt/utils.py @@ -28,11 +28,13 @@ def get_maintenance_end_date(release_date, m_months): ends. Needs the data of the release and the number of months that its is supported as input """ - years = m_months / 12 + # calc end date + years = m_months // 12 months = m_months % 12 support_end_year = (release_date.year + years + - (release_date.month + months)/12) + (release_date.month + months)//12) support_end_month = (release_date.month + months) % 12 + # special case: this happens when e.g. doing 2010-06 + 18 months if support_end_month == 0: support_end_month = 12 support_end_year -= 1 @@ -46,7 +48,7 @@ def get_release_date_from_release_file(path): if not path or not os.path.exists(path): return None tag = apt_pkg.TagFile(open(path)) - section = tag.next() + section = next(tag) if not "Date" in section: return None date = section["Date"] |
