#!/usr/bin/python # # Copyright (C) 2007, Canonical Ltd. # Copyright (C) 2010, Benjamin Drung # # It was initial written by Daniel Holbach. # # 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 csv import getopt import lazr.restfulclient import os import re import subprocess import sys import logging import glob import fnmatch from ubuntutools.lp.libsupport import get_launchpad COMMAND_LINE_SYNTAX_ERROR = 1 VERSION_DETECTION_FAILED = 2 PRIVATE_USER_EMAIL = 3 UPLOAD_NOT_PERMITTED = 4 def get_version(title): m = re.search("[() ][0-9][0-9a-zA-Z.:+-~]*", title) if m is None: print >> sys.stderr, "Version could not be detected. Please specify it with -V." sys.exit(VERSION_DETECTION_FAILED) return m.group(0).strip("() ") def strip_epoch(version): parts = version.split(':') if len(parts) > 1: del parts[0] version = ':'.join(parts) return version def LogCall(command): command = map(str, command) logging.info("Running %s", " ".join(command)) return command def get_source(package, version, section, dist, uploader_name, uploader_email, bug, key, upload): if os.path.isdir("/tmpfs"): workdir = "/tmpfs/ack-sync" else: workdir = "/tmp/ack-sync" if not os.path.isdir(workdir): os.makedirs(workdir) os.chdir(workdir) cmd = ["syncpackage", package, "-r", dist, "-V", version, "-b", str(bug)] if section is not None: cmd += ["-c", section] if uploader_email is not None: cmd += ["-e", uploader_email] if uploader_name is not None: cmd += ["-n", uploader_name] if not upload: cmd += ['--dont-sign',] if upload and key is not None: cmd += ["-k", key] subprocess.check_call(cmd) dsc_file = package + "_" + strip_epoch(version) + "fakesync1.dsc" if not os.path.exists(os.path.join(workdir, dsc_file)): dsc_file = package + "_" + strip_epoch(version) + ".dsc" if not os.path.exists(os.path.join(workdir, dsc_file)): print >> sys.stderr, "E: Failed to find .dsc file created by syncpackage." sys.exit(1) return dsc_file def build_source(dist, dsc_file): try: if sbuild: subprocess.check_call(LogCall(["sbuild", "-d", dist,"-A", dsc_file])) else: if not os.path.isdir("buildresult"): os.makedirs("buildresult") cmd = ["sudo", "-E", "DIST=" + dist, pbuilder, "--build", "--buildresult", "buildresult", dsc_file] subprocess.check_call(LogCall(cmd)) except subprocess.CalledProcessError: print >> sys.stderr, "E: %s failed to build." % (dsc_file) sys.exit(1) def test_install(dist, dsc_file): changes_files=glob.glob(os.path.splitext(dsc_file)[0]+"_*.changes") changes_file = "" for temp_file in changes_files: if not fnmatch.fnmatch(temp_file, '*_source.changes'): changes_file = temp_file if not (os.path.isfile(changes_file)): # if no file exists at all => exit print >> sys.stderr, "E: No .changes file has been generated." sys.exit(1) try: cmd = ["sudo", "piuparts", "-N", "-W", "--single-changes-list", "--log-level=info", "--ignore=/var/log/apt/history.log", "--mirror=http://archive.ubuntu.com/ubuntu main universe restricted multiverse", changes_file] if sbuild: subprocess.check_call(LogCall(cmd + ["--lvm-volume="+lvm+"/"+dist+"_chroot"])) else: subprocess.check_call(LogCall(cmd + ["--pbuilder"])) except subprocess.CalledProcessError: print >> sys.stderr, "E: %s failed to install. Please check log" % (changes_file) def get_email_from_file(name): filename = os.path.expanduser("~/.ack-sync-email.list") if os.path.isfile(filename): csvfile = open(filename) csv_reader = csv.reader(csvfile) for row in csv_reader: if row and row[0] == name: return row[1] return None def unsubscribe_sponsors(launchpad, bug): ums = launchpad.people['ubuntu-main-sponsors'] try: bug.unsubscribe(person=ums) print "ubuntu-main-sponsors unsubscribed (for backward compatibility)" except lazr.restfulclient.errors.HTTPError, http_error: print "failed to unsubscribe ubuntu-main-sponsors: " + http_error.content us = launchpad.people['ubuntu-sponsors'] bug.unsubscribe(person=us) print "ubuntu-sponsors unsubscribed" def main(bug_numbers, all_package, all_version, all_section, update, all_uploader_email, key, upload, verbose=False, silent=False): launchpad = get_launchpad("ubuntu-dev-tools") # TODO: use release-info (once available) series = launchpad.distributions["ubuntu"].current_series dist = series.name # update pbuilder if update: if sbuild: subprocess.call(LogCall(["sbuild-update", dist])) else: cmd = ["sudo", "-E", "DIST=" + dist, pbuilder, "--update"] subprocess.call(LogCall(cmd)) for bug_number in bug_numbers: bug = launchpad.bugs[bug_number] uploader = bug.owner uploader_name = uploader.display_name if all_uploader_email is not None: uploader_email = all_uploader_email elif launchpad.people['ubuntumembers'] in uploader.super_teams: uploader_email = uploader.name + '@ubuntu.com' else: try: uploader_email = uploader.preferred_email_address.email except ValueError: uploader_email = get_email_from_file(uploader.name) if uploader_email is None: if not silent: print >> sys.stderr, "E: Bug owner '%s' does not have a public email address. Specify uploader with '-e'." % (uploader_name) sys.exit(PRIVATE_USER_EMAIL) elif not silent: print "Taking email address from local file: " + uploader_email task = list(bug.bug_tasks)[0] if all_package is not None: package = all_package else: package = task.bug_target_name.split(" ")[0] if package == "ubuntu": words = bug.title.split(" ") # no source package was defined. Guessing that the second or # third word in the title is the package name, because most # titles start with "Please sync " or "Sync " if words[0].lower() == "please": package = words[2] else: package = words[1] if all_version is not None: version = all_version else: version = get_version(bug.title) src_pkg = series.getSourcePackage(name=package) if src_pkg is None: print "%s is NEW in %s." % (package, dist) if src_pkg is not None: # TODO: Port ack-sync to use lpapicache and reduce code-duplication. can_upload = None try: series.main_archive.checkUpload( component=src_pkg.latest_published_component_name, distroseries=series, person=launchpad.me, pocket='Release', sourcepackagename=package) can_upload = True except lazr.restfulclient.errors.HTTPError, e: if e.response.status == 403: can_upload = False elif e.response.status == 400: print "W: Package is probably not in Ubuntu. Can't check upload rights." can_upload = True else: raise e if not can_upload: print >> sys.stderr, ("E: Sorry, you are not allowed to " 'upload package "%s" to %s.' % (package, dist)) sys.exit(UPLOAD_NOT_PERMITTED) if task.assignee == None: task.assignee = launchpad.me print "assigned me" task.lp_save() if task.assignee != launchpad.me: print >> sys.stderr, "E: %s is already assigned to https://launchpad.net/bugs/%i" % \ (task.assignee.display_name, bug.id) sys.exit(1) old_status = task.status task.status = 'In Progress' unsubscribe_sponsors(launchpad, bug) if task.importance == "Undecided": task.importance = "Wishlist" print "importance set to Wishlist" task.lp_save() print "package:", package print "version:", version dsc_file = get_source(package, version, all_section, dist, uploader_name, uploader_email, bug_number, key, upload) if dsc_file.endswith('fakesync1.dsc'): upload = True # extract source subprocess.check_call(["dpkg-source", "-x", dsc_file]) build_source(dist, dsc_file) if piuparts: test_install(dist, dsc_file) print bug.title print '%s (was %s)' % (task.status, old_status) print "Uploader:", uploader_name + " <" + uploader_email + ">" if upload: print "Will upload sync directly, rather than subscribing ubuntu-archive" try: raw_input('Press [Enter] to continue or [Ctrl-C] to abort.') except KeyboardInterrupt: continue bug.subscribe(person=launchpad.me) print "subscribed me" if upload: task.status = 'Fix Committed' task.assignee = None print "unassigned me" task.lp_save() changes_file = dsc_file[:-4] + "_source.changes" subprocess.check_call(["dput", "ubuntu", changes_file]) else: task.status = 'Confirmed' task.assignee = None print "unassigned me" task.lp_save() cmd = ["dpkg-architecture", "-qDEB_BUILD_ARCH"] process = subprocess.Popen(cmd, stdout=subprocess.PIPE) architecture = process.communicate()[0].strip() content = "%s %s builds on %s. Sync request ACK'd." % (package, version, architecture) bug.newMessage(content=content, subject="ack-sync") bug.subscribe(person=launchpad.people['ubuntu-archive']) print "subscribed ubuntu-archive" def usage(): print """ack-sync -e, specify uploader email address -h, --help displays this help -k, --key key used to sign the package (in case of sponsoring) -l, --lvm lvm root dev directory, used for sbuild and piuparts default is /dev/vg -p, --package= set the package -P, --with-piuparts use piuparts to check the instalability --section=
Debian section (one of main, contrib, non-free) -s, --silent be more silent -S, --with-sbuild use sbuild instead of pbuilder -C, --pbuilder= use as pbuilder -u, --update updates pbuilder before building -U, --upload upload the sync immediately rather than ACK-ing it for the archive admins -v, --verbose be more verbosive -V, --version= set the version""" if __name__ == '__main__': try: long_opts = ["help", "key=", "lvm=", "package=", "section=", "silent", "update", "upload", "verbose", "version=", "with-sbuild", "pbuilder=", "with-piuparts"] opts, args = getopt.gnu_getopt(sys.argv[1:], "e:hk:p:PsSC:uUvV:", long_opts) except getopt.GetoptError, e: # will print something like "option -a not recognized" print >> sys.stderr, str(e) sys.exit(COMMAND_LINE_SYNTAX_ERROR) upload = False package = None sbuild = False section = None silent = False update = False uploader_email = None verbose = False version = None piuparts = False pbuilder = 'pbuilder' lvm = "/dev/vg" key = None for o, a in opts: if o in ("-h", "--help"): usage() sys.exit() elif o in ("-e"): uploader_email = a elif o in ("-k", "--key"): key = a elif o in ("-l", "--lvm"): lvm = a elif o in ("-p", "--package"): package = a elif o in ("-P", "--with-piuparts"): piuparts = True elif o in ("--section"): section = a elif o in ("-s", "--silent"): silent = True elif o in ("-S", "--with-sbuild"): sbuild = True elif o in ("-C", "--pbuilder"): pbuilder=a elif o in ("-u", "--update"): update = True elif o in ("-U", "--upload"): upload = True elif o in ("-v", "--verbose"): verbose = True elif o in ("-V", "--version"): version = a else: assert False, "unhandled option" if len(args) == 0: if not silent: print >> sys.stderr, "E: You must specify at least one bug number." sys.exit(COMMAND_LINE_SYNTAX_ERROR) bug_numbers = [] for arg in args: try: number = int(arg) except: if not silent: print >> sys.stderr, "E: '%s' is not a valid bug number." % arg sys.exit(COMMAND_LINE_SYNTAX_ERROR) bug_numbers.append(number) main(bug_numbers, package, version, section, update, uploader_email, key, upload, verbose, silent)