sponsor-patch: Support sync requests and make ack-sync obsolete (LP: #764763).

This commit is contained in:
Benjamin Drung 2011-09-10 22:55:08 +02:00
parent 1e126f74d1
commit 6fdef19b2c
5 changed files with 75 additions and 474 deletions

441
ack-sync
View File

@ -1,441 +0,0 @@
#!/usr/bin/python
#
# Copyright (C) 2007, Canonical Ltd.
# Copyright (C) 2010, Benjamin Drung <bdrung@ubuntu.com>
# Copyright (C) 2010, Stefano Rivera <stefanor@ubuntu.com>
#
# 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 <package>" or "Sync <package>"
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 <bug numbers>
-e, specify uploader email address
-h, --help displays this help
-k, --key key used to sign the package (in case of sponsoring)
--lpinstance=<instance> 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=<package> set the package
-P, --with-piuparts use piuparts to check the instalability
--section=<section> Debian section (one of main, contrib, non-free)
-s, --silent be more silent
-S, --with-sbuild use sbuild instead of pbuilder
-C, --pbuilder=<command> use <command> 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=<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()

4
debian/changelog vendored
View File

@ -1,6 +1,8 @@
ubuntu-dev-tools (0.132) UNRELEASED; urgency=low
* sponsor-patch: Refactor code.
* sponsor-patch:
- Refactor code.
- Support sync requests and make ack-sync obsolete (LP: #764763).
-- Benjamin Drung <bdrung@debian.org> Sat, 10 Sep 2011 19:58:38 +0200

3
debian/copyright vendored
View File

@ -78,8 +78,7 @@ License: GPL-2+
On Debian systems, the complete text of the GNU General Public License
version 2 can be found in the /usr/share/common-licenses/GPL-2 file.
Files: ack-sync
doc/bitesize.1
Files: doc/bitesize.1
doc/grab-merge.1
doc/harvest.1
doc/hugdaylist.1

View File

@ -122,14 +122,15 @@ class SourcePackage(object):
sys.exit(1)
return True
def build(self, update):
def build(self, update, dist=None):
"""Tries to build the package.
Returns true if the package was built successfully. Returns false
if the user wants to change something.
"""
dist = re.sub("-.*$", "", self._changelog.distributions)
if dist is None:
dist = re.sub("-.*$", "", self._changelog.distributions)
build_name = self._package + "_" + strip_epoch(self._version) + \
"_" + self._builder.get_architecture() + ".build"
self._build_log = os.path.join(self._buildresult, build_name)
@ -362,3 +363,22 @@ class SourcePackage(object):
lintian_file.close()
return lintian_filename
def sync(self, upload, bug_number, keyid):
"""Does a sync of the source package."""
if upload == "ubuntu":
cmd = ["syncpackage", self._package, "-b", str(bug_number),
"-V", str(self._version)]
if keyid is not None:
cmd += ["-k", keyid]
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Logger.error("Syncing of %s %s failed.", self._package,
str(self._version))
sys.exit(1)
else:
# FIXME: Support this use case!
Logger.error("Uploading a synced package other than to ubuntu "
"is not supported yet!")
sys.exit(1)

View File

@ -24,11 +24,13 @@ import launchpadlib.launchpad
from devscripts.logger import Logger
from distro_info import UbuntuDistroInfo
from ubuntutools import subprocess
from ubuntutools.update_maintainer import update_maintainer
from ubuntutools.question import input_number
from ubuntutools.sponsor_patch.bugtask import BugTask
from ubuntutools.sponsor_patch.bugtask import BugTask, is_sync
from ubuntutools.sponsor_patch.patch import Patch
from ubuntutools.sponsor_patch.question import ask_for_manual_fixing
from ubuntutools.sponsor_patch.source_package import SourcePackage
@ -98,24 +100,26 @@ def ask_for_patch_or_branch(bug, attached_patches, linked_branches):
def get_patch_or_branch(bug):
patch = None
branch = None
attached_patches = [a for a in bug.attachments if a.type == "Patch"]
linked_branches = [b.branch for b in bug.linked_branches]
if len(attached_patches) == 0 and len(linked_branches) == 0:
if len(bug.attachments) == 0:
Logger.error(("No attachment and no linked branch found on "
"bug #%i.") % bug.id)
if not is_sync(bug):
attached_patches = [a for a in bug.attachments if a.type == "Patch"]
linked_branches = [b.branch for b in bug.linked_branches]
if len(attached_patches) == 0 and len(linked_branches) == 0:
if len(bug.attachments) == 0:
Logger.error("No attachment and no linked branch found on "
"bug #%i. Add the tag sync to the bug if it is "
"a sync request.", bug.id)
else:
Logger.error("No attached patch and no linked branch found. "
"Go to https://launchpad.net/bugs/%i and mark an "
"attachment as patch.", bug.id)
sys.exit(1)
elif len(attached_patches) == 1 and len(linked_branches) == 0:
patch = Patch(attached_patches[0])
elif len(attached_patches) == 0 and len(linked_branches) == 1:
branch = linked_branches[0].bzr_identity
else:
Logger.error(("No attached patch and no linked branch found. Go "
"to https://launchpad.net/bugs/%i and mark an "
"attachment as patch.") % bug.id)
sys.exit(1)
elif len(attached_patches) == 1 and len(linked_branches) == 0:
patch = Patch(attached_patches[0])
elif len(attached_patches) == 0 and len(linked_branches) == 1:
branch = linked_branches[0].bzr_identity
else:
patch, branch = ask_for_patch_or_branch(bug, attached_patches,
linked_branches)
patch, branch = ask_for_patch_or_branch(bug, attached_patches,
linked_branches)
return (patch, branch)
def download_branch(branch):
@ -212,12 +216,21 @@ def _update_timestamp():
def _download_and_change_into(task, dsc_file, patch, branch):
"""Downloads the patch and branch and changes into the source directory."""
if patch:
patch.download()
if branch:
branch_dir = download_branch(task.get_branch_link())
# change directory
Logger.command(["cd", branch_dir])
os.chdir(branch_dir)
else:
if patch:
patch.download()
Logger.info("Ubuntu package: %s" % (task.package))
if task.is_merge():
Logger.info("The task is a merge request.")
if task.is_sync():
Logger.info("The task is a sync request.")
extract_source(dsc_file, Logger.verbose)
@ -225,12 +238,6 @@ def _download_and_change_into(task, dsc_file, patch, branch):
directory = task.package + '-' + task.get_version().upstream_version
Logger.command(["cd", directory])
os.chdir(directory)
elif branch:
branch_dir = download_branch(task.get_branch_link())
# change directory
Logger.command(["cd", branch_dir])
os.chdir(branch_dir)
def sponsor_patch(bug_number, build, builder, edit, keyid, lpinstance, update,
upload, workdir):
@ -250,13 +257,27 @@ def sponsor_patch(bug_number, build, builder, edit, keyid, lpinstance, update,
_download_and_change_into(task, dsc_file, patch, branch)
source_package = SourcePackage(task.package, builder, workdir, branch)
if is_sync(bug) and not edit:
successful = True
source_package.reload_changelog()
if build:
dist = UbuntuDistroInfo().devel()
successful = source_package.build(update, dist)
update = False
if successful:
source_package.sync(upload, bug_number, keyid)
return
else:
edit = True
if patch:
edit |= patch.apply(task)
elif branch:
edit |= merge_branch(branch)
source_package = SourcePackage(task.package, builder, workdir, branch)
while True:
if edit:
edit_source()