Merge trunk.

This commit is contained in:
Evan Broder 2010-12-17 01:32:30 -08:00
commit 8627e0a626
2 changed files with 155 additions and 77 deletions

View File

@ -21,16 +21,15 @@ import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import urllib
from debian.deb822 import Dsc from debian.deb822 import Dsc
import launchpadlib.launchpad import launchpadlib.launchpad
import lsb_release
from ubuntutools.builder import getBuilder from ubuntutools.builder import getBuilder
from ubuntutools.logger import Logger from ubuntutools.logger import Logger
devnull = open('/dev/null', 'r+')
lp = None
def error(msg): def error(msg):
Logger.error(msg) Logger.error(msg)
sys.exit(1) sys.exit(1)
@ -42,15 +41,15 @@ def check_call(cmd, *args, **kwargs):
error('%s returned %d' % (cmd, ret)) error('%s returned %d' % (cmd, ret))
def parse(args): def parse(args):
usage = 'Usage: %prog [options] <source package>' usage = 'Usage: %prog [options] <source package name or .dsc URL/file>'
p = optparse.OptionParser(usage) p = optparse.OptionParser(usage)
p.add_option('-t', '--to', p.add_option('-d', '--destination',
dest='dest_releases', dest='dest_releases',
default=[], default=[],
action='append', action='append',
help='Backport to DEST release (required)', help='Backport to DEST release (default: current release)',
metavar='DEST') metavar='DEST')
p.add_option('-f', '--from', p.add_option('-s', '--source',
dest='source_release', dest='source_release',
default=None, default=None,
help='Backport from SOURCE release (default: devel release)', help='Backport from SOURCE release (default: devel release)',
@ -79,6 +78,11 @@ def parse(args):
default=None, default=None,
help='Package version to backport (or verify)', help='Package version to backport (or verify)',
metavar='VERSION') metavar='VERSION')
p.add_option('-w', '--workdir',
dest='workdir',
default=None,
help='Specify a working directory (default: temporary dir)',
metavar='WORKDIR')
p.add_option('-l', '--launchpad', p.add_option('-l', '--launchpad',
dest='launchpad', dest='launchpad',
default='production', default='production',
@ -87,16 +91,16 @@ def parse(args):
opts, args = p.parse_args(args) opts, args = p.parse_args(args)
if len(args) != 1: if len(args) != 1:
p.error('You must specify a source package') p.error('You must specify a single source package or a .dsc URL/path')
if not opts.dest_releases: if not opts.upload and not opts.build:
p.error('You must specify at least one destination release') p.error('Nothing to do')
return opts, args return opts, args
def find_release_package(package, opts): def find_release_package(lp, package, version, source_release):
ubuntu = lp.distributions['ubuntu'] ubuntu = lp.distributions['ubuntu']
archive = ubuntu.main_archive archive = ubuntu.main_archive
series = ubuntu.getSeries(name_or_version=opts.source_release) series = ubuntu.getSeries(name_or_version=source_release)
status = 'Published' status = 'Published'
for pocket in ('Updates', 'Security', 'Release'): for pocket in ('Updates', 'Security', 'Release'):
try: try:
@ -110,50 +114,67 @@ def find_release_package(package, opts):
continue continue
else: else:
error('Unable to find package %s in release %s' % error('Unable to find package %s in release %s' %
(package, opts.source_release)) (package, source_release))
if opts.version and opts.version != srcpkg.source_package_version: if version and version != srcpkg.source_package_version:
error('Requested backport of version %s but %s is at version %s' % error('Requested backport of version %s but %s is at version %s' %
(opts.version, package, srcpkg.source_package_version)) (version, package, srcpkg.source_package_version))
return srcpkg return srcpkg
def find_version_package(package, opts): def find_version_package(lp, package, version):
ubuntu = lp.distributions['ubuntu'] ubuntu = lp.distributions['ubuntu']
archive = ubuntu.main_archive archive = ubuntu.main_archive
try: try:
# Might get more than one (i.e. same version in multiple # Might get more than one (i.e. same version in multiple
# releases), but they should all be identical # releases), but they should all be identical
return archive.getPublishedSources(source_name=package, return archive.getPublishedSources(source_name=package,
version=opts.version)[0] version=version)[0]
except IndexError: except IndexError:
error('Package %s was never published with version %s in Ubuntu' % error('Version %s of package %s was never published in Ubuntu' %
(package, opts.version)) (version, package))
def fetch_package(workdir, package, opts): def dscurl_from_package(lp, workdir, package, version, source_release):
# Returns the path to the .dsc file that was fetched if not source_release and not version:
source_release = lp.distributions['ubuntu'].current_series.name
if not opts.source_release and not opts.version:
opts.source_release = lp.distributions['ubuntu'].current_series.name
# If source_release is specified, then version is just for # If source_release is specified, then version is just for
# verification # verification
if opts.source_release: if source_release:
srcpkg = find_release_package(package, opts) srcpkg = find_release_package(lp, package, version, source_release)
else: else:
srcpkg = find_version_package(package, opts) srcpkg = find_version_package(lp, package, version)
for f in srcpkg.sourceFileUrls(): for f in srcpkg.sourceFileUrls():
if f.endswith('.dsc'): if f.endswith('.dsc'):
check_call(['dget', return urllib.unquote(f)
'--download-only',
'--allow-unauthenticated',
f],
cwd=workdir)
return os.path.join(workdir, os.path.basename(f))
else: else:
error('Package %s contains no .dsc file' % package) error('Package %s contains no .dsc file' % package)
def dscurl_from_dsc(package):
path = os.path.abspath(os.path.expanduser(package))
if os.path.exists(path):
return 'file://%s' % path
else:
# Can't resolve it as a local path? Let's just hope it's good
# as-is
return package
def fetch_package(lp, workdir, package, version, source_release):
# Returns the path to the .dsc file that was fetched
if package.endswith('.dsc'):
dsc = dscurl_from_dsc(package)
else:
dsc = dscurl_from_package(lp, workdir, package, version, source_release)
check_call(['dget',
'--download-only',
'--allow-unauthenticated',
dsc],
cwd=workdir)
return os.path.join(workdir, os.path.basename(dsc))
def get_backport_version(version, upload, release): def get_backport_version(version, upload, release):
v = version + ('~%s1' % release) v = version + ('~%s1' % release)
if upload and upload.startswith('ppa:'): if upload and upload.startswith('ppa:'):
@ -166,21 +187,21 @@ def get_backport_dist(upload, release):
else: else:
return release return release
def do_build(workdir, package, release, bp_version, opts): def do_build(workdir, package, release, bp_version, builder):
builder = getBuilder(opts.builder) builder = getBuilder(builder)
if not builder: if not builder:
return return
if opts.update: if opts.update:
builder.update(release) builder.update(release)
builder.build(os.path.join(workdir, return builder.build(os.path.join(workdir,
'%s_%s.dsc' % (package, bp_version)), '%s_%s.dsc' % (package, bp_version)),
release, release,
workdir) workdir)
def do_upload(workdir, package, bp_version, opts): def do_upload(workdir, package, bp_version, upload):
prompt = 'Do you want to upload this to %s? [Y/n]' % opts.upload prompt = 'Do you want to upload this to %s? [Y/n]' % upload
while True: while True:
answer = raw_input(prompt).strip().lower() answer = raw_input(prompt).strip().lower()
if answer in ('', 'y', 'yes'): if answer in ('', 'y', 'yes'):
@ -189,16 +210,13 @@ def do_upload(workdir, package, bp_version, opts):
return return
check_call(['dput', check_call(['dput',
opts.upload, upload,
'%s_%s_source.changes' % '%s_%s_source.changes' %
(package, bp_version)], (package, bp_version)],
cwd=workdir) cwd=workdir)
def do_backport(workdir, package, dscfile, release, opts): def do_backport(workdir, package, dscfile, version, release, build, builder, upload):
dsc = Dsc(open(os.path.join(workdir, dscfile)))
v = dsc['Version']
check_call(['dpkg-source', check_call(['dpkg-source',
'-x', '-x',
dscfile, dscfile,
@ -206,8 +224,8 @@ def do_backport(workdir, package, dscfile, release, opts):
cwd=workdir) cwd=workdir)
srcdir = os.path.join(workdir, package) srcdir = os.path.join(workdir, package)
bp_version = get_backport_version(v, opts.upload, release) bp_version = get_backport_version(version, upload, release)
bp_dist = get_backport_dist(opts.upload, release) bp_dist = get_backport_dist(upload, release)
check_call(['dch', check_call(['dch',
'--force-bad-version', '--force-bad-version',
@ -223,32 +241,61 @@ def do_backport(workdir, package, dscfile, release, opts):
bp_version = bp_version[bp_version.find(':')+1:] bp_version = bp_version[bp_version.find(':')+1:]
print 'Please check the package in file://%s carefully' % workdir print 'Please check the package in file://%s carefully' % workdir
if opts.build: if build:
do_build(workdir, package, release, bp_version, opts) if 0 != do_build(workdir, package, release, bp_version, builder):
if opts.upload: error('Package failed to build; aborting')
do_upload(workdir, package, bp_version, opts) if upload:
do_upload(workdir, package, bp_version, upload)
shutil.rmtree(srcdir) shutil.rmtree(srcdir)
def main(args): def main(args):
global lp
os.environ['DEB_VENDOR'] = 'Ubuntu' os.environ['DEB_VENDOR'] = 'Ubuntu'
opts, (package,) = parse(args[1:]) opts, (package_or_dsc,) = parse(args[1:])
script_name = os.path.basename(sys.argv[0]) script_name = os.path.basename(sys.argv[0])
lp = launchpadlib.launchpad.Launchpad.login_anonymously(script_name, lp = launchpadlib.launchpad.Launchpad.login_anonymously(script_name,
opts.launchpad) opts.launchpad)
workdir = tempfile.mkdtemp(prefix='backportpackage-') if not opts.dest_releases:
try:
distinfo = lsb_release.get_distro_information()
opts.dest_releases = [distinfo['CODENAME']]
except:
error('No destination release specified and unable to guess yours')
if opts.workdir:
workdir = os.path.expanduser(opts.workdir)
else:
workdir = tempfile.mkdtemp(prefix='backportpackage-')
if not os.path.exists(workdir):
os.makedirs(workdir)
try: try:
dscfile = fetch_package(workdir, package, opts) dscfile = fetch_package(lp,
workdir,
package_or_dsc,
opts.version,
opts.source_release)
dsc = Dsc(open(os.path.join(workdir, dscfile)))
package = dsc['Source']
version = dsc['Version']
for release in opts.dest_releases: for release in opts.dest_releases:
do_backport(workdir, package, dscfile, release, opts) do_backport(workdir,
package,
dscfile,
version,
release,
opts.build,
opts.builder,
opts.upload)
finally: finally:
shutil.rmtree(workdir) if not opts.workdir:
shutil.rmtree(workdir)
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main(sys.argv)) sys.exit(main(sys.argv))

View File

@ -4,21 +4,19 @@ backportpackage \- helper to test package backports
.SH SYNOPSIS .SH SYNOPSIS
.TP .TP
.B backportpackage \fR[\fIadditional options\fR] .B backportpackage \fR[\fIadditional options\fR]
\-\-to <\fIdest release\fR>
.br
\-\-upload <\fIupload target\fR> \-\-upload <\fIupload target\fR>
.br .br
<\fIsource package\fR> <\fIsource package name or .dsc URL/file\fR>
.PP .PP
.B backportpackage \-h .B backportpackage \-h
.SH OPTIONS .SH OPTIONS
.TP .TP
.B \-t \fIDEST\fR, \-\-to=\fIDEST\fR .B \-d \fIDEST\fR, \-\-destination=\fIDEST\fR
\fBRequired\fR. Backport the package to the specified Ubuntu \fBRequired\fR. Backport the package to the specified Ubuntu
release. This option may be specified multiple times, but must be release. If this option is unspecified, then \fBbackportpackage\fR
specified at least once. defaults to the release on which it is currently running.
.TP .TP
.B \-f \fISOURCE\fR, \-\-from=\fISOURCE\fR .B \-s \fISOURCE\fR, \-\-source=\fISOURCE\fR
Backport the package from the specified Ubuntu release. If neither Backport the package from the specified Ubuntu release. If neither
this option nor \fB\-\-version\fR are specified, then this option nor \fB\-\-version\fR are specified, then
\fBbackportpackage\fR defaults to the current Ubuntu development \fBbackportpackage\fR defaults to the current Ubuntu development
@ -42,24 +40,32 @@ Update the builder environment before attempting to build.
Upload to \fIUPLOAD\fR with \fBdput\fR(1) (after confirmation). Upload to \fIUPLOAD\fR with \fBdput\fR(1) (after confirmation).
.TP .TP
.B \-v \fIVERSION\fR, \-\-version=\fIVERSION\fR .B \-v \fIVERSION\fR, \-\-version=\fIVERSION\fR
If the \fB\-\-from\fR option is specified, then \fBbackportpackage\fR If the \fB\-\-source\fR option is specified, then
verifies that the current version of \fIsource package\fR in \fBbackportpackage\fR verifies that the current version of \fIsource
\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, regardless of the release in which it was published (or if
that version is still current). 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, \-\-workdir=\fIWORKDIR\fR
If \fIWORKDIR\fR is specified, then all files are downloaded,
unpacked, built into, and otherwise manipulated in
\fIWORKDIR\fR. Otherwise, a temporary directory is created, which is
deleted before \fIbackportpackage\fR exits.
.TP .TP
.B \-l \fIINSTANCE\fR, \-\-launchpad=\fIINSTANCE\fR .B \-l \fIINSTANCE\fR, \-\-launchpad=\fIINSTANCE\fR
Use the specified instance of Launchpad (e.g. "staging"), instead of Use the specified instance of Launchpad (e.g. "staging"), instead of
the default of "production". the default of "production".
.SH DESCRIPTION .SH DESCRIPTION
\fBbackportpackage\fR fetches a package from one Ubuntu release and \fBbackportpackage\fR fetches a package from one Ubuntu release or
creates a no-change backport of that package to a previous release, from a specified .dsc path or URL and creates a no-change backport of
optionally doing a test build of the package and/or uploading the that package to a previous release, optionally doing a test build of
resulting backport for testing. the package and/or uploading the resulting backport for testing.
.PP .PP
The backported package is fetched and built in a temporary directory Unless a working directory is specified, the backported package is
in \fB/tmp\fR, which is removed once the script finishes running. fetched and built in a temporary directory in \fB/tmp\fR, which is
removed once the script finishes running.
.PP .PP
\fBbackportpackage\fR is only recommended for testing backports in a \fBbackportpackage\fR is only recommended for testing backports in a
PPA, not uploading backports to the Ubuntu archive. PPA, not uploading backports to the Ubuntu archive.
@ -67,9 +73,34 @@ PPA, not uploading backports to the Ubuntu archive.
.TP .TP
.B UBUNTUTOOLS_BUILDER .B UBUNTUTOOLS_BUILDER
The default builder for Ubuntu development tools that support it The default builder for Ubuntu development tools that support it
(including \fBbackportpackage\fR. Supported are \fBpbuilder\fR(8) and (including \fBbackportpackage\fR). Supported are \fBpbuilder\fR(8) and
\fBsbuild\fR(1). If unset and not provided on the command line, \fBsbuild\fR(1). If unset and not provided on the command line,
\fBpbuilder\fR(8) is used. \fBpbuilder\fR(8) is used.
.SH EXAMPLES
Test-build in your PPA a backport of znc from the current development
release to your workstation's release, deleting the build products
afterwards:
.IP
.nf
.B backportpackage -u ppa:\fIuser\fR/\fIppa\fB znc
.fi
.PP
Backport squashfs-tools from Maverick to both Karmic and Lucid and
test-build both locally, leaving all build products in the current
working directory:
.IP
.nf
.B backportpackage -b -s maverick -d karmic -d lucid -w . \\\\
.B " "squashfs-tools
.fi
.PP
Fetch a package from a PPA, backport it to Hardy, then upload it back
to the same PPA:
.IP
.nf
.B backportpackage -d hardy -u ppa:\fIuser\fR/\fIppa\fR \\\\
.B " "https://launchpad.net/\fIsome/file.dsc\fR
.fi
.SH AUTHOR .SH AUTHOR
\fBbackportpackage\fR and this manpage were written by Evan Broder \fBbackportpackage\fR and this manpage were written by Evan Broder
<evan@ebroder.net> <evan@ebroder.net>