#!/usr/bin/python # -*- coding: utf-8 -*- # ################################################################## # # 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; version 2. # # 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. # # See file /usr/share/common-licenses/GPL-2 for more details. # # ################################################################## import logging import optparse import os import shutil import subprocess import sys import tempfile from debian.deb822 import Dsc import launchpadlib.launchpad devnull = open('/dev/null', 'r+') lp = None def error(msg, *args, **kwargs): logging.error(msg, *args, **kwargs) sys.exit(1) def check_call(cmd, *args, **kwargs): ret = subprocess.call(cmd, *args, **kwargs) if ret != 0: error('%s returned %d' % (cmd, ret)) def parse(args): usage = 'Usage: %prog [options] ' p = optparse.OptionParser(usage) p.add_option('-t', '--to', dest='dest_releases', default=[], action='append', help='Backport to DEST release (required)', metavar='DEST') p.add_option('-f', '--from', dest='source_release', default=None, help='Backport from SOURCE release (default: devel release)', metavar='SOURCE') p.add_option('-v', '--version', dest='version', default=None, help='Package version to backport (or verify)', metavar='VERSION') p.add_option('-u', '--upload', dest='upload', help='Specify an upload destination (required)', metavar='UPLOAD') p.add_option('-l', '--launchpad', dest='launchpad', default='production', help='Launchpad instance to connect to (default: %default)', metavar='INSTANCE') opts, args = p.parse_args(args) if len(args) != 1: p.error('You must specify a source package') if not opts.dest_releases: p.error('You must specify at least one destination release') if not opts.upload: p.error('You must specify an upload destination') return opts, args def find_release_package(workdir, package, opts): ubuntu = lp.distributions['ubuntu'] archive = ubuntu.main_archive series = ubuntu.getSeries(name_or_version=opts.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: continue else: error('Unable to find package %s in release %s' % (package, opts.source_release)) if opts.version and opts.version != srcpkg.source_package_version: error('Requested backport of version %s but %s is at version %s' % (opts.version, package, srcpkg.source_package_version)) return srcpkg def find_version_package(workdir, package, opts): ubuntu = lp.distributions['ubuntu'] archive = ubuntu.main_archive try: # Might get more than one (i.e. same version in multiple # releases), but they should all be identical return archive.getPublishedSources(source_name=package, version=opts.version)[0] except IndexError: error('Package %s was never published with version %s in Ubuntu' % (package, opts.version)) def fetch_package(workdir, package, opts): # Returns the path to the .dsc file that was fetched ubuntu = lp.distributions['ubuntu'] 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 # verification if opts.source_release: srcpkg = find_release_package(workdir, package, opts) else: srcpkg = find_version_package(workdir, package, opts) for f in srcpkg.sourceFileUrls(): if f.endswith('.dsc'): check_call(['dget', '--download-only', '--allow-unauthenticated', f], cwd=workdir) return os.path.join(workdir, os.path.basename(f)) else: error('Package %s contains no .dsc file' % package) def get_backport_version(version, upload, release): v = version + ('~%s1' % release) if upload.startswith('ppa:'): v += '~ppa1' return v def get_backport_dist(upload, release): if upload == 'ubuntu': return '%s-backports' % release else: return release def do_backport(workdir, package, dscfile, release, opts): dsc = Dsc(open(os.path.join(workdir, dscfile))) v = dsc['Version'] check_call(['dpkg-source', '-x', dscfile, package], cwd=workdir) srcdir = os.path.join(workdir, package) bp_version = get_backport_version(v, opts.upload, release) bp_dist = get_backport_dist(opts.upload, release) check_call(['dch', '--force-bad-version', '--preserve', '--newversion', bp_version, '--distribution', release, 'No-change backport to %s' % release], cwd=srcdir) check_call(['debuild', '-S', '-sa'], cwd=srcdir) if ':' in bp_version: bp_version = bp_version[bp_version.find(':')+1:] print 'Please check the package in file://%s carefully' % workdir prompt = 'Do you want to upload this to %s? [Y/n]' % opts.upload while True: answer = raw_input(prompt).strip().lower() if answer in ('', 'y', 'yes'): check_call(['dput', opts.upload, '%s_%s_source.changes' % (package, bp_version)], cwd=workdir) break elif answer in ('n', 'no'): break shutil.rmtree(srcdir) def main(args): global lp logging.basicConfig(level=logging.INFO) os.environ['DEB_VENDOR'] = 'Ubuntu' opts, (package,) = parse(args[1:]) script_name = os.path.basename(sys.argv[0]) lp = launchpadlib.launchpad.Launchpad.login_anonymously(script_name, opts.launchpad) workdir = tempfile.mkdtemp(prefix='backportpackage-') try: dscfile = fetch_package(workdir, package, opts) for release in opts.dest_releases: do_backport(workdir, package, dscfile, release, opts) finally: shutil.rmtree(workdir) if __name__ == '__main__': sys.exit(main(sys.argv))