From 649f7adfc42eacc96c2cd7d28e5851124699eceb Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 15 Jul 2015 19:58:19 +0000 Subject: [PATCH] Move release_notes.py into releasetools package Start a new releasetools package and move the release notes generation script into it, turning it into a more reusable library at the same time. Change-Id: I9f1f38cba34fc63d3de4e3bb7eae97e26a72db35 --- README.rst | 57 +++++--- list_unreleased_changes.sh | 7 +- release_postversion.sh | 2 +- releasetools/__init__.py | 0 .../release_notes.py | 131 +++++++++++++----- setup.cfg | 12 +- tox.ini | 6 +- 7 files changed, 153 insertions(+), 62 deletions(-) create mode 100644 releasetools/__init__.py rename release_notes.py => releasetools/release_notes.py (74%) mode change 100755 => 100644 diff --git a/README.rst b/README.rst index 1a21c85..373751e 100644 --- a/README.rst +++ b/README.rst @@ -12,6 +12,35 @@ similar_tarballs.sh also requires that you have tardiff installed. If it's not packaged for your distribution, you can find it at http://tardiff.coolprojects.org/. +Tox Targets +=========== + +Because some of the scripts have special dependencies, they can be run +through tox as a convenience for managing those requirements. + +release_postversion +------------------- + +This script is used to publish alphas and final releases for Oslo +libraries, other libraries, and projects that use post-versioning +(Ironic) instead of pre-versioning (Nova, etc.). FixCommitted bugs +are turned to FixReleased, the next-$SERIES milestone is renamed or a +version-based milestone is created, and the milestone is marked +released. + +Examples: + +tox -e release_postversion -- juno 1.3.0.0a3 HEAD oslo.rootwrap + + Push a 1.3.0.0a3 tag to oslo.rootwrap current HEAD. Mark all FixCommitted + bugs in oslo.rootwrap to FixReleased in next-juno. + +tox -e release_postversion -- juno 1.3.0 HEAD oslo.rootwrap + + Rename the next-juno milestone to '1.3.0'. Push a 1.3.0 tag to oslo.rootwrap + current HEAD. Mark all FixCommitted bugs in oslo.rootwrap (if any) to + FixReleased in 1.3.0, and mark 1.3.0 released. + Top-level scripts ================= @@ -122,25 +151,7 @@ Examples: release_postversion.sh ---------------------- -This script is used to publish alphas and final releases for Oslo -libraries, other libraries, and projects that use post-versioning -(Ironic) instead of pre-versioning (Nova, etc.). FixCommitted bugs -are turned to FixReleased, the next-$SERIES milestone is renamed or a -version-based milestone is created, and the milestone is marked -released. - -Examples: - -./release_postversion.sh juno 1.3.0.0a3 HEAD oslo.rootwrap" - - Push a 1.3.0.0a3 tag to oslo.rootwrap current HEAD. Mark all FixCommitted - bugs in oslo.rootwrap to FixReleased in next-juno. - -./release_postversion.sh juno 1.3.0 HEAD oslo.rootwrap" - - Rename the next-juno milestone to '1.3.0'. Push a 1.3.0 tag to oslo.rootwrap - current HEAD. Mark all FixCommitted bugs in oslo.rootwrap (if any) to - FixReleased in 1.3.0, and mark 1.3.0 released. +See the 'release_postversion' tox environment, above. release_many.sh --------------- @@ -155,8 +166,8 @@ Optionally, the line can also include a series name, for example:: 1.13.0 85c069e oslo.messaging 1.8.3 0f24108 oslo.messaging kilo -release_notes.py ----------------- +release-notes +------------- This produces a set of release notes intended to be sent as an announcement email when a new library or package is produced. It is @@ -176,12 +187,12 @@ the one-line description to include in the output text. Examples: -./release_notes.py ~/repos/openstack/oslo.config 1.7.0 1.8.0 +release-notes ~/repos/openstack/oslo.config 1.7.0 1.8.0 Print the release notes between versions 1.7.0 and 1.8.0 for the project in the ``~/repos/openstack/oslo.config`` directory. -./release_notes.py --show-dates --changes-only ~/repos/openstack/oslo.config 1.8.0 HEAD +release-notes --show-dates --changes-only ~/repos/openstack/oslo.config 1.8.0 HEAD Print the list of changes after 1.8.0 for the project in the ``~/repos/openstack/oslo.config`` directory, including the date of diff --git a/list_unreleased_changes.sh b/list_unreleased_changes.sh index 7d1fa1c..93854d6 100755 --- a/list_unreleased_changes.sh +++ b/list_unreleased_changes.sh @@ -27,6 +27,11 @@ repos="$@" TOOLSDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source $TOOLSDIR/functions +if [[ -z "$VIRTUAL_ENV" ]]; then + tox -e venv --notest + source ./.tox/venv/bin/activate +fi + # Make sure no pager is configured so the output is not blocked export PAGER= @@ -44,7 +49,7 @@ function list_changes { else echo local end_sha=$(git log -n 1 --pretty=tformat:%h) - $TOOLSDIR/release_notes.py \ + release-notes \ --show-dates \ --changes-only \ . $prev_tag $end_sha diff --git a/release_postversion.sh b/release_postversion.sh index 52005b8..9bc243c 100755 --- a/release_postversion.sh +++ b/release_postversion.sh @@ -127,7 +127,7 @@ else if [[ "$EMAIL_TAGS" != "" ]]; then email_tags="--email-tags $EMAIL_TAGS" fi - ${TOOLSDIR}/release_notes.py \ + release-notes \ --email \ $email_tags \ --series $SERIES \ diff --git a/releasetools/__init__.py b/releasetools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/release_notes.py b/releasetools/release_notes.py old mode 100755 new mode 100644 similarity index 74% rename from release_notes.py rename to releasetools/release_notes.py index c7ff806..fc8791e --- a/release_notes.py +++ b/releasetools/release_notes.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # 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 @@ -180,8 +178,8 @@ def run_cmd(cmd, cwd=None): return stdout, stderr -def is_skippable_commit(args, line): - return (args.skip_requirement_merges and +def is_skippable_commit(skip_requirement_merges, line): + return (skip_requirement_merges and line.lower().endswith('updated from global requirements')) @@ -263,10 +261,74 @@ def main(): args = parser.parse_args() library_path = os.path.abspath(args.library) + + notable_changes = '' + if args.notable_changes: + with open(args.notable_changes, 'r') as fh: + notable_changes = fh.read().rstrip() + + notes = generate_release_notes( + library=args.library, + library_path=library_path, + start_revision=args.start_revision, + end_revision=args.end_revision, + show_dates=args.show_dates, + skip_requirement_merges=args.skip_requirement_merges, + notable_changes=notable_changes, + is_stable=args.stable, + series=args.series, + email=args.email, + email_from=args.email_from, + email_to=args.email_to, + email_reply_to=args.email_reply_to, + email_tags=args.email_tags, + include_pypi_link=args.include_pypi_link, + changes_only=args.changes_only, + ) + print(notes) + return 0 + + +def generate_release_notes(library, library_path, + start_revision, end_revision, + show_dates, skip_requirement_merges, + notable_changes, + is_stable, series, + email, email_from, + email_to, email_reply_to, email_tags, + include_pypi_link, + changes_only, + ): + """Return the text of the release notes. + + :param library: The name of the library. + :param library_path: Path to the library repository on disk. + :param start_revision: First reference for finding change log. + :param end_revision: Final reference for finding change log. + :param show_dates: Boolean indicating whether or not to show dates + in the output. + :param skip_requirement_merges: Boolean indicating whether to + skip merge commits for requirements changes. + :param notable_changes: Highlights from the release. + :param is_stable: Boolean indicating whether this is a stable + series or not. + :param series: String holding the name of the series. + :param email: Boolean indicating whether the output format should + be an email message. + :param email_from: String containing the sender email address. + :param email_to: String containing the email recipient. + :param email_reply_to: String containing the email reply-to address. + :param email_tags: String containing the email header topic tags to add. + :param include_pypi_link: Boolean indicating whether or not to + include an automatically generated link to the PyPI package + page. + :param changes_only: Boolean indicating whether to limit output to + the list of changes, without any extra data. + + """ + if not os.path.isfile(os.path.join(library_path, "setup.py")): - sys.stderr.write("No 'setup.py' file found in %s\n" % library_path) - sys.stderr.write("This will not end well...\n") - return 1 + raise RuntimeError("No 'setup.py' file found in %s\n" % library_path) # Get the python library/program description... cmd = [sys.executable, 'setup.py', '--description'] @@ -279,8 +341,8 @@ def main(): library_name = stdout.strip() # Get the commits that are in the desired range... - git_range = "%s..%s" % (args.start_revision, args.end_revision) - if args.show_dates: + git_range = "%s..%s" % (start_revision, end_revision) + if show_dates: format = "--format=%h %ci %s" else: format = "--oneline" @@ -289,7 +351,8 @@ def main(): changes = [] for commit_line in stdout.splitlines(): commit_line = commit_line.strip() - if not commit_line or is_skippable_commit(args, commit_line): + if not commit_line or is_skippable_commit(skip_requirement_merges, + commit_line): continue else: changes.append(commit_line) @@ -315,60 +378,60 @@ def main(): continue diff_stats.append(line) - notables = '' - if args.notable_changes: - with open(args.notable_changes, 'r') as fh: - notables = fh.read().rstrip() - # Extract + valdiate needed sections from readme... readme_sections = parse_readme(library_path) bug_url = readme_sections['bug_url'] if bug_url: lp_url = bug_url.replace("bugs.", "").rstrip("/") - milestone_url = lp_url + "/+milestone/%s" % args.end_revision + milestone_url = lp_url + "/+milestone/%s" % end_revision else: lp_url = '' milestone_url = '' change_header = ["Changes in %s %s" % (library_name, git_range)] change_header.append("-" * len(change_header[0])) + if not email_from: + raise RuntimeError('No email-from specified') + params = dict(readme_sections) params.update({ 'project': os.path.basename(library_path), 'description': description, - 'end_rev': args.end_revision, + 'end_rev': end_revision, 'range': git_range, 'lib': library_path, 'milestone_url': milestone_url, - 'skip_requirement_merges': args.skip_requirement_merges, + 'skip_requirement_merges': skip_requirement_merges, 'changes': changes, 'requirement_changes': requirement_changes, 'diff_stats': diff_stats, - 'notables': notables, + 'notables': notable_changes, 'change_header': "\n".join(change_header), 'emotion': random.choice(EMOTIONS), - 'stable_series': args.stable, - 'series': args.series, - 'email': args.email, - 'email_from': args.email_from, - 'email_to': args.email_to, - 'email_reply_to': args.email_reply_to, - 'email_tags': args.email_tags, + 'stable_series': is_stable, + 'series': series, + 'email': email, + 'email_from': email_from, + 'email_to': email_to, + 'email_reply_to': email_reply_to, + 'email_tags': email_tags, }) - if args.include_pypi_link: + if include_pypi_link: params['pypi_url'] = PYPI_URL_TPL % library_name else: params['pypi_url'] = None - if args.changes_only: - print(expand_template(CHANGES_ONLY_TPL, params)) + + response = [] + if changes_only: + response.append(expand_template(CHANGES_ONLY_TPL, params)) else: - if args.email: + if email: email_header = expand_template(EMAIL_HEADER_TPL.strip(), params) - print(email_header.lstrip()) + response.append(email_header.lstrip()) header = expand_template(HEADER_RELEASE_TPL.strip(), params) - print(parawrap.fill(header)) - print(expand_template(CHANGE_RELEASE_TPL, params)) - return 0 + response.append(parawrap.fill(header)) + response.append(expand_template(CHANGE_RELEASE_TPL, params)) + return '\n'.join(response) if __name__ == '__main__': diff --git a/setup.cfg b/setup.cfg index d3642a1..2fb5c84 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,12 @@ [metadata] -name = release-tools +name = releasetools summary = OpenStack Release Tools description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ + classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -15,4 +16,11 @@ classifier = Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 - Programming Language :: Python :: 2.6 + +[files] +packages = + releasetools + +[entry_points] +console_scripts = + release-notes = releasetools.release_notes:main diff --git a/tox.ini b/tox.ini index 3cb283a..b9e7b5a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = py27,pep8,bashate skipsdist = True [testenv] +passenv=EMAIL usedevelop = True install_command = pip install -U {opts} {packages} --allow-external lazr.authentication @@ -30,7 +31,10 @@ commands = bash -c "find {toxinidir} \ -print0 | xargs -0 bashate -v" [testenv:venv] -#commands = {posargs} +commands = {posargs} + +[testenv:release_postversion] +commands = {toxinidir}/release_postversion.sh {posargs} [testenv:cover] #commands = python setup.py testr --coverage --testr-args='{posargs}'