#!/usr/bin/python # -*- coding: utf-8 -*- # ################################################################## # # Copyright (C) 2010-2011, Evan Broder # Copyright (C) 2010, Benjamin Drung # # 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 optparse import os import shutil import sys import tempfile import lsb_release from debian.debian_support import Version from devscripts.logger import Logger from ubuntutools.archive import (SourcePackage, DebianSourcePackage, UbuntuSourcePackage, DownloadError, rmadison) from ubuntutools.config import UDTConfig, ubu_email from ubuntutools.builder import get_builder from ubuntutools.lp.lpapicache import (Launchpad, Distribution, SeriesNotFoundException, PackageNotFoundException) from ubuntutools.misc import (system_distribution, vendor_to_distroinfo, codename_to_distribution) from ubuntutools.question import YesNoQuestion from ubuntutools import subprocess def error(msg): Logger.error(msg) sys.exit(1) def check_call(cmd, *args, **kwargs): Logger.command(cmd) ret = subprocess.call(cmd, *args, **kwargs) if ret != 0: error('%s returned %d.' % (cmd[0], ret)) def parse(args): usage = 'Usage: %prog [options] ' parser = optparse.OptionParser(usage) parser.add_option('-d', '--destination', metavar='DEST', dest='dest_releases', default=[], action='append', help='Backport to DEST release ' '(default: current release)') parser.add_option('-s', '--source', metavar='SOURCE', dest='source_release', help='Backport from SOURCE release ' '(default: devel release)') parser.add_option('-S', '--suffix', metavar='SUFFIX', help='Suffix to append to version number ' '(default: ~ppa1 when uploading to a PPA)') parser.add_option('-b', '--build', default=False, action='store_true', help='Build the package before uploading ' '(default: %default)') parser.add_option('-B', '--builder', metavar='BUILDER', help='Specify the package builder (default: pbuilder)') parser.add_option('-U', '--update', default=False, action='store_true', help='Update the build environment before ' 'attempting to build') parser.add_option('-u', '--upload', metavar='UPLOAD', help='Specify an upload destination') parser.add_option('-y', '--yes', dest='prompt', default=True, action='store_false', help='Do not prompt before uploading to a PPA') parser.add_option('-v', '--version', metavar='VERSION', help='Package version to backport (or verify)') parser.add_option('-w', '--workdir', metavar='WORKDIR', help='Specify a working directory ' '(default: temporary dir)') parser.add_option('-r', '--release-pocket', default=False, action='store_true', help='Target the release pocket in the .changes file. ' 'Necessary (and default) for uploads to PPAs') parser.add_option('-m', '--mirror', metavar='INSTANCE', help='Preferred mirror (default: Launchpad)') parser.add_option('-l', '--lpinstance', metavar='INSTANCE', help='Launchpad instance to connect to ' '(default: production)') parser.add_option('--no-conf', default=False, action='store_true', help="Don't read config files or environment variables") opts, args = parser.parse_args(args) if len(args) != 1: parser.error('You must specify a single source package or a .dsc ' 'URL/path.') config = UDTConfig(opts.no_conf) if opts.builder is None: opts.builder = config.get_value('BUILDER') if not opts.update: opts.update = config.get_value('UPDATE_BUILDER', boolean=True) if opts.workdir is None: opts.workdir = config.get_value('WORKDIR') if opts.lpinstance is None: opts.lpinstance = config.get_value('LPINSTANCE') if not opts.upload and not opts.workdir: parser.error('Please specify either a working dir or an upload target!') if opts.upload and opts.upload.startswith('ppa:'): opts.release_pocket = True return opts, args, config def find_release_package(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: archive = Distribution(distribution.lower()).getArchive() try: spph = archive.getSourcePackage(package, source_release) except (SeriesNotFoundException, PackageNotFoundException), e: error(str(e)) version = spph.getVersion() if distribution == 'Debian': srcpkg = DebianSourcePackage(package, version, workdir=workdir, mirrors=mirrors) elif distribution == 'Ubuntu': srcpkg = UbuntuSourcePackage(package, version, workdir=workdir, mirrors=mirrors) return srcpkg def find_package(mirror, workdir, package, version, source_release, config): "Returns the SourcePackage" if package.endswith('.dsc'): return SourcePackage(version=version, dscfile=package, workdir=workdir, mirrors=(mirror,)) if not source_release and not version: info = vendor_to_distroinfo(system_distribution()) source_release = info().devel() srcpkg = find_release_package(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 srcpkg def get_backport_version(version, suffix, upload, release): backport_version = version + ('~%s1' % release) if suffix is not None: backport_version += suffix elif upload and upload.startswith('ppa:'): backport_version += '~ppa1' return backport_version def get_backport_dist(release, release_pocket): if release_pocket: return release else: return '%s-backports' % release def do_build(workdir, dsc, release, builder, update): builder = get_builder(builder) if not builder: return if update: if 0 != builder.update(release): sys.exit(1) # builder.build is going to chdir to buildresult: workdir = os.path.realpath(workdir) return builder.build(os.path.join(workdir, dsc), release, os.path.join(workdir, "buildresult")) def do_upload(workdir, package, bp_version, changes, upload, prompt): print 'Please check %s %s in file://%s carefully!' % \ (package, bp_version, workdir) if prompt or upload == 'ubuntu': question = 'Do you want to upload the package to %s' % upload answer = YesNoQuestion().ask(question, "yes") if answer == "no": return check_call(['dput', upload, changes], cwd=workdir) def do_backport(workdir, pkg, suffix, release, release_pocket, build, builder, update, upload, prompt): dirname = '%s-%s' % (pkg.source, release) pkg.unpack(dirname) srcdir = os.path.join(workdir, dirname) bp_version = get_backport_version(pkg.version.full_version, suffix, upload, release) bp_dist = get_backport_dist(release, release_pocket) check_call(['dch', '--force-bad-version', '--force-distribution', '--preserve', '--newversion', bp_version, '--distribution', bp_dist, 'No-change backport to %s' % release], cwd=srcdir) check_call(['debuild', '--no-lintian', '-S', '-sa'], cwd=srcdir) fn_base = pkg.source + '_' + bp_version.split(':', 1)[-1] if build: if 0 != do_build(workdir, fn_base + '.dsc', release, builder, update): sys.exit(1) if upload: do_upload(workdir, pkg.source, bp_version, fn_base + '_source.changes', upload, prompt) shutil.rmtree(srcdir) def main(args): ubu_email() opts, (package_or_dsc,), config = parse(args[1:]) Launchpad.login_anonymously(service=opts.lpinstance) if not opts.dest_releases: distinfo = lsb_release.get_distro_information() try: opts.dest_releases = [distinfo['CODENAME']] except KeyError: 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: pkg = find_package(opts.mirror, workdir, package_or_dsc, opts.version, opts.source_release, config) pkg.pull() for release in opts.dest_releases: do_backport(workdir, pkg, opts.suffix, release, opts.release_pocket, opts.build, opts.builder, opts.update, opts.upload, opts.prompt) except DownloadError, e: error(str(e)) finally: if not opts.workdir: shutil.rmtree(workdir) if __name__ == '__main__': sys.exit(main(sys.argv))