2020-01-28 15:39:10 +01:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
2020-01-28 16:05:06 +01:00
|
|
|
import argparse
|
2020-02-13 17:34:05 +01:00
|
|
|
from collections import defaultdict
|
2020-02-21 17:58:57 +01:00
|
|
|
from itertools import chain
|
2020-01-28 15:39:10 +01:00
|
|
|
import logging
|
2020-01-31 17:00:38 +01:00
|
|
|
import re
|
2020-01-28 15:39:10 +01:00
|
|
|
import sys
|
2020-02-13 17:34:05 +01:00
|
|
|
import typing
|
2020-01-28 15:39:10 +01:00
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
from .misc import disttag_re, get_package_builds, koji_init, parse_evr, parse_release_tag
|
|
|
|
from .misc import rpmvercmp_key
|
2020-01-28 15:39:10 +01:00
|
|
|
|
|
|
|
|
2020-01-31 18:34:57 +01:00
|
|
|
_log = logging.getLogger(__name__)
|
2020-01-28 15:39:10 +01:00
|
|
|
|
|
|
|
|
2020-02-21 16:50:00 +01:00
|
|
|
def register_subcommand(subparsers):
|
|
|
|
subcmd_name = "calculate-release"
|
|
|
|
|
|
|
|
calc_release_parser = subparsers.add_parser(
|
|
|
|
subcmd_name, help="Calculate the next release tag for a package build",
|
2020-01-31 18:34:57 +01:00
|
|
|
)
|
2020-02-21 16:50:00 +01:00
|
|
|
|
|
|
|
calc_release_parser.add_argument(
|
2020-02-11 15:14:06 +01:00
|
|
|
"--algorithm",
|
|
|
|
"--algo",
|
2020-02-21 16:50:00 +01:00
|
|
|
help="The algorithm with which to calculate the next release",
|
2020-02-11 15:14:06 +01:00
|
|
|
choices=["sequential_builds", "holistic_heuristic"],
|
|
|
|
default="sequential_builds",
|
|
|
|
)
|
2020-02-21 16:50:00 +01:00
|
|
|
calc_release_parser.add_argument("package", help="The name of the package of interest")
|
|
|
|
calc_release_parser.add_argument("dist", help="The dist-tag of interest")
|
|
|
|
calc_release_parser.add_argument(
|
2020-02-21 18:21:19 +01:00
|
|
|
"evr", help="The [epoch:]version[-release] of the package", nargs="?", type=parse_evr,
|
2020-02-13 17:34:05 +01:00
|
|
|
)
|
2020-01-28 16:05:06 +01:00
|
|
|
|
2020-02-21 16:50:00 +01:00
|
|
|
return subcmd_name
|
2020-01-28 16:05:06 +01:00
|
|
|
|
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
def main_sequential_builds_algo(args):
|
2020-01-28 15:39:10 +01:00
|
|
|
n_builds = 1
|
2020-01-31 18:34:57 +01:00
|
|
|
last_build = last_version = None
|
2020-02-24 11:41:57 +01:00
|
|
|
for build in get_package_builds(args.package):
|
2020-01-31 18:34:57 +01:00
|
|
|
if args.dist in build["release"]:
|
2020-01-28 15:39:10 +01:00
|
|
|
if n_builds == 1:
|
|
|
|
last_build = build
|
2020-01-31 18:34:57 +01:00
|
|
|
last_version = build["version"]
|
|
|
|
if build["version"] == last_version:
|
2020-01-28 15:39:10 +01:00
|
|
|
n_builds += 1
|
|
|
|
|
2020-01-28 16:07:24 +01:00
|
|
|
if not last_build:
|
|
|
|
print("No build found")
|
|
|
|
return
|
|
|
|
|
2020-01-31 18:34:57 +01:00
|
|
|
print(f"Last build: {last_build['nvr']}")
|
|
|
|
pkgrel, middle, minorbump = parse_release_tag(last_build["release"])
|
2020-01-28 15:39:10 +01:00
|
|
|
try:
|
2020-01-31 17:00:38 +01:00
|
|
|
n_builds = max([pkgrel + 1, n_builds])
|
|
|
|
except TypeError:
|
2020-01-28 15:39:10 +01:00
|
|
|
pass
|
2020-02-21 18:21:19 +01:00
|
|
|
print(f"Next build: {last_build['name']}-{last_build['version']}-{n_builds}.{args.dist}")
|
2020-01-28 15:39:10 +01:00
|
|
|
|
|
|
|
|
2020-02-13 17:34:05 +01:00
|
|
|
def holistic_heuristic_calculate_release(
|
2020-02-21 18:21:19 +01:00
|
|
|
args: argparse.Namespace, lower_bound: dict, higher_bound: typing.Optional[dict],
|
2020-02-13 17:34:05 +01:00
|
|
|
):
|
|
|
|
dist = args.dist
|
|
|
|
|
|
|
|
# So what package EVR are we going for again? Default to "same as lower bound".
|
|
|
|
try:
|
|
|
|
epoch, version, release = args.evr
|
|
|
|
except TypeError:
|
|
|
|
epoch, version, release = (
|
2020-02-21 18:21:19 +01:00
|
|
|
lower_bound["epoch"],
|
|
|
|
lower_bound["version"],
|
|
|
|
lower_bound["release"],
|
2020-02-13 17:34:05 +01:00
|
|
|
)
|
|
|
|
|
2020-02-21 18:21:19 +01:00
|
|
|
new_evr = {"epoch": epoch, "version": version, "release": release}
|
2020-02-24 11:41:57 +01:00
|
|
|
if rpmvercmp_key(new_evr) > rpmvercmp_key(lower_bound):
|
2020-02-13 17:34:05 +01:00
|
|
|
lower_bound = new_evr
|
|
|
|
if not release:
|
2020-02-21 18:21:19 +01:00
|
|
|
lower_bound["release"] = f"1.{dist}"
|
2020-02-13 17:34:05 +01:00
|
|
|
|
2020-02-21 18:21:19 +01:00
|
|
|
lpkgrel, _, lminorbump = parse_release_tag(lower_bound["release"])
|
2020-02-13 17:34:05 +01:00
|
|
|
|
|
|
|
# If the higher bound has the same version, bump its release to give it enough wiggle-room for
|
|
|
|
# parallel builds of the (almost) same EVRs against several Fedora versions.
|
2020-02-21 18:21:19 +01:00
|
|
|
if higher_bound and higher_bound["version"] == version:
|
2020-02-13 17:34:05 +01:00
|
|
|
higher_bound = higher_bound.copy()
|
2020-02-21 18:21:19 +01:00
|
|
|
hbpkgrel, hbmiddle, _ = parse_release_tag(higher_bound["release"])
|
|
|
|
higher_bound["release"] = f"{hbpkgrel + 1}{hbmiddle}"
|
2020-02-13 17:34:05 +01:00
|
|
|
|
|
|
|
# Bump the left-most release number and check that it doesn't violate the higher bound, if it
|
|
|
|
# exists.
|
2020-02-21 18:21:19 +01:00
|
|
|
new_evr["release"] = f"{lpkgrel + 1}.{dist}"
|
2020-02-13 17:34:05 +01:00
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
if not higher_bound or rpmvercmp_key(new_evr) < rpmvercmp_key(higher_bound):
|
2020-02-13 17:34:05 +01:00
|
|
|
# No (satisfiable) higher bound exists or it has a higher epoch-version-release.
|
|
|
|
return new_evr
|
|
|
|
|
|
|
|
if lminorbump:
|
|
|
|
nminorbump = lminorbump + 1
|
|
|
|
else:
|
|
|
|
nminorbump = 1
|
|
|
|
|
2020-02-21 18:21:19 +01:00
|
|
|
new_evr["release"] = rel_bak = f"{lpkgrel}.{dist}.{nminorbump}"
|
2020-02-13 17:34:05 +01:00
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
if rpmvercmp_key(new_evr) < rpmvercmp_key(higher_bound):
|
2020-02-13 17:34:05 +01:00
|
|
|
return new_evr
|
|
|
|
|
|
|
|
# Oops. Attempt appending '.1' to the minor bump, ...
|
2020-02-21 18:21:19 +01:00
|
|
|
new_evr["release"] += ".1"
|
2020-02-13 17:34:05 +01:00
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
if rpmvercmp_key(new_evr) < rpmvercmp_key(higher_bound):
|
2020-02-13 17:34:05 +01:00
|
|
|
return new_evr
|
|
|
|
|
|
|
|
# ... otherwise don't bother.
|
2020-02-21 18:21:19 +01:00
|
|
|
new_evr["release"] = rel_bak
|
2020-02-13 17:34:05 +01:00
|
|
|
return new_evr
|
|
|
|
|
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
def main_holistic_heuristic_algo(args):
|
|
|
|
match = disttag_re.match(args.dist)
|
2020-02-13 17:34:05 +01:00
|
|
|
if not match:
|
|
|
|
print(
|
|
|
|
f"Dist tag {args.dist!r} has wrong format (should be e.g. 'fc31', 'epel7')",
|
|
|
|
file=sys.stderr,
|
|
|
|
)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
distcode = match.group("distcode")
|
|
|
|
pkgdistver = int(match.group("distver"))
|
|
|
|
|
|
|
|
dtag_re = re.compile(fr"\.{distcode}(?P<distver>\d+)")
|
|
|
|
|
|
|
|
builds = [
|
2020-02-24 11:41:57 +01:00
|
|
|
build for build in get_package_builds(args.package) if dtag_re.search(build["release"])
|
2020-02-13 17:34:05 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
# builds by distro release
|
|
|
|
builds_per_distver = defaultdict(list)
|
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
for build in builds:
|
2020-02-21 18:21:19 +01:00
|
|
|
match = dtag_re.search(build["release"])
|
2020-02-13 17:34:05 +01:00
|
|
|
|
|
|
|
if not match:
|
|
|
|
# ignore builds for other distro types (e.g. Fedora vs. EPEL), or modular builds
|
|
|
|
continue
|
|
|
|
|
2020-02-21 18:21:19 +01:00
|
|
|
distver = int(match.group("distver"))
|
2020-02-13 17:34:05 +01:00
|
|
|
builds_per_distver[distver].append(build)
|
|
|
|
|
|
|
|
if not builds_per_distver:
|
|
|
|
_log.warning(f"No matching builds found for dist tag pattern '{distcode}<number>'.")
|
|
|
|
return
|
|
|
|
|
|
|
|
for builds in builds_per_distver.values():
|
2020-02-24 11:41:57 +01:00
|
|
|
builds.sort(key=rpmvercmp_key, reverse=True)
|
2020-02-13 17:34:05 +01:00
|
|
|
|
|
|
|
# All builds that should be 'lower' than what we are targetting, sorted by 'highest first'.
|
|
|
|
# We get by throwing all lower/current distro versions into one list because the new release
|
|
|
|
# absolutely has to be higher than the highest in this list.
|
|
|
|
lower_bound_builds = sorted(
|
|
|
|
chain(*(builds for dver, builds in builds_per_distver.items() if dver <= pkgdistver)),
|
2020-02-24 11:41:57 +01:00
|
|
|
key=rpmvercmp_key,
|
2020-02-13 17:34:05 +01:00
|
|
|
reverse=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
# TODO: Cope with epoch-version being higher in a previous Fedora release.
|
|
|
|
|
|
|
|
# Lower bound: the RPM-wise "highest" build which this release has to exceed.
|
|
|
|
lower_bound = lower_bound_builds[0]
|
2020-02-21 18:21:19 +01:00
|
|
|
lower_bound_nvr = lower_bound["nvr"]
|
2020-02-13 17:34:05 +01:00
|
|
|
|
|
|
|
# All builds that should be 'higher' than what we are targetting, i.e. the highest build of each
|
|
|
|
# newer release. We aim at a new release which is lower than every one of them, but if this
|
|
|
|
# can't be done, accommodate at least some.
|
|
|
|
higher_bound_builds = sorted(
|
|
|
|
(builds[0] for dver, builds in builds_per_distver.items() if dver > pkgdistver),
|
2020-02-24 11:41:57 +01:00
|
|
|
key=rpmvercmp_key,
|
2020-02-13 17:34:05 +01:00
|
|
|
)
|
2020-02-21 18:21:19 +01:00
|
|
|
higher_bound_builds_nvr = [b["nvr"] for b in higher_bound_builds]
|
2020-02-13 17:34:05 +01:00
|
|
|
|
|
|
|
print(f"Highest build of lower or current distro versions: {lower_bound_nvr}")
|
|
|
|
print(f"Highest builds of higher distro versions: {', '.join(higher_bound_builds_nvr)}")
|
|
|
|
|
2020-02-24 11:41:57 +01:00
|
|
|
lower_bound_rpmvercmp_key = rpmvercmp_key(lower_bound)
|
2020-02-13 17:34:05 +01:00
|
|
|
|
|
|
|
# Disregard builds of higher distro versions that we can't go below. Sort so the first element
|
|
|
|
# is the lowest build we can (and should) go "under".
|
|
|
|
satisfiable_higher_bound_builds = sorted(
|
2020-02-24 11:41:57 +01:00
|
|
|
(b for b in higher_bound_builds if lower_bound_rpmvercmp_key < rpmvercmp_key(b)),
|
|
|
|
key=rpmvercmp_key,
|
2020-02-13 17:34:05 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
if satisfiable_higher_bound_builds:
|
|
|
|
# Find the higher bound which we can stay below.
|
|
|
|
higher_bound = satisfiable_higher_bound_builds[0]
|
2020-02-21 18:21:19 +01:00
|
|
|
higher_bound_nvr = higher_bound["nvr"]
|
2020-02-13 17:34:05 +01:00
|
|
|
else:
|
|
|
|
higher_bound = higher_bound_nvr = None
|
|
|
|
|
|
|
|
print(f"Lowest satisfiable higher build in higher distro version: {higher_bound_nvr}")
|
|
|
|
|
|
|
|
new_evr = holistic_heuristic_calculate_release(args, lower_bound, higher_bound)
|
2020-02-21 18:21:19 +01:00
|
|
|
if new_evr["epoch"]:
|
2020-02-13 17:34:05 +01:00
|
|
|
new_evr_str = f"{new_evr['epoch']}:{new_evr['version']}-{new_evr['release']}"
|
|
|
|
else:
|
|
|
|
new_evr_str = f"{new_evr['version']}-{new_evr['release']}"
|
|
|
|
|
|
|
|
print(f"Calculated new release, EVR: {new_evr['release']}, {new_evr_str}")
|
2020-02-11 15:14:06 +01:00
|
|
|
|
|
|
|
|
|
|
|
def main(args):
|
|
|
|
""" Main method. """
|
2020-02-24 11:41:57 +01:00
|
|
|
koji_init(args.koji_url)
|
2020-02-11 15:14:06 +01:00
|
|
|
|
|
|
|
if args.algorithm == "sequential_builds":
|
2020-02-24 11:41:57 +01:00
|
|
|
main_sequential_builds_algo(args)
|
2020-02-11 15:14:06 +01:00
|
|
|
elif args.algorithm == "holistic_heuristic":
|
2020-02-24 11:41:57 +01:00
|
|
|
main_holistic_heuristic_algo(args)
|