From 7618feaeb12c4128ebe31ed7af3110d15746d0d7 Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Mon, 2 Jul 2012 14:58:10 -0700 Subject: [PATCH] Consume version info from pkg_resources. Documenation builds specify a version in doc/source/conf.py that is used in appropriate places through out the documentation. Previously this value had not been defined properly and documentation builds failed. Retrieve the version info using pkg_resources and set it properly. Use openstack.common.version to consume the generated version information for documentation. Additional, add a swiftclient.__version__ member which will return the version of swiftclient being used. Change-Id: I14f3abdf00da3f9ea7d0651efe76b08f69ddabae --- doc/source/conf.py | 7 +- openstack-common.conf | 2 +- swiftclient/__init__.py | 3 + swiftclient/openstack/common/setup.py | 49 ++++---- swiftclient/openstack/common/version.py | 149 ++++++++++++++++++++++++ swiftclient/version.py | 21 ++++ tox.ini | 2 +- 7 files changed, 208 insertions(+), 25 deletions(-) create mode 100644 swiftclient/openstack/common/version.py create mode 100644 swiftclient/version.py diff --git a/doc/source/conf.py b/doc/source/conf.py index 86a8c586..3c577a7a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -53,10 +53,11 @@ copyright = u'2012 OpenStack, LLC.' # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '.'.join(str(v) for v in swiftclient.version_info[:-1]) +from swiftclient.version import version_info as swiftclient_version # The full version, including alpha/beta/rc tags. -release = swiftclient.version +release = swiftclient_version.version_string() +# The short X.Y version. +version = swiftclient_version.canonical_version_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/openstack-common.conf b/openstack-common.conf index d3694ce9..00af84ba 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=setup +modules=setup,version # The base module to hold the copy of openstack.common base=swiftclient diff --git a/swiftclient/__init__.py b/swiftclient/__init__.py index ba0b41a3..cdeb9efa 100644 --- a/swiftclient/__init__.py +++ b/swiftclient/__init__.py @@ -3,3 +3,6 @@ OpenStack Swift Python client binding. """ from client import * +from swiftclient import version + +__version__ = version.version_info.deferred_version_string() diff --git a/swiftclient/openstack/common/setup.py b/swiftclient/openstack/common/setup.py index caf06fa5..88c72528 100644 --- a/swiftclient/openstack/common/setup.py +++ b/swiftclient/openstack/common/setup.py @@ -35,7 +35,8 @@ def parse_mailmap(mailmap='.mailmap'): for l in fp: l = l.strip() if not l.startswith('#') and ' ' in l: - canonical_email, alias = l.split(' ') + canonical_email, alias = [x for x in l.split(' ') + if x.startswith('<')] mapping[alias] = canonical_email return mapping @@ -126,6 +127,13 @@ def _run_shell_command(cmd): return out[0].strip() +def _get_git_branch_name(): + branch_ref = _run_shell_command("git symbolic-ref -q HEAD") + if branch_ref is None: + return "HEAD" + return branch_ref[len("refs/heads/"):] + + def _get_git_next_version_suffix(branch_name): datestamp = datetime.datetime.now().strftime('%Y%m%d') if branch_name == 'milestone-proposed': @@ -136,10 +144,16 @@ def _get_git_next_version_suffix(branch_name): milestone_cmd = "git show meta/openstack/release:%s" % branch_name milestonever = _run_shell_command(milestone_cmd) if not milestonever: - milestonever = "" + milestonever = branch_name post_version = _get_git_post_version() - revno = post_version.split(".")[-1] - return "%s~%s.%s%s" % (milestonever, datestamp, revno_prefix, revno) + # post version should look like: + # 0.1.1.4.cc9e28a + # where the bit after the last . is the short sha, and the bit between + # the last and second to last is the revno count + (revno, sha) = post_version.split(".")[-2:] + first_half = "%(milestonever)s~%(datestamp)s" % locals() + second_half = "%(revno_prefix)s%(revno)s.%(sha)s" % locals() + return ".".join((first_half, second_half)) def _get_git_current_tag(): @@ -161,11 +175,14 @@ def _get_git_post_version(): cmd = "git --no-pager log --oneline" out = _run_shell_command(cmd) revno = len(out.split("\n")) + sha = _run_shell_command("git describe --always") else: tag_infos = tag_info.split("-") base_version = "-".join(tag_infos[:-2]) - revno = tag_infos[-2] - return "%s.%s" % (base_version, revno) + (revno, sha) = tag_infos[-2:] + # git describe prefixes the sha with a g + sha = sha[1:] + return "%s.%s.%s" % (base_version, revno, sha) def write_git_changelog(): @@ -207,7 +224,7 @@ _rst_template = """%(heading)s def read_versioninfo(project): """Read the versioninfo file. If it doesn't exist, we're in a github - zipball, and there's really know way to know what version we really + zipball, and there's really no way to know what version we really are, but that should be ok, because the utility of that should be just about nil if this code path is in use in the first place.""" versioninfo_path = os.path.join(project, 'versioninfo') @@ -302,17 +319,9 @@ def get_cmdclass(): return cmdclass -def get_git_branchname(): - for branch in _run_shell_command("git branch --color=never").split("\n"): - if branch.startswith('*'): - _branch_name = branch.split()[1].strip() - if _branch_name == "(no": - _branch_name = "no-branch" - return _branch_name - - def get_pre_version(projectname, base_version): - """Return a version which is based""" + """Return a version which is leading up to a version that will + be released in the future.""" if os.path.isdir('.git'): current_tag = _get_git_current_tag() if current_tag is not None: @@ -320,14 +329,14 @@ def get_pre_version(projectname, base_version): else: branch_name = os.getenv('BRANCHNAME', os.getenv('GERRIT_REFNAME', - get_git_branchname())) + _get_git_branch_name())) version_suffix = _get_git_next_version_suffix(branch_name) version = "%s~%s" % (base_version, version_suffix) write_versioninfo(projectname, version) - return version.split('~')[0] + return version else: version = read_versioninfo(projectname) - return version.split('~')[0] + return version def get_post_version(projectname): diff --git a/swiftclient/openstack/common/version.py b/swiftclient/openstack/common/version.py new file mode 100644 index 00000000..ca350ed9 --- /dev/null +++ b/swiftclient/openstack/common/version.py @@ -0,0 +1,149 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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. + +""" +Utilities for consuming the auto-generated versioninfo files. +""" + +import datetime +import pkg_resources +import os + +import setup + + +class _deferred_version_string(object): + """Internal helper class which provides delayed version calculation.""" + def __init__(self, version_info, prefix): + self.version_info = version_info + self.prefix = prefix + + def __str__(self): + return "%s%s" % (self.prefix, self.version_info.version_string()) + + def __repr__(self): + return "%s%s" % (self.prefix, self.version_info.version_string()) + + +class VersionInfo(object): + + def __init__(self, package, python_package=None, pre_version=None): + """Object that understands versioning for a package + :param package: name of the top level python namespace. For glance, + this would be "glance" for python-glanceclient, it + would be "glanceclient" + :param python_package: optional name of the project name. For + glance this can be left unset. For + python-glanceclient, this would be + "python-glanceclient" + :param pre_version: optional version that the project is working to + """ + self.package = package + if python_package is None: + self.python_package = package + else: + self.python_package = python_package + self.pre_version = pre_version + self.version = None + + def _generate_version(self): + """Defer to the openstack.common.setup routines for making a + version from git.""" + if self.pre_version is None: + return setup.get_post_version(self.python_package) + else: + return setup.get_pre_version(self.python_package, self.pre_version) + + def _newer_version(self, pending_version): + """Check to see if we're working with a stale version or not. + We expect a version string that either looks like: + 2012.2~f3~20120708.10.4426392 + which is an unreleased version of a pre-version, or: + 0.1.1.4.gcc9e28a + which is an unreleased version of a post-version, or: + 0.1.1 + Which is a release and which should match tag. + For now, if we have a date-embedded version, check to see if it's + old, and if so re-generate. Otherwise, just deal with it. + """ + try: + version_date = int(self.version.split("~")[-1].split('.')[0]) + if version_date < int(datetime.date.today().strftime('%Y%m%d')): + return self._generate_version() + else: + return pending_version + except Exception: + return pending_version + + def version_string_with_vcs(self, always=False): + """Return the full version of the package including suffixes indicating + VCS status. + + For instance, if we are working towards the 2012.2 release, + canonical_version_string should return 2012.2 if this is a final + release, or else something like 2012.2~f1~20120705.20 if it's not. + + :param always: if true, skip all version caching + """ + if always: + self.version = self._generate_version() + + if self.version is None: + + requirement = pkg_resources.Requirement.parse(self.python_package) + versioninfo = "%s/versioninfo" % self.package + try: + raw_version = pkg_resources.resource_string(requirement, + versioninfo) + self.version = self._newer_version(raw_version.strip()) + except (IOError, pkg_resources.DistributionNotFound): + self.version = self._generate_version() + + return self.version + + def canonical_version_string(self, always=False): + """Return the simple version of the package excluding any suffixes. + + For instance, if we are working towards the 2012.2 release, + canonical_version_string should return 2012.2 in all cases. + + :param always: if true, skip all version caching + """ + return self.version_string_with_vcs(always).split('~')[0] + + def version_string(self, always=False): + """Return the base version of the package. + + For instance, if we are working towards the 2012.2 release, + version_string should return 2012.2 if this is a final release, or + 2012.2-dev if it is not. + + :param always: if true, skip all version caching + """ + version_parts = self.version_string_with_vcs(always).split('~') + if len(version_parts) == 1: + return version_parts[0] + else: + return '%s-dev' % (version_parts[0],) + + def deferred_version_string(self, prefix=""): + """Generate an object which will expand in a string context to + the results of version_string(). We do this so that don't + call into pkg_resources every time we start up a program when + passing version information into the CONF constructor, but + rather only do the calculation when and if a version is requested + """ + return _deferred_version_string(self, prefix) diff --git a/swiftclient/version.py b/swiftclient/version.py new file mode 100644 index 00000000..e47b914a --- /dev/null +++ b/swiftclient/version.py @@ -0,0 +1,21 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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. + + +from swiftclient.openstack.common import version as common_version + +version_info = common_version.VersionInfo('swiftclient', + python_package='python-swiftclient') diff --git a/tox.ini b/tox.ini index 1112dfe1..46411412 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ commands = nosetests [testenv:pep8] deps = pep8 -commands = pep8 --repeat --show-source swiftclient setup.py +commands = pep8 --repeat --show-source --exclude=openstack swiftclient setup.py [testenv:venv] commands = {posargs}