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.
|
|
|
|
|
2023-01-31 13:33:18 +01:00
|
|
|
import argparse
|
2011-11-07 23:20:37 +02:00
|
|
|
import sys
|
2023-01-30 21:28:47 +01:00
|
|
|
from collections import defaultdict
|
2011-11-07 23:20:37 +02:00
|
|
|
|
2011-11-12 10:57:16 +02:00
|
|
|
import apt
|
2011-11-07 23:20:37 +02:00
|
|
|
from distro_info import UbuntuDistroInfo
|
|
|
|
|
2023-01-30 21:28:47 +01:00
|
|
|
from ubuntutools import getLogger
|
2013-03-19 00:18:02 +01:00
|
|
|
from ubuntutools.config import UDTConfig
|
2023-01-30 21:28:47 +01:00
|
|
|
from ubuntutools.lp.lpapicache import Distribution, Launchpad
|
2011-11-07 23:20:37 +02:00
|
|
|
from ubuntutools.lp.udtexceptions import PackageNotFoundException
|
2023-01-30 21:28:47 +01:00
|
|
|
from ubuntutools.question import EditBugReport, YesNoQuestion, confirmation_prompt
|
|
|
|
from ubuntutools.rdepends import RDependsException, query_rdepends
|
2023-01-30 19:45:36 +01:00
|
|
|
|
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:
|
2023-01-30 19:45:36 +01:00
|
|
|
raise DestinationException("Destination release %s does not exist" % destination)
|
2011-11-07 23:20:37 +02:00
|
|
|
if destination not in ubuntu_info.supported():
|
2023-01-30 19:45:36 +01:00
|
|
|
raise DestinationException("Destination release %s is not supported" % destination)
|
2011-11-07 23:20:37 +02:00
|
|
|
|
|
|
|
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():
|
2023-01-30 19:45:36 +01: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"""
|
2023-01-30 19:45:36 +01:00
|
|
|
distro = Distribution("ubuntu")
|
2022-08-04 03:40:04 +02:00
|
|
|
srcpkg = distro.getSourcePackage(name=package.getPackageName())
|
|
|
|
|
2023-01-30 19:45:36 +01:00
|
|
|
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
|
|
|
|
|
2023-01-30 19:45:36 +01: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]
|
|
|
|
|
2023-01-30 19:45:36 +01: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:
|
2023-01-30 19:45:36 +01:00
|
|
|
if rdep["Package"] in published_binaries:
|
2011-11-12 01:54:04 +02:00
|
|
|
continue
|
2012-07-18 12:45:47 +02:00
|
|
|
# arch==any queries return Reverse-Build-Deps:
|
2023-01-30 19:45:36 +01:00
|
|
|
if arch == "any" and rdep.get("Architectures", []) == ["source"]:
|
2012-07-18 12:45:47 +02:00
|
|
|
continue
|
2023-01-30 19:45:36 +01: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():
|
2023-01-30 19:45:36 +01:00
|
|
|
output += ["", binpkg, "-" * len(binpkg)]
|
2019-09-04 16:09:16 -03:00
|
|
|
for pkg, appearences in rdeps.items():
|
2023-01-30 19:45:36 +01:00
|
|
|
output += ["* %s" % pkg]
|
2011-11-12 01:04:11 +02:00
|
|
|
for release, relationship in appearences:
|
2023-01-30 19:45:36 +01:00
|
|
|
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 "
|
2023-01-30 19:45:36 +01:00
|
|
|
"tested, and are listed for completeness-sake.",
|
|
|
|
] + output
|
2011-11-13 02:25:42 +02:00
|
|
|
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):
|
2023-01-30 19:45:36 +01:00
|
|
|
archive = Distribution("ubuntu").getArchive()
|
|
|
|
for pass_ in ("source", "binary"):
|
2011-11-12 10:57:16 +02:00
|
|
|
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
|
2023-01-30 19:45:36 +01:00
|
|
|
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:
|
2023-01-30 19:45:36 +01:00
|
|
|
Logger.error(
|
|
|
|
"%s (%s) has no published binaries in %s. ",
|
|
|
|
package_spph.getPackageName(),
|
|
|
|
package_spph.getVersion(),
|
|
|
|
source,
|
|
|
|
)
|
|
|
|
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"]
|
2023-01-30 19:45:36 +01:00
|
|
|
testing += [
|
|
|
|
" [ ] %s installs cleanly and runs" % binary for binary in published_binaries
|
|
|
|
]
|
2011-11-12 11:22:55 +02:00
|
|
|
|
2011-11-07 23:20:37 +02:00
|
|
|
subst = {
|
2023-01-30 19:45:36 +01:00
|
|
|
"package": package_spph.getPackageName(),
|
|
|
|
"version": package_spph.getVersion(),
|
|
|
|
"component": package_spph.getComponent(),
|
|
|
|
"source": package_spph.getSeriesAndPocket(),
|
|
|
|
"destinations": ", ".join(destinations),
|
2011-11-07 23:20:37 +02:00
|
|
|
}
|
2022-08-04 03:40:04 +02:00
|
|
|
subject = "[BPO] %(package)s %(version)s to %(destinations)s" % subst
|
2023-01-30 19:45:36 +01:00
|
|
|
body = (
|
|
|
|
"\n".join(
|
2011-11-13 02:25:42 +02:00
|
|
|
[
|
2023-01-30 19:45:36 +01:00
|
|
|
"[Impact]",
|
|
|
|
"",
|
|
|
|
" * Justification for backporting the new version to the stable release.",
|
|
|
|
"",
|
|
|
|
"[Scope]",
|
|
|
|
"",
|
|
|
|
" * List the Ubuntu release you will backport from,"
|
|
|
|
" and the specific package version.",
|
|
|
|
"",
|
|
|
|
" * List the Ubuntu release(s) you will backport to.",
|
|
|
|
"",
|
|
|
|
"[Other Info]",
|
|
|
|
"",
|
|
|
|
" * 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)
|
2023-01-30 19:45:36 +01: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
|
|
|
|
2023-01-30 19:45:36 +01: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)
|
|
|
|
|
2023-01-30 19:45:36 +01:00
|
|
|
distro = Distribution("ubuntu")
|
2022-08-04 03:40:04 +02:00
|
|
|
pkgname = package_spph.getPackageName()
|
|
|
|
|
2023-01-30 19:45:36 +01:00
|
|
|
bug = Launchpad.bugs.createBug(
|
|
|
|
title=subject, description=body, target=distro.getSourcePackage(name=pkgname)
|
|
|
|
)
|
2022-08-04 03:40:04 +02:00
|
|
|
|
2023-01-30 19:45:36 +01:00
|
|
|
bug.subscribe(person=Launchpad.people["ubuntu-backporters"])
|
2022-08-04 03:40:04 +02:00
|
|
|
|
|
|
|
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():
|
2023-01-31 13:33:18 +01:00
|
|
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] package")
|
|
|
|
parser.add_argument(
|
2023-01-30 19:45:36 +01:00
|
|
|
"-d",
|
|
|
|
"--destination",
|
|
|
|
metavar="DEST",
|
|
|
|
help="Backport to DEST release and necessary "
|
|
|
|
"intermediate releases "
|
|
|
|
"(default: current LTS release)",
|
|
|
|
)
|
2023-01-31 13:33:18 +01:00
|
|
|
parser.add_argument(
|
2023-01-30 19:45:36 +01:00
|
|
|
"-s",
|
|
|
|
"--source",
|
|
|
|
metavar="SOURCE",
|
|
|
|
help="Backport from SOURCE release (default: current devel release)",
|
|
|
|
)
|
2023-01-31 13:33:18 +01:00
|
|
|
parser.add_argument(
|
2023-01-30 19:45:36 +01:00
|
|
|
"-l",
|
|
|
|
"--lpinstance",
|
|
|
|
metavar="INSTANCE",
|
|
|
|
default=None,
|
|
|
|
help="Launchpad instance to connect to (default: production).",
|
|
|
|
)
|
2023-01-31 13:33:18 +01:00
|
|
|
parser.add_argument(
|
2023-01-30 19:45:36 +01:00
|
|
|
"--no-conf",
|
|
|
|
action="store_true",
|
|
|
|
dest="no_conf",
|
|
|
|
default=False,
|
|
|
|
help="Don't read config files or environment variables",
|
|
|
|
)
|
2023-01-31 13:33:18 +01:00
|
|
|
parser.add_argument("package", help=argparse.SUPPRESS)
|
|
|
|
args = parser.parse_args()
|
2011-11-12 10:57:16 +02:00
|
|
|
|
2023-01-31 13:33:18 +01:00
|
|
|
config = UDTConfig(args.no_conf)
|
2011-11-12 10:57:16 +02:00
|
|
|
|
2023-01-31 13:33:18 +01:00
|
|
|
if args.lpinstance is None:
|
|
|
|
args.lpinstance = config.get_value("LPINSTANCE")
|
|
|
|
Launchpad.login(args.lpinstance)
|
2011-11-12 10:57:16 +02:00
|
|
|
|
2023-01-31 13:33:18 +01:00
|
|
|
if args.source is None:
|
|
|
|
args.source = Distribution("ubuntu").getDevelopmentSeries().name
|
2011-11-12 10:57:16 +02:00
|
|
|
|
|
|
|
try:
|
2023-01-31 13:33:18 +01:00
|
|
|
destinations = determine_destinations(args.source, args.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()
|
|
|
|
|
2023-01-31 13:33:18 +01:00
|
|
|
package_spph = locate_package(args.package, args.source)
|
2022-08-04 03:40:04 +02:00
|
|
|
|
|
|
|
check_existing(package_spph)
|
2023-01-31 13:33:18 +01:00
|
|
|
request_backport(package_spph, args.source, destinations)
|
2011-11-12 10:57:16 +02:00
|
|
|
|
|
|
|
|
2023-01-30 19:45:36 +01:00
|
|
|
if __name__ == "__main__":
|
2011-11-07 23:20:37 +02:00
|
|
|
main()
|