mirror of
				https://git.launchpad.net/ubuntu-dev-tools
				synced 2025-11-04 07:54:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			419 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			419 lines
		
	
	
		
			13 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 ubuntutools.config import UDTConfig
 | 
						|
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, lpinstance, verbose=False,
 | 
						|
         silent=False):
 | 
						|
	launchpad = get_launchpad("ubuntu-dev-tools", server=lpinstance)
 | 
						|
	# 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 <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)
 | 
						|
 | 
						|
		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 <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"""
 | 
						|
 | 
						|
if __name__ == '__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
 | 
						|
 | 
						|
	main(bug_numbers, package, version, section, update, uploader_email, key,
 | 
						|
		upload, lpinstance, verbose, silent)
 |