From b7bb0f1e087046fee9ca8bd147fddbb58d5b1aa2 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 24 Aug 2017 12:04:30 +0100 Subject: [PATCH] Integrate a setuptools command Add a 'build_reno' setuptools command. 'skipsdist' is removed from 'tox', because there's no reason not to run this step as part of tox. Change-Id: I49b659948f35381a44e2fb5f91dd6bf9e8ef619e --- .gitignore | 3 + doc/source/user/index.rst | 1 + doc/source/user/setuptools.rst | 60 ++++++++ doc/source/user/usage.rst | 2 + ...tuptools-integration-950bd8ab6d2970c7.yaml | 6 + reno/defaults.py | 9 ++ reno/setup_command.py | 138 ++++++++++++++++++ setup.cfg | 2 + tox.ini | 1 - 9 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 doc/source/user/setuptools.rst create mode 100644 releasenotes/notes/setuptools-integration-950bd8ab6d2970c7.yaml create mode 100644 reno/setup_command.py diff --git a/.gitignore b/.gitignore index 7c5611f..3b982a9 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,9 @@ doc/build AUTHORS ChangeLog +# reno generates these +RELEASENOTES.rst + # Editors *~ .*.swp diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst index 3dc9ce2..09416a4 100644 --- a/doc/source/user/index.rst +++ b/doc/source/user/index.rst @@ -8,4 +8,5 @@ design usage sphinxext + setuptools examples diff --git a/doc/source/user/setuptools.rst b/doc/source/user/setuptools.rst new file mode 100644 index 0000000..631f27c --- /dev/null +++ b/doc/source/user/setuptools.rst @@ -0,0 +1,60 @@ +============================== + Python Packaging Integration +============================== + +*reno* supports integration with `setuptools`_ and *setuptools* derivatives +like *pbr* through a custom command - ``build_reno``. + +.. _pbr: http://docs.openstack.org/developer/pbr/ +.. _setuptools: https://setuptools.readthedocs.io/en/latest/ + +Using setuptools integration +---------------------------- + +To enable the ``build_reno`` command, you simply need to install *reno*. Once +done, simply run: + +.. code-block:: shell + + python setup.py build_reno + +You can configure the command in ``setup.py`` or ``setup.cfg``. To configure it +from ``setup.py``, add a ``build_reno`` section to ``command_options`` like so: + +.. code-block:: python + + from setuptools import setup + + setup( + name='mypackage', + version='0.1', + ... + command_options={ + 'build_reno': { + 'output_file': ('setup.py', 'RELEASENOTES.txt'), + }, + }, + ) + +To configure the command from ``setup.cfg``, add a ``build_reno`` section. For +example: + +.. code-block:: ini + + [build_reno] + output-file = RELEASENOTES.txt + +Options for setuptools integration +---------------------------------- + +These options related to the *setuptools* integration only. For general +configuration of *reno*, refer to :ref:`configuration`. + +``repo-root`` + The root directory of the Git repository; defaults to ``.`` + +``rel-notes-dir`` + The parent directory; defaults to ``releasenotes`` + +``output-file`` + The filename of the release notes file; defaults to ``RELEASENOTES.rst`` diff --git a/doc/source/user/usage.rst b/doc/source/user/usage.rst index f9ad134..01de922 100644 --- a/doc/source/user/usage.rst +++ b/doc/source/user/usage.rst @@ -176,6 +176,8 @@ mistakes. The command exits with an error code if there are any mistakes, so it can be used in a build pipeline to force some correctness. +.. _configuration: + Configuring Reno ================ diff --git a/releasenotes/notes/setuptools-integration-950bd8ab6d2970c7.yaml b/releasenotes/notes/setuptools-integration-950bd8ab6d2970c7.yaml new file mode 100644 index 0000000..29ae876 --- /dev/null +++ b/releasenotes/notes/setuptools-integration-950bd8ab6d2970c7.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add a ``build_reno`` setuptools command that allows users to generate a + release notes document and a reno cache file that can be used to build + release notes documents without the full Git history present. diff --git a/reno/defaults.py b/reno/defaults.py index 927656d..ded78f2 100644 --- a/reno/defaults.py +++ b/reno/defaults.py @@ -11,8 +11,11 @@ # under the License. RELEASE_NOTES_SUBDIR = 'releasenotes' + NOTES_SUBDIR = 'notes' + PRELUDE_SECTION_NAME = 'prelude' + # This is a format string, so it needs to be formatted wherever it is used. TEMPLATE = """\ --- @@ -81,3 +84,9 @@ other: available in another section, such as the prelude. This may mean repeating some details. """ + +# default filename of a release notes file generated by the setuptool extension +RELEASE_NOTES_FILENAME = 'RELEASENOTES.rst' + +# default path to the root of the repo, used by the setuptools extension +REPO_ROOT = '.' diff --git a/reno/setup_command.py b/reno/setup_command.py new file mode 100644 index 0000000..102ab25 --- /dev/null +++ b/reno/setup_command.py @@ -0,0 +1,138 @@ +# Copyright 2017, Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Custom distutils command. + +For more information, refer to the distutils and setuptools source: + +- https://github.com/python/cpython/blob/3.6/Lib/distutils/cmd.py +- https://github.com/pypa/setuptools/blob/v36.0.0/setuptools/command/sdist.py +""" + +from distutils import cmd +from distutils import errors +from distutils import log + +import six + +from reno import cache +from reno import config +from reno import defaults +from reno import formatter +from reno import loader + +COMMAND_NAME = 'build_reno' # duplicates what's found in setup.cfg + + +def load_config(distribution): + """Utility method to parse distutils/setuptools configuration. + + This is for use by other libraries to extract the command configuration. + + :param distribution: A :class:`distutils.dist.Distribution` object + :returns: A tuple of a :class:`reno.config.Config` object, the output path + of the human-readable release notes file, and the output file of the + reno cache file + """ + option_dict = distribution.get_option_dict(COMMAND_NAME) + + if option_dict.get('repo_root') is not None: + repo_root = option_dict.get('repo_root')[1] + else: + repo_root = defaults.REPO_ROOT + + if option_dict.get('rel_notes_dir') is not None: + rel_notes_dir = option_dict.get('rel_notes_dir')[1] + else: + rel_notes_dir = defaults.RELEASE_NOTES_SUBDIR + + if option_dict.get('output_file') is not None: + output_file = option_dict.get('output_file')[1] + else: + output_file = defaults.RELEASE_NOTES_FILENAME + + conf = config.Config(repo_root, rel_notes_dir) + cache_file = loader.get_cache_filename(conf) + + return (conf, output_file, cache_file) + + +class BuildReno(cmd.Command): + """Distutils command to build reno release notes. + + The release note build can be triggered from distutils, and some + configuration can be included in ``setup.py`` or ``setup.cfg`` instead of + being specified from the command-line. + """ + description = 'Build reno release notes' + user_options = [ + ('repo-root=', None, 'the root directory of the Git repository; ' + 'defaults to "."'), + ('rel-notes-dir=', None, 'the parent directory; defaults to ' + '"releasenotes"'), + ('output-file=', None, 'the filename of the release notes file'), + ] + + def initialize_options(self): + self.repo_root = None + self.rel_notes_dir = None + self.output_file = None + + def finalize_options(self): + if self.repo_root is None: + self.repo_root = defaults.REPO_ROOT + + if self.rel_notes_dir is None: + self.rel_notes_dir = defaults.RELEASE_NOTES_SUBDIR + + if self.output_file is None: + self.output_file = defaults.RELEASE_NOTES_FILENAME + + # Overriding distutils' Command._ensure_stringlike which doesn't support + # unicode, causing finalize_options to fail if invoked again. Workaround + # for http://bugs.python.org/issue19570 + def _ensure_stringlike(self, option, what, default=None): + # type: (unicode, unicode, Any) -> Any + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, six.string_types): + raise errors.DistutilsOptionError("'%s' must be a %s (got `%s`)" + % (option, what, val)) + return val + + def run(self): + conf = config.Config(self.repo_root, self.rel_notes_dir) + + # Generate the cache using the configuration options found + # in the release notes directory and the default output + # filename. + cache_filename = cache.write_cache_db( + conf=conf, + versions_to_include=[], # include all versions + outfilename=None, # generate the default name + ) + log.info('wrote cache file to %s', cache_filename) + + ldr = loader.Loader(conf) + text = formatter.format_report( + ldr, + conf, + ldr.versions, + title=self.distribution.metadata.name, + ) + with open(self.output_file, 'w') as f: + f.write(text) + log.info('wrote release notes to %s', self.output_file) diff --git a/setup.cfg b/setup.cfg index 4f304ec..2646fd0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,8 @@ packages = [entry_points] console_scripts = reno = reno.main:main +distutils.commands = + build_reno = reno.setup_command:BuildReno [extras] sphinx = diff --git a/tox.ini b/tox.ini index 88a0101..b7c6c61 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,6 @@ [tox] minversion = 1.6 envlist = py35,py27,pep8 -skipsdist = True [testenv] usedevelop = True