ubuntu-dev-tools/ack-sync
2011-05-23 23:44:45 +02:00

442 lines
16 KiB
Python
Executable File

#!/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 subprocess
import sys
import logging
import glob
import fnmatch
from launchpadlib.launchpad import Launchpad
from ubuntutools.config import UDTConfig
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()