diff options
Diffstat (limited to 'release')
| -rwxr-xr-x | release/sign-launcher.py | 178 | ||||
| -rwxr-xr-x | release/sign-tag.py | 171 | ||||
| -rw-r--r-- | release/update_manpages.py | 186 | ||||
| -rw-r--r-- | release/util.py | 78 |
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 | ||
| 30 | def sign(opts): | 30 | def 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 | ||
| 48 | def check(opts): | 60 | def 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 | ||
| 53 | def get_version(opts): | 65 | def 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 | ||
| 65 | def postmsg(opts, version): | 77 | def 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""" | ||
| 68 | Repo launcher bucket: | 81 | Repo 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 | ||
| 87 | def get_parser(): | 101 | def 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 | ||
| 105 | def main(argv): | 133 | def 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 | |
| 134 | if __name__ == '__main__': | 162 | |
| 135 | sys.exit(main(sys.argv[1:])) | 163 | |
| 164 | if __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 | |||
| 35 | KEYID = util.KEYID_DSA | 35 | KEYID = util.KEYID_DSA |
| 36 | 36 | ||
| 37 | # Regular expression to validate tag names. | 37 | # Regular expression to validate tag names. |
| 38 | RE_VALID_TAG = r'^v([0-9]+[.])+[0-9]+$' | 38 | RE_VALID_TAG = r"^v([0-9]+[.])+[0-9]+$" |
| 39 | 39 | ||
| 40 | 40 | ||
| 41 | def sign(opts): | 41 | def 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 | ||
| 61 | def check(opts): | 70 | def 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 | ||
| 66 | def postmsg(opts): | 75 | def 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""" | ||
| 78 | Here's the short log since the last release. | 93 | Here'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 | ||
| 85 | To roll back a release: | 100 | To 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 | ||
| 90 | def get_parser(): | 106 | def 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 | ||
| 113 | def main(argv): | 138 | def 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 | ||
| 139 | if __name__ == '__main__': | 166 | if __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 | |||
| 29 | import tempfile | 29 | import tempfile |
| 30 | 30 | ||
| 31 | TOPDIR = Path(__file__).resolve().parent.parent | 31 | TOPDIR = Path(__file__).resolve().parent.parent |
| 32 | MANDIR = TOPDIR.joinpath('man') | 32 | MANDIR = TOPDIR.joinpath("man") |
| 33 | 33 | ||
| 34 | # Load repo local modules. | 34 | # Load repo local modules. |
| 35 | sys.path.insert(0, str(TOPDIR)) | 35 | sys.path.insert(0, str(TOPDIR)) |
| 36 | from git_command import RepoSourceVersion | 36 | from git_command import RepoSourceVersion |
| 37 | import subcmds | 37 | import subcmds |
| 38 | 38 | ||
| 39 | |||
| 39 | def worker(cmd, **kwargs): | 40 | def worker(cmd, **kwargs): |
| 40 | subprocess.run(cmd, **kwargs) | 41 | subprocess.run(cmd, **kwargs) |
| 42 | |||
| 41 | 43 | ||
| 42 | def main(argv): | 44 | def 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 | ||
| 102 | def replace_regex(data): | 136 | def 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 | |||
| 20 | import sys | 20 | import sys |
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | assert sys.version_info >= (3, 6), 'This module requires Python 3.6+' | 23 | assert sys.version_info >= (3, 6), "This module requires Python 3.6+" |
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | TOPDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 26 | TOPDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 27 | HOMEDIR = os.path.expanduser('~') | 27 | HOMEDIR = 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. |
| 31 | KEYID_DSA = '8BB9AD793E8E6153AF0F9A4416530D5E920F5C65' | 31 | KEYID_DSA = "8BB9AD793E8E6153AF0F9A4416530D5E920F5C65" |
| 32 | KEYID_RSA = 'A34A13BE8E76BFF46A0C022DA2E75A824AAB9624' | 32 | KEYID_RSA = "A34A13BE8E76BFF46A0C022DA2E75A824AAB9624" |
| 33 | KEYID_ECC = 'E1F9040D7A3F6DAFAC897CD3D3B95DA243E48A39' | 33 | KEYID_ECC = "E1F9040D7A3F6DAFAC897CD3D3B95DA243E48A39" |
| 34 | 34 | ||
| 35 | 35 | ||
| 36 | def cmdstr(cmd): | 36 | def 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 | ||
| 46 | def run(opts, cmd, check=True, **kwargs): | 46 | def 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 | ||
| 58 | def import_release_key(opts): | 58 | def 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 | ) | ||
