#4 Common Python package, modules and misc cleanup
Merged 4 years ago by pingou. Opened 4 years ago by nphilipp.
fedora-infra/ nphilipp/generate_changelog master--package-and-cleanup  into  master

file removed
-282
@@ -1,282 +0,0 @@ 

- #!/usr/bin/python3

- 

- import argparse

- from collections import defaultdict

- from itertools import chain

- from functools import cmp_to_key

- import logging

- import re

- import sys

- import typing

- 

- import koji

- import rpm

- 

- 

- _log = logging.getLogger(__name__)

- 

- _release_re = re.compile(r"^(?P<pkgrel>\d+)(?:(?P<middle>.*?)(?:\.(?P<minorbump>\d+))?)?$")

- _disttag_re = re.compile(r"^\.?(?P<distcode>[^\d\.]+)(?P<distver>\d+)")

- _evr_re = re.compile(r"^(?:(?P<epoch>\d+):)?(?P<version>[^-:]+)(?:-(?P<release>[^-:]+))?$")

- 

- 

- def get_cli_arguments(args):

-     """ Set the CLI argument parser and return the argument parsed.

-     """

-     parser = argparse.ArgumentParser(

-         description="Script to determine the next NVR of a build"

-     )

-     parser.add_argument(

-         "--koji-url",

-         help="The base URL of the Koji hub",

-         default="https://koji.fedoraproject.org/kojihub",

-     )

-     parser.add_argument(

-         "--algorithm",

-         "--algo",

-         help="The algorithm with which to determine the next release",

-         choices=["sequential_builds", "holistic_heuristic"],

-         default="sequential_builds",

-     )

-     parser.add_argument("package", help="The name of the package of interest")

-     parser.add_argument("dist", help="The dist-tag of interest")

-     parser.add_argument(

-         "evr",

-         help="The [epoch:]version[-release] of the package",

-         nargs="?",

-         type=parse_evr,

-     )

- 

-     return parser.parse_args(args)

- 

- 

- def parse_evr(evr_str):

-     match = _evr_re.match(evr_str)

- 

-     if not match:

-         raise ValueError(str)

- 

-     epoch = match.group('epoch') or 0

-     epoch = int(epoch)

- 

-     return epoch, match.group('version'), match.group('release')

- 

- 

- def parse_release_tag(tag):

-     pkgrel = middle = minorbump = None

-     match = _release_re.match(tag)

-     if match:

-         pkgrel = int(match.group("pkgrel"))

-         middle = match.group("middle")

-         try:

-             minorbump = int(match.group("minorbump"))

-         except TypeError:

-             pass

-     return pkgrel, middle, minorbump

- 

- 

- def main_sequential_builds_algo(args, client, pkgid):

-     n_builds = 1

-     last_build = last_version = None

-     for build in client.listBuilds(pkgid, type="rpm", queryOpts={"order": "-nvr"}):

-         if args.dist in build["release"]:

-             if n_builds == 1:

-                 last_build = build

-                 last_version = build["version"]

-             if build["version"] == last_version:

-                 n_builds += 1

- 

-     if not last_build:

-         print("No build found")

-         return

- 

-     print(f"Last build: {last_build['nvr']}")

-     pkgrel, middle, minorbump = parse_release_tag(last_build["release"])

-     try:

-         n_builds = max([pkgrel + 1, n_builds])

-     except TypeError:

-         pass

-     print(

-         f"Next build: {last_build['name']}-{last_build['version']}-{n_builds}.{args.dist}"

-     )

- 

- 

- _rpmvercmp_key = cmp_to_key(lambda b1, b2: rpm.labelCompare(

-     (str(b1["epoch"]), b1["version"], b1["release"]),

-     (str(b2["epoch"]), b2["version"], b2["release"]),

- ))

- 

- 

- def holistic_heuristic_calculate_release(

-     args: argparse.Namespace,

-     lower_bound: dict,

-     higher_bound: typing.Optional[dict],

- ):

-     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 = (

-             lower_bound['epoch'],

-             lower_bound['version'],

-             lower_bound['release'],

-         )

- 

-     new_evr = {'epoch': epoch, 'version': version, 'release': release}

-     if _rpmvercmp_key(new_evr) > _rpmvercmp_key(lower_bound):

-         lower_bound = new_evr

-         if not release:

