diff --git a/bob/extension/scripts/__init__.py b/bob/extension/scripts/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..60b3f6d43e89d1f4dfbe246105d1e47f1a23021c --- /dev/null +++ b/bob/extension/scripts/__init__.py @@ -0,0 +1,4 @@ +from .new_version import main as new_version + +# gets sphinx autodoc done right - don't remove it +__all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/extension/scripts/new_version.py b/bob/extension/scripts/new_version.py new file mode 100755 index 0000000000000000000000000000000000000000..75546695dade4a3c99770e4586dcc9c50e63269c --- /dev/null +++ b/bob/extension/scripts/new_version.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +from __future__ import print_function +import sys, os +import subprocess + +import argparse +from distutils.version import StrictVersion as Version + +def main(command_line_options = None): + """ + This script will push a new ``'stable'`` version of the current package on GitHub and PyPI, and update the new version of the package to the given ``'latest'`` version. + It assumes that you are in the main directory of the package and have successfully ran bootstrap, and that you have submitted all changes that should go into the new version. + Preferably, the build on Travis passed. + For database packages, it also assumes that the ``.sql3`` files have been generated. + Also, it assumes that the ``'stable'`` version has not yet been uploaded to PyPI, and that no GitHub tag with this version exists. + + The ``'stable'`` version (i.e., what will be downloadable from PyPI) can be current version of the package, but not lower than that. + The ``'latest'`` version (i.e., what will be the new master branch on GitHub) must be higher than the current and than the stable version. + By default, five steps are executed, in this order: + + - ``tag``: If given, the --stable-version will be set and added to GitHub; and the version is tagged in GitHub and pushed. + - ``build``: The package will be (re-)built with bin/buildout using the provided build options. + - ``pypi``: The --stable-version (or the current version) will be registered and uploaded to PyPI + - ``docs``: The documentation will be generated and uploaded to PythonHosted + - ``latest``: The --latest-version will be set and committed to GitHub + + If any of these commands fail, the remaining steps will be skipped, unless you specify the ``--keep-going`` option. + + If you only want a subset of the steps to be executed, you can limit them using the ``--steps`` option. + A valid use case, e.g., is only to re-upload the documentation. + """ + + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument("--latest-version", '-l', required = True, help = "The latest version for the package") + parser.add_argument("--stable-version", '-s', help = "The stable version for the package; if not given, the current version is used") + parser.add_argument("--build-options", '-b', nargs='+', default = [], help = "Add options to build your package") + parser.add_argument("--steps", nargs = "+", choices = ['tag', 'build', 'pypi', 'docs', 'latest'], default = ['tag', 'build', 'pypi', 'docs', 'latest'], help = "Select the steps that you want to execute") + parser.add_argument("--dry-run", '-q', action = 'store_true', help = "Only print the actions, but do not execute them") + parser.add_argument("--keep-going", '-f', action = 'store_true', help = "Run all steps, even if some of them fail. HANDLE THIS FLAG WITH CARE!") + parser.add_argument("--verbose", '-v', action = 'store_true', help = "Print more information") + + args = parser.parse_args(command_line_options) + + + # assert the the version file is there + version_file = 'version.txt' + if not os.path.exists(version_file): + raise ValueError("Could not find the file '%s' containing the version number. Are you inside the root directory of your package?") + + def run_commands(version, *calls): + """Updates the version.txt to the given version and runs the given commands.""" + if version is not None and (args.verbose or args.dry_run): + print (" - cat '%s' > %s" % (version, version_file)) + if not args.dry_run and version is not None: + # update version to stable version, if not done yet + with open(version_file, 'w') as f: + f.write(version) + + # get all calls + for call in calls: + if args.verbose or args.dry_run: + print (' - ' + ' '.join(call)) + if not args.dry_run: + # execute call + if subprocess.call(call): + # call failed (has non-zero exit status) + print ("Command '%s' failed; stopping" % ' '.join(call)) + if not args.keep_going: + raise ValueError("Command '%s' failed; stopping" % ' '.join(call)) + + # get current version + current_version = open(version_file).read().rstrip() + + # check the versions + if args.stable_version is not None and Version(args.latest_version) <= Version(args.stable_version): + raise ValueError("The latest version '%s' must be greater than the stable version '%s'" % (args.latest_version, args.stable_version)) + if Version(current_version) >= Version(args.latest_version): + raise ValueError("The latest version '%s' must be greater than the current version '%s'" % (args.latest_version, current_version)) + if args.stable_version is not None and Version(current_version) > Version(args.stable_version): + raise ValueError("The stable version '%s' cannot be smaller than the current version '%s'" % (args.stable_version, current_version)) + + + if 'tag' in args.steps: + if args.stable_version is not None and Version(args.stable_version) > Version(current_version): + # update stable version on github + run_commands(args.stable_version, ['git', 'add', 'version.txt'], ['git', 'commit', '-m', 'Increased version to %s [skip ci]' % args.stable_version]) + else: + # assure that we have the current version + args.stable_version = current_version + # add a github tag + print ("\nTagging version '%s'" % args.stable_version) + run_commands(None, ['git', 'tag', 'v%s' % args.stable_version], ['git', 'push', '--tags']) + + + if 'build' in args.steps: + print ("\nBuilding the package") + run_commands(None, ['./bin/buildout'] + args.build_options) + + + if 'pypi' in args.steps: + print ("\nUploading version '%s' to PyPI" % args.stable_version) + # update version on github and add a tag + run_commands(None, ['./bin/python', 'setup.py', 'register'], ['./bin/python', 'setup.py', 'sdist', '--formats', 'zip', 'upload']) + + + if 'docs' in args.steps: + # Documentation can be uploaded, independent of the versions + print ("\nUploading documentation to PythonHosted.org") + run_commands(None, ["./bin/python", "setup.py", "build_sphinx", "--source-dir", "doc", "--build-dir", "build/doc", "--all-files"], ["./bin/python", "setup.py", "upload_docs", "--upload-dir", "build/doc/html"]) + + + if 'latest' in args.steps: + # update GitHub version to latest version + print ("\nSetting latest version '%s'" % args.latest_version) + run_commands(args.latest_version, ['git', 'add', 'version.txt'], ['git', 'commit', '-m', 'Increased version to %s' % args.latest_version], ['git', 'push']) + + +if __name__ == '__main__': + main() + diff --git a/bob/extension/test_scripts.py b/bob/extension/test_scripts.py new file mode 100644 index 0000000000000000000000000000000000000000..a710a41d6a0fccf0ff194cc9bfd39e5b9dd68e6b --- /dev/null +++ b/bob/extension/test_scripts.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# Andre Anjos <andre.anjos@idiap.ch> +# Thu 20 Mar 2014 12:43:48 CET + +"""Tests for scripts +""" + +import os +import sys +import nose.tools + +def test_new_version(): + # Tests the bin/bob_new_version.py script + + from bob.extension.scripts import new_version + + # test the script using the dry-run option (to avoid to actually tag and push code) + + # assert that it does not raise an exception, when both latest and stable version are specified + new_version(['--dry-run', '--stable-version', '20.7.0', '--latest-version', '20.8.0']) + + # assert that it does not raise an exception, when only the latest version is specified + new_version(['--dry-run', '--latest-version', '20.8.0']) + + # assert that it does raise an exception, when the latest version is too low + nose.tools.assert_raises(ValueError, new_version, ['--dry-run', '--latest-version', '0.8.0']) + + # assert that it does raise an exception, when the stable version is too low + nose.tools.assert_raises(ValueError, new_version, ['--dry-run', '--stable-version', '0.8.0', '--latest-version', '0.9.0']) + + # assert that it does raise an exception, when the stable version is higher than latest version + nose.tools.assert_raises(ValueError, new_version, ['--dry-run', '--stable-version', '20.8.0', '--latest-version', '20.8.0a1']) + diff --git a/doc/guide.rst b/doc/guide.rst index fce96db1da2c934f8707a8298c3adfc52e97a561..7df091be61b954675ded4e1ae9e86db6c8acc811 100644 --- a/doc/guide.rst +++ b/doc/guide.rst @@ -324,7 +324,7 @@ This file will be used to generate the front page of your package on PyPI_ and w The ``README.rst`` of **this package** (``bob.extension``) is a good example, including all the badges that show the current status of the package and the link to relevant information. To ease up your life, we also provide a script to run all steps to publish your package. -Please read the following paragraphs to understand the steps in the ``./scripts/new_version.py`` script that will be explained at the end of this section. +Please read the following paragraphs to understand the steps in the ``./bin/bob_new_version.py`` script that will be explained at the end of this section. Version Numbering Scheme ======================== @@ -472,13 +472,13 @@ and, finally, to keep track of new changes: 5. Switch to a new version number. -All these steps are combined in the ``./scripts/new_version.py`` script. +All these steps are combined in the ``./bin/bob_new_version.py`` script. This script needs to be run from within the root directory of your package. Please run: .. code-block:: sh - $ [PATH/TO/]bob.extension/script/new_version.py --help + $ ./bin/bob_new_version.py --help to see a list of options. diff --git a/doc/py_api.rst b/doc/py_api.rst index e4e924cb50df1d1305af207d9c0d268ada2418bc..258431af5ace26b03c527340470858e3f6dc7468 100644 --- a/doc/py_api.rst +++ b/doc/py_api.rst @@ -6,7 +6,14 @@ Python API to `bob.extension` ================================ -This section includes information for using the Python API of -``bob.extension``. +This section includes information for using the Python API of ``bob.extension``. + +Functions +--------- .. automodule:: bob.extension + +Scripts +------- + +.. automodule:: bob.extension.scripts diff --git a/scripts/new_version.py b/scripts/new_version.py deleted file mode 100755 index 9c40544a86f810216ab1015ee3f374777213daea..0000000000000000000000000000000000000000 --- a/scripts/new_version.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function -import sys, os -import subprocess - -import argparse -from distutils.version import StrictVersion as Version - -doc = """ -This script will push a new 'stable' version of the current package on GitHub and PyPI, and update the new version of the package to the given 'latest' version. -It assumes that you are in the main directory of the package and have successfully ran bootstrap, and that you have submitted all changes that should go into the new version. -For database packages, it also assumes that the .sql3 files have been generated. -Also, it assumes that the 'stable' version has not yet been uploaded to PyPI, and that no GitHub tag with this version exists. - -The 'stable' version (i.e., what will be downloadable from PyPI) can be current version of the package, but not lower than that. -The 'latest' version (i.e., what will be the new master branch on GitHub) must be higher than the current and than the stable version. -By default, five steps are executed, in this order: - -- tag: If given, the --stable-version will be set and added to GitHub; and the version is tagged in GitHub and pushed. -- build: The package will be (re-)built with bin/buildout using the provided build options. -- pypi: The --stable-version (or the current version) will be registered and uploaded to PyPI -- docs: The documentation will be generated and uploaded to PythonHosted -- latest: The --latest-version will be set and committed to GitHub - -If any of these commands fail, the remaining steps will be skipped, unless you specify the --keep-going option. - -If you only want a subset of the steps to be executed, you can limit them using the --steps option. -A valid use case, e.g., is only to re-upload the documentation. -""" - -parser = argparse.ArgumentParser(description=doc, formatter_class=argparse.RawDescriptionHelpFormatter) - -parser.add_argument("--latest-version", '-l', required = True, help = "The latest version for the package") -parser.add_argument("--stable-version", '-s', help = "The stable version for the package; if not given, the current version is used") -parser.add_argument("--build-options", '-b', nargs='+', default = [], help = "Add options to build your package") -parser.add_argument("--steps", nargs = "+", choices = ['tag', 'build', 'pypi', 'docs', 'latest'], default = ['tag', 'build', 'pypi', 'docs', 'latest'], help = "Select the steps that you want to execute") -parser.add_argument("--dry-run", '-q', action = 'store_true', help = "Only print the actions, but do not execute them") -parser.add_argument("--keep-going", '-f', action = 'store_true', help = "Run all steps, even if some of them fail. HANDLE THIS FLAG WITH CARE!") -parser.add_argument("--verbose", '-v', action = 'store_true', help = "Print more information") - -args = parser.parse_args() - - -# assert the the version file is there -version_file = 'version.txt' -if not os.path.exists(version_file): - print("Could not find the file '%s' containing the version number. Are you inside the root directory of your package?") - sys.exit(-1) - -def run_commands(version, *calls): - """Updates the version.txt to the given version and runs the given commands.""" - if version is not None and (args.verbose or args.dry_run): - print (" - cat '%s' > %s" % (version, version_file)) - if not args.dry_run and version is not None: - # update version to stable version, if not done yet - with open(version_file, 'w') as f: - f.write(version) - - # get all calls - for call in calls: - if args.verbose or args.dry_run: - print (' - ' + ' '.join(call)) - if not args.dry_run: - # execute call - if subprocess.call(call): - # call failed (has non-zero exit status) - print ("Command '%s' failed; stopping" % ' '.join(call)) - if not args.keep_going: - sys.exit(-1) - -# get current version -current_version = open(version_file).read().rstrip() - -# check the versions -if args.stable_version is not None and Version(args.latest_version) <= Version(args.stable_version): - print ("The latest version '%s' must be greater than the stable version '%s'" % (args.latest_version, args.stable_version)) - sys.exit(-1) -if Version(current_version) >= Version(args.latest_version): - print ("The latest version '%s' must be greater than the current version '%s'" % (args.latest_version, current_version)) - sys.exit(-1) -if args.stable_version is not None and Version(current_version) > Version(args.stable_version): - print ("The stable version '%s' cannot be smaller than the current version '%s'" % (args.stable_version, current_version)) - sys.exit(-1) - - -if 'tag' in args.steps: - if args.stable_version is not None and Version(args.stable_version) > Version(current_version): - # update stable version on github - run_commands(args.stable_version, ['git', 'add', 'version.txt'], ['git', 'commit', '-m', 'Increased version to %s [skip ci]' % args.stable_version]) - else: - # assure that we have the current version - args.stable_version = current_version - # add a github tag - print ("\nTagging version '%s'" % args.stable_version) - run_commands(None, ['git', 'tag', 'v%s' % args.stable_version], ['git', 'push', '--tags']) - - -if 'build' in args.steps: - print ("\nBuilding the package") - run_commands(None, ['./bin/buildout'] + args.build_options) - - -if 'pypi' in args.steps: - print ("\nUploading version '%s' to PyPI" % args.stable_version) - # update version on github and add a tag - run_commands(None, ['./bin/python', 'setup.py', 'register'], ['./bin/python', 'setup.py', 'sdist', '--formats', 'zip', 'upload']) - - -if 'docs' in args.steps: - # Documentation can be uploaded, independent of the versions - print ("\nUploading documentation to PythonHosted.org") - run_commands(None, ["./bin/python", "setup.py", "build_sphinx", "--source-dir", "doc", "--build-dir", "build/doc", "--all-files"], ["./bin/python", "setup.py", "upload_docs", "--upload-dir", "build/doc/html"]) - - -if 'latest' in args.steps: - # update GitHub version to latest version - print ("\nSetting latest version '%s'" % args.latest_version) - run_commands(args.latest_version, ['git', 'add', 'version.txt'], ['git', 'commit', '-m', 'Increased version to %s' % args.latest_version], ['git', 'push']) - diff --git a/setup.py b/setup.py index 580ef281ea73d62c6a3a90e5070ce1e1bb8f20a1..6a2f8d5960b1e1a1e63e73a7ee1ca73e112de818 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,12 @@ setup( 'setuptools', ], + entry_points = { + 'console_scripts': [ + 'bob_new_version.py = bob.extension.scripts:new_version', + ], + }, + classifiers = [ 'Framework :: Bob', 'Development Status :: 4 - Beta',