From 99586e05d7f70e0adb2eddaff7f079ec04b464fd Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 25 Jun 2012 11:31:49 -0500 Subject: [PATCH] Add post-tag versioning. Change-Id: I98e5f7aa788b1ab1a866b21e0a17a63b8d5efae3 --- .gitignore | 1 + MANIFEST.in | 1 + openstackclient/openstack/common/setup.py | 228 +++++++++++++++++++--- setup.py | 14 +- tools/pip-requires | 5 +- 5 files changed, 215 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 159851432e..4624f7dd23 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ python_openstackclient.egg-info .tox/ ChangeLog TAGS +openstackclient/versioninfo diff --git a/MANIFEST.in b/MANIFEST.in index fa21b6cf55..0856b5efb6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include AUTHORS include LICENSE include README.rst +include openstackclient/versioninfo recursive-include doc * recursive-include tests * diff --git a/openstackclient/openstack/common/setup.py b/openstackclient/openstack/common/setup.py index 60c731a9a2..e9cd340088 100644 --- a/openstackclient/openstack/common/setup.py +++ b/openstackclient/openstack/common/setup.py @@ -19,9 +19,13 @@ Utilities with minimum-depends for use in setup.py """ +import datetime import os import re import subprocess +import sys + +from setuptools.command import sdist def parse_mailmap(mailmap='.mailmap'): @@ -58,11 +62,25 @@ def parse_requirements(requirements_files=['requirements.txt', 'tools/pip-requires']): requirements = [] for line in get_reqs_from_files(requirements_files): + # For the requirements list, we need to inject only the portion + # after egg= so that distutils knows the package it's looking for + # such as: + # -e git://github.com/openstack/nova/master#egg=nova if re.match(r'\s*-e\s+', line): requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', line)) + # such as: + # http://github.com/openstack/nova/zipball/master#egg=nova + elif re.match(r'\s*https?:', line): + requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1', + line)) + # -f lines are for index locations, and don't get used here elif re.match(r'\s*-f\s+', line): pass + # argparse is part of the standard library starting with 2.7 + # adding it to the requirements list screws distro installs + elif line == 'argparse' and sys.version_info >= (2, 7): + pass else: requirements.append(line) @@ -72,11 +90,18 @@ def parse_requirements(requirements_files=['requirements.txt', def parse_dependency_links(requirements_files=['requirements.txt', 'tools/pip-requires']): dependency_links = [] + # dependency_links inject alternate locations to find packages listed + # in requirements for line in get_reqs_from_files(requirements_files): + # skip comments and blank lines if re.match(r'(\s*#)|(\s*$)', line): continue + # lines with -e or -f need the whole line, minus the flag if re.match(r'\s*-[ef]\s+', line): dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) + # lines that are only urls can go in unmolested + elif re.match(r'\s*https?:', line): + dependency_links.append(line) return dependency_links @@ -93,28 +118,54 @@ def write_requirements(): def _run_shell_command(cmd): output = subprocess.Popen(["/bin/sh", "-c", cmd], stdout=subprocess.PIPE) - return output.communicate()[0].strip() + out = output.communicate() + if len(out) == 0: + return None + if len(out[0].strip()) == 0: + return None + return out[0].strip() -def write_vcsversion(location): - """Produce a vcsversion dict that mimics the old one produced by bzr. - """ - if os.path.isdir('.git'): - branch_nick_cmd = 'git branch | grep -Ei "\* (.*)" | cut -f2 -d" "' - branch_nick = _run_shell_command(branch_nick_cmd) - revid_cmd = "git rev-parse HEAD" - revid = _run_shell_command(revid_cmd).split()[0] - revno_cmd = "git log --oneline | wc -l" - revno = _run_shell_command(revno_cmd) - with open(location, 'w') as version_file: - version_file.write(""" -# This file is automatically generated by setup.py, So don't edit it. :) -version_info = { - 'branch_nick': '%s', - 'revision_id': '%s', - 'revno': %s -} -""" % (branch_nick, revid, revno)) +def _get_git_next_version_suffix(branch_name): + datestamp = datetime.datetime.now().strftime('%Y%m%d') + if branch_name == 'milestone-proposed': + revno_prefix = "r" + else: + revno_prefix = "" + _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") + milestone_cmd = "git show meta/openstack/release:%s" % branch_name + milestonever = _run_shell_command(milestone_cmd) + if not milestonever: + milestonever = "" + post_version = _get_git_post_version() + revno = post_version.split(".")[-1] + return "%s~%s.%s%s" % (milestonever, datestamp, revno_prefix, revno) + + +def _get_git_current_tag(): + return _run_shell_command("git tag --contains HEAD") + + +def _get_git_tag_info(): + return _run_shell_command("git describe --tags") + + +def _get_git_post_version(): + current_tag = _get_git_current_tag() + if current_tag is not None: + return current_tag + else: + tag_info = _get_git_tag_info() + if tag_info is None: + base_version = "0.0" + cmd = "git --no-pager log --oneline" + out = _run_shell_command(cmd) + revno = len(out.split("\n")) + else: + tag_infos = tag_info.split("-") + base_version = "-".join(tag_infos[:-2]) + revno = tag_infos[-2] + return "%s.%s" % (base_version, revno) def write_git_changelog(): @@ -134,8 +185,8 @@ def generate_authors(): new_authors = 'AUTHORS' if os.path.isdir('.git'): # don't include jenkins email address in AUTHORS file - git_log_cmd = "git log --format='%aN <%aE>' | sort -u | " \ - "grep -v " + jenkins_email + git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " + "grep -v " + jenkins_email) changelog = _run_shell_command(git_log_cmd) mailmap = parse_mailmap() with open(new_authors, 'w') as new_authors_fh: @@ -143,3 +194,136 @@ def generate_authors(): if os.path.exists(old_authors): with open(old_authors, "r") as old_authors_fh: new_authors_fh.write('\n' + old_authors_fh.read()) + +_rst_template = """%(heading)s +%(underline)s + +.. automodule:: %(module)s + :members: + :undoc-members: + :show-inheritance: +""" + + +def write_versioninfo(project, version): + """Write a simple file containing the version of the package.""" + open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version) + + +def get_cmdclass(): + """Return dict of commands to run from setup.py.""" + + cmdclass = dict() + + def _find_modules(arg, dirname, files): + for filename in files: + if filename.endswith('.py') and filename != '__init__.py': + arg["%s.%s" % (dirname.replace('/', '.'), + filename[:-3])] = True + + class LocalSDist(sdist.sdist): + """Builds the ChangeLog and Authors files from VC first.""" + + def run(self): + write_git_changelog() + generate_authors() + # sdist.sdist is an old style class, can't use super() + sdist.sdist.run(self) + + cmdclass['sdist'] = LocalSDist + + # If Sphinx is installed on the box running setup.py, + # enable setup.py to build the documentation, otherwise, + # just ignore it + try: + from sphinx.setup_command import BuildDoc + + class LocalBuildDoc(BuildDoc): + def generate_autoindex(self): + print "**Autodocumenting from %s" % os.path.abspath(os.curdir) + modules = {} + option_dict = self.distribution.get_option_dict('build_sphinx') + source_dir = os.path.join(option_dict['source_dir'][1], 'api') + if not os.path.exists(source_dir): + os.makedirs(source_dir) + for pkg in self.distribution.packages: + if '.' not in pkg: + os.path.walk(pkg, _find_modules, modules) + module_list = modules.keys() + module_list.sort() + autoindex_filename = os.path.join(source_dir, 'autoindex.rst') + with open(autoindex_filename, 'w') as autoindex: + autoindex.write(""".. toctree:: + :maxdepth: 1 + +""") + for module in module_list: + output_filename = os.path.join(source_dir, + "%s.rst" % module) + heading = "The :mod:`%s` Module" % module + underline = "=" * len(heading) + values = dict(module=module, heading=heading, + underline=underline) + + print "Generating %s" % output_filename + with open(output_filename, 'w') as output_file: + output_file.write(_rst_template % values) + autoindex.write(" %s.rst\n" % module) + + def run(self): + if not os.getenv('SPHINX_DEBUG'): + self.generate_autoindex() + + for builder in ['html', 'man']: + self.builder = builder + self.finalize_options() + self.project = self.distribution.get_name() + self.version = self.distribution.get_version() + self.release = self.distribution.get_version() + BuildDoc.run(self) + cmdclass['build_sphinx'] = LocalBuildDoc + except ImportError: + pass + + 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""" + if os.path.isdir('.git'): + current_tag = _get_git_current_tag() + if current_tag is not None: + version = current_tag + else: + branch_name = os.getenv('BRANCHNAME', + os.getenv('GERRIT_REFNAME', + get_git_branchname())) + version_suffix = _get_git_next_version_suffix(branch_name) + version = "%s~%s" % (base_version, version_suffix) + write_versioninfo(projectname, version) + return version.split('~')[0] + else: + with open(os.path.join(projectname, 'versioninfo'), 'r') as vinfo: + full_version = vinfo.read().strip() + return full_version.split('~')[0] + + +def get_post_version(projectname): + """Return a version which is equal to the tag that's on the current + revision if there is one, or tag plus number of additional revisions + if the current revision has no tag.""" + + if os.path.isdir('.git'): + version = _get_git_post_version() + write_versioninfo(projectname, version) + return version + return open(os.path.join(projectname, 'versioninfo'), 'r').read().strip() diff --git a/setup.py b/setup.py index 3bce07393d..9a6b5b199d 100644 --- a/setup.py +++ b/setup.py @@ -19,16 +19,11 @@ import os import setuptools -from openstackclient.openstack.common.setup import generate_authors -from openstackclient.openstack.common.setup import parse_requirements -from openstackclient.openstack.common.setup import parse_dependency_links -from openstackclient.openstack.common.setup import write_git_changelog +from openstackclient.openstack.common import setup -requires = parse_requirements() -dependency_links = parse_dependency_links() -write_git_changelog() -generate_authors() +requires = setup.parse_requirements() +dependency_links = setup.parse_dependency_links() def read(fname): @@ -36,7 +31,7 @@ def read(fname): setuptools.setup( name="python-openstackclient", - version="0.1", + version=setup.get_post_version('openstackclient'), description="OpenStack command-line client", long_description=read('README.rst'), url='https://github.com/openstack/python-openstackclient', @@ -55,6 +50,7 @@ setuptools.setup( ], install_requires=requires, dependency_links=dependency_links, + cmdclass=setup.get_cmdclass(), test_suite="nose.collector", entry_points={ 'console_scripts': ['openstack=openstackclient.shell:main'], diff --git a/tools/pip-requires b/tools/pip-requires index 5faba27504..dcb4d0cac8 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -3,6 +3,5 @@ argparse httplib2 prettytable simplejson --e git://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient --e git+https://github.com/openstack/python-novaclient.git#egg=python_novaclient - +https://github.com/openstack/python-keystoneclient/zipball/master#egg=python-keystoneclient +https://github.com/openstack/python-novaclient/zipball/master#egg=python-novaclient