github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/release-tools/changelog.py (about)

     1  #!/usr/bin/env python3
     2  
     3  import argparse
     4  import datetime
     5  import os
     6  import re
     7  from typing import NamedTuple
     8  
     9  import debian.changelog
    10  
    11  
    12  def parse_arguments():
    13      parser = argparse.ArgumentParser(description="automatic changelog writer for snapd")
    14      parser.add_argument("version", type=str, help="new snapd version")
    15      parser.add_argument("lpbug", type=str, help="new snapd major release LP bug")
    16      parser.add_argument(
    17          "changelog",
    18          type=argparse.FileType("r"),
    19          help="path to new changelog entry as generated by snappy-dch",
    20      )
    21      return parser.parse_args()
    22  
    23  
    24  class Distro(NamedTuple):
    25      name: str  # name of the distro in the packaging directory
    26      debian_name: str  # debian distribution name
    27      version_suffix: str  # suffix to add to the version number in changelogs
    28  
    29  
    30  debianish_distros = [
    31      Distro("ubuntu-14.04", "trusty", "~14.04"),
    32      Distro("ubuntu-16.04", "xenial", ""),
    33      Distro("debian-sid", "unstable", "-1"),
    34  ]
    35  
    36  
    37  other_distros = [
    38      "opensuse",
    39      "fedora",
    40      "arch",
    41  ]
    42  
    43  
    44  def rewrite_version_number_file(file_name, pattern, version, write):
    45      """rewrite_version_number_file reads a packaging file with a version number
    46      in it and updates the version number to the new one specified using the
    47      regex pattern.
    48      It returns the new file contents and optionally writes out the new contents
    49      back to the file as well.
    50      """
    51  
    52      with open(file_name, "r") as fh:
    53          file_contents = fh.read()
    54  
    55      # replace the pattern (which should have one capturing group in it for the
    56      # version specifier pattern) with the captured group + the new version such
    57      # that we can keep any whitespace, etc in between the version specified and
    58      # the version number
    59      new_contents, n = re.subn(
    60          pattern, r"\g<1>" + version, file_contents, flags=re.MULTILINE
    61      )
    62  
    63      # check that we only did one replacement in the file
    64      if n > 1:
    65          raise RuntimeError(
    66              f"too many version patterns ({n}) matched in packaging file {file_name}"
    67          )
    68      elif n < 1:
    69          raise RuntimeError(f"version pattern not matched in packaging file {file_name}")
    70  
    71      if write is True:
    72          with open(file_name, "w") as fh:
    73              fh.write(new_contents)
    74  
    75      return new_contents
    76  
    77  
    78  def update_fedora_changelog(opts, snapd_packaging_dir, new_changelog_entry, maintainer):
    79      spec_file = os.path.join(snapd_packaging_dir, "fedora", "snapd.spec")
    80  
    81      # rewrite the snapd.spec file with the right version
    82      spec_file_content = rewrite_version_number_file(
    83          spec_file,
    84          r"^(Version:\s+).*$",
    85          opts.version,
    86          False,
    87      )
    88  
    89      # now we also need to add the changelog entry to the snapd.spec file
    90      # this is a bit tricky, since we want a different format for the
    91      # changelog in snapd.spec than we have for debian, but luckily it's
    92      # just trimming whitespace off the front of each line in the
    93      # changelog
    94  
    95      dedented_changelog_lines = []
    96      for line in new_changelog_entry.splitlines():
    97          # strip the first 3 characters which are space characters so
    98          # that we only have one single whitespace
    99          dedented_changelog_lines.append(line[3:] + "\n")
   100  
   101      date = datetime.datetime.now().strftime("%a %b %d %Y")
   102  
   103      date_and_maintainer_header = f"* {date} {maintainer[0]} <{maintainer[1]}>\n"
   104      changelog_header = f"- New upstream release {opts.version}\n"
   105      fedora_changelog_lines = [
   106          date_and_maintainer_header,
   107          changelog_header,
   108      ] + dedented_changelog_lines
   109  
   110      # find the start of the changelog section in the rewritten spec file bytes
   111      changelog_section = "\n%changelog\n"
   112      idx = spec_file_content.find(changelog_section)
   113      if idx < 0:
   114          raise RuntimeError(
   115              "'%changelog' line in fedora spec file not found (was a comment or whitespace added to that line?)"
   116          )
   117      # rewrite the spec file using the replaced bits up to the changelog section,
   118      # then insert our new changelog entry lines, then add the rest of the
   119      # replaced bits of the spec file
   120      with open(spec_file, "w") as fh:
   121          # write the spec file up to and including the changelog section
   122          fh.write(spec_file_content[: idx + len(changelog_section)])
   123          # insert our new changelog entry
   124          for ch_line in fedora_changelog_lines:
   125              fh.write(ch_line)
   126          fh.write("\n")
   127          # write the rest of the original spec file
   128          fh.write(spec_file_content[idx + len(changelog_section) :])
   129  
   130  
   131  def update_opensuse_changelog(
   132      opts, snapd_packaging_dir, new_changelog_entry, maintainer
   133  ):
   134      spec_file = os.path.join(snapd_packaging_dir, "opensuse", "snapd.spec")
   135      changes_file = os.path.join(snapd_packaging_dir, "opensuse", "snapd.changes")
   136  
   137      rewrite_version_number_file(
   138          spec_file,
   139          r"^(Version:\s+).*$",
   140          opts.version,
   141          True,
   142      )
   143  
   144      # add a template changelog to the changes file
   145      date = datetime.datetime.now(tz=datetime.timezone.utc).strftime(
   146          "%a %b %d %H:%M:%S %Z %Y"
   147      )
   148  
   149      email = maintainer[1]
   150      templ = f"""-------------------------------------------------------------------
   151  {date} - {email}
   152  
   153  - Update to upstream release {opts.version}
   154  
   155  """
   156  
   157      # read the existing changes file and then write the new changelog entry at
   158      # the top and then write the rest of the file
   159      with open(changes_file, "r") as fh:
   160          current = fh.read()
   161      with open(changes_file, "w") as fh:
   162          fh.write(templ)
   163          fh.write(current)
   164  
   165  
   166  def write_github_release_entry(opts, new_changelog_entry):
   167      with open(f"snapd-{opts.version}-github-release.md", "w") as fh:
   168          # write the prefix header
   169          fh.write(
   170              f"""New snapd release {opts.version}
   171  
   172  See https://forum.snapcraft.io/t/the-snapd-roadmap/1973 for high-level overview.
   173  
   174  """
   175          )
   176  
   177          # write the rest of the actual changelog
   178          for line in new_changelog_entry.splitlines():
   179              # strip the first 4 characters which are space characters so
   180              # that there's no leading prefix
   181              fh.write(line.lstrip())
   182              fh.write("\n")
   183  
   184  
   185  def main(opts):
   186      this_script = os.path.realpath(__file__)
   187      snapd_root_git_dir = os.path.dirname(os.path.dirname(this_script))
   188      snapd_packaging_dir = os.path.join(snapd_root_git_dir, "packaging")
   189  
   190      # read all the changelog entries, expected to be formatted by snappy-dch
   191      new_changelog_entry = opts.changelog.read()
   192  
   193      # verify that the changelog entry lines are all in the right format
   194      for line_number, line in enumerate(new_changelog_entry.splitlines(), start=1):
   195          # each line should start with either 4 spaces, a - and then another
   196          # space, or 6 spaces
   197          if not line.startswith("    - ") and not line.startswith("      "):
   198              raise RuntimeError(
   199                  f"unexpected changelog line format in line {line_number}"
   200              )
   201          if len(line) > 72:
   202              raise RuntimeError(
   203                  f"line {line_number} too long, should wrap properly to next line"
   204              )
   205  
   206      # read the name and email of the person running the script using i.e. dch
   207      # conventions
   208      maintainer = debian.changelog.get_maintainer()
   209  
   210      # first handle all of the debian packaging files
   211      for distro in debianish_distros:
   212          debian_packaging_changelog = os.path.join(
   213              snapd_packaging_dir, distro.name, "changelog"
   214          )
   215          with open(debian_packaging_changelog) as fh:
   216              ch = debian.changelog.Changelog(fh)
   217  
   218          # setup a new block
   219          ch.new_block(
   220              package="snapd",
   221              version=opts.version + distro.version_suffix,
   222              distributions=distro.debian_name,
   223              urgency="medium",
   224              author=f"{maintainer[0]} <{maintainer[1]}>",
   225              date=debian.changelog.format_date(),
   226          )
   227  
   228          # add the new changelog entry with our standard header
   229          # the spacing here is manually adjusted, the top of the comment is always
   230          # the same
   231          templ = f"\n  * New upstream release, LP: #{opts.lpbug}\n" + new_changelog_entry
   232          ch.add_change(templ)
   233  
   234          # write it out back to the changelog file
   235          with open(debian_packaging_changelog, "w") as fh:
   236              ch.write_to_open_file(fh)
   237  
   238      # now handle all of the non-debian packaging files
   239      for distro in other_distros:
   240          if distro == "arch":
   241              # for arch all we need to do is change the PKGBUILD "pkgver" key
   242              rewrite_version_number_file(
   243                  os.path.join(snapd_packaging_dir, "arch", "PKGBUILD"),
   244                  r"^(pkgver=).*$",
   245                  opts.version,
   246                  True,
   247              )
   248          elif distro == "fedora":
   249              update_fedora_changelog(
   250                  opts, snapd_packaging_dir, new_changelog_entry, maintainer
   251              )
   252  
   253          elif distro == "opensuse":
   254              update_opensuse_changelog(
   255                  opts, snapd_packaging_dir, new_changelog_entry, maintainer
   256              )
   257  
   258      write_github_release_entry(opts, new_changelog_entry)
   259  
   260  
   261  if __name__ == "__main__":
   262      opts = parse_arguments()
   263      main(opts)