summaryrefslogtreecommitdiffstats
path: root/release
diff options
context:
space:
mode:
Diffstat (limited to 'release')
-rwxr-xr-xrelease/sign-launcher.py178
-rwxr-xr-xrelease/sign-tag.py171
-rw-r--r--release/update_manpages.py186
-rw-r--r--release/util.py78
4 files changed, 355 insertions, 258 deletions
diff --git a/release/sign-launcher.py b/release/sign-launcher.py
index ffe23cc5..86566122 100755
--- a/release/sign-launcher.py
+++ b/release/sign-launcher.py
@@ -28,43 +28,56 @@ import util
28 28
29 29
30def sign(opts): 30def sign(opts):
31 """Sign the launcher!""" 31 """Sign the launcher!"""
32 output = '' 32 output = ""
33 for key in opts.keys: 33 for key in opts.keys:
34 # We use ! at the end of the key so that gpg uses this specific key. 34 # We use ! at the end of the key so that gpg uses this specific key.
35 # Otherwise it uses the key as a lookup into the overall key and uses the 35 # Otherwise it uses the key as a lookup into the overall key and uses
36 # default signing key. i.e. It will see that KEYID_RSA is a subkey of 36 # the default signing key. i.e. It will see that KEYID_RSA is a subkey
37 # another key, and use the primary key to sign instead of the subkey. 37 # of another key, and use the primary key to sign instead of the subkey.
38 cmd = ['gpg', '--homedir', opts.gpgdir, '-u', f'{key}!', '--batch', '--yes', 38 cmd = [
39 '--armor', '--detach-sign', '--output', '-', opts.launcher] 39 "gpg",
40 ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE) 40 "--homedir",
41 output += ret.stdout 41 opts.gpgdir,
42 42 "-u",
43 # Save the combined signatures into one file. 43 f"{key}!",
44 with open(f'{opts.launcher}.asc', 'w', encoding='utf-8') as fp: 44 "--batch",
45 fp.write(output) 45 "--yes",
46 "--armor",
47 "--detach-sign",
48 "--output",
49 "-",
50 opts.launcher,
51 ]
52 ret = util.run(opts, cmd, encoding="utf-8", stdout=subprocess.PIPE)
53 output += ret.stdout
54
55 # Save the combined signatures into one file.
56 with open(f"{opts.launcher}.asc", "w", encoding="utf-8") as fp:
57 fp.write(output)
46 58
47 59
48def check(opts): 60def check(opts):
49 """Check the signature.""" 61 """Check the signature."""
50 util.run(opts, ['gpg', '--verify', f'{opts.launcher}.asc']) 62 util.run(opts, ["gpg", "--verify", f"{opts.launcher}.asc"])
51 63
52 64
53def get_version(opts): 65def get_version(opts):
54 """Get the version from |launcher|.""" 66 """Get the version from |launcher|."""
55 # Make sure we don't search $PATH when signing the "repo" file in the cwd. 67 # Make sure we don't search $PATH when signing the "repo" file in the cwd.
56 launcher = os.path.join('.', opts.launcher) 68 launcher = os.path.join(".", opts.launcher)
57 cmd = [launcher, '--version'] 69 cmd = [launcher, "--version"]
58 ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE) 70 ret = util.run(opts, cmd, encoding="utf-8", stdout=subprocess.PIPE)
59 m = re.search(r'repo launcher version ([0-9.]+)', ret.stdout) 71 m = re.search(r"repo launcher version ([0-9.]+)", ret.stdout)
60 if not m: 72 if not m:
61 sys.exit(f'{opts.launcher}: unable to detect repo version') 73 sys.exit(f"{opts.launcher}: unable to detect repo version")
62 return m.group(1) 74 return m.group(1)
63 75
64 76
65def postmsg(opts, version): 77def postmsg(opts, version):
66 """Helpful info to show at the end for release manager.""" 78 """Helpful info to show at the end for release manager."""
67 print(f""" 79 print(
80 f"""
68Repo launcher bucket: 81Repo launcher bucket:
69 gs://git-repo-downloads/ 82 gs://git-repo-downloads/
70 83
@@ -81,55 +94,72 @@ NB: If a rollback is necessary, the GS bucket archives old versions, and may be
81 gsutil ls -la gs://git-repo-downloads/repo gs://git-repo-downloads/repo.asc 94 gsutil ls -la gs://git-repo-downloads/repo gs://git-repo-downloads/repo.asc
82 gsutil cp -a public-read gs://git-repo-downloads/repo#<unique id> gs://git-repo-downloads/repo 95 gsutil cp -a public-read gs://git-repo-downloads/repo#<unique id> gs://git-repo-downloads/repo
83 gsutil cp -a public-read gs://git-repo-downloads/repo.asc#<unique id> gs://git-repo-downloads/repo.asc 96 gsutil cp -a public-read gs://git-repo-downloads/repo.asc#<unique id> gs://git-repo-downloads/repo.asc
84""") 97""" # noqa: E501
98 )
85 99
86 100
87def get_parser(): 101def get_parser():
88 """Get a CLI parser.""" 102 """Get a CLI parser."""
89 parser = argparse.ArgumentParser(description=__doc__) 103 parser = argparse.ArgumentParser(description=__doc__)
90 parser.add_argument('-n', '--dry-run', 104 parser.add_argument(
91 dest='dryrun', action='store_true', 105 "-n",
92 help='show everything that would be done') 106 "--dry-run",
93 parser.add_argument('--gpgdir', 107 dest="dryrun",
94 default=os.path.join(util.HOMEDIR, '.gnupg', 'repo'), 108 action="store_true",
95 help='path to dedicated gpg dir with release keys ' 109 help="show everything that would be done",
96 '(default: ~/.gnupg/repo/)') 110 )
97 parser.add_argument('--keyid', dest='keys', default=[], action='append', 111 parser.add_argument(
98 help='alternative signing keys to use') 112 "--gpgdir",
99 parser.add_argument('launcher', 113 default=os.path.join(util.HOMEDIR, ".gnupg", "repo"),
100 default=os.path.join(util.TOPDIR, 'repo'), nargs='?', 114 help="path to dedicated gpg dir with release keys "
101 help='the launcher script to sign') 115 "(default: ~/.gnupg/repo/)",
102 return parser 116 )
117 parser.add_argument(
118 "--keyid",
119 dest="keys",
120 default=[],
121 action="append",
122 help="alternative signing keys to use",
123 )
124 parser.add_argument(
125 "launcher",
126 default=os.path.join(util.TOPDIR, "repo"),
127 nargs="?",
128 help="the launcher script to sign",
129 )
130 return parser
103 131
104 132
105def main(argv): 133def main(argv):
106 """The main func!""" 134 """The main func!"""
107 parser = get_parser() 135 parser = get_parser()
108 opts = parser.parse_args(argv) 136 opts = parser.parse_args(argv)
109 137
110 if not os.path.exists(opts.gpgdir): 138 if not os.path.exists(opts.gpgdir):
111 parser.error(f'--gpgdir does not exist: {opts.gpgdir}') 139 parser.error(f"--gpgdir does not exist: {opts.gpgdir}")
112 if not os.path.exists(opts.launcher): 140 if not os.path.exists(opts.launcher):
113 parser.error(f'launcher does not exist: {opts.launcher}') 141 parser.error(f"launcher does not exist: {opts.launcher}")
114 142
115 opts.launcher = os.path.relpath(opts.launcher) 143 opts.launcher = os.path.relpath(opts.launcher)
116 print(f'Signing "{opts.launcher}" launcher script and saving to ' 144 print(
117 f'"{opts.launcher}.asc"') 145 f'Signing "{opts.launcher}" launcher script and saving to '
118 146 f'"{opts.launcher}.asc"'
119 if opts.keys: 147 )
120 print(f'Using custom keys to sign: {" ".join(opts.keys)}') 148
121 else: 149 if opts.keys:
122 print('Using official Repo release keys to sign') 150 print(f'Using custom keys to sign: {" ".join(opts.keys)}')
123 opts.keys = [util.KEYID_DSA, util.KEYID_RSA, util.KEYID_ECC] 151 else:
124 util.import_release_key(opts) 152 print("Using official Repo release keys to sign")
125 153 opts.keys = [util.KEYID_DSA, util.KEYID_RSA, util.KEYID_ECC]
126 version = get_version(opts) 154 util.import_release_key(opts)
127 sign(opts) 155
128 check(opts) 156 version = get_version(opts)
129 postmsg(opts, version) 157 sign(opts)
130 158 check(opts)
131 return 0 159 postmsg(opts, version)
132 160
133 161 return 0
134if __name__ == '__main__': 162
135 sys.exit(main(sys.argv[1:])) 163
164if __name__ == "__main__":
165 sys.exit(main(sys.argv[1:]))
diff --git a/release/sign-tag.py b/release/sign-tag.py
index 605437c9..fbfe7b26 100755
--- a/release/sign-tag.py
+++ b/release/sign-tag.py
@@ -35,46 +35,61 @@ import util
35KEYID = util.KEYID_DSA 35KEYID = util.KEYID_DSA
36 36
37# Regular expression to validate tag names. 37# Regular expression to validate tag names.
38RE_VALID_TAG = r'^v([0-9]+[.])+[0-9]+$' 38RE_VALID_TAG = r"^v([0-9]+[.])+[0-9]+$"
39 39
40 40
41def sign(opts): 41def sign(opts):
42 """Tag the commit & sign it!""" 42 """Tag the commit & sign it!"""
43 # We use ! at the end of the key so that gpg uses this specific key. 43 # We use ! at the end of the key so that gpg uses this specific key.
44 # Otherwise it uses the key as a lookup into the overall key and uses the 44 # Otherwise it uses the key as a lookup into the overall key and uses the
45 # default signing key. i.e. It will see that KEYID_RSA is a subkey of 45 # default signing key. i.e. It will see that KEYID_RSA is a subkey of
46 # another key, and use the primary key to sign instead of the subkey. 46 # another key, and use the primary key to sign instead of the subkey.
47 cmd = ['git', 'tag', '-s', opts.tag, '-u', f'{opts.key}!', 47 cmd = [
48 '-m', f'repo {opts.tag}', opts.commit] 48 "git",
49 49 "tag",
50 key = 'GNUPGHOME' 50 "-s",
51 print('+', f'export {key}="{opts.gpgdir}"') 51 opts.tag,
52 oldvalue = os.getenv(key) 52 "-u",
53 os.putenv(key, opts.gpgdir) 53 f"{opts.key}!",
54 util.run(opts, cmd) 54 "-m",
55 if oldvalue is None: 55 f"repo {opts.tag}",
56 os.unsetenv(key) 56 opts.commit,
57 else: 57 ]
58 os.putenv(key, oldvalue) 58
59 key = "GNUPGHOME"
60 print("+", f'export {key}="{opts.gpgdir}"')
61 oldvalue = os.getenv(key)
62 os.putenv(key, opts.gpgdir)
63 util.run(opts, cmd)
64 if oldvalue is None:
65 os.unsetenv(key)
66 else:
67 os.putenv(key, oldvalue)
59 68
60 69
61def check(opts): 70def check(opts):
62 """Check the signature.""" 71 """Check the signature."""
63 util.run(opts, ['git', 'tag', '--verify', opts.tag]) 72 util.run(opts, ["git", "tag", "--verify", opts.tag])
64 73
65 74
66def postmsg(opts): 75def postmsg(opts):
67 """Helpful info to show at the end for release manager.""" 76 """Helpful info to show at the end for release manager."""
68 cmd = ['git', 'rev-parse', 'remotes/origin/stable'] 77 cmd = ["git", "rev-parse", "remotes/origin/stable"]
69 ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE) 78 ret = util.run(opts, cmd, encoding="utf-8", stdout=subprocess.PIPE)
70 current_release = ret.stdout.strip() 79 current_release = ret.stdout.strip()
71 80
72 cmd = ['git', 'log', '--format=%h (%aN) %s', '--no-merges', 81 cmd = [
73 f'remotes/origin/stable..{opts.tag}'] 82 "git",
74 ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE) 83 "log",
75 shortlog = ret.stdout.strip() 84 "--format=%h (%aN) %s",
76 85 "--no-merges",
77 print(f""" 86 f"remotes/origin/stable..{opts.tag}",
87 ]
88 ret = util.run(opts, cmd, encoding="utf-8", stdout=subprocess.PIPE)
89 shortlog = ret.stdout.strip()
90
91 print(
92 f"""
78Here's the short log since the last release. 93Here's the short log since the last release.
79{shortlog} 94{shortlog}
80 95
@@ -84,57 +99,69 @@ NB: People will start upgrading to this version immediately.
84 99
85To roll back a release: 100To roll back a release:
86 git push origin --force {current_release}:stable -n 101 git push origin --force {current_release}:stable -n
87""") 102"""
103 )
88 104
89 105
90def get_parser(): 106def get_parser():
91 """Get a CLI parser.""" 107 """Get a CLI parser."""
92 parser = argparse.ArgumentParser( 108 parser = argparse.ArgumentParser(
93 description=__doc__, 109 description=__doc__,
94 formatter_class=argparse.RawDescriptionHelpFormatter) 110 formatter_class=argparse.RawDescriptionHelpFormatter,
95 parser.add_argument('-n', '--dry-run', 111 )
96 dest='dryrun', action='store_true', 112 parser.add_argument(
97 help='show everything that would be done') 113 "-n",
98 parser.add_argument('--gpgdir', 114 "--dry-run",
99 default=os.path.join(util.HOMEDIR, '.gnupg', 'repo'), 115 dest="dryrun",
100 help='path to dedicated gpg dir with release keys ' 116 action="store_true",
101 '(default: ~/.gnupg/repo/)') 117 help="show everything that would be done",
102 parser.add_argument('-f', '--force', action='store_true', 118 )
103 help='force signing of any tag') 119 parser.add_argument(
104 parser.add_argument('--keyid', dest='key', 120 "--gpgdir",
105 help='alternative signing key to use') 121 default=os.path.join(util.HOMEDIR, ".gnupg", "repo"),
106 parser.add_argument('tag', 122 help="path to dedicated gpg dir with release keys "
107 help='the tag to create (e.g. "v2.0")') 123 "(default: ~/.gnupg/repo/)",
108 parser.add_argument('commit', default='HEAD', nargs='?', 124 )
109 help='the commit to tag') 125 parser.add_argument(
110 return parser 126 "-f", "--force", action="store_true", help="force signing of any tag"
127 )
128 parser.add_argument(
129 "--keyid", dest="key", help="alternative signing key to use"
130 )
131 parser.add_argument("tag", help='the tag to create (e.g. "v2.0")')
132 parser.add_argument(
133 "commit", default="HEAD", nargs="?", help="the commit to tag"
134 )
135 return parser
111 136
112 137
113def main(argv): 138def main(argv):
114 """The main func!""" 139 """The main func!"""
115 parser = get_parser() 140 parser = get_parser()
116 opts = parser.parse_args(argv) 141 opts = parser.parse_args(argv)
117 142
118 if not os.path.exists(opts.gpgdir): 143 if not os.path.exists(opts.gpgdir):
119 parser.error(f'--gpgdir does not exist: {opts.gpgdir}') 144 parser.error(f"--gpgdir does not exist: {opts.gpgdir}")
120 145
121 if not opts.force and not re.match(RE_VALID_TAG, opts.tag): 146 if not opts.force and not re.match(RE_VALID_TAG, opts.tag):
122 parser.error(f'tag "{opts.tag}" does not match regex "{RE_VALID_TAG}"; ' 147 parser.error(
123 'use --force to sign anyways') 148 f'tag "{opts.tag}" does not match regex "{RE_VALID_TAG}"; '
149 "use --force to sign anyways"
150 )
124 151
125 if opts.key: 152 if opts.key:
126 print(f'Using custom key to sign: {opts.key}') 153 print(f"Using custom key to sign: {opts.key}")
127 else: 154 else:
128 print('Using official Repo release key to sign') 155 print("Using official Repo release key to sign")
129 opts.key = KEYID 156 opts.key = KEYID
130 util.import_release_key(opts) 157 util.import_release_key(opts)
131 158
132 sign(opts) 159 sign(opts)
133 check(opts) 160 check(opts)
134 postmsg(opts) 161 postmsg(opts)
135 162
136 return 0 163 return 0
137 164
138 165
139if __name__ == '__main__': 166if __name__ == "__main__":
140 sys.exit(main(sys.argv[1:])) 167 sys.exit(main(sys.argv[1:]))
diff --git a/release/update_manpages.py b/release/update_manpages.py
index d1bf8928..cd2acc01 100644
--- a/release/update_manpages.py
+++ b/release/update_manpages.py
@@ -29,91 +29,125 @@ import sys
29import tempfile 29import tempfile
30 30
31TOPDIR = Path(__file__).resolve().parent.parent 31TOPDIR = Path(__file__).resolve().parent.parent
32MANDIR = TOPDIR.joinpath('man') 32MANDIR = TOPDIR.joinpath("man")
33 33
34# Load repo local modules. 34# Load repo local modules.
35sys.path.insert(0, str(TOPDIR)) 35sys.path.insert(0, str(TOPDIR))
36from git_command import RepoSourceVersion 36from git_command import RepoSourceVersion
37import subcmds 37import subcmds
38 38
39
39def worker(cmd, **kwargs): 40def worker(cmd, **kwargs):
40 subprocess.run(cmd, **kwargs) 41 subprocess.run(cmd, **kwargs)
42
41 43
42def main(argv): 44def main(argv):
43 parser = argparse.ArgumentParser(description=__doc__) 45 parser = argparse.ArgumentParser(description=__doc__)
44 opts = parser.parse_args(argv) 46 parser.parse_args(argv)
45 47
46 if not shutil.which('help2man'): 48 if not shutil.which("help2man"):
47 sys.exit('Please install help2man to continue.') 49 sys.exit("Please install help2man to continue.")
48 50
49 # Let repo know we're generating man pages so it can avoid some dynamic 51 # Let repo know we're generating man pages so it can avoid some dynamic
50 # behavior (like probing active number of CPUs). We use a weird name & 52 # behavior (like probing active number of CPUs). We use a weird name &
51 # value to make it less likely for users to set this var themselves. 53 # value to make it less likely for users to set this var themselves.
52 os.environ['_REPO_GENERATE_MANPAGES_'] = ' indeed! ' 54 os.environ["_REPO_GENERATE_MANPAGES_"] = " indeed! "
53 55
54 # "repo branch" is an alias for "repo branches". 56 # "repo branch" is an alias for "repo branches".
55 del subcmds.all_commands['branch'] 57 del subcmds.all_commands["branch"]
56 (MANDIR / 'repo-branch.1').write_text('.so man1/repo-branches.1') 58 (MANDIR / "repo-branch.1").write_text(".so man1/repo-branches.1")
57 59
58 version = RepoSourceVersion() 60 version = RepoSourceVersion()
59 cmdlist = [['help2man', '-N', '-n', f'repo {cmd} - manual page for repo {cmd}', 61 cmdlist = [
60 '-S', f'repo {cmd}', '-m', 'Repo Manual', f'--version-string={version}', 62 [
61 '-o', MANDIR.joinpath(f'repo-{cmd}.1.tmp'), './repo', 63 "help2man",
62 '-h', f'help {cmd}'] for cmd in subcmds.all_commands] 64 "-N",
63 cmdlist.append(['help2man', '-N', '-n', 'repository management tool built on top of git', 65 "-n",
64 '-S', 'repo', '-m', 'Repo Manual', f'--version-string={version}', 66 f"repo {cmd} - manual page for repo {cmd}",
65 '-o', MANDIR.joinpath('repo.1.tmp'), './repo', 67 "-S",
66 '-h', '--help-all']) 68 f"repo {cmd}",
67 69 "-m",
68 with tempfile.TemporaryDirectory() as tempdir: 70 "Repo Manual",
69 tempdir = Path(tempdir) 71 f"--version-string={version}",
70 repo_dir = tempdir / '.repo' 72 "-o",
71 repo_dir.mkdir() 73 MANDIR.joinpath(f"repo-{cmd}.1.tmp"),
72 (repo_dir / 'repo').symlink_to(TOPDIR) 74 "./repo",
73 75 "-h",
74 # Create a repo wrapper using the active Python executable. We can't pass 76 f"help {cmd}",
75 # this directly to help2man as it's too simple, so insert it via shebang. 77 ]
76 data = (TOPDIR / 'repo').read_text(encoding='utf-8') 78 for cmd in subcmds.all_commands
77 tempbin = tempdir / 'repo' 79 ]
78 tempbin.write_text(f'#!{sys.executable}\n' + data, encoding='utf-8') 80 cmdlist.append(
79 tempbin.chmod(0o755) 81 [
80 82 "help2man",
81 # Run all cmd in parallel, and wait for them to finish. 83 "-N",
82 with multiprocessing.Pool() as pool: 84 "-n",
83 pool.map(partial(worker, cwd=tempdir, check=True), cmdlist) 85 "repository management tool built on top of git",
84 86 "-S",
85 for tmp_path in MANDIR.glob('*.1.tmp'): 87 "repo",
86 path = tmp_path.parent / tmp_path.stem 88 "-m",
87 old_data = path.read_text() if path.exists() else '' 89 "Repo Manual",
88 90 f"--version-string={version}",
89 data = tmp_path.read_text() 91 "-o",
90 tmp_path.unlink() 92 MANDIR.joinpath("repo.1.tmp"),
91 93 "./repo",
92 data = replace_regex(data) 94 "-h",
93 95 "--help-all",
94 # If the only thing that changed was the date, don't refresh. This avoids 96 ]
95 # a lot of noise when only one file actually updates. 97 )
96 old_data = re.sub(r'^(\.TH REPO "1" ")([^"]+)', r'\1', old_data, flags=re.M) 98
97 new_data = re.sub(r'^(\.TH REPO "1" ")([^"]+)', r'\1', data, flags=re.M) 99 with tempfile.TemporaryDirectory() as tempdir:
98 if old_data != new_data: 100 tempdir = Path(tempdir)
99 path.write_text(data) 101 repo_dir = tempdir / ".repo"
102 repo_dir.mkdir()
103 (repo_dir / "repo").symlink_to(TOPDIR)
104
105 # Create a repo wrapper using the active Python executable. We can't
106 # pass this directly to help2man as it's too simple, so insert it via
107 # shebang.
108 data = (TOPDIR / "repo").read_text(encoding="utf-8")
109 tempbin = tempdir / "repo"
110 tempbin.write_text(f"#!{sys.executable}\n" + data, encoding="utf-8")
111 tempbin.chmod(0o755)
112
113 # Run all cmd in parallel, and wait for them to finish.
114 with multiprocessing.Pool() as pool:
115 pool.map(partial(worker, cwd=tempdir, check=True), cmdlist)
116
117 for tmp_path in MANDIR.glob("*.1.tmp"):
118 path = tmp_path.parent / tmp_path.stem
119 old_data = path.read_text() if path.exists() else ""
120
121 data = tmp_path.read_text()
122 tmp_path.unlink()
123
124 data = replace_regex(data)
125
126 # If the only thing that changed was the date, don't refresh. This
127 # avoids a lot of noise when only one file actually updates.
128 old_data = re.sub(
129 r'^(\.TH REPO "1" ")([^"]+)', r"\1", old_data, flags=re.M
130 )
131 new_data = re.sub(r'^(\.TH REPO "1" ")([^"]+)', r"\1", data, flags=re.M)
132 if old_data != new_data:
133 path.write_text(data)
100 134
101 135
102def replace_regex(data): 136def replace_regex(data):
103 """Replace semantically null regexes in the data. 137 """Replace semantically null regexes in the data.
104 138
105 Args: 139 Args:
106 data: manpage text. 140 data: manpage text.
107 141
108 Returns: 142 Returns:
109 Updated manpage text. 143 Updated manpage text.
110 """ 144 """
111 regex = ( 145 regex = (
112 (r'(It was generated by help2man) [0-9.]+', r'\g<1>.'), 146 (r"(It was generated by help2man) [0-9.]+", r"\g<1>."),
113 (r'^\033\[[0-9;]*m([^\033]*)\033\[m', r'\g<1>'), 147 (r"^\033\[[0-9;]*m([^\033]*)\033\[m", r"\g<1>"),
114 (r'^\.IP\n(.*:)\n', r'.SS \g<1>\n'), 148 (r"^\.IP\n(.*:)\n", r".SS \g<1>\n"),
115 (r'^\.PP\nDescription', r'.SH DETAILS'), 149 (r"^\.PP\nDescription", r".SH DETAILS"),
116 ) 150 )
117 for pattern, replacement in regex: 151 for pattern, replacement in regex:
118 data = re.sub(pattern, replacement, data, flags=re.M) 152 data = re.sub(pattern, replacement, data, flags=re.M)
119 return data 153 return data
diff --git a/release/util.py b/release/util.py
index 9d0eb1dc..df7a5638 100644
--- a/release/util.py
+++ b/release/util.py
@@ -20,54 +20,60 @@ import subprocess
20import sys 20import sys
21 21
22 22
23assert sys.version_info >= (3, 6), 'This module requires Python 3.6+' 23assert sys.version_info >= (3, 6), "This module requires Python 3.6+"
24 24
25 25
26TOPDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 26TOPDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27HOMEDIR = os.path.expanduser('~') 27HOMEDIR = os.path.expanduser("~")
28 28
29 29
30# These are the release keys we sign with. 30# These are the release keys we sign with.
31KEYID_DSA = '8BB9AD793E8E6153AF0F9A4416530D5E920F5C65' 31KEYID_DSA = "8BB9AD793E8E6153AF0F9A4416530D5E920F5C65"
32KEYID_RSA = 'A34A13BE8E76BFF46A0C022DA2E75A824AAB9624' 32KEYID_RSA = "A34A13BE8E76BFF46A0C022DA2E75A824AAB9624"
33KEYID_ECC = 'E1F9040D7A3F6DAFAC897CD3D3B95DA243E48A39' 33KEYID_ECC = "E1F9040D7A3F6DAFAC897CD3D3B95DA243E48A39"
34 34
35 35
36def cmdstr(cmd): 36def cmdstr(cmd):
37 """Get a nicely quoted shell command.""" 37 """Get a nicely quoted shell command."""
38 ret = [] 38 ret = []
39 for arg in cmd: 39 for arg in cmd:
40 if not re.match(r'^[a-zA-Z0-9/_.=-]+$', arg): 40 if not re.match(r"^[a-zA-Z0-9/_.=-]+$", arg):
41 arg = f'"{arg}"' 41 arg = f'"{arg}"'
42 ret.append(arg) 42 ret.append(arg)
43 return ' '.join(ret) 43 return " ".join(ret)
44 44
45 45
46def run(opts, cmd, check=True, **kwargs): 46def run(opts, cmd, check=True, **kwargs):
47 """Helper around subprocess.run to include logging.""" 47 """Helper around subprocess.run to include logging."""
48 print('+', cmdstr(cmd)) 48 print("+", cmdstr(cmd))
49 if opts.dryrun: 49 if opts.dryrun:
50 cmd = ['true', '--'] + cmd 50 cmd = ["true", "--"] + cmd
51 try: 51 try:
52 return subprocess.run(cmd, check=check, **kwargs) 52 return subprocess.run(cmd, check=check, **kwargs)
53 except subprocess.CalledProcessError as e: 53 except subprocess.CalledProcessError as e:
54 print(f'aborting: {e}', file=sys.stderr) 54 print(f"aborting: {e}", file=sys.stderr)
55 sys.exit(1) 55 sys.exit(1)
56 56
57 57
58def import_release_key(opts): 58def import_release_key(opts):
59 """Import the public key of the official release repo signing key.""" 59 """Import the public key of the official release repo signing key."""
60 # Extract the key from our repo launcher. 60 # Extract the key from our repo launcher.
61 launcher = getattr(opts, 'launcher', os.path.join(TOPDIR, 'repo')) 61 launcher = getattr(opts, "launcher", os.path.join(TOPDIR, "repo"))
62 print(f'Importing keys from "{launcher}" launcher script') 62 print(f'Importing keys from "{launcher}" launcher script')
63 with open(launcher, encoding='utf-8') as fp: 63 with open(launcher, encoding="utf-8") as fp:
64 data = fp.read() 64 data = fp.read()
65 65
66 keys = re.findall( 66 keys = re.findall(
67 r'\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n[^-]*' 67 r"\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n[^-]*"
68 r'\n-----END PGP PUBLIC KEY BLOCK-----\n', data, flags=re.M) 68 r"\n-----END PGP PUBLIC KEY BLOCK-----\n",
69 run(opts, ['gpg', '--import'], input='\n'.join(keys).encode('utf-8')) 69 data,
70 70 flags=re.M,
71 print('Marking keys as fully trusted') 71 )
72 run(opts, ['gpg', '--import-ownertrust'], 72 run(opts, ["gpg", "--import"], input="\n".join(keys).encode("utf-8"))
73 input=f'{KEYID_DSA}:6:\n'.encode('utf-8')) 73
74 print("Marking keys as fully trusted")
75 run(
76 opts,
77 ["gpg", "--import-ownertrust"],
78 input=f"{KEYID_DSA}:6:\n".encode("utf-8"),
79 )