2019-09-04 16:09:16 -03:00
|
|
|
#!/usr/bin/python3
|
2011-11-07 23:20:37 +02:00
|
|
|
#
|
2011-11-12 13:12:15 +02:00
|
|
|
# Copyright (C) 2011, Stefano Rivera <stefanor@ubuntu.com>
|
2011-11-07 23:20:37 +02:00
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
2011-11-12 01:20:47 +02:00
|
|
|
from collections import defaultdict
|
2011-11-07 23:20:37 +02:00
|
|
|
import optparse
|
|
|
|
import sys
|
|
|
|
|
2011-11-12 10:57:16 +02:00
|
|
|
import apt
|
2011-11-07 23:20:37 +02:00
|
|
|
from distro_info import UbuntuDistroInfo
|
|
|
|
|
2013-03-19 00:18:02 +01:00
|
|
|
from ubuntutools.config import UDTConfig
|
2011-11-07 23:35:14 +02:00
|
|
|
from ubuntutools.lp.lpapicache import Launchpad, Distribution
|
2011-11-07 23:20:37 +02:00
|
|
|
from ubuntutools.lp.udtexceptions import PackageNotFoundException
|
2011-11-22 23:48:30 +02:00
|
|
|
from ubuntutools.question import (YesNoQuestion, EditBugReport,
|
|
|
|
confirmation_prompt)
|
2013-03-19 00:18:02 +01:00
|
|
|
from ubuntutools.rdepends import query_rdepends, RDependsException
|
2011-11-07 23:20:37 +02:00
|
|
|
|
2018-10-12 18:54:07 -04:00
|
|
|
from ubuntutools import getLogger
|
2021-02-01 18:26:13 -05:00
|
|
|
Logger = getLogger()
|
2018-10-12 18:54:07 -04:00
|
|
|
|
2011-11-07 23:35:14 +02:00
|
|
|
|
2011-11-07 23:20:37 +02:00
|
|
|
class DestinationException(Exception):
|
|
|
|
pass
|
|
|
|
|
2011-11-07 23:35:14 +02:00
|
|
|
|
2011-11-07 23:20:37 +02:00
|
|
|
def determine_destinations(source, destination):
|
|
|
|
ubuntu_info = UbuntuDistroInfo()
|
|
|
|
if destination is None:
|
2022-08-04 03:40:04 +02:00
|
|
|
destination = ubuntu_info.lts()
|
2011-11-07 23:20:37 +02:00
|
|
|
|
|
|
|
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:
|
2011-11-08 00:03:45 +02:00
|
|
|
if support_gap:
|
|
|
|
if ubuntu_info.is_lts(release):
|
|
|
|
support_gap = False
|
|
|
|
else:
|
|
|
|
continue
|
2011-11-07 23:20:37 +02:00
|
|
|
if release not in ubuntu_info.supported():
|
|
|
|
support_gap = True
|
|
|
|
continue
|
|
|
|
destinations.append(release)
|
|
|
|
|
|
|
|
assert found
|
|
|
|
assert len(destinations) > 0
|
|
|
|
|
|
|
|
return destinations
|
|
|
|
|
|
|
|
|
2012-07-18 12:53:50 +02:00
|
|
|
def disclaimer():
|
2017-05-01 00:20:03 +02:00
|
|
|
print("Ubuntu's backports are not for fixing bugs in stable releases, "
|
|
|
|
"but for bringing new features to older, stable releases.\n"
|
|
|
|
"See https://wiki.ubuntu.com/UbuntuBackports for the Ubuntu "
|
|
|
|
"Backports policy and processes.\n"
|
|
|
|
"See https://wiki.ubuntu.com/StableReleaseUpdates for the process "
|
|
|
|
"for fixing bugs in stable releases.")
|
2012-07-18 12:53:50 +02:00
|
|
|
confirmation_prompt()
|
|
|
|
|
|
|
|
|
2022-08-04 03:40:04 +02:00
|
|
|
def check_existing(package):
|
2011-11-22 23:48:30 +02:00
|
|
|
"""Search for possible existing bug reports"""
|
2022-08-04 03:40:04 +02:00
|
|
|
distro = Distribution('ubuntu')
|
|
|
|
srcpkg = distro.getSourcePackage(name=package.getPackageName())
|
|
|
|
|
|
|
|
bugs = srcpkg.searchTasks(omit_duplicates=True,
|
|
|
|
search_text="[BPO]",
|
|
|
|
status=["Incomplete", "New", "Confirmed",
|
|
|
|
"Triaged", "In Progress",
|
|
|
|
"Fix Committed"])
|
2011-11-22 23:48:30 +02:00
|
|
|
if not bugs:
|
|
|
|
return
|
|
|
|
|
2018-10-12 18:54:07 -04:00
|
|
|
Logger.info("There are existing bug reports that look similar to your "
|
|
|
|
"request. Please check before continuing:")
|
2019-09-04 19:32:59 -03:00
|
|
|
|
2022-08-04 03:40:04 +02:00
|
|
|
for bug in sorted([bug_task.bug for bug_task in bugs], key=lambda bug: bug.id):
|
2018-10-12 18:54:07 -04:00
|
|
|
Logger.info(" * LP: #%-7i: %s %s", bug.id, bug.title, bug.web_link)
|
2011-11-22 23:48:30 +02:00
|
|
|
|
|
|
|
confirmation_prompt()
|
|
|
|
|
|
|
|
|
2011-11-15 01:40:23 +02:00
|
|
|
def find_rdepends(releases, published_binaries):
|
2011-11-12 01:20:47 +02:00
|
|
|
intermediate = defaultdict(lambda: defaultdict(list))
|
|
|
|
|
|
|
|
# We want to display every pubilshed binary, even if it has no rdepends
|
|
|
|
for binpkg in published_binaries:
|
|
|
|
intermediate[binpkg]
|
|
|
|
|
2011-11-12 00:46:48 +02:00
|
|
|
for arch in ('any', 'source'):
|
2011-11-12 01:04:11 +02:00
|
|
|
for release in releases:
|
2011-11-12 01:54:04 +02:00
|
|
|
for binpkg in published_binaries:
|
|
|
|
try:
|
|
|
|
raw_rdeps = query_rdepends(binpkg, release, arch)
|
|
|
|
except RDependsException:
|
|
|
|
# Not published? TODO: Check
|
|
|
|
continue
|
2019-09-04 16:09:16 -03:00
|
|
|
for relationship, rdeps in raw_rdeps.items():
|
2011-11-12 01:54:04 +02:00
|
|
|
for rdep in rdeps:
|
2012-07-18 12:45:47 +02:00
|
|
|
# Ignore circular deps:
|
2011-11-12 01:54:04 +02:00
|
|
|
if rdep['Package'] in published_binaries:
|
|
|
|
continue
|
2012-07-18 12:45:47 +02:00
|
|
|
# arch==any queries return Reverse-Build-Deps:
|
2017-05-01 00:20:03 +02:00
|
|
|
if arch == 'any' and rdep.get('Architectures', []) == ['source']:
|
2012-07-18 12:45:47 +02:00
|
|
|
continue
|
2017-05-01 00:20:03 +02:00
|
|
|
intermediate[binpkg][rdep['Package']].append((release, relationship))
|
2011-11-12 00:46:48 +02:00
|
|
|
|
|
|
|
output = []
|
2019-09-04 16:09:16 -03:00
|
|
|
for binpkg, rdeps in intermediate.items():
|
2011-11-12 11:22:55 +02:00
|
|
|
output += ['', binpkg, '-' * len(binpkg)]
|
2019-09-04 16:09:16 -03:00
|
|
|
for pkg, appearences in rdeps.items():
|
2011-11-12 01:04:11 +02:00
|
|
|
output += ['* %s' % pkg]
|
|
|
|
for release, relationship in appearences:
|
|
|
|
output += [' [ ] %s (%s)' % (release, relationship)]
|
2011-11-12 00:46:48 +02:00
|
|
|
|
2019-09-04 16:09:16 -03:00
|
|
|
found_any = sum(len(rdeps) for rdeps in intermediate.values())
|
2011-11-13 02:25:42 +02:00
|
|
|
if found_any:
|
|
|
|
output = [
|
|
|
|
"Reverse dependencies:",
|
|
|
|
"=====================",
|
|
|
|
"The following reverse-dependencies need to be tested against the "
|
|
|
|
"new version of %(package)s. "
|
|
|
|
"For reverse-build-dependencies (-Indep), 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 "
|
|
|
|
"%(package)s installed. "
|
|
|
|
"Reverse- Recommends, Suggests, and Enhances don't need to be "
|
|
|
|
"tested, and are listed for completeness-sake."
|
|
|
|
] + output
|
|
|
|
else:
|
|
|
|
output = ["No reverse dependencies"]
|
|
|
|
|
|
|
|
return output
|
2011-11-12 00:46:48 +02:00
|
|
|
|
|
|
|
|
2011-11-12 10:57:16 +02:00
|
|
|
def locate_package(package, distribution):
|
2011-11-07 23:20:37 +02:00
|
|
|
archive = Distribution('ubuntu').getArchive()
|
2011-11-12 10:57:16 +02:00
|
|
|
for pass_ in ('source', 'binary'):
|
|
|
|
try:
|
|
|
|
package_spph = archive.getSourcePackage(package, distribution)
|
|
|
|
return package_spph
|
2019-09-04 16:09:16 -03:00
|
|
|
except PackageNotFoundException as e:
|
2022-08-04 03:40:04 +02:00
|
|
|
try:
|
|
|
|
apt_pkg = apt.Cache()[package]
|
|
|
|
except KeyError:
|
2011-11-12 10:57:16 +02:00
|
|
|
Logger.error(str(e))
|
|
|
|
sys.exit(1)
|
2022-08-04 03:40:04 +02:00
|
|
|
package = apt_pkg.candidate.source_name
|
|
|
|
Logger.info("Binary package specified, considering its source "
|
|
|
|
"package instead: %s", package)
|
2011-11-12 10:57:16 +02:00
|
|
|
|
|
|
|
|
|
|
|
def request_backport(package_spph, source, destinations):
|
2011-11-13 02:00:06 +02:00
|
|
|
published_binaries = set()
|
2011-11-15 01:50:04 +02:00
|
|
|
for bpph in package_spph.getBinaries():
|
|
|
|
published_binaries.add(bpph.getPackageName())
|
2011-11-13 02:00:06 +02:00
|
|
|
|
2011-11-25 15:40:05 +02:00
|
|
|
if not published_binaries:
|
|
|
|
Logger.error("%s (%s) has no published binaries in %s. ",
|
|
|
|
package_spph.getPackageName(), package_spph.getVersion(),
|
|
|
|
source)
|
2018-10-12 18:54:07 -04:00
|
|
|
Logger.info("Is it stuck in bin-NEW? It can't be backported until "
|
|
|
|
"the binaries have been accepted.")
|
2011-11-25 15:40:05 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
2022-08-04 03:40:04 +02:00
|
|
|
testing = ["[Testing]", ""]
|
2011-11-13 02:00:06 +02:00
|
|
|
for dest in destinations:
|
2022-08-04 03:40:04 +02:00
|
|
|
testing += [" * %s:" % dest.capitalize()]
|
|
|
|
testing += [" [ ] Package builds without modification"]
|
|
|
|
testing += [" [ ] %s installs cleanly and runs" % binary
|
2011-11-13 02:00:06 +02:00
|
|
|
for binary in published_binaries]
|
2011-11-12 11:22:55 +02:00
|
|
|
|
2011-11-07 23:20:37 +02:00
|
|
|
subst = {
|
|
|
|
'package': package_spph.getPackageName(),
|
|
|
|
'version': package_spph.getVersion(),
|
|
|
|
'component': package_spph.getComponent(),
|
2011-11-23 01:45:49 +02:00
|
|
|
'source': package_spph.getSeriesAndPocket(),
|
2011-11-07 23:20:37 +02:00
|
|
|
'destinations': ', '.join(destinations),
|
|
|
|
}
|
2022-08-04 03:40:04 +02:00
|
|
|
subject = "[BPO] %(package)s %(version)s to %(destinations)s" % subst
|
2011-11-13 02:25:42 +02:00
|
|
|
body = ('\n'.join(
|
|
|
|
[
|
2022-08-04 03:40:04 +02:00
|
|
|
"[Impact]",
|
|
|
|
"",
|
|
|
|
" * Justification for backporting the new version to the stable release.",
|
|
|
|
"",
|
|
|
|
"[Scope]",
|
|
|
|
"",
|
2023-01-30 19:29:30 +01:00
|
|
|
" * List the Ubuntu release you will backport from,"
|
|
|
|
" and the specific package version.",
|
2022-08-04 03:40:04 +02:00
|
|
|
"",
|
|
|
|
" * List the Ubuntu release(s) you will backport to.",
|
2011-11-13 02:25:42 +02:00
|
|
|
"",
|
2022-08-04 03:40:04 +02:00
|
|
|
"[Other Info]",
|
2011-11-13 02:25:42 +02:00
|
|
|
"",
|
2022-08-04 03:40:04 +02:00
|
|
|
" * Anything else you think is useful to include",
|
2011-11-13 02:25:42 +02:00
|
|
|
""
|
|
|
|
]
|
|
|
|
+ testing
|
|
|
|
+ [""]
|
2011-11-15 01:40:23 +02:00
|
|
|
+ find_rdepends(destinations, published_binaries)
|
2017-05-01 00:20:03 +02:00
|
|
|
+ [""]) % subst)
|
2011-11-07 23:20:37 +02:00
|
|
|
|
2011-11-13 20:15:19 +02:00
|
|
|
editor = EditBugReport(subject, body)
|
|
|
|
editor.edit()
|
|
|
|
subject, body = editor.get_report()
|
2011-11-07 23:20:37 +02:00
|
|
|
|
2018-10-12 18:54:07 -04:00
|
|
|
Logger.info('The final report is:\nSummary: %s\nDescription:\n%s\n',
|
|
|
|
subject, body)
|
2011-11-07 23:20:37 +02:00
|
|
|
if YesNoQuestion().ask("Request this backport", "yes") == "no":
|
|
|
|
sys.exit(1)
|
|
|
|
|
2022-08-04 03:40:04 +02:00
|
|
|
distro = Distribution('ubuntu')
|
|
|
|
pkgname = package_spph.getPackageName()
|
|
|
|
|
2011-11-07 23:20:37 +02:00
|
|
|
bug = Launchpad.bugs.createBug(title=subject, description=body,
|
2022-08-04 03:40:04 +02:00
|
|
|
target=distro.getSourcePackage(name=pkgname))
|
|
|
|
|
|
|
|
bug.subscribe(person=Launchpad.people['ubuntu-backporters'])
|
|
|
|
|
|
|
|
for dest in destinations:
|
|
|
|
series = distro.getSeries(dest)
|
|
|
|
try:
|
|
|
|
bug.addTask(target=series.getSourcePackage(name=pkgname))
|
2023-01-30 19:29:30 +01:00
|
|
|
except Exception:
|
2022-08-04 03:40:04 +02:00
|
|
|
break
|
2011-11-07 23:20:37 +02:00
|
|
|
|
2018-10-12 18:54:07 -04:00
|
|
|
Logger.info("Backport request filed as %s", bug.web_link)
|
2011-11-07 23:20:37 +02:00
|
|
|
|
2011-11-12 10:57:16 +02:00
|
|
|
|
|
|
|
def main():
|
2011-11-15 12:58:04 +02:00
|
|
|
parser = optparse.OptionParser('%prog [options] package')
|
2011-11-12 10:57:16 +02:00
|
|
|
parser.add_option('-d', '--destination', metavar='DEST',
|
|
|
|
help='Backport to DEST release and necessary '
|
|
|
|
'intermediate releases '
|
2022-08-04 03:40:04 +02:00
|
|
|
'(default: current LTS release)')
|
2011-11-12 10:57:16 +02:00
|
|
|
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)
|
2019-09-04 16:09:16 -03:00
|
|
|
except DestinationException as e:
|
2011-11-12 10:57:16 +02:00
|
|
|
Logger.error(str(e))
|
|
|
|
sys.exit(1)
|
|
|
|
|
2012-07-18 12:53:50 +02:00
|
|
|
disclaimer()
|
|
|
|
|
2011-11-12 10:57:16 +02:00
|
|
|
package_spph = locate_package(package, options.source)
|
2022-08-04 03:40:04 +02:00
|
|
|
|
|
|
|
check_existing(package_spph)
|
2011-11-12 10:57:16 +02:00
|
|
|
request_backport(package_spph, options.source, destinations)
|
|
|
|
|
|
|
|
|
2011-11-07 23:20:37 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|