From 9d0bdad71b4f10dd0ef995c65599d4c48f0a179a Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 13 Apr 2017 20:45:31 +0100 Subject: [PATCH] [setup.py] add 'bump_version' and 'release' setuptools sub-commands --- setup.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index e9e1acdb6..1e1bbf97e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,132 @@ from __future__ import print_function, division, absolute_import import sys -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Command +from distutils import log + + +class bump_version(Command): + + description = "increment the package version and commit the changes" + + user_options = [ + ("major", None, "bump the first digit, for incompatible API changes"), + ("minor", None, "bump the second digit, for new backward-compatible features"), + ("patch", None, "bump the third digit, for bug fixes (default)"), + ] + + def initialize_options(self): + self.minor = False + self.major = False + self.patch = False + + def finalize_options(self): + part = None + for attr in ("major", "minor", "patch"): + if getattr(self, attr, False): + if part is None: + part = attr + else: + from distutils.errors import DistutilsOptionError + raise DistutilsOptionError( + "version part options are mutually exclusive") + self.part = part or "patch" + + def bumpversion(self, part, tag=False, message=None): + """ Run bumpversion.main() with the specified arguments, and return the + new computed version string. + """ + import bumpversion + + args = ( + (['--verbose'] if self.verbose > 1 else []) + + (['--tag'] if tag else ['--no-tag']) + + (['--message', message] if message is not None else []) + + [part] + ) + log.debug( + "$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args)) + + bumpversion.main(args) + + def run(self): + log.info("bumping '%s' version" % self.part) + self.bumpversion(self.part) + + +class release(bump_version): + """Drop the developmental release '.devN' suffix from the package version, + open the default text $EDITOR to write release notes, commit the changes + and generate a git tag. + + Release notes can also be set with the -m/--message option, or by reading + from standard input. + """ + + description = "tag a new release" + + user_options = [ + ("message=", 'm', "message containing the release notes"), + ] + + def initialize_options(self): + self.message = None + + def finalize_options(self): + import re + + current_version = self.distribution.metadata.get_version() + if not re.search(r"\.dev[0-9]+", current_version): + from distutils.errors import DistutilsSetupError + raise DistutilsSetupError( + "current version (%s) has no '.devN' suffix.\n " + "Run 'setup.py bump_version', or use any of " + "--major, --minor, --patch options" % current_version) + + message = self.message + if message is None: + if sys.stdin.isatty(): + # stdin is interactive, use editor to write release notes + message = self.edit_release_notes() + else: + # read release notes from stdin pipe + message = sys.stdin.read() + + if not message.strip(): + from distutils.errors import DistutilsSetupError + raise DistutilsSetupError("release notes message is empty") + + self.message = "Release {new_version}\n\n%s" % (message) + + @staticmethod + def edit_release_notes(): + """Use the default text $EDITOR to write release notes. + If $EDITOR is not set, use 'nano'.""" + from tempfile import mkstemp + import os + import shlex + import subprocess + + text_editor = shlex.split(os.environ.get('EDITOR', 'nano')) + + fd, tmp = mkstemp(prefix='bumpversion-') + try: + os.close(fd) + with open(tmp, 'w') as f: + f.write("\n\n# Write release notes.\n" + "# Lines starting with '#' will be ignored.") + subprocess.check_call(text_editor + [tmp]) + with open(tmp, 'r') as f: + changes = "".join( + l for l in f.readlines() if not l.startswith('#')) + finally: + os.remove(tmp) + return changes + + def run(self): + log.info("stripping developmental release suffix") + # drop '.dev0' suffix, commit with given message and create git tag + self.bumpversion("release", tag=True, message=self.message) needs_pytest = {'pytest', 'test'}.intersection(sys.argv) @@ -27,7 +152,8 @@ packages=find_packages("Lib"), include_package_data=True, license="MIT", - setup_requires=pytest_runner + wheel, + setup_requires=pytest_runner + wheel + ( + ['bumpversion'] if {'release', 'bump_version'}.intersection(sys.argv) else []), tests_require=[ 'pytest>=2.8', ], @@ -38,6 +164,10 @@ "cu2qu>=1.1.1", "compreffor>=0.4.4", ], + cmdclass={ + "release": release, + "bump_version": bump_version, + }, classifiers=[ 'Development Status :: 4 - Beta', "Environment :: Console",