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)