#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2008-2010 Martin Pitt , # 2010 Benjamin Drung , # 2010-2011 Stefano Rivera # # ################################################################## # # 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 3. # # 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-3 for more details. # # ################################################################## import debian.deb822 import debian.debian_support import optparse import os import shutil import subprocess import sys from devscripts.logger import Logger from ubuntutools.archive import (DebianSourcePackage, UbuntuSourcePackage, DownloadError) from ubuntutools.config import UDTConfig, ubu_email from ubuntutools.requestsync.mail import (getDebianSrcPkg as requestsync_mail_getDebianSrcPkg) from ubuntutools.requestsync.lp import getDebianSrcPkg, getUbuntuSrcPkg from ubuntutools.lp import udtexceptions from ubuntutools.lp.lpapicache import Launchpad class Version(debian.debian_support.Version): def strip_epoch(self): '''Removes the epoch from a Debian version string. strip_epoch(1:1.52-1) will return "1.52-1" and strip_epoch(1.1.3-1) will return "1.1.3-1".''' parts = self.full_version.split(':') if len(parts) > 1: del parts[0] version_without_epoch = ':'.join(parts) return version_without_epoch def get_related_debian_version(self): related_debian_version = self.full_version uidx = related_debian_version.find('ubuntu') if uidx > 0: related_debian_version = related_debian_version[:uidx] uidx = related_debian_version.find('build') if uidx > 0: related_debian_version = related_debian_version[:uidx] return Version(related_debian_version) def is_modified_in_ubuntu(self): return 'ubuntu' in self.full_version def remove_signature(dscname): '''Removes the signature from a .dsc file if the .dsc file is signed.''' dsc_file = open(dscname) if dsc_file.readline().strip() == "-----BEGIN PGP SIGNED MESSAGE-----": unsigned_file = [] # search until begin of body found for line in dsc_file: if line.strip() == "": break # search for end of body for line in dsc_file: if line.strip() == "": break unsigned_file.append(line) dsc_file.close() dsc_file = open(dscname, "w") dsc_file.writelines(unsigned_file) dsc_file.close() def add_fixed_bugs(changes, bugs): '''Add additional Launchpad bugs to the list of fixed bugs in changes file.''' changes = [l for l in changes.split("\n") if l.strip() != ""] # Remove duplicates bugs = set(bugs) for i in xrange(len(changes)): if changes[i].startswith("Launchpad-Bugs-Fixed:"): bugs.update(changes[i][22:].strip().split(" ")) changes[i] = "Launchpad-Bugs-Fixed: %s" % (" ".join(bugs)) break elif i == len(changes) - 1: # Launchpad-Bugs-Fixed entry does not exist in changes file line = "Launchpad-Bugs-Fixed: %s" % (" ".join(bugs)) changes.append(line) return "\n".join(changes + [""]) def sync_dsc(src_pkg, debian_dist, release, name, email, bugs, ubuntu_mirror, keyid=None): uploader = name + " <" + email + ">" src_pkg.pull_dsc() new_ver = Version(src_pkg.dsc["Version"]) try: ubuntu_source = getUbuntuSrcPkg(src_pkg.source, release) ubuntu_ver = Version(ubuntu_source.getVersion()) ubu_pkg = UbuntuSourcePackage(src_pkg.source, ubuntu_ver.full_version, ubuntu_source.getComponent(), mirrors=[ubuntu_mirror]) ubu_pkg.pull_dsc() need_orig = ubuntu_ver.upstream_version != new_ver.upstream_version except udtexceptions.PackageNotFoundException: ubuntu_ver = Version('~') ubu_pkg = None need_orig = True Logger.info('%s does not exist in Ubuntu.', name) Logger.debug('Source %s: current version %s, new version %s', src_pkg.source, ubuntu_ver, new_ver) Logger.debug('Needs source tarball: %s', str(need_orig)) cur_ver = ubuntu_ver.get_related_debian_version() if ubuntu_ver.is_modified_in_ubuntu(): Logger.warn('Overwriting modified Ubuntu version %s, ' 'setting current version to %s', ubuntu_ver.full_version, cur_ver.full_version) try: src_pkg.pull() except DownloadError, e: Logger.error('Failed to download: %s', str(e)) sys.exit(1) src_pkg.unpack() fakesync = not (need_orig or ubu_pkg.verify_orig()) if fakesync: Logger.warn('The checksums of the Debian and Ubuntu packages mismatch. ' 'A fake sync is required.') # Download Ubuntu files (override Debian source tarballs) try: ubu_pkg.pull() except DownloadError, e: Logger.error('Failed to download: %s', str(e)) sys.exit(1) # change into package directory directory = src_pkg.source + '-' + new_ver.upstream_version Logger.command(('cd', directory)) os.chdir(directory) # read Debian distribution from debian/changelog if not specified if debian_dist is None: line = open("debian/changelog").readline() debian_dist = line.split(" ")[2].strip(";") if not fakesync: # create the changes file changes_filename = "%s_%s_source.changes" % \ (src_pkg.source, new_ver.strip_epoch()) cmd = ["dpkg-genchanges", "-S", "-v" + cur_ver.full_version, "-DDistribution=" + release, "-DOrigin=debian/" + debian_dist, "-e" + uploader] if need_orig: cmd.append("-sa") else: cmd.append("-sd") if not Logger.verbose: cmd += ["-q"] Logger.command(cmd + ['>', '../' + changes_filename]) changes = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] # Add additional bug numbers if len(bugs) > 0: changes = add_fixed_bugs(changes, bugs) # remove extracted (temporary) files Logger.command(('cd', '..')) os.chdir('..') shutil.rmtree(directory, True) # write changes file changes_file = open(changes_filename, "w") changes_file.writelines(changes) changes_file.close() # remove signature and sign package remove_signature(src_pkg.dsc_name) if keyid is not False: cmd = ["debsign", changes_filename] if not keyid is None: cmd.insert(1, "-k" + keyid) Logger.command(cmd) subprocess.check_call(cmd) else: # Create fakesync changelog entry new_ver = Version(new_ver.full_version + "fakesync1") changes_filename = "%s_%s_source.changes" % \ (src_pkg.source, new_ver.strip_epoch()) if len(bugs) > 0: message = "Fake sync due to mismatching orig tarball (LP: %s)." % \ (", ".join(["#" + str(b) for b in bugs])) else: message = "Fake sync due to mismatching orig tarball." cmd = ['dch', '-v', new_ver.full_version, '--force-distribution', '-D', release, message] env = {'DEBFULLNAME': name, 'DEBEMAIL': email} Logger.command(cmd) subprocess.check_call(cmd, env=env) # update the Maintainer field cmd = ["update-maintainer"] if not Logger.verbose: cmd.append("-q") Logger.command(cmd) subprocess.check_call(cmd) # Build source package cmd = ["debuild", "--no-lintian", "-S", "-v" + cur_ver.full_version] if need_orig: cmd += ['-sa'] if keyid: cmd += ["-k" + keyid] Logger.command(cmd) returncode = subprocess.call(cmd) if returncode != 0: Logger.error('Source-only build with debuild failed. ' 'Please check build log above.') sys.exit(1) def fetch_source_pkg(package, dist, version, component, ubuntu_release, mirror): """Download the specified source package. dist, version, component, mirror can all be None. """ if package.endswith('.dsc'): return DebianSourcePackage(dscfile=package, mirrors=[mirror]) if dist is None: dist = "unstable" requested_version = version if type(version) == str: version = Version(version) if version is None or component is None: try: debian_srcpkg = getDebianSrcPkg(package, dist) except (udtexceptions.PackageNotFoundException, udtexceptions.SeriesNotFoundException), e: Logger.error(str(e)) sys.exit(1) if version is None: version = Version(debian_srcpkg.getVersion()) try: ubuntu_srcpkg = getUbuntuSrcPkg(package, ubuntu_release) ubuntu_version = Version(ubuntu_srcpkg.getVersion()) except udtexceptions.PackageNotFoundException: ubuntu_version = Version('~') except udtexceptions.SeriesNotFoundException, e: Logger.error(str(e)) sys.exit(1) if ubuntu_version >= version: # The LP importer is maybe out of date debian_srcpkg = requestsync_mail_getDebianSrcPkg(package, dist) if requested_version is None: version = Version(debian_srcpkg.getVersion()) if ubuntu_version >= version: Logger.error("Version in Debian %s (%s) isn't newer than " "Ubuntu %s (%s)", version, dist, ubuntu_version, ubuntu_release) sys.exit(1) if component is None: component = debian_srcpkg.getComponent() assert component in ('main', 'contrib', 'non-free') return DebianSourcePackage(package, version.full_version, component, mirrors=[mirror]) def main(): usage = "%prog [options] <.dsc URL/path or package name>" epilog = "See %s(1) for more info." % os.path.basename(sys.argv[0]) parser = optparse.OptionParser(usage=usage, epilog=epilog) parser.add_option("-d", "--distribution", dest="dist", default=None, help="Debian distribution to sync from.") parser.add_option("-r", "--release", dest="release", default=None, help="Specify target Ubuntu release.") parser.add_option("-V", "--debian-version", dest="debversion", default=None, help="Specify the version to sync from.") parser.add_option("-c", "--component", dest="component", default=None, help="Specify the Debian component to sync from.") parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, help="Display more progress information.") parser.add_option("-n", "--uploader-name", dest="uploader_name", default=None, help="Use UPLOADER_NAME as the name of the maintainer " "for this upload.") parser.add_option("-e", "--uploader-email", dest="uploader_email", default=None, help="Use UPLOADER_EMAIL as email address of the " "maintainer for this upload.") parser.add_option("-k", "--key", dest="keyid", default=None, help="Specify the key ID to be used for signing.") parser.add_option('--dont-sign', dest='keyid', action='store_false', help='Do not sign the upload.') parser.add_option("-b", "--bug", metavar="BUG", dest="bugs", action="append", default=list(), help="Mark Launchpad bug BUG as being fixed by this " "upload.") parser.add_option('-D', '--debian-mirror', metavar='DEBIAN_MIRROR', dest='debian_mirror', help='Preferred Debian mirror ' '(default: %s)' % UDTConfig.defaults['DEBIAN_MIRROR']) parser.add_option('-U', '--ubuntu-mirror', metavar='UBUNTU_MIRROR', dest='ubuntu_mirror', help='Preferred Ubuntu mirror ' '(default: %s)' % UDTConfig.defaults['UBUNTU_MIRROR']) parser.add_option('--no-conf', dest='no_conf', default=False, action='store_true', help="Don't read config files or environment variables.") (options, args) = parser.parse_args() if len(args) == 0: parser.error('No .dsc URL/path or package name specified.') if len(args) > 1: parser.error('Multiple .dsc URLs/paths or package names specified: ' + ', '.join(args)) invalid_bug_numbers = [bug for bug in options.bugs if not bug.isdigit()] if len(invalid_bug_numbers) > 0: parser.error('Invalid bug number(s) specified: ' + ', '.join(invalid_bug_numbers)) if options.component not in (None, "main", "contrib", "non-free"): parser.error('%s is not a valid Debian component. ' 'It should be one of main, contrib, or non-free.' % options.component) Logger.verbose = options.verbose config = UDTConfig(options.no_conf) if options.debian_mirror is None: options.debian_mirror = config.get_value('DEBIAN_MIRROR') if options.ubuntu_mirror is None: options.ubuntu_mirror = config.get_value('UBUNTU_MIRROR') if options.uploader_name is None: options.uploader_name = ubu_email(export=False)[0] if options.uploader_email is None: options.uploader_email = ubu_email(export=False)[1] Launchpad.login_anonymously() if options.release is None: options.release = Launchpad.distributions["ubuntu"].current_series.name os.environ['DEB_VENDOR'] = 'Ubuntu' src_pkg = fetch_source_pkg(args[0], options.dist, options.debversion, options.component, options.release, options.debian_mirror) sync_dsc(src_pkg, options.dist, options.release, options.uploader_name, options.uploader_email, options.bugs, options.ubuntu_mirror, options.keyid) if __name__ == "__main__": main()