1""" 2Invoke development tasks. 3""" 4from pathlib import Path 5from subprocess import check_output, check_call 6 7import invoke 8 9 10@invoke.task(help={"version": "version being released"}) 11def announce(ctx, version): 12 """Generates a new release announcement entry in the docs.""" 13 # Get our list of authors 14 stdout = check_output(["git", "describe", "--abbrev=0", "--tags"]) 15 stdout = stdout.decode("utf-8") 16 last_version = stdout.strip() 17 18 stdout = check_output( 19 ["git", "log", "{}..HEAD".format(last_version), "--format=%aN"] 20 ) 21 stdout = stdout.decode("utf-8") 22 23 contributors = set(stdout.splitlines()) 24 25 template_name = "release.minor.rst" if version.endswith( 26 ".0" 27 ) else "release.patch.rst" 28 template_text = Path(__file__).parent.joinpath(template_name).read_text( 29 encoding="UTF-8" 30 ) 31 32 contributors_text = "\n".join( 33 "* {}".format(name) for name in sorted(contributors) 34 ) + "\n" 35 text = template_text.format(version=version, contributors=contributors_text) 36 37 target = Path(__file__).parent.joinpath( 38 "../doc/en/announce/release-{}.rst".format(version) 39 ) 40 target.write_text(text, encoding="UTF-8") 41 print("[generate.announce] Generated {}".format(target.name)) 42 43 # Update index with the new release entry 44 index_path = Path(__file__).parent.joinpath("../doc/en/announce/index.rst") 45 lines = index_path.read_text(encoding="UTF-8").splitlines() 46 indent = " " 47 for index, line in enumerate(lines): 48 if line.startswith("{}release-".format(indent)): 49 new_line = indent + target.stem 50 if line != new_line: 51 lines.insert(index, new_line) 52 index_path.write_text("\n".join(lines) + "\n", encoding="UTF-8") 53 print("[generate.announce] Updated {}".format(index_path.name)) 54 else: 55 print( 56 "[generate.announce] Skip {} (already contains release)".format( 57 index_path.name 58 ) 59 ) 60 break 61 62 check_call(["git", "add", str(target)]) 63 64 65@invoke.task() 66def regen(ctx): 67 """Call regendoc tool to update examples and pytest output in the docs.""" 68 print("[generate.regen] Updating docs") 69 check_call(["tox", "-e", "regen"]) 70 71 72@invoke.task() 73def make_tag(ctx, version): 74 """Create a new, local tag for the release, only if the repository is clean.""" 75 from git import Repo 76 77 repo = Repo(".") 78 if repo.is_dirty(): 79 print("Current repository is dirty. Please commit any changes and try again.") 80 raise invoke.Exit(code=2) 81 82 tag_names = [x.name for x in repo.tags] 83 if version in tag_names: 84 print("[generate.make_tag] Delete existing tag {}".format(version)) 85 repo.delete_tag(version) 86 87 print("[generate.make_tag] Create tag {}".format(version)) 88 repo.create_tag(version) 89 90 91@invoke.task(help={"version": "version being released"}) 92def pre_release(ctx, version): 93 """Generates new docs, release announcements and creates a local tag.""" 94 announce(ctx, version) 95 regen(ctx) 96 changelog(ctx, version, write_out=True) 97 98 msg = "Preparing release version {}".format(version) 99 check_call(["git", "commit", "-a", "-m", msg]) 100 101 make_tag(ctx, version) 102 103 print() 104 print("[generate.pre_release] Please push your branch and open a PR.") 105 106 107@invoke.task( 108 help={ 109 "version": "version being released", 110 "write_out": "write changes to the actual changelog", 111 } 112) 113def changelog(ctx, version, write_out=False): 114 if write_out: 115 addopts = [] 116 else: 117 addopts = ["--draft"] 118 check_call(["towncrier", "--yes", "--version", version] + addopts) 119