diff --git a/bin/README.rst b/bin/README.rst new file mode 100644 index 0000000..9c746f9 --- /dev/null +++ b/bin/README.rst @@ -0,0 +1,37 @@ +==================== +Build scripts folder +==================== + + +Files: +====== + +- ``bumpver.py`` : Bump, commit and tag new project versions +- ``check_readme.sh`` : Check that README has no RsT-syntactic errors. +- ``commit-standalone.sh`` : Create an executable file and add it into `standalone` branch. +- ``gen-standalone.sh`` : Invoked by `commit-standalone.sh`. +- ``git-new-workdir`` : Invoked by `gen-standalone.sh`. +- ``package.sh`` : Build deployable artifact (wheel) in ``/dist/`` folder. +- ``README.rst`` : This file. + + +Release check-list: +=================== +1. Update ``/CHANGES.rst`` (+ Title + Date) & ``/README.rst``. + +2. Push to GitHub to run all TCs once more. + +3. Bump version: commit & tag it with ``/bin/bumpver.py``. + Use ``--help``. + Read `PEP-440 dist/*``: + +7. Run ``commit-standalone.sh``. + +8. Copy release notes from ``/CHANGES.rst`` in GitHub as new *"release"* page + on the new tag. Check syntactic differences between ``.md`` and ``.rst`` files. diff --git a/bin/bumpver.py b/bin/bumpver.py new file mode 100644 index 0000000..ba624d3 --- /dev/null +++ b/bin/bumpver.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# +# NEED POSIX (i.e. *Cygwin* on Windows). +""" +Script to bump, commit and tag new versions. + +USAGE: + bumpver + bumpver [-n] [-f] [-c] [-a] [-t ] + +Without prints version extracted from current file. +Don't add a 'v' prefix! + +OPTIONS: + -a, --amend Amend current commit for setting the "chore(ver): ..." msg. + -f, --force Bump (and optionally) commit/tag if version exists/is same. + -n, --dry-run Do not write files - just pretend. + -c, --commit Commit afterwardswith a commit-message describing version bump. + -t, --tag= Adds a signed tag with the given message (commit implied). + + +- Pre-releases: when working on some verion + X.YbN # Beta release + X.YrcN or X.YcN # Release Candidate + X.Y # Final release +- Post-release: + X.YaN.postM # Post-release of an alpha release + X.YrcN.postM # Post-release of a release candidate +- Dev-release: + X.YaN.devM # Developmental release of an alpha release + X.Y.postN.devM # Developmental release of a post-release + +EXAMPLE: + bumpver -t 'Mostly model changes' 1.6.2b0 + +""" + +import os.path as osp +import sys +import re +import functools as fnt + +import docopt + + +my_dir = osp.dirname(__file__) + +VFILE = osp.join(my_dir, '..', 'pypiserver', '__init__.py') +VFILE_regex_v = re.compile(r'version *= *__version__ *= *"([^"]+)"') +VFILE_regex_d = re.compile(r'__updated__ *= *"([^"]+)"') + +RFILE = osp.join(my_dir, '..', 'README.rst') + +PYTEST_ARGS = [osp.join('tests', 'test_docs.py')] + + +class CmdException(Exception): + pass + + +@fnt.lru_cache() +def read_txtfile(fpath): + with open(fpath, 'rt', encoding='utf-8') as fp: + return fp.read() + + +def extract_file_regexes(fpath, regexes): + """ + :param regexes: + A sequence of regexes to "search", having a single capturing-group. + :return: + One groups per regex, or raise if any regex did not match. + """ + txt = read_txtfile(fpath) + matches = [regex.search(txt) for regex in regexes] + + if not all(matches): + raise CmdException("Failed extracting current versions with: %s" + "\n matches: %s" % + (regexes, matches)) + + return [m.group(1) for m in matches] + + +def replace_substrings(files, subst_pairs): + for fpath in files: + txt = read_txtfile(fpath) + + replacements = [] + for old, new in subst_pairs: + replacements.append((old, new, txt.count(old))) + txt = txt.replace(old, new) + + yield (txt, fpath, replacements) + + +def format_syscmd(cmd): + if isinstance(cmd, (list, tuple)): + cmd = ' '.join('"%s"' % s if ' ' in s else s + for s in cmd) + else: + assert isinstance(cmd, str), cmd + + return cmd + + +def strip_ver2_commonprefix(ver1, ver2): + cprefix = osp.commonprefix([ver1, ver2]) + if cprefix: + striplen = cprefix.rfind('.') + if striplen > 0: + striplen += 1 + else: + striplen = len(cprefix) + ver2 = ver2[striplen:] + + return ver2 + + +def run_testcases(): + import pytest + + retcode = pytest.main(PYTEST_ARGS) + if retcode: + raise CmdException( + "Doc TCs failed(%s), probably version-bumping has failed!" % retcode) + + +def exec_cmd(cmd): + import subprocess as sbp + + err = sbp.call(cmd, stderr=sbp.STDOUT) + if err: + raise CmdException("Failed(%i) on: %s" % (err, format_syscmd(cmd))) + + +def do_commit(new_ver, old_ver, dry_run, amend, ver_files): + import pathlib + + #new_ver = strip_ver2_commonprefix(old_ver, new_ver) + cmt_msg = 'chore(ver): bump %s-->%s' % (old_ver, new_ver) + + ver_files = [pathlib.Path(f).as_posix() for f in ver_files] + git_add = ['git', 'add'] + ver_files + git_cmt = ['git', 'commit', '-m', cmt_msg] + if amend: + git_cmt.append('--amend') + commands = [git_add, git_cmt] + + for cmd in commands: + cmd_str = format_syscmd(cmd) + if dry_run: + yield "DRYRUN: %s" % cmd_str + else: + yield "EXEC: %s" % cmd_str + exec_cmd(cmd) + + +def do_tag(tag, tag_msg, dry_run, force): + cmd = ['git', 'tag', tag, '-s', '-m', tag_msg] + if force: + cmd.append('--force') + cmd_str = format_syscmd(cmd) + if dry_run: + yield "DRYRUN: %s" % cmd_str + else: + yield "EXEC: %s" % cmd_str + exec_cmd(cmd) + + +def bumpver(new_ver, dry_run=False, force=False, amend=False, + tag_name_or_commit=None): + """ + :param tag_name_or_commit: + if true, do `git commit`, if string, also `git tag` with that as msg. + """ + if amend: + ## Restore previous version before extracting it. + cmd = 'git checkout HEAD~ --'.split() + cmd.append(VFILE) + cmd.append(RFILE) + exec_cmd(cmd) + + regexes = [VFILE_regex_v, VFILE_regex_d] + old_ver, old_date = extract_file_regexes(VFILE, regexes) + + if not new_ver: + yield old_ver + yield old_date + else: + if new_ver == old_ver: + msg = "Version '%s'already bumped" + if force: + msg += ", but --force effected." + yield msg % new_ver + else: + msg += "!\n Use of --force recommended." + raise CmdException(msg % new_ver) + + from datetime import datetime + + new_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S%z') + + ver_files = [osp.normpath(f) for f in [VFILE, RFILE]] + subst_pairs = [(old_ver, new_ver), (old_date, new_date)] + + for repl in replace_substrings(ver_files, subst_pairs): + new_txt, fpath, replacements = repl + + if not dry_run: + with open(fpath, 'wt', encoding='utf-8') as fp: + fp.write(new_txt) + + yield '%s: ' % fpath + for old, new, nrepl in replacements: + yield ' %i x (%24s --> %s)' % (nrepl, old, new) + + yield "...now launching DocTCs..." + run_testcases() + + if tag_name_or_commit is not None: + yield from do_commit(new_ver, old_ver, dry_run, amend, ver_files) + + if isinstance(tag_name_or_commit, str): + tag = 'v%s' % new_ver + yield from do_tag(tag, tag_name_or_commit, dry_run, force) + + +def main(*args): + opts = docopt.docopt(__doc__, argv=args) + + new_ver = opts[''] + + assert not new_ver or new_ver[0] != 'v', ( + "Version '%s' must NOT start with `v`!" % new_ver) + + commit = opts['--commit'] + tag = opts['--tag'] + if tag: + tag_name_or_commit = tag + elif commit: + tag_name_or_commit = True + else: + tag_name_or_commit = None + + try: + for i in bumpver(new_ver, + opts['--dry-run'], + opts['--force'], + opts['--amend'], + tag_name_or_commit): + print(i) + except CmdException as ex: + sys.exit(str(ex)) + except Exception as ex: + raise ex + + +if __name__ == '__main__': + main(*sys.argv[1:]) diff --git a/bin/commit-standalone.sh b/bin/commit-standalone.sh index 7f50091..05acf7f 100755 --- a/bin/commit-standalone.sh +++ b/bin/commit-standalone.sh @@ -1,9 +1,9 @@ #! /bin/sh -## +# ## Create an executable file and add it into `standalone` branch -## -## Invoke it directly on the commmit "tagged" for a release. -## Invoke it with any arg to avoid committing into `standalone` branch. +# +# Invoke it directly on the commmit "tagged" for a release. +# Invoke it with any arg to avoid committing into `standalone` branch. set -x diff --git a/bin/gen-standalone.sh b/bin/gen-standalone.sh index 97dedbb..d490391 100755 --- a/bin/gen-standalone.sh +++ b/bin/gen-standalone.sh @@ -1,7 +1,7 @@ #! /bin/sh -## +# ## Create an executable zip file. -## Invoked by `commit-standalone.sh`. +# Invoked by `commit-standalone.sh`. set -x diff --git a/bin/package.sh b/bin/package.sh index ffc2d6f..d137d01 100644 --- a/bin/package.sh +++ b/bin/package.sh @@ -1,3 +1,7 @@ +#! /bin/sh +# +## + my_dir=`dirname "$0"` cd $my_dir/.. diff --git a/requirements/dev.pip b/requirements/dev.pip index f522864..f6699bf 100644 --- a/requirements/dev.pip +++ b/requirements/dev.pip @@ -20,3 +20,4 @@ twine>=1.7,<=1.7.4 WebOb==0.9.6.1; python_version == '2.5' BeautifulSoup==3.2.1; python_version == '2.5' WebTest==1.4.3; python_version == '2.5' +docopt # For `/bin/bumpver.py`. \ No newline at end of file