forked from github.com/pypiserver
Merge pull request #185 from ankostis/bumpver
feat(build): add `/bin/bumpver.py` script to facilitate new releases
This commit is contained in:
commit
35f9740429
|
@ -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 <https://www.python.org/dev/peps/pep-0440/`_ to decide the version.
|
||||||
|
|
||||||
|
4. Push it in GitHub with ``--tag``.
|
||||||
|
|
||||||
|
5. Generate package *wheel* with ``/bin/package.sh``.
|
||||||
|
|
||||||
|
6. Upload to PyPi with ``twine upload -su <gpg-user> 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.
|
|
@ -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 <message>] <new-ver>
|
||||||
|
|
||||||
|
Without <new-ver> 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=<msg> 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['<new-ver>']
|
||||||
|
|
||||||
|
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:])
|
|
@ -1,9 +1,9 @@
|
||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
##
|
#
|
||||||
## Create an executable file and add it into `standalone` branch
|
## Create an executable file and add it into `standalone` branch
|
||||||
##
|
#
|
||||||
## Invoke it directly on the commmit "tagged" for a release.
|
# Invoke it directly on the commmit "tagged" for a release.
|
||||||
## Invoke it with any arg to avoid committing into `standalone` branch.
|
# Invoke it with any arg to avoid committing into `standalone` branch.
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
##
|
#
|
||||||
## Create an executable zip file.
|
## Create an executable zip file.
|
||||||
## Invoked by `commit-standalone.sh`.
|
# Invoked by `commit-standalone.sh`.
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#! /bin/sh
|
||||||
|
#
|
||||||
|
##
|
||||||
|
|
||||||
my_dir=`dirname "$0"`
|
my_dir=`dirname "$0"`
|
||||||
cd $my_dir/..
|
cd $my_dir/..
|
||||||
|
|
||||||
|
|
|
@ -20,3 +20,4 @@ twine>=1.7,<=1.7.4
|
||||||
WebOb==0.9.6.1; python_version == '2.5'
|
WebOb==0.9.6.1; python_version == '2.5'
|
||||||
BeautifulSoup==3.2.1; python_version == '2.5'
|
BeautifulSoup==3.2.1; python_version == '2.5'
|
||||||
WebTest==1.4.3; python_version == '2.5'
|
WebTest==1.4.3; python_version == '2.5'
|
||||||
|
docopt # For `/bin/bumpver.py`.
|
Loading…
Reference in New Issue