* 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 -*-
# ##################################################################
#
# 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>
#
# This program is free software; you can redistribute it and/or
@ -28,11 +28,16 @@ import tempfile
from launchpadlib.launchpad import Launchpad
import lsb_release
from debian.debian_support import Version
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.builder import get_builder
from ubuntutools.misc import system_distribution, vendor_to_distroinfo, \
codename_to_distribution
from ubuntutools.question import YesNoQuestion
def error(msg):
@ -105,9 +110,9 @@ def parse(args):
'(default: temporary dir)',
metavar='WORKDIR')
parser.add_option('-m', '--mirror',
dest='ubuntu_mirror',
dest='mirror',
default=None,
help='Preferred Ubuntu mirror (default: Launchpad)',
help='Preferred mirror (default: Launchpad)',
metavar='INSTANCE')
parser.add_option('-l', '--lpinstance',
dest='lpinstance',
@ -134,58 +139,83 @@ def parse(args):
opts.workdir = config.get_value('WORKDIR')
if opts.lpinstance is None:
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:
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):
ubuntu = launchpad.distributions['ubuntu']
archive = ubuntu.main_archive
series = ubuntu.getSeries(name_or_version=source_release)
status = 'Published'
for pocket in ('Updates', 'Security', 'Release'):
try:
srcpkg = archive.getPublishedSources(source_name=package,
distro_series=series,
pocket=pocket,
status=status,
exact_match=True)[0]
break
except IndexError:
def get_current_version(package, distribution, source_release):
info = vendor_to_distroinfo(distribution)
source_release = info().codename(source_release, default=source_release)
latest_version = None
for record in rmadison(distribution.lower(), package, suite=source_release):
if 'source' not in record:
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:
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.' %
(package, source_release))
if version and version != srcpkg.source_package_version:
error('Requested backport of version %s but %s is at version %s.' %
(version, package, srcpkg.source_package_version))
if distribution == 'Debian':
srcpkg = DebianSourcePackage(package,
version,
workdir=workdir,
lp=launchpad,
mirrors=mirrors)
elif distribution == 'Ubuntu':
srcpkg = UbuntuSourcePackage(package,
version,
workdir=workdir,
lp=launchpad,
mirrors=mirrors)
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"
if package.endswith('.dsc'):
return UbuntuSourcePackage(version=version, dscfile=package,
workdir=workdir, lp=launchpad,
mirrors=[mirror])
return SourcePackage(version=version, dscfile=package,
workdir=workdir, lp=launchpad,
mirrors=(mirror,))
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
# If source_release is specified, then version is just for verification
if source_release:
srcpkg = find_release_package(launchpad, package, version,
source_release)
version = srcpkg.source_package_version
component = srcpkg.component_name
srcpkg = find_release_package(launchpad, mirror, workdir, package, version,
source_release, config)
if version and srcpkg.version != version:
error('Requested backport of version %s but version of %s in %s is %s'
% (version, package, source_release, srcpkg.version))
return UbuntuSourcePackage(package, version, component, workdir=workdir,
lp=launchpad, mirrors=[mirror])
return srcpkg
def get_backport_version(version, suffix, upload, release):
backport_version = version + ('~%s1' % release)
@ -257,10 +287,9 @@ def do_backport(workdir, pkg, suffix, release, build, builder, update, upload,
shutil.rmtree(srcdir)
def main(args):
os.environ['DEB_VENDOR'] = 'Ubuntu'
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])
launchpad = Launchpad.login_anonymously(script_name, opts.lpinstance)
@ -282,11 +311,12 @@ def main(args):
try:
pkg = find_package(launchpad,
opts.ubuntu_mirror,
opts.mirror,
workdir,
package_or_dsc,
opts.version,
opts.source_release)
opts.source_release,
config)
pkg.pull()
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
[ Benjamin Drung ]

View File

@ -10,10 +10,11 @@ backportpackage \- helper to test package backports
.PP
.B backportpackage \-h
.SH DESCRIPTION
\fBbackportpackage\fR fetches a package from one Ubuntu release or
from a specified .dsc path or URL and creates a no-change backport of
that package to a previous release, optionally doing a test build of
the package and/or uploading the resulting backport for testing.
\fBbackportpackage\fR fetches a package from one distribution release
or from a specified .dsc path or URL and creates a no-change backport
of that package to one or more Ubuntu releases release, optionally
doing a test build of the package and/or uploading the resulting
backport for testing.
.PP
Unless a working directory is specified, the backported package 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.
.TP
.B \-s \fISOURCE\fR, \fB\-\-source\fR=\fISOURCE\fR
Backport the package from the specified Ubuntu release. If neither
this option nor \fB\-\-version\fR are specified, then
\fBbackportpackage\fR defaults to the current Ubuntu development
release.
Backport the package from the specified release, which can be any
release of your distribution or any of your distribution's parent
distributions. If neither this option nor \fB\-\-version\fR are
specified, then \fBbackportpackage\fR defaults to the current
development release for your distribution.
.TP
.B \-S \fISUFFIX\fR, \fB\-\-suffix\fR=\fISUFFIX\fR
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
package\fR in \fISOURCE\fR is the same as \fIVERSION\fR. Otherwise,
\fBbackportpackage\fR finds version \fIVERSION\fR of \fIsource
package\fR, regardless of the release in which it was published (or if
that version is still current). This option is ignored if a .dsc URL
or path is passed in instead of a source package name.
package\fR in your distribution's publishing history, regardless of
the release in which it was published (or if that version is still
current). This option is ignored if a .dsc URL or path is passed in
instead of a source package name.
.TP
.B \-w \fIWORKDIR\fR, \fB\-\-workdir\fR=\fIWORKDIR\fR
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
deleted before \fIbackportpackage\fR exits.
.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.
Should be in the form \fBhttp://archive.ubuntu.com/ubuntu\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.
.TP
.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
.BR BACKPORTPACKAGE_LPINSTANCE ", " UBUNTUTOOLS_LPINSTANCE
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."""
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):
"""Get list of all unsupported distributions based on the given date."""
if date is None:
@ -167,6 +175,11 @@ class DebianDistroInfo(DistroInfo):
raise DistroDataOutdated()
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):
"""provides information about Ubuntu's distributions"""

View File

@ -4,6 +4,7 @@
# Copyright (C) 2008, Jonathan Davies <jpds@ubuntu.com>,
# 2008-2009, Siegfried-Angel Gevatter Pujals <rainct@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
import sys
from ubuntutools import distro_info
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():
""" system_distro() -> string
@ -38,24 +79,7 @@ def system_distribution():
name of the distribution can't be determined, print an error message
and return None.
"""
global _system_distribution
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
return system_distribution_chain()[0]
def host_architecture():
""" host_architecture -> string
@ -128,3 +152,30 @@ def require_utf8():
print >> sys.stderr, ("This program only functions in a UTF-8 locale. "
"Aborting.")
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."""
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):
"""Test: List all unsupported Debian distribution."""
unsupported = ["buzz", "rex", "bo", "hamm", "slink", "potato", "woody",
@ -111,3 +117,8 @@ class UbuntuDistroInfoTestCase(unittest.TestCase):
"gutsy", "intrepid", "jaunty"])
self.assertEqual(unsupported -
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"))