diff options
| author | Gertjan van den Burg <gertjanvandenburg@gmail.com> | 2021-01-15 16:14:38 +0000 |
|---|---|---|
| committer | Gertjan van den Burg <gertjanvandenburg@gmail.com> | 2021-01-15 16:14:38 +0000 |
| commit | 1fcf71c55757a238d63815632d42e8c625460b26 (patch) | |
| tree | edfc001e5920d795d9697fc658cb96a9ade4a3dc | |
| parent | Add copy directive to makefile (diff) | |
| download | SyncRNG-1fcf71c55757a238d63815632d42e8c625460b26.tar.gz SyncRNG-1fcf71c55757a238d63815632d42e8c625460b26.zip | |
Add first version of release script
| -rw-r--r-- | python/make_release.py | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/python/make_release.py b/python/make_release.py new file mode 100644 index 0000000..67e179e --- /dev/null +++ b/python/make_release.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Do-nothing script for making a release + +This idea comes from here: +https://blog.danslimmon.com/2019/07/15/do-nothing-scripting-the-key-to-gradual-automation/ + +Author: Gertjan van den Burg +Date: 2021-01-15 + +""" + +import colorama +import os +import sys +import tempfile +import webbrowser + +URLS = { + "CI": "https://github.com/GjjvdBurg/SyncRNG", +} + + +def colored(msg, color=None, style=None): + colors = { + "red": colorama.Fore.RED, + "green": colorama.Fore.GREEN, + "cyan": colorama.Fore.CYAN, + "yellow": colorama.Fore.YELLOW, + "magenta": colorama.Fore.MAGENTA, + None: "", + } + styles = { + "bright": colorama.Style.BRIGHT, + "dim": colorama.Style.DIM, + None: "", + } + pre = colors[color] + styles[style] + post = colorama.Style.RESET_ALL + return f"{pre}{msg}{post}" + + +def cprint(msg, color=None, style=None): + print(colored(msg, color=color, style=style)) + + +def wait_for_enter(): + input(colored("\nPress Enter to continue", style="dim")) + print() + + +def get_package_name(): + with open("./setup.py", "r") as fp: + nameline = next( + (l.strip() for l in fp if l.startswith("NAME = ")), None + ) + return nameline.split("=")[-1].strip().strip('"') + + +def get_package_version(pkgname): + ctx = {} + with open(f"{pkgname}/__version__.py", "r") as fp: + exec(fp.read(), ctx) + return ctx["__version__"] + + +class Step: + def pre(self, context): + pass + + def post(self, context): + wait_for_enter() + + def run(self, context): + try: + self.pre(context) + self.action(context) + self.post(context) + except KeyboardInterrupt: + cprint("\nInterrupted.", color="red") + raise SystemExit(1) + + def instruct(self, msg): + cprint(msg, color="green") + + def print_run(self, msg): + cprint("Run:", color="cyan", style="bright") + self.print_cmd(msg) + + def print_cmd(self, msg): + cprint("\t" + msg, color="cyan", style="bright") + + def do_cmd(self, cmd): + cprint(f"Going to run: {cmd}", color="magenta", style="bright") + wait_for_enter() + os.system(cmd) + + +class GitToMaster(Step): + def action(self, context): + self.instruct("Make sure you're on master and changes are merged in") + self.print_run("git checkout master") + + +class UpdateChangelog(Step): + def action(self, context): + self.instruct(f"Update change log for version {context['version']}") + self.print_run("vi CHANGELOG.md") + + +class UpdateReadme(Step): + def action(self, context): + self.instruct("Update readme if necessary") + self.print_run("vi README.md") + + +class CopyFile(Step): + def __init__(self, source, target): + self._source = source + self._target = target + + def action(self, context): + self.do_cmd(f"cp -v {self.source} {self.target}") + + +class RunTests(Step): + def action(self, context): + self.do_cmd("make test") + + +class BumpVersionPackage(Step): + def action(self, context): + self.instruct("Update __version__.py with new version") + self.do_cmd(f"vi {context['pkgname']}/__version__.py") + + def post(self, context): + wait_for_enter() + context["version"] = self._get_version(context) + + def _get_version(self, context): + # Get the version from the version file + return get_package_version(context["pkgname"]) + + +class MakeClean(Step): + def action(self, context): + self.do_cmd("make clean") + + +class InstallFromTestPyPI(Step): + def action(self, context): + tmpvenv = tempfile.mkdtemp(prefix="p2r_venv_") + self.do_cmd( + f"python -m venv {tmpvenv} && source {tmpvenv}/bin/activate && " + "pip install --no-cache-dir --index-url " + "https://test.pypi.org/simple/ " + "--extra-index-url https://pypi.org/simple " + f"{context['pkgname']}=={context['version']}" + ) + context["tmpvenv"] = tmpvenv + + +class TestPackage(Step): + def action(self, context): + self.instruct( + f"Ensure that the following command gives version {context['version']}" + ) + self.do_cmd( + f"source {context['tmpvenv']}/bin/activate && pip list | grep {context['pkgname']}" + ) + + +class RemoveVenv(Step): + def action(self, context): + self.do_cmd(f"rm -rf {context['tmpvenv']}") + + +class GitTagVersion(Step): + def action(self, context): + self.do_cmd(f"git tag v{context['version']}") + + +class GitTagPreRelease(Step): + def action(self, context): + self.instruct("Tag version as a pre-release (increment as needed)") + self.print_run(f"git tag v{context['version']}-rc.1") + + +class GitAdd(Step): + def action(self, context): + self.instruct("Add everything to git and commit") + self.print_run("git gui") + + +class GitAddRelease(Step): + def action(self, context): + self.instruct("Add Changelog & Readme to git") + self.instruct( + f"Commit with title: {context['pkgname']} Release {context['version']}" + ) + self.instruct("Embed changelog in body commit message") + self.print_run("git gui") + + +class PushToPyPI(Step): + def action(self, context): + self.do_cmd("twine upload dist/*") + + +class PushToGitHub(Step): + def action(self, context): + self.do_cmd("git push -u --tags origin master") + + +class WaitForCI(Step): + def action(self, context): + webbrowser.open(URLS["CI"]) + self.instruct("Wait for CI to complete and verify that its successful") + + +def main(target=None): + colorama.init() + procedure = [ + ("gittomaster", GitToMaster()), + ("copy_readme", CopyFile("../README.md", "./README.md")), + ("copy_license", CopyFile("../LICENSE", "./LICENSE")), + ("gitadd1", GitAdd()), + ("clean1", MakeClean()), + ("runtests", RunTests()), + # trigger CI to run tests on all platforms + ("push1", PushToGitHub()), + ("ci1", WaitForCI()), + ("bumpversion", BumpVersionPackage()), + ("gitadd2", GitAdd()), + ("gittagpre", GitTagPreRelease()), + # trigger CI to run tests using cibuildwheel + ("push2", PushToGitHub()), + ("ci2", WaitForCI()), + ("changelog", UpdateChangelog()), + ("readme", UpdateReadme()), + ("install", InstallFromTestPyPI()), + ("testpkg", TestPackage()), + ("remove_venv", RemoveVenv()), + ("addrelease", GitAddRelease()), + ("tagfinal", GitTagVersion()), + # triggers Travis to build with cibw and push to PyPI + ("push3", PushToGitHub()), + ("ci3", WaitForCI()), + ] + context = {} + context["pkgname"] = get_package_name() + context["version"] = get_package_version(context["pkgname"]) + skip = True if target else False + for name, step in procedure: + if not name == target and skip: + continue + skip = False + step.run(context) + cprint("\nDone!", color="yellow", style="bright") + + +if __name__ == "__main__": + target = sys.argv[1] if len(sys.argv) > 1 else None + main(target=target) |
