#!/usr/bin/python # # Copyright (C) 2007, Canonical Ltd. # Copyright (C) 2010, Benjamin Drung # Copyright (C) 2010, Stefano Rivera # # 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 sys import logging import glob import fnmatch from launchpadlib.launchpad import Launchpad from ubuntutools.config import UDTConfig from ubuntutools import subprocess 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 log_call(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, pbuilder, sbuild): try: if sbuild: cmd = ["sbuild", "-d", dist, "-A", dsc_file] subprocess.check_call(log_call(cmd)) else: if not os.path.isdir("buildresult"): os.makedirs("buildresult") cmd = ["sudo", "-E", "DIST=" + dist, pbuilder, "--build", "--buildresult", "buildresult", dsc_file] subprocess.check_call(log_call(cmd)) except subprocess.CalledProcessError: print >> sys.stderr, "E: %s failed to build." % (dsc_file) sys.exit(1) def test_install(dist, dsc_file, sbuild, lvm): 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: lvm_volume = lvm + "/" + dist + "_chroot" subprocess.check_call(log_call(cmd + ["--lvm-volume="+lvm_volume])) else: subprocess.check_call(log_call(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): us = launchpad.people['ubuntu-sponsors'] bug.unsubscribe(person=us) print "ubuntu-sponsors unsubscribed" def ack_sync(bug_numbers, all_package, all_version, all_section, update, all_uploader_email, key, upload, lpinstance, pbuilder, sbuild, lvm, piuparts, verbose=False, silent=False): launchpad = Launchpad.login_with("ubuntu-dev-tools", lpinstance) # TODO: use release-info (once available) series = launchpad.distributions["ubuntu"].current_series dist = series.name # update pbuilder if update: if sbuild: subprocess.call(log_call(["sbuild-update", dist])) else: cmd = ["sudo", "-E", "DIST=" + dist, pbuilder, "--update"] subprocess.call(log_call(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 # Try to find a Ubuntu bug task, against which the package is raised. for t in bug.bug_tasks: if t.bug_target_name.endswith(' (Ubuntu)'): task = t break try: print "Using Ubuntu bug task: %s" % task.bug_target_name except NameError: # We failed, use the first task (revert to previous behavior) task = bug.bug_tasks[0] print ("W: Could not find bug task for a Ubuntu package." " Using task '%s'" % task.bug_target_name) 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 env = os.environ env['DEB_VENDOR'] = 'Ubuntu' subprocess.check_call(["dpkg-source", "-x", dsc_file], env=env) build_source(dist, dsc_file, pbuilder, sbuild) if piuparts: test_install(dist, dsc_file, sbuild, lvm) 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) --lpinstance= Launchpad instance to connect to (default: production) -l, --lvm lvm root dev directory, used for sbuild and piuparts default is /dev/vg --no-conf Don't read config files or environment variables -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""" def main(): try: long_opts = ["help", "key=", "lvm=", "package=", "section=", "silent", "update", "upload", "verbose", "version=", "with-sbuild", "pbuilder=", "with-piuparts", "lpinstance=", "no-conf"] 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 = None lvm = "/dev/vg" key = None lpinstance = None no_conf = False 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 ("--lpinstance"): lpinstance = a elif o in ("-l", "--lvm"): lvm = a elif o in ("--no-conf"): no_conf = True 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) config = UDTConfig(no_conf) if lpinstance is None: lpinstance = config.get_value('LPINSTANCE') if pbuilder is None and not sbuild: builder = config.get_value('BUILDER') if builder == 'pbuilder': pbuilder = 'pbuilder' elif builder == 'sbuild': sbuild = True else: print >> sys.stderr, "E: Unsupported build-system: %s" % builder sys.exit(COMMAND_LINE_SYNTAX_ERROR) if not update: update = config.get_value('UPDATE_BUILDER', boolean=True) #TODO: Support WORKDIR ack_sync(bug_numbers, package, version, section, update, uploader_email, key, upload, lpinstance, pbuilder, sbuild, lvm, piuparts, verbose, silent) if __name__ == '__main__': main()