Removed bells and whistles. Use at your own risk, but hey, at least releases will happen once I'm gone :)

pull/55/head
Krista Bennett 2021-07-26 11:22:06 +02:00
parent f8ec952131
commit 5676efd6d3
2 changed files with 76 additions and 411 deletions

View File

@ -3,29 +3,27 @@
import subprocess
import re
import sys
import operator
import argparse
import os
from time import sleep
import git.util
from git import Repo, TagReference, Git
from git import Repo, TagReference
from enum import Enum
def replace_src_versions(src_lines, new_major, new_minor, new_patch, new_rc, with_rc, comment, src_repo):
def replace_src_versions(src_lines, new_major, new_minor, new_patch, new_rc, with_rc, comment, src_repo, src_path):
header_file_path = src_path + "/pEpEngine.h"
if not src_lines:
print("Information: Not writing version/RC info to src/pEpEngine.h")
sys.exit()
exit("Information: Not writing version/RC info to " + header_file_path)
filedata = None
# If successful, then bump the RC
with open('src/pEpEngine.h', 'r') as file:
with open(header_file_path, 'r') as file:
filedata = file.read()
file.close()
cmd = ["grep", "-E", "#define PEP_ENGINE_VERSION[ \t]+\"[0-9]+.[0-9]+.[0-9]+\"", "src/pEpEngine.h"]
cmd = ["grep", "-E", "#define PEP_ENGINE_VERSION[ \t]+\"[0-9]+.[0-9]+.[0-9]+\"", header_file_path]
result = subprocess.run(cmd, capture_output=True)
grep_output = result.stdout.decode("utf-8")
@ -38,13 +36,13 @@ def replace_src_versions(src_lines, new_major, new_minor, new_patch, new_rc, wit
version_str = str(new_major) + "." + str(new_minor) + "." + str(new_patch)
filedata = filedata.replace(grep_output, "#define PEP_ENGINE_VERSION \"" + version_str + "\"\n")
filedata = filedata.replace(src_lines[0], "#define PEP_ENGINE_VERSION_MAJOR " + str(new_major))
filedata = filedata.replace(src_lines[1], "#define PEP_ENGINE_VERSION_MINOR " + str(new_minor))
filedata = filedata.replace(src_lines[2], "#define PEP_ENGINE_VERSION_PATCH " + str(new_patch))
filedata = filedata.replace(src_lines[3], "#define PEP_ENGINE_VERSION_RC " + str(new_rc))
filedata = filedata.replace(src_lines[0], "#define PEP_ENGINE_VERSION_MAJOR " + major_str)
filedata = filedata.replace(src_lines[1], "#define PEP_ENGINE_VERSION_MINOR " + minor_str)
filedata = filedata.replace(src_lines[2], "#define PEP_ENGINE_VERSION_PATCH " + patch_str)
filedata = filedata.replace(src_lines[3], "#define PEP_ENGINE_VERSION_RC " + rc_str)
# Write the file out again
with open('src/pEpEngine.h', 'w') as file:
with open(header_file_path, 'w') as file:
file.write(filedata)
file.close()
@ -63,16 +61,13 @@ def replace_src_versions(src_lines, new_major, new_minor, new_patch, new_rc, wit
def bool_prompt(prompt):
reply = str(input(prompt)).lower().strip()
if reply != "y":
exit(42)
exit("Aborting at user request.")
class ReleaseType(Enum):
UNKNOWN = 0
RC = 1
PATCH = 2
MINOR = 3
MAJOR = 4
# Init some things to be used later
rel_type = ReleaseType.UNKNOWN
@ -82,15 +77,11 @@ bumped_comment = "Bumped header patch number for NEXT release"
# Parse args
parser = argparse.ArgumentParser(description='Automate the RC release process as sanely as possible.')
parser.add_argument('-r', '--repo', help="Repository root - [default: current working directory]")
parser.add_argument('-a', '--note',
help="Tag annotation for release - short description of what this release is [default: none]")
parser.add_argument('-j', '--major', help="Branch off new major release", action='store_true')
parser.add_argument('-n', '--minor', help="Branch off new minor release", action='store_true')
parser.add_argument('-M', '--next_major', help="Only valid for major and minor releases: make next release in "
"master source a major release", action='store_true')
parser.add_argument('REASON',
help="Tag annotation for showing reason for release - short description of what this release is")
args = parser.parse_args()
annotation = args.note
annotation = args.REASON
# Set up repo object
repo_path = args.repo
@ -103,12 +94,12 @@ if not repo_path:
repo_path = os.getcwd()
if not repo_path:
exit(-1)
exit("Can't get repository path.")
repo = Repo(repo_path)
if not repo or repo.bare:
exit(-1)
exit("No extant repository at " + repo_path)
# DO THE THING!
@ -117,13 +108,11 @@ if not repo or repo.bare:
#
branch = repo.active_branch
if not branch:
exit(-1)
exit("Can't get current branch.")
start_branch = branch.name
if not start_branch:
exit(-1)
branch_release = args.major or args.minor
exit("Can't get current branch name.")
#
# 2. Figure out what kind of a release we're doing
@ -136,21 +125,12 @@ patch = 0
rc = 0
if start_branch == 'master':
if args.major:
rel_type = ReleaseType.MAJOR
elif args.minor:
rel_type = ReleaseType.MINOR
else:
rel_type = ReleaseType.RC
rel_type = ReleaseType.RC
else:
if branch_release:
print("RELEASE SCRIPT: New major or minor release branching is only supported from the 'master' branch.")
print("RELEASE SCRIPT: If you have a good reason for what you're doing, do it by hand.")
exit(-1)
release_re = re.compile('^Release_([0-9])+[.]([0-9])+')
release_match = release_re.match(start_branch)
if not release_match:
exit(-1)
exit("Not in a release branch. Aborting. (Release branches are of the form 'Release_<major>.<minor>')")
else:
rel_type = ReleaseType.PATCH
major = int(release_match.groups()[0])
@ -173,11 +153,12 @@ result = subprocess.run(cmd, capture_output=True)
grep_output = result.stdout.decode("utf-8")
grep_lines = grep_output.splitlines();
version_re = re.compile('PEP_ENGINE_VERSION_([A-Z]+)[ \t]+([0-9]+)')
version_re = re.compile('#define PEP_ENGINE_VERSION_([A-Z]+)[ \t]+([0-9]+)')
for line in grep_lines:
m = version_re.search(line)
if not m:
exit(-1)
exit("Can't find matching version information in header file to determine source version info.")
key = m.group(1)
value = int(m.group(2))
@ -190,7 +171,7 @@ for line in grep_lines:
elif key == "RC":
src_rc = value
else:
exit(-1)
exit("Additional information has been added matching '#define PEP_ENGINE_VERSION_.*' - please fix this script.")
# This is tentative based on tag checks:
if rel_type == ReleaseType.RC:
@ -200,168 +181,70 @@ if rel_type == ReleaseType.RC:
rc = src_rc # we still have checks to run
elif rel_type == ReleaseType.PATCH:
patch = src_patch
elif branch_release:
# Note: This will NOT keep people from doing something stupid with a remote.
# There's a limited amount of time I want to put into this script to protect
# people from themselves
local_branches = repo.branches
branch_check = 'Release_([0-9]+)[.]([0-9]+)$'
branch_check_re = re.compile(branch_check)
branch_matches = []
branch_matches = [[int(m.group(1)), int(m.group(2))] for branch in local_branches for m in
[branch_check_re.match(branch.name)] if m]
branch_matches = sorted(branch_matches, key=operator.itemgetter(0, 1))
if not branch_matches:
exit(-1)
major_minor_pair = branch_matches[-1]
manual_required = False;
# Make sure we don't have an extant release branch! RCs are OK, others are not.
if rel_type == ReleaseType.MAJOR:
if major_minor_pair[0] >= src_major or src_minor != 0:
manual_required = True
elif rel_type == ReleaseType.MINOR:
if major_minor_pair[0] > src_major or major_minor_pair[1] >= src_minor:
manual_required = True
if manual_required:
print("RELEASE SCRIPT: The highest release branch version, according to tags, is 'Release_" +
str(major_minor_pair[0]) + "." + str(major_minor_pair[1]) + "'.")
print("RELEASE SCRIPT: It is intended that master always has the next intended branch in the source")
print("RELEASE SCRIPT: constants in src/pEpEngine.h for tracking reasons. However, a branch greater or")
print("RELEASE SCRIPT: equal to the source release information (Release_" + str(src_major) + "."
+ str(src_minor) + ")")
print("RELEASE SCRIPT: has already been released. Aborting - fix this manually in pEpEngine.h first.")
exit(-1)
# This should only be 0 for releases (in the src). RC candidates should
# always have a positive RC val > 1 in master in src awaiting the first RC release.
major = src_major
minor = src_minor
rc = 0
patch = 0
else:
exit(-1)
exit("Internal script error. Probably bad cleanup.")
# Unsolicited commentary: I hate that Python doesn't have a switch construct
new_tag = None
#
# 3. If this isn't a new branch, get last release tag for this branch to check we aren't messing things up
# 3. Get last release tag for this branch to check we aren't messing things up
# Then tag and release
#
if not branch_release:
tag_maj = 0
tag_min = 0
tag_patch = 0
tag_rc = 0
tag_maj = 0
tag_min = 0
tag_patch = 0
tag_rc = 0
compile_string = ''
if rel_type == ReleaseType.RC:
major = src_major
minor = src_minor
patch = 0 # Should be anyway, but...
compile_string = ''
if rel_type == ReleaseType.RC:
major = src_major
minor = src_minor
patch = 0 # Should be anyway, but...
compile_string = '^Release_' + str(src_major) + '[.]' + str(src_minor) + '[.]0-RC([0-9])+'
elif rel_type == ReleaseType.PATCH:
compile_string = '^Release_' + str(major) + '[.]' + str(minor) + '[.]([0-9]+)$'
else:
exit(-1)
tag_re = re.compile(compile_string)
tag_refs = TagReference.list_items(repo)
candidates = [int(m.group(1)) for tag in tag_refs for m in [tag_re.search(tag.name)] if m]
# FIXME
if candidates:
candidates.sort(reverse=True)
src_val = 0
tag_val = candidates[0]
if rel_type == ReleaseType.RC:
src_val = src_rc
elif rel_type == ReleaseType.PATCH:
src_val = src_patch
if src_val <= tag_val:
# Somebody done messed up. Sorry, folks, we're not tagging today. (Do better here)
exit(-1)
new_tag = "Release_" + str(major) + "." + str(minor) + "." + str(patch)
if rel_type == ReleaseType.RC:
new_tag += "-RC" + str(rc)
#
# We REALLY want to encourage annotation.
# While culturally, we try not to be about blame, this is really for accountability in the release
# process, something we have trouble with.
#
# Delete it once I'm gone, if you want. I could make annotating mandatory, but of course sometimes
# people need to make quick releases, so I don't want to prevent that.
#
notify_addendum = ""
if not annotation:
notify_addendum = " (if you don't like this, please run script w/ -a option)"
print("\nRELEASE SCRIPT: About to tag release " + new_tag + " on branch '" + start_branch + "'.")
print("RELEASE SCRIPT: This tag will be annotated as follows" + notify_addendum + ":")
if not annotation:
g = Git(repo_path)
username = g.config("user.name")
if not username:
username = g.config("user.email")
if not username:
username = "Anonymous BadGuy" # We could go down to the system level, but that's
# a bit much, no?
annotation = username + " has cowardly failed to provide a release description!"
print("RELEASE_SCRIPT: ***\t" + annotation)
bool_prompt("\nRELEASE SCRIPT: Continue? Y/[N]")
repo.git.tag(new_tag, '-a', '-m', annotation)
if rel_type == ReleaseType.PATCH:
patch = patch + 1
else:
rc = rc + 1
replace_src_versions(grep_lines, major, minor, patch, rc, rel_type == ReleaseType.RC, bumped_comment, repo)
#
# 4. Otherwise, if this is a new branch, we need to branch off, write the source, and commit.
#
compile_string = '^Release_' + str(src_major) + '[.]' + str(src_minor) + '[.]0-RC([0-9])+'
elif rel_type == ReleaseType.PATCH:
compile_string = '^Release_' + str(major) + '[.]' + str(minor) + '[.]([0-9]+)$'
else:
if branch_release:
new_branch_name = "Release_" + str(src_major) + "." + str(src_minor)
print("\nRELEASE SCRIPT: About to create release branch '" + new_branch_name + "'")
bool_prompt("\nRELEASE SCRIPT: Continue? Y/[N]")
exit("Internal script error. Probably bad cleanup.")
repo.git.checkout('-b', new_branch_name)
tag_re = re.compile(compile_string)
tag_refs = TagReference.list_items(repo)
candidates = [int(m.group(1)) for tag in tag_refs for m in [tag_re.search(tag.name)] if m]
tmp_annotate = annotation
if not tmp_annotate:
tmp_annotate = '"Initial release for ' + new_branch_name + '"'
if candidates:
candidates.sort(reverse=True)
repo.git.commit('--allow-empty', '-m', tmp_annotate)
repo.git.tag(new_branch_name + ".0", '-a', '-m', tmp_annotate)
replace_src_versions(grep_lines, major, minor, 1, 0, False, bumped_comment, repo)
repo.git.checkout(start_branch)
if args.next_major:
major = major + 1
minor = 0
else:
minor = minor + 1
src_val = 0
tag_val = candidates[0]
replace_src_versions(grep_lines, major, minor, 0, 1, True, bumped_comment + " (first RC in new cycle)", repo)
exit(0)
if rel_type == ReleaseType.RC:
src_val = src_rc
elif rel_type == ReleaseType.PATCH:
src_val = src_patch
if src_val <= tag_val:
# Somebody done messed up. Sorry, folks, we're not tagging today. (Do better here)
exit("Mismatch between tag values and values in the source. Please release manually.")
new_tag = "Release_" + str(major) + "." + str(minor) + "." + str(patch)
if rel_type == ReleaseType.RC:
new_tag += "-RC" + str(rc)
print("\nRELEASE SCRIPT: About to tag release " + new_tag + " on branch '" + start_branch + "'.")
print("RELEASE SCRIPT: This tag will be annotated as follows:")
print("RELEASE_SCRIPT:\nRELEASE_SCRIPT: \"" + annotation + "\"")
bool_prompt("RELEASE_SCRIPT:\nRELEASE SCRIPT: Continue? Y/[N]")
repo.git.tag(new_tag, '-a', '-m', annotation)
if rel_type == ReleaseType.PATCH:
patch = patch + 1
else:
rc = rc + 1
replace_src_versions(grep_lines, major, minor, patch, rc, rel_type == ReleaseType.RC, bumped_comment, repo,
repo_path + "/src")

View File

@ -1,218 +0,0 @@
import subprocess
import re
import sys
import argparse
parser = argparse.ArgumentParser(description='Automate the RC release process as sanely as possible.')
parser.add_argument('-r','--rev',nargs=1, help="revision number or changeset to tag as next RC")
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('-v','--version',nargs=4, type=int, help="force this version - bypasses version inference. Format: major minor patch RC")
group.add_argument('--rc', type=int, help="force this RC number on the inferred version")
args = parser.parse_args()
# Note: we could get this from the macros, but since folks seem to be actually upgrading
# the release tag manually correctly and not the macro, let's just check both
cmd = ["hg", "log", "-r", ".", "--template", "\"{latesttag(r're:Release_[0-9]+\.[0-9]+\.[0-9]+-RC[0-9]+')}\""]
result = subprocess.run(cmd, capture_output=True)
release_string = result.stdout.decode("utf-8").replace('"','')
#print(release_string)
changeset = args.rev # can be None :)
major = -1
minor = -1
patch = -1
rc = -1
if args.version:
major = args.version[0]
minor = args.version[1]
patch = args.version[2]
rc = args.version[3]
#print(rc)
if (major < 0) or (minor < 0) or (patch < 0) or (rc < 0):
raise Exception("Version numbers must all be positive values.")
elif args.rc:
rc = args.rc
if (rc < 0):
raise Exception("RC numbers must all be positive values.")
#define PEP_ENGINE_VERSION_MAJOR 2
#define PEP_ENGINE_VERSION_MINOR 1
#define PEP_ENGINE_VERSION_PATCH 0
#define PEP_ENGINE_VERSION_RC 13
# Amateur hour. Biteme.
cmd = ["grep", "-E", "#define PEP_ENGINE_VERSION_[A-Z]+[ \t]+[0-9]+", "src/pEpEngine.h"]
result = subprocess.run(cmd, capture_output=True)
grep_output = result.stdout.decode("utf-8")
#print(grep_output)
if not args.version:
src_nums = []
src_nums = re.findall(r'([0-9]+)', grep_output)
if not src_nums:
raise Exception("Somehow, the source values for the engine versions were not found in src/pEpEngine.h. Aborting.")
if len(src_nums) != 4:
raise Exception("Somehow, we could not extract all version numbers from the header file src/pEpEngine.h. Aborting.")
tag_nums = []
if release_string.startswith("Release_"):
tag_nums = re.findall(r'([0-9]+)', release_string)
# for num in tagnums:
# print (num)
if not tag_nums or len(tag_nums) != 4:
if not tag_nums:
print("Wow... there is no extant release tag. What did you do, wipe the repository?")
else:
print("Somehow, there was an error with the numbering of the tag \"" + release_string + "\"")
print("Do you want to continue? We'll make a tag from the source RC info. (Y/N)[enter]")
a = input().lower()
if not (a.startswith("y") or a.startswith("Y")):
sys.exit()
force = False
if len(tag_nums) == 4 and src_nums:
major_tag = int(tag_nums[0])
major_src = int(src_nums[0])
minor_tag = int(tag_nums[1])
minor_src = int(src_nums[1])
patch_tag = int(tag_nums[2])
patch_src = int(src_nums[2])
rc_tag = int(tag_nums[3])
rc_src = int(src_nums[3])
print("Inferring current/next version info for automatic upgrade:")
print("Tagged (should show current): " + str(major_tag) + "." + str(minor_tag) + "." + str(patch_tag) + "." + str(rc_tag))
print("Source (should show *next* (i.e. this upgrade)): " + str(major_src) + "." + str(minor_src) + "." + str(patch_src) + "." + str(rc_src))
if (major_tag == major_src):
major = major_tag
if (minor_tag == minor_src):
minor = minor_tag
if (patch_tag == patch_src):
patch = patch_tag
# Hoorah, we're just changing the RC number.
if (rc < 0):
if (rc_tag == (rc_src - 1)):
# Best case!
rc = rc_src
elif (rc_tag == rc_src):
print("Someone was naughty and didn't bump the RC number in the src, or you made a mistake you want to fix.")
print("Current tagged version is " + str(major) + "." + str(minor) + "." + str(patch) + " RC" + rc_tag + ".")
print("(I)ncrement,(F)orce same version,(A)bort? [enter]")
a = input().lower()
a = lower(a)
if (a.startswith(i)):
rc = rc_tag + 1
elif (a.startswith(f)):
rc = rc_tag
force = True
else:
print("Aborting...")
sys.exit()
else:
print("RC numbers are messed up. The last tagged version is " + str(rc_tag) + ", while the last source version is " + str(rc_src) + ".")
print("Please enter the RC version you want to use, followed by enter:")
a = input().lower()
rc = int(a) # Will raise value error if not a number. User deserves it, frankly.
#Ok, we now have a value. Good.
# This feels extremely suboptimal, but I'm tired and don't care
if (rc < 0):
if (major < 0):
if (major_src == major_tag + 1) and (minor_src == 0) and (patch_src == 0):
major = major_src
minor = 0
patch = 0
else:
print("Tagged release major version and source versions are too different for automatic deduction. Please do this manually.")
sys.exit()
elif (minor < 0):
if (minor_src == minor_tag + 1) and (patch_src == 0):
minor = minor_src
patch = 0
else:
print("Tagged release major version and source versions are too different for automatic deduction. Please do this manually.")
sys.exit()
elif (patch_src == patch_tag + 1):
patch = patch_src
else:
print("Tagged release major version and source versions are too different for automatic deduction. Please do this manually.")
sys.exit()
# if we got this far, it was a version upgrade.
if (rc_src > 0):
print("We detected a version upgrade, but the source indicates the next RC is RC " + str(rc_src))
print("(K)eep,(R)eset to 0,(A)bort? [enter]")
a = input().lower()
if a.startswith("k"):
rc = rc_src
elif a.startswith("r"):
rc = 0
else:
print("Aborting...")
else:
rc = 0
# Ok, so now, after all that, we should have the right version numbers.
# If there's no changeset to tag, we take the latest local default
if not changeset:
cmd = ["hg", "id", "-i", "-r", "default"]
result = subprocess.run(cmd, capture_output=True)
changeset = result.stdout.decode("utf-8").replace('"','').replace('\n','')
if not changeset:
raise Exception("Unable to determine latest default changeset. Aborting.")
rev_tag = "Release_" + str(major) + "." + str(minor) + "." + str(patch) + "-RC" + str(rc)
print("Preparing to tag changeset " + changeset + " with tag " + rev_tag + ".\n\nProceed? (Y/N) [enter]")
a = input().lower()
if not (a.startswith("y")):
sys.exit()
cmd = ["hg", "tag", "-r", changeset, rev_tag]
subprocess.run(cmd, check=True, capture_output=False)
if not grep_output:
print("Information: Not writing version/RC info to src/pEpEngine.h")
sys.exit()
# If successful, then bump the RC
with open('src/pEpEngine.h', 'r') as file :
filedata = file.read()
grep_strs = grep_output.split("\n")
cmd = ["grep", "-E", "#define PEP_ENGINE_VERSION[ \t]+\"[0-9]+.[0-9]+.[0-9]+\"", "src/pEpEngine.h"]
result = subprocess.run(cmd, capture_output=True)
grep_output = result.stdout.decode("utf-8")
#define PEP_ENGINE_VERSION "2.1.0"
version_str = str(major) + "." + str(minor) + "." + str(patch)
filedata = filedata.replace(grep_output, "#define PEP_ENGINE_VERSION \"" + version_str + "\"\n")
filedata = filedata.replace(grep_strs[0], "#define PEP_ENGINE_VERSION_MAJOR " + str(major))
filedata = filedata.replace(grep_strs[1], "#define PEP_ENGINE_VERSION_MINOR " + str(minor))
filedata = filedata.replace(grep_strs[2], "#define PEP_ENGINE_VERSION_PATCH " + str(patch))
filedata = filedata.replace(grep_strs[3], "#define PEP_ENGINE_VERSION_RC " + str(rc + 1))
# Write the file out again
with open('src/pEpEngine.h', 'w') as file:
file.write(filedata)
comment = "Automatically bumped RC in source for future release. Next RC after this one will be " + version_str + "-RC" + str(rc + 1) + " **if released**."
#print("about to run with this comment:")
print(comment)
cmd = ["hg", "commit", "-m", comment]
subprocess.run(cmd, capture_output=False)
print("New engine release: " + rev_tag + " Changeset: " + changeset)