From 30cc01ef13108b3da9d5b18deb52abc6ffbf51c3 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 12:14:46 -0700 Subject: [PATCH 01/29] Start on a make-release.py script; currently just arg parsing and unittests [unittests fail]. --- zcutil/make-release.py | 140 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100755 zcutil/make-release.py diff --git a/zcutil/make-release.py b/zcutil/make-release.py new file mode 100755 index 000000000..f54c5116d --- /dev/null +++ b/zcutil/make-release.py @@ -0,0 +1,140 @@ +#! /usr/bin/env python2 + +import os +import re +import sys +import time +import logging +import argparse +import unittest + + +def main(args=sys.argv[1:]): + """ + Perform the final Zcash release process up to the git tag. + """ + chdir_to_repo() + opts = parse_args(args) + initialize_logging() + logging.debug('argv %r parsed %r', sys.argv, opts) + raise NotImplementedError((main, opts)) + + +def chdir_to_repo(): + dn = os.path.dirname + repodir = dn(dn(os.path.abspath(sys.argv[0]))) + os.chdir(repodir) + + +def initialize_logging(): + TIME_FMT = '%Y-%m-%dT%H:%M:%S' + logname = './zcash-make-release.{}.log'.format(time.strftime(TIME_FMT)) + fmtr = logging.Formatter( + '%(asctime)s L%(lineno)-4d %(levelname)-5s | %(message)s', + TIME_FMT, + ) + + hout = logging.StreamHandler(sys.stdout) + hout.setLevel(logging.INFO) + hout.setFormatter(fmtr) + + hpath = logging.FileHandler(logname, mode='a') + hpath.setLevel(logging.DEBUG) + hpath.setFormatter(fmtr) + + root = logging.getLogger() + root.setLevel(logging.DEBUG) + root.addHandler(hout) + root.addHandler(hpath) + logging.info('zcash make-release.py logging to: %r', logname) + + +def parse_args(args): + p = argparse.ArgumentParser(description=main.__doc__) + p.add_argument( + 'RELEASE_VERSION', + type=Version.parse_arg, + help='The release version: vX.Y.Z-N', + ) + return p.parse_args(args) + + +class Version (object): + '''A release version.''' + + RGX = re.compile( + r'^v([1-9]\d*)\.([1-9]\d*)\.([1-9]\d*)(-([1-9]\d*))?$', + ) + + @staticmethod + def parse_arg(text): + m = Version.RGX.match(text) + if m is None: + raise argparse.ArgumentTypeError( + 'Could not parse version {!r} against regex {}'.format( + text, + Version.RGX.pattern, + ), + ) + else: + [major, minor, patch, _, hotfix] = m.groups() + return Version( + int(major), + int(minor), + int(patch), + int(hotfix) if hotfix is not None else None, + ) + + def __init__(self, major, minor, patch, hotfix): + self.major = major + self.minor = minor + self.patch = patch + self.hotfix = hotfix + + self.vtext = 'v{}.{}.{}'.format(major, minor, patch) + if hotfix is not None: + self.vtext += '-{}'.format(hotfix) + + def __repr__(self): + return ''.format(self.vtext) + + +# Unit Tests +class TestVersion (unittest.TestCase): + def test_arg_parse_and_vtext_identity(self): + cases = [ + 'v0.0.0', + 'v1.0.0', + 'v1.0.0-7', + 'v1.2.3-0', + ] + + for case in cases: + v = Version.parse_arg(case) + self.assertEqual(v.vtext, case) + + def test_arg_parse_negatives(self): + cases = [ + 'v07.0.0', + 'v1.0.03', + 'v1.0.0-rc2', + 'v1.2.3~0', + '1.2.3', + ] + + for case in cases: + self.assertRaises( + argparse.ArgumentTypeError, + Version.parse_arg, + case, + ) + + +if __name__ == '__main__': + if len(sys.argv) >= 2 and sys.argv[1] == 'test': + sys.argv.pop(1) + if len(sys.argv) == 1: + sys.argv.append('--verbose') + unittest.main() + else: + main() From 6e5b990a35ce1e2f5ac43e9b20b93e17884cdb63 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 12:16:13 -0700 Subject: [PATCH 02/29] Update version spec by altering test; also update regex to pass single 0 digits in major/minor/patch. --- zcutil/make-release.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index f54c5116d..8f288ddca 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -63,7 +63,7 @@ class Version (object): '''A release version.''' RGX = re.compile( - r'^v([1-9]\d*)\.([1-9]\d*)\.([1-9]\d*)(-([1-9]\d*))?$', + r'^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-([1-9]\d*))?$', ) @staticmethod @@ -106,7 +106,7 @@ class TestVersion (unittest.TestCase): 'v0.0.0', 'v1.0.0', 'v1.0.0-7', - 'v1.2.3-0', + 'v1.2.3-1', ] for case in cases: @@ -118,6 +118,7 @@ class TestVersion (unittest.TestCase): 'v07.0.0', 'v1.0.03', 'v1.0.0-rc2', + 'v1.2.3-0', # Hotfix numbers must begin w/ 1 'v1.2.3~0', '1.2.3', ] From 987d040660e38b1227409846c737a4019900217e Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 12:17:07 -0700 Subject: [PATCH 03/29] Add another case from debian-style versions. --- zcutil/make-release.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 8f288ddca..0a10c1e99 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -120,6 +120,7 @@ class TestVersion (unittest.TestCase): 'v1.0.0-rc2', 'v1.2.3-0', # Hotfix numbers must begin w/ 1 'v1.2.3~0', + 'v1.2.3+0', '1.2.3', ] From a93f4de2f6b1f1a9f27fd8f6184c6af13fddb83b Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 12:20:14 -0700 Subject: [PATCH 04/29] Add all of the zcash release tags in my current repo as positive test vector. --- zcutil/make-release.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 0a10c1e99..7df62b7a8 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -107,6 +107,24 @@ class TestVersion (unittest.TestCase): 'v1.0.0', 'v1.0.0-7', 'v1.2.3-1', + + # These are taken from: git tag --list | grep '^v1' + 'v1.0.0', + 'v1.0.0-beta1', + 'v1.0.0-beta2', + 'v1.0.0-rc1', + 'v1.0.0-rc2', + 'v1.0.0-rc3', + 'v1.0.0-rc4', + 'v1.0.1', + 'v1.0.2', + 'v1.0.3', + 'v1.0.4', + 'v1.0.5', + 'v1.0.6', + 'v1.0.7-1', + 'v1.0.8', + 'v1.0.8-1', ] for case in cases: From 7f537857357a67a2a7abba75637a42524e931fc9 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 12:24:22 -0700 Subject: [PATCH 05/29] Add support for beta/rc release versions. --- zcutil/make-release.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 7df62b7a8..99cf25c45 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -63,7 +63,7 @@ class Version (object): '''A release version.''' RGX = re.compile( - r'^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-([1-9]\d*))?$', + r'^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(beta|rc)?([1-9]\d*))?$', ) @staticmethod @@ -77,23 +77,33 @@ class Version (object): ), ) else: - [major, minor, patch, _, hotfix] = m.groups() + [major, minor, patch, _, betarc, hotfix] = m.groups() return Version( int(major), int(minor), int(patch), + betarc, int(hotfix) if hotfix is not None else None, ) - def __init__(self, major, minor, patch, hotfix): + def __init__(self, major, minor, patch, betarc, hotfix): + for i in [major, minor, patch]: + assert type(i) is int, i + assert betarc in {None, 'rc', 'beta'}, betarc + assert hotfix is None or type(hotfix) is int, hotfix + self.major = major self.minor = minor self.patch = patch + self.betarc = betarc self.hotfix = hotfix self.vtext = 'v{}.{}.{}'.format(major, minor, patch) if hotfix is not None: - self.vtext += '-{}'.format(hotfix) + self.vtext += '-{}{}'.format( + '' if betarc is None else betarc, + hotfix, + ) def __repr__(self): return ''.format(self.vtext) @@ -135,7 +145,6 @@ class TestVersion (unittest.TestCase): cases = [ 'v07.0.0', 'v1.0.03', - 'v1.0.0-rc2', 'v1.2.3-0', # Hotfix numbers must begin w/ 1 'v1.2.3~0', 'v1.2.3+0', From 3250b3d34e228cfa0df193d15f87a81eb6065ea8 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 13:00:59 -0700 Subject: [PATCH 06/29] Add version sorting, assert that RELEASE_PREV is the most recent release. --- zcutil/make-release.py | 146 +++++++++++++++++++++++++++++++---------- 1 file changed, 111 insertions(+), 35 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 99cf25c45..0f6ae63ce 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -6,7 +6,10 @@ import sys import time import logging import argparse +import subprocess +import traceback import unittest +import random def main(args=sys.argv[1:]): @@ -17,9 +20,61 @@ def main(args=sys.argv[1:]): opts = parse_args(args) initialize_logging() logging.debug('argv %r parsed %r', sys.argv, opts) - raise NotImplementedError((main, opts)) + + try: + main_logged(opts.RELEASE_VERSION, opts.RELEASE_PREV) + except SystemExit as e: + logging.error(str(e)) + raise + except: + logging.error(traceback.format_exc()) + raise +# Top-level flow: +def main_logged(release, releaseprev): + verify_releaseprev_tag(releaseprev) + raise NotImplementedError(main_logged) + + +def parse_args(args): + p = argparse.ArgumentParser(description=main.__doc__) + p.add_argument( + 'RELEASE_VERSION', + type=Version.parse_arg, + help='The release version: vX.Y.Z-N', + ) + p.add_argument( + 'RELEASE_PREV', + type=Version.parse_arg, + help='The previously released version.', + ) + return p.parse_args(args) + + +def verify_releaseprev_tag(releaseprev): + candidates = [] + for tag in sh_out('git', 'tag', '--list').splitlines(): + if tag.startswith('v1'): # Ignore v0.* bitcoin tags and other stuff. + candidates.append(Version.parse_arg(tag)) + + candidates.sort() + try: + latest = candidates[-1] + except IndexError: + raise SystemExit('No previous releases found by `git tag --list`.') + + if releaseprev != latest: + raise SystemExit( + 'The latest candidate in `git tag --list` is {} not {}' + .format( + latest.vtext, + releaseprev.vtext, + ), + ) + + +# Helper code: def chdir_to_repo(): dn = os.path.dirname repodir = dn(dn(os.path.abspath(sys.argv[0]))) @@ -49,14 +104,9 @@ def initialize_logging(): logging.info('zcash make-release.py logging to: %r', logname) -def parse_args(args): - p = argparse.ArgumentParser(description=main.__doc__) - p.add_argument( - 'RELEASE_VERSION', - type=Version.parse_arg, - help='The release version: vX.Y.Z-N', - ) - return p.parse_args(args) +def sh_out(*args): + logging.debug('Run: %r', args) + return subprocess.check_output(args) class Version (object): @@ -91,6 +141,8 @@ class Version (object): assert type(i) is int, i assert betarc in {None, 'rc', 'beta'}, betarc assert hotfix is None or type(hotfix) is int, hotfix + if betarc is not None: + assert hotfix is not None, (betarc, hotfix) self.major = major self.minor = minor @@ -108,36 +160,48 @@ class Version (object): def __repr__(self): return ''.format(self.vtext) + def _sort_tup(self): + if self.hotfix is None: + prio = 2 + else: + prio = {'beta': 0, 'rc': 1, None: 3}[self.betarc] + + return ( + self.major, + self.minor, + self.patch, + prio, + self.hotfix, + ) + + def __cmp__(self, other): + return cmp(self._sort_tup(), other._sort_tup()) + # Unit Tests class TestVersion (unittest.TestCase): + ValidVersions = [ + # These are taken from: git tag --list | grep '^v1' + 'v1.0.0-beta1', + 'v1.0.0-beta2', + 'v1.0.0-rc1', + 'v1.0.0-rc2', + 'v1.0.0-rc3', + 'v1.0.0-rc4', + 'v1.0.0', + 'v1.0.1', + 'v1.0.2', + 'v1.0.3', + 'v1.0.4', + 'v1.0.5', + 'v1.0.6', + 'v1.0.7-1', + 'v1.0.8', + 'v1.0.8-1', + ] + def test_arg_parse_and_vtext_identity(self): - cases = [ - 'v0.0.0', - 'v1.0.0', - 'v1.0.0-7', - 'v1.2.3-1', - - # These are taken from: git tag --list | grep '^v1' - 'v1.0.0', - 'v1.0.0-beta1', - 'v1.0.0-beta2', - 'v1.0.0-rc1', - 'v1.0.0-rc2', - 'v1.0.0-rc3', - 'v1.0.0-rc4', - 'v1.0.1', - 'v1.0.2', - 'v1.0.3', - 'v1.0.4', - 'v1.0.5', - 'v1.0.6', - 'v1.0.7-1', - 'v1.0.8', - 'v1.0.8-1', - ] - - for case in cases: + for case in self.ValidVersions: v = Version.parse_arg(case) self.assertEqual(v.vtext, case) @@ -158,6 +222,18 @@ class TestVersion (unittest.TestCase): case, ) + def test_version_sort(self): + expected = [Version.parse_arg(v) for v in self.ValidVersions] + + rng = random.Random() + rng.seed(0) + + for _ in range(1024): + vec = list(expected) + rng.shuffle(vec) + vec.sort() + self.assertEqual(vec, expected) + if __name__ == '__main__': if len(sys.argv) >= 2 and sys.argv[1] == 'test': From b9b7f25fb9b070554aafbcfb8b529d19bf233dcb Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 13:05:55 -0700 Subject: [PATCH 07/29] Make SystemExit errors less redundant in output; verify clean git status on master. --- zcutil/make-release.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 0f6ae63ce..b44c0e19a 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -25,7 +25,7 @@ def main(args=sys.argv[1:]): main_logged(opts.RELEASE_VERSION, opts.RELEASE_PREV) except SystemExit as e: logging.error(str(e)) - raise + raise SystemExit(1) except: logging.error(traceback.format_exc()) raise @@ -34,6 +34,7 @@ def main(args=sys.argv[1:]): # Top-level flow: def main_logged(release, releaseprev): verify_releaseprev_tag(releaseprev) + verify_git_clean_master() raise NotImplementedError(main_logged) @@ -74,6 +75,20 @@ def verify_releaseprev_tag(releaseprev): ) +def verify_git_clean_master(): + junk = sh_out('git', 'status', '--porcelain') + if junk.strip(): + raise SystemExit('There are uncommitted changes:\n' + junk) + + branch = sh_out('git', 'rev-parse', '--abbrev-ref', 'HEAD').strip() + if branch != 'master': + raise SystemExit( + "Expected branch 'master', found branch {!r}".format( + branch, + ), + ) + + # Helper code: def chdir_to_repo(): dn = os.path.dirname From 1ecb494896d0033acbbe1d25f23c98f1ad69b67b Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 13:08:26 -0700 Subject: [PATCH 08/29] Always run unittests prior to actual runs. --- zcutil/make-release.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index b44c0e19a..01d492bcd 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -251,10 +251,16 @@ class TestVersion (unittest.TestCase): if __name__ == '__main__': - if len(sys.argv) >= 2 and sys.argv[1] == 'test': - sys.argv.pop(1) - if len(sys.argv) == 1: - sys.argv.append('--verbose') + actualargs = sys.argv + sys.argv = [sys.argv[0], '--verbose'] + + print '=== Self Test ===' + try: unittest.main() - else: - main() + except SystemExit as e: + if e.args[0] != 0: + raise + + sys.argv = actualargs + print '=== Running ===' + main() From b43194baee708eaf6733b871c6615910d860a224 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 13:09:24 -0700 Subject: [PATCH 09/29] Make --help output clean by not running self-test. --- zcutil/make-release.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 01d492bcd..323a3dbef 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -251,16 +251,19 @@ class TestVersion (unittest.TestCase): if __name__ == '__main__': - actualargs = sys.argv - sys.argv = [sys.argv[0], '--verbose'] + if len(sys.argv) == 2 and sys.argv[1] == '--help': + main() + else: + actualargs = sys.argv + sys.argv = [sys.argv[0], '--verbose'] - print '=== Self Test ===' - try: - unittest.main() - except SystemExit as e: - if e.args[0] != 0: - raise + print '=== Self Test ===' + try: + unittest.main() + except SystemExit as e: + if e.args[0] != 0: + raise - sys.argv = actualargs - print '=== Running ===' - main() + sys.argv = actualargs + print '=== Running ===' + main() From ea5abe2ca76c6eef3ff74837617c74b59aec46ba Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 15:15:59 -0700 Subject: [PATCH 10/29] Add an option to run against a different repo directory. --- zcutil/make-release.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 323a3dbef..14c9b959e 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -16,8 +16,8 @@ def main(args=sys.argv[1:]): """ Perform the final Zcash release process up to the git tag. """ - chdir_to_repo() opts = parse_args(args) + chdir_to_repo(opts.REPO) initialize_logging() logging.debug('argv %r parsed %r', sys.argv, opts) @@ -40,6 +40,12 @@ def main_logged(release, releaseprev): def parse_args(args): p = argparse.ArgumentParser(description=main.__doc__) + p.add_argument( + '--repo', + dest='REPO', + type=str, + help='Path to repository root.', + ) p.add_argument( 'RELEASE_VERSION', type=Version.parse_arg, @@ -90,10 +96,11 @@ def verify_git_clean_master(): # Helper code: -def chdir_to_repo(): - dn = os.path.dirname - repodir = dn(dn(os.path.abspath(sys.argv[0]))) - os.chdir(repodir) +def chdir_to_repo(repo): + if repo is None: + dn = os.path.dirname + repo = dn(dn(os.path.abspath(sys.argv[0]))) + os.chdir(repo) def initialize_logging(): From fad06b020be27542749752c539f84dba525a428b Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 15:25:39 -0700 Subject: [PATCH 11/29] Make sure to pull the latest master. --- zcutil/make-release.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 14c9b959e..449d4d557 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -94,6 +94,9 @@ def verify_git_clean_master(): ), ) + out = sh_out('git', 'pull', '--ff-only') + logging.debug('+ git pull --ff-only\n%s', out) + # Helper code: def chdir_to_repo(repo): From 2d8d493ff9b1f52d11366aaac6b542e13d007fc0 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 15:27:07 -0700 Subject: [PATCH 12/29] Exit instead of raising an unexpected exception, since it's already logged. --- zcutil/make-release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 449d4d557..e3c81fe80 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -28,7 +28,7 @@ def main(args=sys.argv[1:]): raise SystemExit(1) except: logging.error(traceback.format_exc()) - raise + raise SystemExit(2) # Top-level flow: From 68ab915a24e1b5e78337f462696c049cebfaa9dc Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 16:32:09 -0700 Subject: [PATCH 13/29] Implement `PathPatcher` abstraction, `clientversion.h` rewrite, and build numbering w/ unittests. --- zcutil/make-release.py | 141 +++++++++++++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 27 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index e3c81fe80..78bc8e82e 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -10,6 +10,7 @@ import subprocess import traceback import unittest import random +from cStringIO import StringIO def main(args=sys.argv[1:]): @@ -34,7 +35,9 @@ def main(args=sys.argv[1:]): # Top-level flow: def main_logged(release, releaseprev): verify_releaseprev_tag(releaseprev) - verify_git_clean_master() + initialize_git(release) + patch_version_in_files(release, releaseprev) + raise NotImplementedError(main_logged) @@ -81,7 +84,7 @@ def verify_releaseprev_tag(releaseprev): ) -def verify_git_clean_master(): +def initialize_git(release): junk = sh_out('git', 'status', '--porcelain') if junk.strip(): raise SystemExit('There are uncommitted changes:\n' + junk) @@ -94,8 +97,18 @@ def verify_git_clean_master(): ), ) - out = sh_out('git', 'pull', '--ff-only') - logging.debug('+ git pull --ff-only\n%s', out) + logging.info('Pulling to latest master.') + sh_out_logged('git', 'pull', '--ff-only') + + branch = 'release-' + release.vtext + logging.info('Creating release branch: %r', branch) + sh_out_logged('git', 'checkout', '-b', branch) + return branch + + +def patch_version_in_files(release, releaseprev): + patch_README(release, releaseprev) + patch_clientversion_h(release, releaseprev) # Helper code: @@ -106,6 +119,36 @@ def chdir_to_repo(repo): os.chdir(repo) +def patch_README(release, releaseprev): + with PathPatcher('README.md') as (inf, outf): + firstline = inf.readline() + assert firstline == 'Zcash {}\n'.format(releaseprev.novtext), \ + repr(firstline) + + outf.write('Zcash {}\n'.format(release.novtext)) + outf.write(inf.read()) + + +def patch_clientversion_h(release, releaseprev): + rgx = re.compile( + r'^(#define CLIENT_VERSION_(MAJOR|MINOR|REVISION|BUILD)) \d+$' + ) + with PathPatcher('src/clientversion.h') as (inf, outf): + for line in inf: + m = rgx.match(line) + if m: + prefix, label = m.groups() + repl = { + 'MAJOR': release.major, + 'MINOR': release.minor, + 'REVISION': release.patch, + 'BUILD': release.build, + }[label] + outf.write('{} {}\n'.format(prefix, repl)) + else: + outf.write(line) + + def initialize_logging(): TIME_FMT = '%Y-%m-%dT%H:%M:%S' logname = './zcash-make-release.{}.log'.format(time.strftime(TIME_FMT)) @@ -134,6 +177,12 @@ def sh_out(*args): return subprocess.check_output(args) +def sh_out_logged(*args): + out = sh_out(*args) + logging.debug('Output:\n%s', out) + return out + + class Version (object): '''A release version.''' @@ -175,12 +224,22 @@ class Version (object): self.betarc = betarc self.hotfix = hotfix - self.vtext = 'v{}.{}.{}'.format(major, minor, patch) - if hotfix is not None: - self.vtext += '-{}{}'.format( - '' if betarc is None else betarc, - hotfix, - ) + self.novtext = '{}.{}.{}'.format(major, minor, patch) + + if hotfix is None: + self.build = 50 + else: + assert hotfix > 0, hotfix + if betarc is None: + assert hotfix < 50, hotfix + self.build = 50 + hotfix + self.novtext += '-{}'.format(hotfix) + else: + assert hotfix < 26, hotfix + self.novtext += '-{}{}'.format(betarc, hotfix) + self.build = {'beta': 0, 'rc': 25}[betarc] + hotfix - 1 + + self.vtext = 'v' + self.novtext def __repr__(self): return ''.format(self.vtext) @@ -203,26 +262,49 @@ class Version (object): return cmp(self._sort_tup(), other._sort_tup()) +class PathPatcher (object): + def __init__(self, path): + self._path = path + + def __enter__(self): + logging.info('Patching %r', self._path) + self._inf = file(self._path, 'r') + self._outf = StringIO() + return (self._inf, self._outf) + + def __exit__(self, et, ev, tb): + if (et, ev, tb) == (None, None, None): + self._inf.close() + with file(self._path, 'w') as f: + f.write(self._outf.getvalue()) + + # Unit Tests class TestVersion (unittest.TestCase): - ValidVersions = [ + ValidVersionsAndBuilds = [ # These are taken from: git tag --list | grep '^v1' - 'v1.0.0-beta1', - 'v1.0.0-beta2', - 'v1.0.0-rc1', - 'v1.0.0-rc2', - 'v1.0.0-rc3', - 'v1.0.0-rc4', - 'v1.0.0', - 'v1.0.1', - 'v1.0.2', - 'v1.0.3', - 'v1.0.4', - 'v1.0.5', - 'v1.0.6', - 'v1.0.7-1', - 'v1.0.8', - 'v1.0.8-1', + ('v1.0.0-beta1', 0), + ('v1.0.0-beta2', 1), + ('v1.0.0-rc1', 25), + ('v1.0.0-rc2', 26), + ('v1.0.0-rc3', 27), + ('v1.0.0-rc4', 28), + ('v1.0.0', 50), + ('v1.0.1', 50), + ('v1.0.2', 50), + ('v1.0.3', 50), + ('v1.0.4', 50), + ('v1.0.5', 50), + ('v1.0.6', 50), + ('v1.0.7-1', 51), + ('v1.0.8', 50), + ('v1.0.8-1', 51), + ] + + ValidVersions = [ + v + for (v, _) + in ValidVersionsAndBuilds ] def test_arg_parse_and_vtext_identity(self): @@ -259,6 +341,11 @@ class TestVersion (unittest.TestCase): vec.sort() self.assertEqual(vec, expected) + def test_build_nums(self): + for (text, expected) in self.ValidVersionsAndBuilds: + version = Version.parse_arg(text) + self.assertEqual(version.build, expected) + if __name__ == '__main__': if len(sys.argv) == 2 and sys.argv[1] == '--help': From 482291582e755596c2b2051da041ad5397de0341 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 16:35:44 -0700 Subject: [PATCH 14/29] Implement the IS_RELEASE rule for betas. --- zcutil/make-release.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 78bc8e82e..b0d578798 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -131,7 +131,8 @@ def patch_README(release, releaseprev): def patch_clientversion_h(release, releaseprev): rgx = re.compile( - r'^(#define CLIENT_VERSION_(MAJOR|MINOR|REVISION|BUILD)) \d+$' + r'^(#define CLIENT_VERSION_(MAJOR|MINOR|REVISION|BUILD|IS_RELEASE))' + r' \d+$' ) with PathPatcher('src/clientversion.h') as (inf, outf): for line in inf: @@ -143,6 +144,9 @@ def patch_clientversion_h(release, releaseprev): 'MINOR': release.minor, 'REVISION': release.patch, 'BUILD': release.build, + 'IS_RELEASE': ( + 'false' if release.betarc == 'beta' else 'true' + ), }[label] outf.write('{} {}\n'.format(prefix, repl)) else: From ab55424763abfd8bbb19558d62c924854c9cf4d4 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 16:45:01 -0700 Subject: [PATCH 15/29] Generalize buildnum patching for both `clientversion.h` and `configure.ac`. --- zcutil/make-release.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index b0d578798..fb397c466 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -108,7 +108,8 @@ def initialize_git(release): def patch_version_in_files(release, releaseprev): patch_README(release, releaseprev) - patch_clientversion_h(release, releaseprev) + patch_clientversion_h(release) + patch_configure_ac(release) # Helper code: @@ -129,16 +130,31 @@ def patch_README(release, releaseprev): outf.write(inf.read()) -def patch_clientversion_h(release, releaseprev): - rgx = re.compile( - r'^(#define CLIENT_VERSION_(MAJOR|MINOR|REVISION|BUILD|IS_RELEASE))' - r' \d+$' +def patch_clientversion_h(release): + _patch_build_defs( + release, + 'src/clientversion.h', + (r'^(#define CLIENT_VERSION_(MAJOR|MINOR|REVISION|BUILD|IS_RELEASE))' + r' \d+()$'), ) - with PathPatcher('src/clientversion.h') as (inf, outf): + + +def patch_configure_ac(release): + _patch_build_defs( + release, + 'configure.ac', + (r'^(define\(_CLIENT_VERSION_(MAJOR|MINOR|REVISION|BUILD|IS_RELEASE),)' + r' \d+(\))$'), + ) + + +def _patch_build_defs(release, path, pattern): + rgx = re.compile(pattern) + with PathPatcher(path) as (inf, outf): for line in inf: m = rgx.match(line) if m: - prefix, label = m.groups() + prefix, label, suffix = m.groups() repl = { 'MAJOR': release.major, 'MINOR': release.minor, @@ -148,7 +164,7 @@ def patch_clientversion_h(release, releaseprev): 'false' if release.betarc == 'beta' else 'true' ), }[label] - outf.write('{} {}\n'.format(prefix, repl)) + outf.write('{} {}{}\n'.format(prefix, repl, suffix)) else: outf.write(line) From 4dbb6c0b1dda4971564720f7a386ea0a7f7c9a6d Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 17:09:37 -0700 Subject: [PATCH 16/29] Modify the `APPROX_RELEASE_HEIGHT`. --- zcutil/make-release.py | 49 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index fb397c466..a94e26f80 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -23,7 +23,11 @@ def main(args=sys.argv[1:]): logging.debug('argv %r parsed %r', sys.argv, opts) try: - main_logged(opts.RELEASE_VERSION, opts.RELEASE_PREV) + main_logged( + opts.RELEASE_VERSION, + opts.RELEASE_PREV, + opts.RELEASE_HEIGHT, + ) except SystemExit as e: logging.error(str(e)) raise SystemExit(1) @@ -33,10 +37,11 @@ def main(args=sys.argv[1:]): # Top-level flow: -def main_logged(release, releaseprev): +def main_logged(release, releaseprev, releaseheight): verify_releaseprev_tag(releaseprev) initialize_git(release) patch_version_in_files(release, releaseprev) + patch_release_height(releaseheight) raise NotImplementedError(main_logged) @@ -59,6 +64,11 @@ def parse_args(args): type=Version.parse_arg, help='The previously released version.', ) + p.add_argument( + 'RELEASE_HEIGHT', + type=int, + help='A block height approximately occuring on release day.', + ) return p.parse_args(args) @@ -110,6 +120,27 @@ def patch_version_in_files(release, releaseprev): patch_README(release, releaseprev) patch_clientversion_h(release) patch_configure_ac(release) + patch_gitian_linux_yml(release, releaseprev) + + +def patch_release_height(releaseheight): + rgx = re.compile( + r'^(static const int APPROX_RELEASE_HEIGHT = )\d+(;)$', + ) + with PathPatcher('src/deprecation.h') as (inf, outf): + for line in inf: + m = rgx.match(line) + if m is None: + outf.write(line) + else: + [prefix, suffix] = m.groups() + outf.write( + '{}{}{}\n'.format( + prefix, + releaseheight, + suffix, + ), + ) # Helper code: @@ -148,6 +179,20 @@ def patch_configure_ac(release): ) +def patch_gitian_linux_yml(release, releaseprev): + path = 'contrib/gitian-descriptors/gitian-linux.yml' + with PathPatcher(path) as (inf, outf): + outf.write(inf.readline()) + + secondline = inf.readline() + assert secondline == 'name: "zcash-{}"\n'.format( + releaseprev.novtext + ), repr(secondline) + + outf.write('name: "zcash-{}"\n'.format(release.novtext)) + outf.write(inf.read()) + + def _patch_build_defs(release, path, pattern): rgx = re.compile(pattern) with PathPatcher(path) as (inf, outf): From cf97f669874872dc2edf005ab8aaad88aef701f7 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 17:16:59 -0700 Subject: [PATCH 17/29] Remove portions of `./doc/release-process.md` now implemented in `make-release.py`. --- doc/release-process.md | 51 ++++++------------------------------------ 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/doc/release-process.md b/doc/release-process.md index 45dd3af22..f16325984 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -16,57 +16,20 @@ The following should have been checked well in advance of the release: - miniupnpc - OpenSSL +If this release changes the behavior of the protocol or fixes a serious +bug, verify that a pre-release PR merge updated `PROTOCOL_VERSION` in +`version.h` correctly. + ## Release process -## A. Define the release version as: +Run the release script: - $ ZCASH_RELEASE=MAJOR.MINOR.REVISION(-BUILD_STRING) + $ ./zcutil/make-release.py Example: - $ ZCASH_RELEASE=1.0.0-beta2 - -Also, the following commands use the `ZCASH_RELEASE_PREV` bash variable for the -previous release: - - $ ZCASH_RELEASE_PREV=1.0.0-beta1 - -## B. Create a new release branch / github PR - -### B1. Check that you are up-to-date with current master, then create a release branch. - -### B2. Update (commit) version and deprecation in sources. - -Update the client version in these files: - - README.md - src/clientversion.h - configure.ac - contrib/gitian-descriptors/gitian-linux.yml - -In `configure.ac` and `clientversion.h`: - -- Increment `CLIENT_VERSION_BUILD` according to the following schema: - - - 0-24: `1.0.0-beta1`-`1.0.0-beta25` - - 25-49: `1.0.0-rc1`-`1.0.0-rc25` - - 50: `1.0.0` - - 51-99: `1.0.0-1`-`1.0.0-49` - - (`CLIENT_VERSION_REVISION` rolls over) - - 0-24: `1.0.1-beta1`-`1.0.1-beta25` - -- Change `CLIENT_VERSION_IS_RELEASE` to false while Zcash is in beta-test phase. - -Update `APPROX_RELEASE_HEIGHT` and `WEEKS_UNTIL_DEPRECATION` in `src/deprecation.h` -so that `APPROX_RELEASE_HEIGHT` will be reached shortly after release, and -`WEEKS_UNTIL_DEPRECATION` is the number of weeks from release day until the -deprecation target (as defined by the current deprecation policy). - -If this release changes the behavior of the protocol or fixes a serious bug, we may -also wish to change the `PROTOCOL_VERSION` in `version.h`. - -Commit these changes. (Be sure to do this before building, or else the built binary will include the flag `-dirty`) + $ ./zcutil/make-release.py v1.0.8-1 v1.0.9 Build by running `./zcutil/build.sh`. From 14b2a420d32d96cb40929d52050110366e6acc75 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 17:35:20 -0700 Subject: [PATCH 18/29] Switch from `sh_out_logged` to `sh_log`. --- zcutil/make-release.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index a94e26f80..7b09c31ed 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -108,11 +108,11 @@ def initialize_git(release): ) logging.info('Pulling to latest master.') - sh_out_logged('git', 'pull', '--ff-only') + sh_log('git', 'pull', '--ff-only') branch = 'release-' + release.vtext logging.info('Creating release branch: %r', branch) - sh_out_logged('git', 'checkout', '-b', branch) + sh_log('git', 'checkout', '-b', branch) return branch @@ -238,14 +238,19 @@ def initialize_logging(): def sh_out(*args): - logging.debug('Run: %r', args) + logging.debug('Run (out): %r', args) return subprocess.check_output(args) -def sh_out_logged(*args): - out = sh_out(*args) - logging.debug('Output:\n%s', out) - return out +def sh_log(*args): + logging.debug('Run (log): %r', args) + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + logging.debug('PID: %r', p.pid) + for line in p.stdout: + logging.debug('> %s', line.rstrip()) + status = p.wait() + if status != 0: + raise SystemExit('Nonzero exit status: {!r}'.format(status)) class Version (object): From f6b9ffefb7d2204cb908e71c92e87ef9c8cbcf27 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 17:47:21 -0700 Subject: [PATCH 19/29] Shorten the arg log line. --- zcutil/make-release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 7b09c31ed..0b4d222c1 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -20,7 +20,7 @@ def main(args=sys.argv[1:]): opts = parse_args(args) chdir_to_repo(opts.REPO) initialize_logging() - logging.debug('argv %r parsed %r', sys.argv, opts) + logging.debug('argv %r', sys.argv) try: main_logged( From 785244020a66ad19aedbd5314dbb0b24a00cdf60 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 17:47:34 -0700 Subject: [PATCH 20/29] Commit the version changes and build. --- zcutil/make-release.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 0b4d222c1..9303fdb8d 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -43,6 +43,15 @@ def main_logged(release, releaseprev, releaseheight): patch_version_in_files(release, releaseprev) patch_release_height(releaseheight) + logging.info('Committing version changes.') + sh_log( + 'git', + 'commit', + '--all', + '-m', 'make-release.py versioning changes.', + ) + + build() raise NotImplementedError(main_logged) @@ -143,6 +152,12 @@ def patch_release_height(releaseheight): ) +def build(): + logging.info('Building...') + nproc = sh_out('nproc').strip() + sh_log('./zcutil/build.sh', '-j', nproc) + + # Helper code: def chdir_to_repo(repo): if repo is None: From c62edf9471472e8546281ef7a72b701c755ac4a4 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 18:03:28 -0700 Subject: [PATCH 21/29] Generate manpages; commit that; improve error output in sh_log. --- doc/release-process.md | 8 -------- zcutil/make-release.py | 33 ++++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/doc/release-process.md b/doc/release-process.md index f16325984..d681040f7 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -31,14 +31,6 @@ Example: $ ./zcutil/make-release.py v1.0.8-1 v1.0.9 -Build by running `./zcutil/build.sh`. - -Then perform the following command: - - $ bash contrib/devtools/gen-manpages.sh - -Commit the changes. - ### B3. Generate release notes Run the release-notes.py script to generate release notes and update authors.md file. For example: diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 9303fdb8d..97661c1b5 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -42,16 +42,11 @@ def main_logged(release, releaseprev, releaseheight): initialize_git(release) patch_version_in_files(release, releaseprev) patch_release_height(releaseheight) - - logging.info('Committing version changes.') - sh_log( - 'git', - 'commit', - '--all', - '-m', 'make-release.py versioning changes.', - ) + commit('Versioning changes.') build() + gen_manpages() + commit('Updated manpages.') raise NotImplementedError(main_logged) @@ -158,7 +153,18 @@ def build(): sh_log('./zcutil/build.sh', '-j', nproc) +def gen_manpages(): + logging.info('Generating manpages.') + sh_log('./contrib/devtools/gen-manpages.sh') + + # Helper code: +def commit(message): + logging.info('Committing: %r', message) + fullmsg = 'make-release.py: {}'.format(message) + sh_log('git', 'commit', '--all', '-m', fullmsg) + + def chdir_to_repo(repo): if repo is None: dn = os.path.dirname @@ -258,9 +264,14 @@ def sh_out(*args): def sh_log(*args): - logging.debug('Run (log): %r', args) - p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - logging.debug('PID: %r', p.pid) + PIPE = subprocess.PIPE + try: + p = subprocess.Popen(args, stdout=PIPE, stderr=PIPE) + except OSError: + logging.error('Error launching %r...', args) + raise + + logging.debug('Run (log PID %r): %r', p.pid, args) for line in p.stdout: logging.debug('> %s', line.rstrip()) status = p.wait() From 08d66a9c01c9f144f1822a052553477bdff6af65 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 18:09:35 -0700 Subject: [PATCH 22/29] Polish logging a bit more. --- zcutil/make-release.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 97661c1b5..b91d83916 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -3,7 +3,6 @@ import os import re import sys -import time import logging import argparse import subprocess @@ -236,11 +235,10 @@ def _patch_build_defs(release, path, pattern): def initialize_logging(): - TIME_FMT = '%Y-%m-%dT%H:%M:%S' - logname = './zcash-make-release.{}.log'.format(time.strftime(TIME_FMT)) + logname = './zcash-make-release.log' fmtr = logging.Formatter( '%(asctime)s L%(lineno)-4d %(levelname)-5s | %(message)s', - TIME_FMT, + '%Y-%m-%d %H:%M:%S' ) hout = logging.StreamHandler(sys.stdout) @@ -255,7 +253,7 @@ def initialize_logging(): root.setLevel(logging.DEBUG) root.addHandler(hout) root.addHandler(hpath) - logging.info('zcash make-release.py logging to: %r', logname) + logging.info('zcash make-release.py debug log: %r', logname) def sh_out(*args): From c66c5ab21f25f2c76130694df523040e6b203659 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Fri, 19 May 2017 18:20:00 -0700 Subject: [PATCH 23/29] Tidy up / systematize logging output a bit more. --- zcutil/make-release.py | 50 ++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index b91d83916..40151b70c 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -10,6 +10,7 @@ import traceback import unittest import random from cStringIO import StringIO +from functools import wraps def main(args=sys.argv[1:]): @@ -35,20 +36,6 @@ def main(args=sys.argv[1:]): raise SystemExit(2) -# Top-level flow: -def main_logged(release, releaseprev, releaseheight): - verify_releaseprev_tag(releaseprev) - initialize_git(release) - patch_version_in_files(release, releaseprev) - patch_release_height(releaseheight) - commit('Versioning changes.') - - build() - gen_manpages() - commit('Updated manpages.') - raise NotImplementedError(main_logged) - - def parse_args(args): p = argparse.ArgumentParser(description=main.__doc__) p.add_argument( @@ -75,6 +62,32 @@ def parse_args(args): return p.parse_args(args) +# Top-level flow: +def main_logged(release, releaseprev, releaseheight): + verify_releaseprev_tag(releaseprev) + initialize_git(release) + patch_version_in_files(release, releaseprev) + patch_release_height(releaseheight) + commit('Versioning changes.') + + build() + gen_manpages() + commit('Updated manpages.') + + raise NotImplementedError(main_logged) + + +def phase(message): + def deco(f): + @wraps(f) + def g(*a, **kw): + logging.info('%s', message) + return f(*a, **kw) + return g + return deco + + +@phase('Checking RELEASE_PREV tag.') def verify_releaseprev_tag(releaseprev): candidates = [] for tag in sh_out('git', 'tag', '--list').splitlines(): @@ -97,6 +110,7 @@ def verify_releaseprev_tag(releaseprev): ) +@phase('Initializing git.') def initialize_git(release): junk = sh_out('git', 'status', '--porcelain') if junk.strip(): @@ -119,6 +133,7 @@ def initialize_git(release): return branch +@phase('Patching versioning in files.') def patch_version_in_files(release, releaseprev): patch_README(release, releaseprev) patch_clientversion_h(release) @@ -126,6 +141,7 @@ def patch_version_in_files(release, releaseprev): patch_gitian_linux_yml(release, releaseprev) +@phase('Patching release height for auto-senescence.') def patch_release_height(releaseheight): rgx = re.compile( r'^(static const int APPROX_RELEASE_HEIGHT = )\d+(;)$', @@ -146,14 +162,14 @@ def patch_release_height(releaseheight): ) +@phase('Building...') def build(): - logging.info('Building...') nproc = sh_out('nproc').strip() sh_log('./zcutil/build.sh', '-j', nproc) +@phase('Generating manpages.') def gen_manpages(): - logging.info('Generating manpages.') sh_log('./contrib/devtools/gen-manpages.sh') @@ -361,7 +377,7 @@ class PathPatcher (object): self._path = path def __enter__(self): - logging.info('Patching %r', self._path) + logging.debug('Patching %r', self._path) self._inf = file(self._path, 'r') self._outf = StringIO() return (self._inf, self._outf) From 0df82709b45b973b79d4e9e9b08d31c988506921 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Mon, 22 May 2017 12:34:00 -0700 Subject: [PATCH 24/29] First full-release-branch version of script; rewrite large swatch of release-process.md. [Manually tested.] --- doc/release-process.md | 137 +++++++++++++++++++--------------------- zcutil/make-release.py | 67 ++++++++++++++++++-- zcutil/release-notes.py | 0 3 files changed, 125 insertions(+), 79 deletions(-) mode change 100644 => 100755 zcutil/release-notes.py diff --git a/doc/release-process.md b/doc/release-process.md index d681040f7..8299bf6e4 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -4,26 +4,45 @@ Meta: There should always be a single release engineer to disambiguate responsib ## Pre-release -The following should have been checked well in advance of the release: +### Github Milestone -- All dependencies have been updated as appropriate: - - BDB - - Boost - - ccache - - libgmp - - libsnark (upstream of our fork) - - libsodium - - miniupnpc - - OpenSSL +Ensure all goals for the github milestone are met. If not, remove tickets +or PRs with a comment as to why it is not included. (Running out of time +is a common reason.) + +### Pre-release checklist: + +Check that dependencies are properly hosted by looking at the `check-depends` builder: + + https://ci.z.cash/#/builders/1 + +Check that there are no surprising performance regressions: + + https://speed.z.cash + +Ensure that new performance metrics appear on that site. + +### Protocol Safety Checks: If this release changes the behavior of the protocol or fixes a serious bug, verify that a pre-release PR merge updated `PROTOCOL_VERSION` in `version.h` correctly. +If this release breaks backwards compatibility or needs to prevent +interaction with software forked projects, change the network magic +numbers. Set the four `pchMessageStart` in `CTestNetParams` in +`chainparams.cpp` to random values. + +Both of these should be done in standard PRs ahead of the release +process. If these were not anticipated correctly, this could block the +release, so if you suspect this is necessary, double check with the +whole engineering team. ## Release process -Run the release script: +Run the release script, which will verify you are on the latest clean +checkout of master, create a branch, then commit standard automated +changes to that branch locally: $ ./zcutil/make-release.py @@ -31,58 +50,50 @@ Example: $ ./zcutil/make-release.py v1.0.8-1 v1.0.9 -### B3. Generate release notes +### Create, Review, and Merge the release branch pull request -Run the release-notes.py script to generate release notes and update authors.md file. For example: +Review the automated changes in git: - $ python zcutil/release-notes.py --version $ZCASH_RELEASE + $ git log master..HEAD -Add the newly created release notes to the Git repository: +Push the resulting branch to github: - $ git add ./doc/authors.md ./doc/release-notes/release-notes-$ZCASH_RELEASE.md + $ git push 'git@github.com:$YOUR_GITHUB_NAME/zcash' $(git rev-parse --abbrev-ref HEAD) -Update the Debian package changelog: +Then create the PR on github. Complete the standard review process, +then merge, then wait for CI to complete. - export DEBVERSION=$(echo $ZCASH_RELEASE | sed 's/-beta/~beta/' | sed 's/-rc/~rc/' | sed 's/-/+/') - export DEBEMAIL="${DEBEMAIL:-team@z.cash}" - export DEBFULLNAME="${DEBFULLNAME:-Zcash Company}" - - dch -v $DEBVERSION -D jessie -c contrib/debian/changelog - -(`dch` comes from the devscripts package.) - -### B4. Change the network magics - -If this release breaks backwards compatibility, change the network magic -numbers. Set the four `pchMessageStart` in `CTestNetParams` in `chainparams.cpp` -to random values. - -### B5. Merge the previous changes - -Do the normal pull-request, review, testing process for this release PR. - -## C. Verify code artifact hosting - -### C1. Ensure depends tree is working - -https://ci.z.cash/builders/depends-sources - -### C2. Ensure public parameters work - -Run `./fetch-params.sh`. - -## D. Make tag for the newly merged result +## Make tag for the newly merged result Checkout master and pull the latest version to ensure master is up to date with the release PR which was merged in before. -Check the last commit on the local and remote versions of master to make sure they are the same. + $ git checkout master + $ git pull --ff-only -Then create the git tag: +Check the last commit on the local and remote versions of master to make sure they are the same: - $ git tag -s v${ZCASH_RELEASE} - $ git push origin v${ZCASH_RELEASE} + $ git log -1 -## E. Deploy testnet +The output should include something like, which is created by Homu: + + Auto merge of #4242 - nathan-at-least:release-v1.0.9, r=nathan-at-least + +Then create the git tag. The `-s` means the release tag will be +signed. **CAUTION:** Remember the `v` at the beginning here: + + $ git tag -s v1.0.9 + $ git push origin v1.0.9 + +## Make and deploy deterministic builds + +- Run the [Gitian deterministic build environment](https://github.com/zcash/zcash-gitian) +- Compare the uploaded [build manifests on gitian.sigs](https://github.com/zcash/gitian.sigs) +- If all is well, the DevOps engineer will build the Debian packages and update the + [apt.z.cash package repository](https://apt.z.cash). + +## Post Release Task List + +### Deploy testnet Notify the Zcash DevOps engineer/sysadmin that the release has been tagged. They update some variables in the company's automation code and then run an Ansible playbook, which: @@ -93,26 +104,8 @@ Notify the Zcash DevOps engineer/sysadmin that the release has been tagged. They Then, verify that nodes can connect to the testnet server, and update the guide on the wiki to ensure the correct hostname is listed in the recommended zcash.conf. -## F. Update the 1.0 User Guide +### Update the 1.0 User Guide -## G. Publish the release announcement (blog, zcash-dev, slack) +### Publish the release announcement (blog, zcash-dev, slack) -### G1. Check in with users who opened issues that were resolved in the release - -Contact all users who opened `user support` issues that were resolved in the release, and ask them if the release fixes or improves their issue. - -## H. Make and deploy deterministic builds - -- Run the [Gitian deterministic build environment](https://github.com/zcash/zcash-gitian) -- Compare the uploaded [build manifests on gitian.sigs](https://github.com/zcash/gitian.sigs) -- If all is well, the DevOps engineer will build the Debian packages and update the - [apt.z.cash package repository](https://apt.z.cash). - -## I. Celebrate - -## missing steps -Zcash still needs: - -* thorough pre-release testing (presumably more thorough than standard PR tests) - -* automated release deployment (e.g.: updating build-depends mirror, deploying testnet, etc...) +## Celebrate diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 40151b70c..0ea2b3bac 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -74,7 +74,9 @@ def main_logged(release, releaseprev, releaseheight): gen_manpages() commit('Updated manpages.') - raise NotImplementedError(main_logged) + gen_release_notes(release) + update_debian_changelog(release) + commit('Updated release notes and changelog.') def phase(message): @@ -173,6 +175,30 @@ def gen_manpages(): sh_log('./contrib/devtools/gen-manpages.sh') +@phase('Generating release notes.') +def gen_release_notes(release): + sh_log('python', './zcutil/release-notes.py', '--version', release.novtext) + sh_log( + 'git', + 'add', + './doc/authors.md', + './doc/release-notes/release-notes-{}.md'.format(release.novtext), + ) + + +@phase('Updating debian changelog.') +def update_debian_changelog(release): + os.environ['DEBEMAIL'] = 'team@z.cash' + os.environ['DEBFULLNAME'] = 'Zcash Company' + sh_log( + 'debchange', + '--newversion', release.debversion, + '--distribution', 'stable', + '--changelog', './contrib/debian/changelog', + '{} release.'.format(release.novtext), + ) + + # Helper code: def commit(message): logging.info('Committing: %r', message) @@ -280,7 +306,7 @@ def sh_out(*args): def sh_log(*args): PIPE = subprocess.PIPE try: - p = subprocess.Popen(args, stdout=PIPE, stderr=PIPE) + p = subprocess.Popen(args, stdout=PIPE, stderr=PIPE, stdin=None) except OSError: logging.error('Error launching %r...', args) raise @@ -334,8 +360,6 @@ class Version (object): self.betarc = betarc self.hotfix = hotfix - self.novtext = '{}.{}.{}'.format(major, minor, patch) - if hotfix is None: self.build = 50 else: @@ -343,13 +367,42 @@ class Version (object): if betarc is None: assert hotfix < 50, hotfix self.build = 50 + hotfix - self.novtext += '-{}'.format(hotfix) else: assert hotfix < 26, hotfix - self.novtext += '-{}{}'.format(betarc, hotfix) self.build = {'beta': 0, 'rc': 25}[betarc] + hotfix - 1 - self.vtext = 'v' + self.novtext + @property + def novtext(self): + return self._novtext(debian=False) + + @property + def vtext(self): + return 'v' + self.novtext + + @property + def debversion(self): + return self._novtext(debian=True) + + def _novtext(self, debian): + novtext = '{}.{}.{}'.format(self.major, self.minor, self.patch) + + if self.hotfix is None: + return novtext + else: + assert self.hotfix > 0, self.hotfix + if self.betarc is None: + assert self.hotfix < 50, self.hotfix + sep = '+' if debian else '-' + return '{}{}{}'.format(novtext, sep, self.hotfix) + else: + assert self.hotfix < 26, self.hotfix + sep = '~' if debian else '-' + return '{}{}{}{}'.format( + novtext, + sep, + self.betarc, + self.hotfix, + ) def __repr__(self): return ''.format(self.vtext) diff --git a/zcutil/release-notes.py b/zcutil/release-notes.py old mode 100644 new mode 100755 From 7f10df4785a41b7c4bb156af1521c11fc028deb3 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Mon, 22 May 2017 20:24:42 -0700 Subject: [PATCH 25/29] Fix `release-process.md` doc usage for `make-release.py` to have correct arguments and order. --- doc/release-process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/release-process.md b/doc/release-process.md index 8299bf6e4..d6090dfcd 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -44,11 +44,11 @@ Run the release script, which will verify you are on the latest clean checkout of master, create a branch, then commit standard automated changes to that branch locally: - $ ./zcutil/make-release.py + $ ./zcutil/make-release.py Example: - $ ./zcutil/make-release.py v1.0.8-1 v1.0.9 + $ ./zcutil/make-release.py v1.0.9 v1.0.8-1 120000 ### Create, Review, and Merge the release branch pull request From 73a3f7dc024e0f79e2d434e64dd942359cdffab5 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Mon, 22 May 2017 20:38:06 -0700 Subject: [PATCH 26/29] Include release version in commit comments. --- zcutil/make-release.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 0ea2b3bac..bdbaf03a0 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -68,15 +68,19 @@ def main_logged(release, releaseprev, releaseheight): initialize_git(release) patch_version_in_files(release, releaseprev) patch_release_height(releaseheight) - commit('Versioning changes.') + commit('Versioning changes for {}.'.format(release.novtext)) build() gen_manpages() - commit('Updated manpages.') + commit('Updated manpages for {}.'.format(release.novtext)) gen_release_notes(release) update_debian_changelog(release) - commit('Updated release notes and changelog.') + commit( + 'Updated release notes and changelog for {}.'.format( + release.novtext, + ), + ) def phase(message): From 92333721e59ebbb919d326e4b8486d4e60a17175 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Mon, 22 May 2017 20:38:22 -0700 Subject: [PATCH 27/29] Examine all future versions which are assumed to follow the same Version parser schema. --- zcutil/make-release.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index bdbaf03a0..162cef682 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -96,8 +96,15 @@ def phase(message): @phase('Checking RELEASE_PREV tag.') def verify_releaseprev_tag(releaseprev): candidates = [] + + # Any tag beginning with a 'v' followed by [1-9] must be a version + # matching our Version parser. Tags beginning with v0 may exist from + # upstream and those do not follow our schema and are silently + # ignored. Any other tag is silently ignored. + candidatergx = re.compile('^v[1-9].*$') + for tag in sh_out('git', 'tag', '--list').splitlines(): - if tag.startswith('v1'): # Ignore v0.* bitcoin tags and other stuff. + if candidatergx.match(tag): candidates.append(Version.parse_arg(tag)) candidates.sort() From 187e64c7abff570e1467ddeb1432e040bb6a67e7 Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Mon, 22 May 2017 20:42:00 -0700 Subject: [PATCH 28/29] Consider both beta and rc versions to be `IS_RELEASE == false`. --- zcutil/make-release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 162cef682..07eda1a5a 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -279,7 +279,7 @@ def _patch_build_defs(release, path, pattern): 'REVISION': release.patch, 'BUILD': release.build, 'IS_RELEASE': ( - 'false' if release.betarc == 'beta' else 'true' + 'false' if release.build < 50 else 'true' ), }[label] outf.write('{} {}{}\n'.format(prefix, repl, suffix)) From 88bf7eb0506195eb69a43ea08954225f81c6d31c Mon Sep 17 00:00:00 2001 From: Nathan Wilcox Date: Mon, 22 May 2017 21:23:11 -0700 Subject: [PATCH 29/29] Add a few more version strings to positive parser test. --- zcutil/make-release.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 07eda1a5a..514d5883f 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -473,6 +473,9 @@ class TestVersion (unittest.TestCase): ('v1.0.7-1', 51), ('v1.0.8', 50), ('v1.0.8-1', 51), + ('v1.0.9', 50), + ('v1.0.10', 50), + ('v7.42.1000', 50), ] ValidVersions = [