diff --git a/bin/bump_version.py b/bin/bump_version.py new file mode 100755 index 000000000..8ad630ac6 --- /dev/null +++ b/bin/bump_version.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +import click +from pathlib import Path +import os +import cibuildwheel +from packaging.version import Version, InvalidVersion +import subprocess +import glob + +config = [ + # file path, version find/replace format + ('cibuildwheel/__init__.py', "__version__ = '{}'"), + ('setup.py', "version='{}'"), + ('README.md', "cibuildwheel=={}"), + ('examples/*', "cibuildwheel=={}"), +] + + +@click.command() +def bump_version(): + git_changes_result = subprocess.run(['git diff-index --quiet HEAD --'], shell=True) + repo_has_uncommitted_changes = git_changes_result.returncode != 0 + + if repo_has_uncommitted_changes: + print('error: Uncommitted changes detected.') + exit(1) + + current_version = cibuildwheel.__version__ + print( 'Current version:', current_version) # noqa + new_version = input(' New version: ').strip() + + try: + Version(new_version) + except InvalidVersion: + print("error: This version doesn't conform to PEP440") + print(' https://www.python.org/dev/peps/pep-0440/') + exit(1) + + actions = [] + + for path_pattern, version_pattern in config: + paths = [Path(p) for p in glob.glob(path_pattern)] + + if not paths: + print(f'error: Pattern {path_pattern} didn’t match any files') + exit(1) + + find_pattern = version_pattern.format(current_version) + replace_pattern = version_pattern.format(new_version) + found_at_least_one_file_needing_update = False + + for path in paths: + contents = path.read_text(encoding='utf8') + if find_pattern in contents: + found_at_least_one_file_needing_update = True + actions.append( + (path, find_pattern, replace_pattern) + ) + + if not found_at_least_one_file_needing_update: + print('error: Didn’t find any occurences of “{}” in “{}”'.format(find_pattern, path_pattern)) + exit(1) + + print() + print("Here's the plan:") + print() + + for action in actions: + print('{} {red}{}{off} → {green}{}{off}'.format( + *action, + red="\u001b[31m", green="\u001b[32m", off="\u001b[0m" + )) + + print('Then commit, and tag as v{}'.format(new_version)) + + answer = input('Proceed? [y/N] ').strip() + + if answer != 'y': + print('Aborted') + exit(1) + + for path, find, replace in actions: + contents = path.read_text(encoding='utf8') + contents = contents.replace(find, replace) + path.write_text(contents, encoding='utf8') + + print('Files updated. If you want to update the changelog as part of this') + print('commit, do that now.') + + while input('Type "done" to continue: ').strip().lower() != 'done': + pass + + subprocess.run([ + 'git', 'commit', + '-a', + '-m', f'Bump version: v{new_version}' + ], check=True) + + subprocess.run([ + 'git', 'tag', + '-a', + '-m', f'v{new_version}', + f'v{new_version}' + ], check=True) + + print('Done.') + + +if __name__ == '__main__': + os.chdir(os.path.dirname(__file__)) + os.chdir('..') + bump_version()