ubuntu-dev-tools/requestbackport
2011-11-12 00:46:48 +02:00

196 lines
7.4 KiB
Python
Executable File

#!/usr/bin/python
#
# Copyright (C) 2011, Stefano Rivera <stefanor@debian.org>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import collections
import optparse
import sys
from distro_info import UbuntuDistroInfo
from ubuntutools.lp.lpapicache import Launchpad, Distribution
from ubuntutools.lp.udtexceptions import PackageNotFoundException
from ubuntutools.config import UDTConfig
from ubuntutools.rdepends import query_rdepends
from ubuntutools.requestsync.common import edit_report
from ubuntutools.question import YesNoQuestion
class DestinationException(Exception):
pass
def determine_destinations(source, destination):
ubuntu_info = UbuntuDistroInfo()
if destination is None:
destination = ubuntu_info.stable()
if source not in ubuntu_info.all:
raise DestinationException("Source release %s does not exist" % source)
if destination not in ubuntu_info.all:
raise DestinationException("Destination release %s does not exist"
% destination)
if destination not in ubuntu_info.supported():
raise DestinationException("Destination release %s is not supported"
% destination)
found = False
destinations = []
support_gap = False
for release in ubuntu_info.all:
if release == destination:
found = True
if release == source:
break
if found:
if support_gap:
if ubuntu_info.is_lts(release):
support_gap = False
else:
continue
if release not in ubuntu_info.supported():
support_gap = True
continue
destinations.append(release)
assert found
assert len(destinations) > 0
return destinations
def main():
parser = optparse.OptionParser('%progname [options] package')
parser.add_option('-d', '--destination', metavar='DEST',
help='Backport to DEST release and necessary '
'intermediate releases '
'(default: current stable release)')
parser.add_option('-s', '--source', metavar='SOURCE',
help='Backport from SOURCE release '
'(default: current devel release)')
parser.add_option('-l', '--lpinstance', metavar='INSTANCE', default=None,
help='Launchpad instance to connect to '
'(default: production).')
parser.add_option('--no-conf', action='store_true',
dest='no_conf', default=False,
help="Don't read config files or environment variables")
options, args = parser.parse_args()
if len(args) != 1:
parser.error("One (and only one) package must be specified")
package = args[0]
config = UDTConfig(options.no_conf)
if options.lpinstance is None:
options.lpinstance = config.get_value('LPINSTANCE')
Launchpad.login(options.lpinstance)
if options.source is None:
options.source = Distribution('ubuntu').getDevelopmentSeries().name
try:
destinations = determine_destinations(options.source,
options.destination)
except DestinationException, e:
print str(e)
sys.exit(1)
request_backport(package, options.source, destinations)
def find_rdepends(package, release, releases):
published_binaries = set()
for bpph in package._lpobject.getPublishedBinaries():
published_binaries.add(bpph.binary_package_name)
intermediate = collections.defaultdict(list)
for arch in ('any', 'source'):
raw_rdeps = query_rdepends('src:' + package.getPackageName(),
release, 'any')
for relationship, rdeps in raw_rdeps.iteritems():
for rdep in rdeps:
if rdep['Package'] in published_binaries:
continue
intermediate[rdep['Dependency']].append((rdep['Package'],
relationship))
output = []
for binpkg, rdeps in intermediate.iteritems():
output += ['', binpkg, '=' * len(binpkg)]
for pkg, relationship in rdeps:
output += ['* %s (%s)' % (pkg, relationship)]
output += [' [ ] %s' % release for release in releases]
return '\n'.join(output)
def request_backport(package, source, destinations):
archive = Distribution('ubuntu').getArchive()
try:
package_spph = archive.getSourcePackage(package, source)
except PackageNotFoundException, e:
print str(e)
sys.exit(1)
subst = {
'package': package_spph.getPackageName(),
'version': package_spph.getVersion(),
'component': package_spph.getComponent(),
'rdepends': find_rdepends(package_spph, source, destinations),
'source': source,
'destinations': ', '.join(destinations),
}
subject = ("Please backport %(package)s %(version)s (%(component)s) "
"from %(source)s" % subst)
body = ("Please backport %(package)s %(version)s (%(component)s) "
"from %(source)s to %(destinations)s.\n\n"
"Reason for the backport:\n"
"<<< Enter your reasoning here >>>\n\n"
"Testing performed:\n"
"<<< Mention any build & install tests you've done >>>\n\n"
"Reverse dependencies:\n"
"The following reverse-dependencies need to be tested against the "
"new version of %(package)s. "
"For reverse-build-dependencies, please test that the package "
"still builds against the new %(package)s. "
"For reverse-dependencies, please test that the version of the "
"package currently in the release still works with the new "
"libgdata installed. "
"Mark off items in the checklist [X] as you test them, "
"but please leave the checklist so that backporters can quickly "
"evaluate the state of testing.\n"
"%(rdepends)s\n"
% subst)
subject, body = edit_report(subject, body, changes_required=True)
print ('The final report is:\nSummary: %s\nDescription:\n%s\n'
% (subject, body))
if YesNoQuestion().ask("Request this backport", "yes") == "no":
sys.exit(1)
targets = [Launchpad.projects['%s-backports' % destination]
for destination in destinations]
bug = Launchpad.bugs.createBug(title=subject, description=body,
target=targets[0])
for target in targets[1:]:
bug.addTask(target=target)
print "Backport request filed as %s" % bug.web_link
if __name__ == '__main__':
main()