-             lower_bound['release'] = f"1.{dist}"

- 

-     lpkgrel, _, lminorbump = parse_release_tag(lower_bound['release'])

- 

-     # 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.

-     if higher_bound and higher_bound['version'] == version:

-         higher_bound = higher_bound.copy()

-         hbpkgrel, hbmiddle, _ = parse_release_tag(higher_bound['release'])

-         higher_bound['release'] = f"{hbpkgrel + 1}{hbmiddle}"

- 

-     # Bump the left-most release number and check that it doesn't violate the higher bound, if it

-     # exists.

-     new_evr['release'] = f"{lpkgrel + 1}.{dist}"

- 

-     if not higher_bound or _rpmvercmp_key(new_evr) < _rpmvercmp_key(higher_bound):

-         # No (satisfiable) higher bound exists or it has a higher epoch-version-release.

-         return new_evr

- 

-     if lminorbump:

-         nminorbump = lminorbump + 1

-     else:

-         nminorbump = 1

- 

-     new_evr['release'] = rel_bak = f"{lpkgrel}.{dist}.{nminorbump}"

- 

-     if _rpmvercmp_key(new_evr) < _rpmvercmp_key(higher_bound):

-         return new_evr

- 

-     # Oops. Attempt appending '.1' to the minor bump, ...

-     new_evr['release'] += ".1"

- 

-     if _rpmvercmp_key(new_evr) < _rpmvercmp_key(higher_bound):

-         return new_evr

- 

-     # ... otherwise don't bother.

-     new_evr['release'] = rel_bak

-     return new_evr

- 

- 

- def main_holistic_heuristic_algo(args, client, pkgid):

-     match = _disttag_re.match(args.dist)

-     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 = [

-         build

-         for build in client.listBuilds(pkgid, type="rpm")

-         if dtag_re.search(build['release'])

-     ]

- 

-     # builds by distro release

-     builds_per_distver = defaultdict(list)

- 

-     for build in client.listBuilds(pkgid, type="rpm"):

-         match = dtag_re.search(build['release'])

- 

-         if not match:

-             # ignore builds for other distro types (e.g. Fedora vs. EPEL), or modular builds

-             continue

- 

-         distver = int(match.group('distver'))

-         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():

-         builds.sort(key=_rpmvercmp_key, reverse=True)

- 

-     # 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)),

-         key=_rpmvercmp_key,

-         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]

-     lower_bound_nvr = lower_bound['nvr']

- 

-     # 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),

-         key=_rpmvercmp_key,

-     )

-     higher_bound_builds_nvr = [b['nvr'] for b in higher_bound_builds]

- 

-     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)}")

- 

-     lower_bound_rpmvercmp_key = _rpmvercmp_key(lower_bound)

- 

-     # 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(

-         (b for b in higher_bound_builds if lower_bound_rpmvercmp_key < _rpmvercmp_key(b)),

-         key=_rpmvercmp_key,

-     )

- 

-     if satisfiable_higher_bound_builds:

-         # Find the higher bound which we can stay below.

-         higher_bound = satisfiable_higher_bound_builds[0]

-         higher_bound_nvr = higher_bound['nvr']

-     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)

-     if new_evr['epoch']:

-         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}")

- 

- 

- def main(args):

-     """ Main method. """

-     args = get_cli_arguments(args)

-     client = koji.ClientSession(args.koji_url)

-     pkgid = client.getPackageID(args.package)

- 

-     if not pkgid:

-         print(f"Package {args.package!r} not found!", file=sys.stderr)

-         return 1

- 

-     if args.algorithm == "sequential_builds":

-         main_sequential_builds_algo(args, client, pkgid)

-     elif args.algorithm == "holistic_heuristic":

-         main_holistic_heuristic_algo(args, client, pkgid)

- 

- 

- if __name__ == "__main__":

-     sys.exit(main(sys.argv[1:]))

file added
+2
@@ -0,0 +1,2 @@ 

+ [tool.black]

+     line_length = 100

file added
+6
@@ -0,0 +1,6 @@ 

+ #!/usr/bin/env python

+ 

+ from rpmautospec.cli import main

+ 

+ 

+ main()

empty or binary file added
rpmautospec/changelog.py generate_changelog.py
file renamed
+28 -28
@@ -5,7 +5,6 @@ 

  import logging

  import os

  import subprocess

