* ubuntutools.misc: Add a new "system_distribution_chain", which returns

a list starting with the current distribution and working its way up
  each distribution's parent.
* ubuntutools.misc: Add a function to find the distribution that
  used a given release codename.
* backportpackage, doc/backportpackage.1: Accept codenames from any
  distribution in the parenting chain. Makes it possible to, e.g.,
  backport from Debian. (LP: #703099)
This commit is contained in:
Evan Broder 2011-06-24 16:13:43 +02:00 committed by Stefano Rivera
commit be202b94d5
6 changed files with 201 additions and 74 deletions

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ################################################################## # ##################################################################
# #
# Copyright (C) 2010, Evan Broder <evan@ebroder.net> # Copyright (C) 2010-2011, Evan Broder <evan@ebroder.net>
# Copyright (C) 2010, Benjamin Drung <bdrung@ubuntu.com> # Copyright (C) 2010, Benjamin Drung <bdrung@ubuntu.com>
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
@ -28,11 +28,16 @@ import tempfile
from launchpadlib.launchpad import Launchpad from launchpadlib.launchpad import Launchpad
import lsb_release import lsb_release
from debian.debian_support import Version
from devscripts.logger import Logger from devscripts.logger import Logger
from ubuntutools.archive import UbuntuSourcePackage, DownloadError from ubuntutools.archive import SourcePackage, DebianSourcePackage, \
UbuntuSourcePackage, DownloadError, rmadison
from ubuntutools.config import UDTConfig, ubu_email from ubuntutools.config import UDTConfig, ubu_email
from ubuntutools.builder import get_builder from ubuntutools.builder import get_builder
from ubuntutools.misc import system_distribution, vendor_to_distroinfo, \
codename_to_distribution
from ubuntutools.question import YesNoQuestion from ubuntutools.question import YesNoQuestion
def error(msg): def error(msg):
@ -105,9 +110,9 @@ def parse(args):
'(default: temporary dir)', '(default: temporary dir)',
metavar='WORKDIR') metavar='WORKDIR')
parser.add_option('-m', '--mirror', parser.add_option('-m', '--mirror',
dest='ubuntu_mirror', dest='mirror',
default=None, default=None,
help='Preferred Ubuntu mirror (default: Launchpad)', help='Preferred mirror (default: Launchpad)',
metavar='INSTANCE') metavar='INSTANCE')
parser.add_option('-l', '--lpinstance', parser.add_option('-l', '--lpinstance',
dest='lpinstance', dest='lpinstance',
@ -134,58 +139,83 @@ def parse(args):
opts.workdir = config.get_value('WORKDIR') opts.workdir = config.get_value('WORKDIR')
if opts.lpinstance is None: if opts.lpinstance is None:
opts.lpinstance = config.get_value('LPINSTANCE') opts.lpinstance = config.get_value('LPINSTANCE')
if opts.ubuntu_mirror is None:
opts.ubuntu_mirror = config.get_value('UBUNTU_MIRROR')
if not opts.upload and not opts.workdir: if not opts.upload and not opts.workdir:
parser.error('Please specify either a working dir or an upload target!') parser.error('Please specify either a working dir or an upload target!')
return opts, args return opts, args, config
def find_release_package(launchpad, package, version, source_release): def get_current_version(package, distribution, source_release):
ubuntu = launchpad.distributions['ubuntu'] info = vendor_to_distroinfo(distribution)
archive = ubuntu.main_archive source_release = info().codename(source_release, default=source_release)
series = ubuntu.getSeries(name_or_version=source_release)
status = 'Published' latest_version = None
for pocket in ('Updates', 'Security', 'Release'):
try: for record in rmadison(distribution.lower(), package, suite=source_release):
srcpkg = archive.getPublishedSources(source_name=package, if 'source' not in record:
distro_series=series,
pocket=pocket,
status=status,
exact_match=True)[0]
break
except IndexError:
continue continue
if (not latest_version or
Version(latest_version) < Version(record['version'])):
latest_version = record['version']
return latest_version
def find_release_package(launchpad, mirror, workdir, package, version,
source_release, config):
srcpkg = None
if source_release:
distribution = codename_to_distribution(source_release)
if not distribution:
error('Unknown release codename %s' % source_release)
else: else:
distribution = system_distribution()
mirrors = [mirror] if mirror else []
mirrors.append(config.get_value('%s_MIRROR' % distribution.upper()))
if not version:
version = get_current_version(package, distribution, source_release)
if not version:
error('Unable to find package %s in release %s.' % error('Unable to find package %s in release %s.' %
(package, source_release)) (package, source_release))
if version and version != srcpkg.source_package_version: if distribution == 'Debian':
error('Requested backport of version %s but %s is at version %s.' % srcpkg = DebianSourcePackage(package,
(version, package, srcpkg.source_package_version)) version,
workdir=workdir,
lp=launchpad,
mirrors=mirrors)
elif distribution == 'Ubuntu':
srcpkg = UbuntuSourcePackage(package,
version,
workdir=workdir,
lp=launchpad,
mirrors=mirrors)
return srcpkg return srcpkg
def find_package(launchpad, mirror, workdir, package, version, source_release): def find_package(launchpad, mirror, workdir, package, version, source_release,
config):
"Returns the SourcePackage" "Returns the SourcePackage"
if package.endswith('.dsc'): if package.endswith('.dsc'):
return UbuntuSourcePackage(version=version, dscfile=package, return SourcePackage(version=version, dscfile=package,
workdir=workdir, lp=launchpad, workdir=workdir, lp=launchpad,
mirrors=[mirror]) mirrors=(mirror,))
if not source_release and not version: if not source_release and not version:
source_release = launchpad.distributions['ubuntu'].current_series.name info = vendor_to_distroinfo(system_distribution())
source_release = info().devel()
component = None srcpkg = find_release_package(launchpad, mirror, workdir, package, version,
# If source_release is specified, then version is just for verification source_release, config)
if source_release: if version and srcpkg.version != version:
srcpkg = find_release_package(launchpad, package, version, error('Requested backport of version %s but version of %s in %s is %s'
source_release) % (version, package, source_release, srcpkg.version))
version = srcpkg.source_package_version
component = srcpkg.component_name
return UbuntuSourcePackage(package, version, component, workdir=workdir, return srcpkg
lp=launchpad, mirrors=[mirror])
def get_backport_version(version, suffix, upload, release): def get_backport_version(version, suffix, upload, release):
backport_version = version + ('~%s1' % release) backport_version = version + ('~%s1' % release)
@ -257,10 +287,9 @@ def do_backport(workdir, pkg, suffix, release, build, builder, update, upload,
shutil.rmtree(srcdir) shutil.rmtree(srcdir)
def main(args): def main(args):
os.environ['DEB_VENDOR'] = 'Ubuntu'
ubu_email() ubu_email()
opts, (package_or_dsc,) = parse(args[1:]) opts, (package_or_dsc,), config = parse(args[1:])
script_name = os.path.basename(sys.argv[0]) script_name = os.path.basename(sys.argv[0])
launchpad = Launchpad.login_anonymously(script_name, opts.lpinstance) launchpad = Launchpad.login_anonymously(script_name, opts.lpinstance)
@ -282,11 +311,12 @@ def main(args):
try: try:
pkg = find_package(launchpad, pkg = find_package(launchpad,
opts.ubuntu_mirror, opts.mirror,
workdir, workdir,
package_or_dsc, package_or_dsc,
opts.version, opts.version,
opts.source_release) opts.source_release,
config)
pkg.pull() pkg.pull()
for release in opts.dest_releases: for release in opts.dest_releases:

14
debian/changelog vendored
View File

@ -1,3 +1,17 @@
ubuntu-dev-tools (0.126) UNRELEASED; urgency=low
[ Evan Broder ]
* ubuntutools.misc: Add a new "system_distribution_chain", which returns
a list starting with the current distribution and working its way up
each distribution's parent.
* ubuntutools.misc: Add a function to find the distribution that
used a given release codename.
* backportpackage, doc/backportpackage.1: Accept codenames from any
distribution in the parenting chain. Makes it possible to, e.g.,
backport from Debian. (LP: #703099)
-- Evan Broder <evan@ebroder.net> Sat, 11 Jun 2011 05:11:23 -0700
ubuntu-dev-tools (0.125ubuntu1) oneiric; urgency=low ubuntu-dev-tools (0.125ubuntu1) oneiric; urgency=low
[ Benjamin Drung ] [ Benjamin Drung ]

View File

@ -10,10 +10,11 @@ backportpackage \- helper to test package backports
.PP .PP
.B backportpackage \-h .B backportpackage \-h
.SH DESCRIPTION .SH DESCRIPTION
\fBbackportpackage\fR fetches a package from one Ubuntu release or \fBbackportpackage\fR fetches a package from one distribution release
from a specified .dsc path or URL and creates a no-change backport of or from a specified .dsc path or URL and creates a no-change backport
that package to a previous release, optionally doing a test build of of that package to one or more Ubuntu releases release, optionally
the package and/or uploading the resulting backport for testing. doing a test build of the package and/or uploading the resulting
backport for testing.
.PP .PP
Unless a working directory is specified, the backported package is Unless a working directory is specified, the backported package is
fetched and built in a temporary directory in \fB/tmp\fR, which is fetched and built in a temporary directory in \fB/tmp\fR, which is
@ -29,10 +30,11 @@ is unspecified, then \fBbackportpackage\fR defaults to the release on
which it is currently running. which it is currently running.
.TP .TP
.B \-s \fISOURCE\fR, \fB\-\-source\fR=\fISOURCE\fR .B \-s \fISOURCE\fR, \fB\-\-source\fR=\fISOURCE\fR
Backport the package from the specified Ubuntu release. If neither Backport the package from the specified release, which can be any
this option nor \fB\-\-version\fR are specified, then release of your distribution or any of your distribution's parent
\fBbackportpackage\fR defaults to the current Ubuntu development distributions. If neither this option nor \fB\-\-version\fR are
release. specified, then \fBbackportpackage\fR defaults to the current
development release for your distribution.
.TP .TP
.B \-S \fISUFFIX\fR, \fB\-\-suffix\fR=\fISUFFIX\fR .B \-S \fISUFFIX\fR, \fB\-\-suffix\fR=\fISUFFIX\fR
Add the specified suffix to the version number when Add the specified suffix to the version number when
@ -72,9 +74,10 @@ If the \fB\-\-source\fR option is specified, then
\fBbackportpackage\fR verifies that the current version of \fIsource \fBbackportpackage\fR verifies that the current version of \fIsource
package\fR in \fISOURCE\fR is the same as \fIVERSION\fR. Otherwise, package\fR in \fISOURCE\fR is the same as \fIVERSION\fR. Otherwise,
\fBbackportpackage\fR finds version \fIVERSION\fR of \fIsource \fBbackportpackage\fR finds version \fIVERSION\fR of \fIsource
package\fR, regardless of the release in which it was published (or if package\fR in your distribution's publishing history, regardless of
that version is still current). This option is ignored if a .dsc URL the release in which it was published (or if that version is still
or path is passed in instead of a source package name. current). This option is ignored if a .dsc URL or path is passed in
instead of a source package name.
.TP .TP
.B \-w \fIWORKDIR\fR, \fB\-\-workdir\fR=\fIWORKDIR\fR .B \-w \fIWORKDIR\fR, \fB\-\-workdir\fR=\fIWORKDIR\fR
If \fIWORKDIR\fR is specified, then all files are downloaded, If \fIWORKDIR\fR is specified, then all files are downloaded,
@ -82,7 +85,7 @@ unpacked, built into, and otherwise manipulated in
\fIWORKDIR\fR. Otherwise, a temporary directory is created, which is \fIWORKDIR\fR. Otherwise, a temporary directory is created, which is
deleted before \fIbackportpackage\fR exits. deleted before \fIbackportpackage\fR exits.
.TP .TP
.B \-m \fIUBUNTU_MIRROR\fR, \fB\-\-mirror\fR=\fIUBUNTU_MIRROR\fR .B \-m \fIMIRROR\fR, \fB\-\-mirror\fR=\fIMIRROR\fR
Use the specified mirror. Use the specified mirror.
Should be in the form \fBhttp://archive.ubuntu.com/ubuntu\fR. Should be in the form \fBhttp://archive.ubuntu.com/ubuntu\fR.
If the package isn't found on this mirror, \fBbackportpackage\fR If the package isn't found on this mirror, \fBbackportpackage\fR
@ -124,7 +127,12 @@ The default value for \fB--update\fR.
The default value for \fB--workdir\fR. The default value for \fB--workdir\fR.
.TP .TP
.BR BACKPORTPACKAGE_UBUNTU_MIRROR ", " UBUNTUTOOLS_UBUNTU_MIRROR .BR BACKPORTPACKAGE_UBUNTU_MIRROR ", " UBUNTUTOOLS_UBUNTU_MIRROR
The default value for \fB\-\-mirror\fR. The default value for \fB\-\-mirror\fR if the specified \fISOURCE\fR
release is an Ubuntu release.
.TP
.BR BACKPORTPACKAGE_DEBIAN_MIRROR ", " UBUNTUTOOLS_DEBIAN_MIRROR
The default value for \fB\-\-mirror\fR if the specified \fISOURCE\fR
release is a Debian release.
.TP .TP
.BR BACKPORTPACKAGE_LPINSTANCE ", " UBUNTUTOOLS_LPINSTANCE .BR BACKPORTPACKAGE_LPINSTANCE ", " UBUNTUTOOLS_LPINSTANCE
The default value for \fB--lpinstance\fR. The default value for \fB--lpinstance\fR.

View File

@ -106,6 +106,14 @@ class DistroInfo(object):
"""Get list of all supported distributions based on the given date.""" """Get list of all supported distributions based on the given date."""
raise NotImplementedError() raise NotImplementedError()
def valid(self, codename):
"""Check if the given codename is known."""
return codename in self.all
def codename(self, release, date=None, default=None):
"""Map codename aliases to the codename they describe"""
return release
def unsupported(self, date=None): def unsupported(self, date=None):
"""Get list of all unsupported distributions based on the given date.""" """Get list of all unsupported distributions based on the given date."""
if date is None: if date is None:
@ -167,6 +175,11 @@ class DebianDistroInfo(DistroInfo):
raise DistroDataOutdated() raise DistroDataOutdated()
return distros[-2]["series"] return distros[-2]["series"]
def valid(self, codename):
"""Check if the given codename is known."""
return DistroInfo.valid(self, codename) or \
codename in ["unstable", "testing", "stable", "old"]
class UbuntuDistroInfo(DistroInfo): class UbuntuDistroInfo(DistroInfo):
"""provides information about Ubuntu's distributions""" """provides information about Ubuntu's distributions"""

View File

@ -4,6 +4,7 @@
# Copyright (C) 2008, Jonathan Davies <jpds@ubuntu.com>, # Copyright (C) 2008, Jonathan Davies <jpds@ubuntu.com>,
# 2008-2009, Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>, # 2008-2009, Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>,
# 2010, Stefano Rivera <stefanor@ubuntu.com> # 2010, Stefano Rivera <stefanor@ubuntu.com>
# 2011, Evan Broder <evan@ebroder.net>
# #
# ################################################################## # ##################################################################
# #
@ -28,9 +29,49 @@ import os.path
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
import sys import sys
from ubuntutools import distro_info
from ubuntutools.lp.udtexceptions import PocketDoesNotExistError from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
_system_distribution = None _system_distribution_chain = []
def system_distribution_chain():
""" system_distribution_chain() -> [string]
Detect the system's distribution as well as all of its parent
distributions and return them as a list of strings, with the
system distribution first (and the greatest grandparent last). If
the distribution chain can't be determined, print an error message
and return an empty list.
"""
global _system_distribution_chain
if len(_system_distribution_chain) == 0:
try:
p = Popen(('dpkg-vendor', '--query', 'Vendor'),
stdout=PIPE)
_system_distribution_chain.append(p.communicate()[0].strip())
except OSError:
print ('Error: Could not determine what distribution you are '
'running.')
return []
while True:
try:
p = Popen(('dpkg-vendor',
'--vendor', _system_distribution_chain[-1],
'--query', 'Parent'),
stdout=PIPE)
parent = p.communicate()[0].strip()
# Don't check return code, because if a vendor has no
# parent, dpkg-vendor returns 1
if not parent:
break
_system_distribution_chain.append(parent)
except Exception:
print ('Error: Could not determine the parent of the '
'distribution %s' % _system_distribution_chain[-1])
return []
return _system_distribution_chain
def system_distribution(): def system_distribution():
""" system_distro() -> string """ system_distro() -> string
@ -38,24 +79,7 @@ def system_distribution():
name of the distribution can't be determined, print an error message name of the distribution can't be determined, print an error message
and return None. and return None.
""" """
global _system_distribution return system_distribution_chain()[0]
if _system_distribution is None:
try:
if os.path.isfile('/usr/bin/dpkg-vendor'):
process = Popen(('dpkg-vendor', '--query', 'vendor'),
stdout=PIPE)
else:
process = Popen(('lsb_release', '-cs'), stdout=PIPE)
output = process.communicate()[0]
except OSError:
print ('Error: Could not determine what distribution you are '
'running.')
return None
if process.returncode != 0:
print 'Error determininng system distribution'
return None
_system_distribution = output.strip()
return _system_distribution
def host_architecture(): def host_architecture():
""" host_architecture -> string """ host_architecture -> string
@ -128,3 +152,30 @@ def require_utf8():
print >> sys.stderr, ("This program only functions in a UTF-8 locale. " print >> sys.stderr, ("This program only functions in a UTF-8 locale. "
"Aborting.") "Aborting.")
sys.exit(1) sys.exit(1)
_vendor_to_distroinfo = {"Debian": distro_info.DebianDistroInfo,
"Ubuntu": distro_info.UbuntuDistroInfo}
def vendor_to_distroinfo(vendor):
""" vendor_to_distroinfo(string) -> DistroInfo class
Convert a string name of a distribution into a DistroInfo subclass
representing that distribution, or None if the distribution is
unknown.
"""
return _vendor_to_distroinfo.get(vendor)
def codename_to_distribution(codename):
""" codename_to_distribution(string) -> string
Finds a given release codename in your distribution's genaology
(i.e. looking at the current distribution and its parents), or
print an error message and return None if it can't be found
"""
for distro in system_distribution_chain():
info = vendor_to_distroinfo(distro)
if not info:
continue
if info().valid(codename):
return distro

View File

@ -58,6 +58,12 @@ class DebianDistroInfoTestCase(unittest.TestCase):
"""Test: Get latest testing Debian distribution.""" """Test: Get latest testing Debian distribution."""
self.assertEqual(self._distro_info.testing(self._date), "squeeze") self.assertEqual(self._distro_info.testing(self._date), "squeeze")
def test_valid(self):
"""Test: Check for valid Debian distribution."""
self.assertTrue(self._distro_info.valid("sid"))
self.assertTrue(self._distro_info.valid("stable"))
self.assertFalse(self._distro_info.valid("foobar"))
def test_unsupported(self): def test_unsupported(self):
"""Test: List all unsupported Debian distribution.""" """Test: List all unsupported Debian distribution."""
unsupported = ["buzz", "rex", "bo", "hamm", "slink", "potato", "woody", unsupported = ["buzz", "rex", "bo", "hamm", "slink", "potato", "woody",
@ -111,3 +117,8 @@ class UbuntuDistroInfoTestCase(unittest.TestCase):
"gutsy", "intrepid", "jaunty"]) "gutsy", "intrepid", "jaunty"])
self.assertEqual(unsupported - self.assertEqual(unsupported -
set(self._distro_info.unsupported()), set()) set(self._distro_info.unsupported()), set())
def test_valid(self):
"""Test: Check for valid Ubuntu distribution."""
self.assertTrue(self._distro_info.valid("lucid"))
self.assertFalse(self._distro_info.valid("42"))