- import sys

  import textwrap

  

  import pygit2
@@ -13,6 +12,18 @@ 

  _log = logging.getLogger(__name__)

  

  

+ def register_subcommand(subparsers):

+     subcmd_name = "generate-changelog"

+ 

+     gen_changelog_parser = subparsers.add_parser(

+         subcmd_name, help="Generate changelog entries from git commit logs",

+     )

+ 

+     gen_changelog_parser.add_argument("worktree_path", help="Path to the dist-git worktree")

+ 

+     return subcmd_name

+ 

+ 

  def run_command(command, cwd=None):

      """ Run the specified command in a specific working directory if one

      is specified.
@@ -21,9 +32,7 @@ 

      try:

          output = subprocess.check_output(command, cwd=cwd, stderr=subprocess.PIPE)

      except subprocess.CalledProcessError as e:

-         _log.error(

-             "Command `{}` return code: `{}`".format(" ".join(command), e.returncode)

-         )

+         _log.error("Command `{}` return code: `{}`".format(" ".join(command), e.returncode))

          _log.error("stdout:\n-------\n{}".format(e.stdout))

          _log.error("stderr:\n-------\n{}".format(e.stderr))

          raise Exception("Command failed to run")
@@ -33,14 +42,13 @@ 

  

  def main(args):

      """ Main method. """

-     repo_obj = pygit2.Repository(args[0])

-     name = os.path.basename(args[0])

+     repopath = args.worktree_path

+ 

+     repo_obj = pygit2.Repository(repopath)

+     name = os.path.basename(repopath)

  

      branch = repo_obj.lookup_branch(repo_obj.head.shorthand)

      commit = branch.peel(pygit2.Commit)

-     nvr = None

-     new_nvr = None

-     changed = False

      data = collections.defaultdict(list)

      for commit in repo_obj.walk(commit.hex, pygit2.GIT_SORT_TIME):

          if len(commit.parents) > 1:
@@ -53,10 +61,9 @@ 

              break

  

          repo_obj.checkout_tree(

-             commit,

-             strategy=pygit2.GIT_CHECKOUT_FORCE | pygit2.GIT_CHECKOUT_RECREATE_MISSING,

+             commit, strategy=pygit2.GIT_CHECKOUT_FORCE | pygit2.GIT_CHECKOUT_RECREATE_MISSING,

          )

-         if os.path.exists(os.path.join(args[0], f"{name}.spec")):

+         if os.path.exists(os.path.join(repopath, f"{name}.spec")):

              try:

                  output = run_command(

                      [
@@ -66,16 +73,12 @@ 

                          "--specfile",

                          f"{name}.spec",

                      ],

-                     cwd=args[0],

+                     cwd=repopath,

                  )

              except Exception:

                  continue

              output = tuple(

-                 output.decode("utf-8")

-                 .strip()

-                 .split("\n")[0]

-                 .rsplit(".", 1)[0]

-                 .split("  ")

+                 output.decode("utf-8").strip().split("\n")[0].rsplit(".", 1)[0].split("  ")

              )

              nvr = "-".join(output)

  
@@ -97,25 +100,22 @@ 

              print("No more spec file, bailing")

              break

  

-     for nvr in data:

-         commits = data[nvr].reverse()

-         for idx, commit in enumerate(data[nvr]):

-             last_commit = idx + 1 == len(data[nvr])

+     for nvr, commits in data.items():

+         for idx, commit in enumerate(reversed(commits)):

+             last_commit = idx + 1 == len(commits)

              commit_dt = datetime.datetime.utcfromtimestamp(commit.commit_time)

              wrapper = textwrap.TextWrapper(width=75, subsequent_indent="  ")

              message = wrapper.fill(commit.message.split("\n")[0].strip("- "))

  

              if last_commit:

                  print(

-                     f"* {commit_dt.strftime('%a %b %d %Y')} {commit.author.name} <{commit.author.email}> - {nvr[1]}-{nvr[2]}"

+                     f"* {commit_dt.strftime('%a %b %d %Y')} {commit.author.name}"

+                     f" <{commit.author.email}> - {nvr[1]}-{nvr[2]}"

                  )

              else:

                  print(

-                     f"* {commit_dt.strftime('%a %b %d %Y')} {commit.author.name} <{commit.author.email}>"

+                     f"* {commit_dt.strftime('%a %b %d %Y')} {commit.author.name}"

+                     f" <{commit.author.email}>"

                  )

              print("- %s" % message)

              print()

- 

- 

- if __name__ == "__main__":

-     main(sys.argv[1:])

file added
+43
@@ -0,0 +1,43 @@ 

+ import argparse

+ import sys

+ 

+ from . import changelog, release

+ 

+ 

+ subcmd_modules_by_name = {}

+ 

+ 

+ def get_cli_args(args):

+     global subcmd_modules_by_name

+ 

+     parser = argparse.ArgumentParser(prog="rpmautospec")

+ 

+     # global arguments

+ 

+     parser.add_argument(

+         "--koji-url",

+         help="The base URL of the Koji hub",

+         default="https://koji.fedoraproject.org/kojihub",

+     )

+ 

+     # parsers for sub-commands

+ 

+     subparsers = parser.add_subparsers(dest="subcommand", required=True)

+ 

+     for subcmd_module in (changelog, release):

+         subcmd_name = subcmd_module.register_subcommand(subparsers)

+ 

+         if subcmd_name in subcmd_modules_by_name:

+             raise RuntimeError(f"Sub-command specified more than once: {subcmd_name}.")

+ 

+         subcmd_modules_by_name[subcmd_name] = subcmd_module

+ 

+     return parser.parse_args(args)

+ 

+ 

+ def main():

+     args = get_cli_args(sys.argv[1:])

+ 

+     if args.subcommand:

+         subcmd_module = subcmd_modules_by_name[args.subcommand]

+         subcmd_module.main(args)

file added
+60
@@ -0,0 +1,60 @@ 

+ from functools import cmp_to_key

+ import re

+ from typing import List, Optional, Tuple

+ 

+ import koji

+ import rpm

+ 

+ 

+ release_re = re.compile(r"^(?P<pkgrel>\d+)(?:(?P<middle>.*?)(?:\.(?P<minorbump>\d+))?)?$")

+ disttag_re = re.compile(r"^\.?(?P<distcode>[^\d\.]+)(?P<distver>\d+)")

+ evr_re = re.compile(r"^(?:(?P<epoch>\d+):)?(?P<version>[^-:]+)(?:-(?P<release>[^-:]+))?$")

+ 

+ rpmvercmp_key = cmp_to_key(

+     lambda b1, b2: rpm.labelCompare(

+         (str(b1["epoch"]), b1["version"], b1["release"]),

+         (str(b2["epoch"]), b2["version"], b2["release"]),

+     )

+ )

+ 

+ _kojiclient = None

+ 

+ 

+ def parse_evr(evr_str: str) -> Tuple[int, str, str]:

+     match = evr_re.match(evr_str)

+ 

+     if not match:

+         raise ValueError(str)

+ 

+     epoch = match.group("epoch") or 0

+     epoch = int(epoch)

+ 

+     return epoch, match.group("version"), match.group("release")

+ 

+ 

+ def parse_release_tag(tag: str) -> Tuple[Optional[int], Optional[str], Optional[str]]:

+     pkgrel = middle = minorbump = None

+     match = release_re.match(tag)

+     if match:

+         pkgrel = int(match.group("pkgrel"))

+         middle = match.group("middle")

+         try:

+             minorbump = int(match.group("minorbump"))

+         except TypeError:

+             pass

+     return pkgrel, middle, minorbump

+ 

+ 

+ def koji_init(koji_url: str):

+     global _kojiclient

+     _kojiclient = koji.ClientSession(koji_url)

+ 

+ 

+ def get_package_builds(pkgname: str) -> List[dict]:

+     assert _kojiclient

+ 

+     pkgid = _kojiclient.getPackageID(pkgname)

+     if not pkgid:

+         raise ValueError(f"Package {pkgname!r} not found!")

+ 

+     return _kojiclient.listBuilds(pkgid, type="rpm", queryOpts={"order": "-nvr"})

file added
+223
@@ -0,0 +1,223 @@ 

+ #!/usr/bin/python3

+ 

+ import argparse

+ from collections import defaultdict

+ from itertools import chain

+ import logging

+ import re

+ import sys

+ import typing

+ 

+ from .misc import disttag_re, get_package_builds, koji_init, parse_evr, parse_release_tag

+ from .misc import rpmvercmp_key

+ 

+ 

+ _log = logging.getLogger(__name__)

+ 

+ 

+ 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",

+     )

+ 

+     calc_release_parser.add_argument(

+         "--algorithm",

+         "--algo",

+         help="The algorithm with which to calculate the next release",

+         choices=["sequential_builds", "holistic_heuristic"],

+         default="sequential_builds",

+     )

+     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(

+         "evr", help="The [epoch:]version[-release] of the package", nargs="?", type=parse_evr,

+     )

+ 

+     return subcmd_name

+ 

+ 

+ def main_sequential_builds_algo(args):

+     n_builds = 1

+     last_build = last_version = None

+     for build in get_package_builds(args.package):

+         if args.dist in build["release"]:

+             if n_builds == 1:

+                 last_build = build

+                 last_version = build["version"]

+             if build["version"] == last_version:

+                 n_builds += 1

+ 

+     if not last_build:

+         print("No build found")

+         return

+ 

+     print(f"Last build: {last_build['nvr']}")

+     pkgrel, middle, minorbump = parse_release_tag(last_build["release"])

+     try:

+         n_builds = max([pkgrel + 1, n_builds])

+     except TypeError:

+         pass

+     print(f"Next build: {last_build['name']}-{last_build['version']}-{n_builds}.{args.dist}")

+ 

+ 

+ def holistic_heuristic_calculate_release(

+     args: argparse.Namespace, lower_bound: dict, higher_bound: typing.Optional[dict],

+ ):

+     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 = (

+             lower_bound["epoch"],

+             lower_bound["version"],

+             lower_bound["release"],

+         )

+ 

+     new_evr = {"epoch": epoch, "version": version, "release": release}

+     if rpmvercmp_key(new_evr) > rpmvercmp_key(lower_bound):

+         lower_bound = new_evr

+         if not release:

+             lower_bound["release"] = f"1.{dist}"

+ 

+     lpkgrel, _, lminorbump = parse_release_tag(lower_bound["release"])

+ 

+     # 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.

+     if higher_bound and higher_bound["version"] == version:

+         higher_bound = higher_bound.copy()

+         hbpkgrel, hbmiddle, _ = parse_release_tag(higher_bound["release"])

+         higher_bound["release"] = f"{hbpkgrel + 1}{hbmiddle}"

+ 

+     # Bump the left-most release number and check that it doesn't violate the higher bound, if it

+     # exists.

+     new_evr["release"] = f"{lpkgrel + 1}.{dist}"

+ 

+     if not higher_bound or rpmvercmp_key(new_evr) < rpmvercmp_key(higher_bound):

+         # No (satisfiable) higher bound exists or it has a higher epoch-version-release.

+         return new_evr

+ 

+     if lminorbump:

+         nminorbump = lminorbump + 1

+     else:

+         nminorbump = 1

+ 

+     new_evr["release"] = rel_bak = f"{lpkgrel}.{dist}.{nminorbump}"

+ 

+     if rpmvercmp_key(new_evr) < rpmvercmp_key(higher_bound):

+         return new_evr

+ 

+     # Oops. Attempt appending '.1' to the minor bump, ...

+     new_evr["release"] += ".1"

+ 

+     if rpmvercmp_key(new_evr) < rpmvercmp_key(higher_bound):

+         return new_evr

+ 

+     # ... otherwise don't bother.

+     new_evr["release"] = rel_bak

+     return new_evr

+ 

+ 

+ def main_holistic_heuristic_algo(args):

+     match = disttag_re.match(args.dist)

+     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 = [

+         build for build in get_package_builds(args.package) if dtag_re.search(build["release"])

+     ]

+ 

+     # builds by distro release

+     builds_per_distver = defaultdict(list)

+ 

+     for build in builds:

+         match = dtag_re.search(build["release"])

+ 

+         if not match:

+             # ignore builds for other distro types (e.g. Fedora vs. EPEL), or modular builds

+             continue

+ 

+         distver = int(match.group("distver"))

+         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():

+         builds.sort(key=rpmvercmp_key, reverse=True)

+ 

+     # 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)),

+         key=rpmvercmp_key,

+         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]

+     lower_bound_nvr = lower_bound["nvr"]

+ 

+     # 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),

+         key=rpmvercmp_key,

+     )

+     higher_bound_builds_nvr = [b["nvr"] for b in higher_bound_builds]

+ 

+     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)}")

+ 

+     lower_bound_rpmvercmp_key = rpmvercmp_key(lower_bound)

+ 

+     # 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(

+         (b for b in higher_bound_builds if lower_bound_rpmvercmp_key < rpmvercmp_key(b)),

+         key=rpmvercmp_key,

+     )

+ 

+     if satisfiable_higher_bound_builds:

+         # Find the higher bound which we can stay below.

+         higher_bound = satisfiable_higher_bound_builds[0]

+         higher_bound_nvr = higher_bound["nvr"]

+     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)

+     if new_evr["epoch"]:

+         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}")

+ 

+ 

+ def main(args):

+     """ Main method. """

+     koji_init(args.koji_url)

+ 

+     if args.algorithm == "sequential_builds":

+         main_sequential_builds_algo(args)

+     elif args.algorithm == "holistic_heuristic":

+         main_holistic_heuristic_algo(args)

file modified
+1 -1
@@ -5,5 +5,5 @@ 

  #ignore =

  

  # Configure flake8-import-order

- application-import-names = generate_changelog,next_build

+ application-import-names = rpmautospec

  import-order-style = google

tests/test_calc_release.py tests/test_next_build.py
file renamed
+13 -19
@@ -4,7 +4,7 @@ 

  

  import pytest

  

- import next_build

+ from rpmautospec import release

  

  

  __here__ = os.path.dirname(__file__)
@@ -14,16 +14,8 @@ 

          "package": "gimp",

          "expected_results": [

              # 5 existing builds -> 6

-             {

-                 "dist": "fc32",

-                 "last": "gimp-2.10.14-4.fc32.1",

-                 "next": "gimp-2.10.14-6.fc32",

-             },

-             {

-                 "dist": "fc31",

-                 "last": "gimp-2.10.14-3.fc31",

-                 "next": "gimp-2.10.14-4.fc31",

-             },

+             {"dist": "fc32", "last": "gimp-2.10.14-4.fc32.1", "next": "gimp-2.10.14-6.fc32",},

+             {"dist": "fc31", "last": "gimp-2.10.14-3.fc31", "next": "gimp-2.10.14-4.fc31",},

          ],

      },

  ]
@@ -45,27 +37,29 @@ 

      @pytest.mark.parametrize("test_data", data_as_test_parameters(test_data))

      def test_main(self, test_data, capsys):

          with open(

-             os.path.join(

-                 __here__, "koji-output", "list-builds", test_data["package"] + ".json"

-             ),

+             os.path.join(__here__, "koji-output", "list-builds", test_data["package"] + ".json"),

              "rb",

          ) as f:

              koji_list_builds_output = json.load(f)

  

-         with mock.patch("next_build.koji") as mock_koji:

+         with mock.patch("rpmautospec.misc.koji") as mock_koji:

              mock_client = mock.MagicMock()

              mock_koji.ClientSession.return_value = mock_client

              mock_client.getPackageID.return_value = 1234

              mock_client.listBuilds.return_value = koji_list_builds_output

  

-             next_build.main((test_data["package"], test_data["dist"]))

+             main_args = mock.Mock()

+             main_args.algorithm = "sequential_builds"

+             main_args.package = test_data["package"]

+             main_args.dist = test_data["dist"]

+             main_args.evr = None

+ 

+             release.main(main_args)

  

          mock_client.getPackageID.assert_called_once()

          mock_client.listBuilds.assert_called_once()

  

-         expected_output = (

-             f"Last build: {test_data['last']}\n" f"Next build: {test_data['next']}\n"

-         )

+         expected_output = f"Last build: {test_data['last']}\n" f"Next build: {test_data['next']}\n"

          captured_output = capsys.readouterr()

  

          assert captured_output.out == expected_output

Preliminary to matching up commits with [E]VRs for changelog generation:

  • Move everything into the rpmautospec package, and RPM and Koji into the rpmautospec.misc module. If you have better ideas for the name of the package, let me know! :smiley:
  • Misc. cleanups:
  • configure black for line-length of 100 and rerun
  • fix import order and remove unused imports and variables
  • improve/fix how a dict is iterated over and how the contained lists are reversed before iterating

Reviewed the commits one by one and it looks good to me, thanks! :)

Pull-Request has been merged by pingou

4 years ago