Skip to content

Commit 99586e0

Browse files
committed
Add post-tag versioning.
Change-Id: I98e5f7aa788b1ab1a866b21e0a17a63b8d5efae3
1 parent 01dd454 commit 99586e0

5 files changed

Lines changed: 215 additions & 34 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ python_openstackclient.egg-info
1212
.tox/
1313
ChangeLog
1414
TAGS
15+
openstackclient/versioninfo

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
include AUTHORS
22
include LICENSE
33
include README.rst
4+
include openstackclient/versioninfo
45
recursive-include doc *
56
recursive-include tests *

openstackclient/openstack/common/setup.py

Lines changed: 206 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919
Utilities with minimum-depends for use in setup.py
2020
"""
2121

22+
import datetime
2223
import os
2324
import re
2425
import subprocess
26+
import sys
27+
28+
from setuptools.command import sdist
2529

2630

2731
def parse_mailmap(mailmap='.mailmap'):
@@ -58,11 +62,25 @@ def parse_requirements(requirements_files=['requirements.txt',
5862
'tools/pip-requires']):
5963
requirements = []
6064
for line in get_reqs_from_files(requirements_files):
65+
# For the requirements list, we need to inject only the portion
66+
# after egg= so that distutils knows the package it's looking for
67+
# such as:
68+
# -e git://github.com/openstack/nova/master#egg=nova
6169
if re.match(r'\s*-e\s+', line):
6270
requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1',
6371
line))
72+
# such as:
73+
# http://github.com/openstack/nova/zipball/master#egg=nova
74+
elif re.match(r'\s*https?:', line):
75+
requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1',
76+
line))
77+
# -f lines are for index locations, and don't get used here
6478
elif re.match(r'\s*-f\s+', line):
6579
pass
80+
# argparse is part of the standard library starting with 2.7
81+
# adding it to the requirements list screws distro installs
82+
elif line == 'argparse' and sys.version_info >= (2, 7):
83+
pass
6684
else:
6785
requirements.append(line)
6886

@@ -72,11 +90,18 @@ def parse_requirements(requirements_files=['requirements.txt',
7290
def parse_dependency_links(requirements_files=['requirements.txt',
7391
'tools/pip-requires']):
7492
dependency_links = []
93+
# dependency_links inject alternate locations to find packages listed
94+
# in requirements
7595
for line in get_reqs_from_files(requirements_files):
96+
# skip comments and blank lines
7697
if re.match(r'(\s*#)|(\s*$)', line):
7798
continue
99+
# lines with -e or -f need the whole line, minus the flag
78100
if re.match(r'\s*-[ef]\s+', line):
79101
dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
102+
# lines that are only urls can go in unmolested
103+
elif re.match(r'\s*https?:', line):
104+
dependency_links.append(line)
80105
return dependency_links
81106

82107

@@ -93,28 +118,54 @@ def write_requirements():
93118
def _run_shell_command(cmd):
94119
output = subprocess.Popen(["/bin/sh", "-c", cmd],
95120
stdout=subprocess.PIPE)
96-
return output.communicate()[0].strip()
121+
out = output.communicate()
122+
if len(out) == 0:
123+
return None
124+
if len(out[0].strip()) == 0:
125+
return None
126+
return out[0].strip()
97127

98128

99-
def write_vcsversion(location):
100-
"""Produce a vcsversion dict that mimics the old one produced by bzr.
101-
"""
102-
if os.path.isdir('.git'):
103-
branch_nick_cmd = 'git branch | grep -Ei "\* (.*)" | cut -f2 -d" "'
104-
branch_nick = _run_shell_command(branch_nick_cmd)
105-
revid_cmd = "git rev-parse HEAD"
106-
revid = _run_shell_command(revid_cmd).split()[0]
107-
revno_cmd = "git log --oneline | wc -l"
108-
revno = _run_shell_command(revno_cmd)
109-
with open(location, 'w') as version_file:
110-
version_file.write("""
111-
# This file is automatically generated by setup.py, So don't edit it. :)
112-
version_info = {
113-
'branch_nick': '%s',
114-
'revision_id': '%s',
115-
'revno': %s
116-
}
117-
""" % (branch_nick, revid, revno))
129+
def _get_git_next_version_suffix(branch_name):
130+
datestamp = datetime.datetime.now().strftime('%Y%m%d')
131+
if branch_name == 'milestone-proposed':
132+
revno_prefix = "r"
133+
else:
134+
revno_prefix = ""
135+
_run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*")
136+
milestone_cmd = "git show meta/openstack/release:%s" % branch_name
137+
milestonever = _run_shell_command(milestone_cmd)
138+
if not milestonever:
139+
milestonever = ""
140+
post_version = _get_git_post_version()
141+
revno = post_version.split(".")[-1]
142+
return "%s~%s.%s%s" % (milestonever, datestamp, revno_prefix, revno)
143+
144+
145+
def _get_git_current_tag():
146+
return _run_shell_command("git tag --contains HEAD")
147+
148+
149+
def _get_git_tag_info():
150+
return _run_shell_command("git describe --tags")
151+
152+
153+
def _get_git_post_version():
154+
current_tag = _get_git_current_tag()
155+
if current_tag is not None:
156+
return current_tag
157+
else:
158+
tag_info = _get_git_tag_info()
159+
if tag_info is None:
160+
base_version = "0.0"
161+
cmd = "git --no-pager log --oneline"
162+
out = _run_shell_command(cmd)
163+
revno = len(out.split("\n"))
164+
else:
165+
tag_infos = tag_info.split("-")
166+
base_version = "-".join(tag_infos[:-2])
167+
revno = tag_infos[-2]
168+
return "%s.%s" % (base_version, revno)
118169

119170

120171
def write_git_changelog():
@@ -134,12 +185,145 @@ def generate_authors():
134185
new_authors = 'AUTHORS'
135186
if os.path.isdir('.git'):
136187
# don't include jenkins email address in AUTHORS file
137-
git_log_cmd = "git log --format='%aN <%aE>' | sort -u | " \
138-
"grep -v " + jenkins_email
188+
git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | "
189+
"grep -v " + jenkins_email)
139190
changelog = _run_shell_command(git_log_cmd)
140191
mailmap = parse_mailmap()
141192
with open(new_authors, 'w') as new_authors_fh:
142193
new_authors_fh.write(canonicalize_emails(changelog, mailmap))
143194
if os.path.exists(old_authors):
144195
with open(old_authors, "r") as old_authors_fh:
145196
new_authors_fh.write('\n' + old_authors_fh.read())
197+
198+
_rst_template = """%(heading)s
199+
%(underline)s
200+
201+
.. automodule:: %(module)s
202+
:members:
203+
:undoc-members:
204+
:show-inheritance:
205+
"""
206+
207+
208+
def write_versioninfo(project, version):
209+
"""Write a simple file containing the version of the package."""
210+
open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version)
211+
212+
213+
def get_cmdclass():
214+
"""Return dict of commands to run from setup.py."""
215+
216+
cmdclass = dict()
217+
218+
def _find_modules(arg, dirname, files):
219+
for filename in files:
220+
if filename.endswith('.py') and filename != '__init__.py':
221+
arg["%s.%s" % (dirname.replace('/', '.'),
222+
filename[:-3])] = True
223+
224+
class LocalSDist(sdist.sdist):
225+
"""Builds the ChangeLog and Authors files from VC first."""
226+
227+
def run(self):
228+
write_git_changelog()
229+
generate_authors()
230+
# sdist.sdist is an old style class, can't use super()
231+
sdist.sdist.run(self)
232+
233+
cmdclass['sdist'] = LocalSDist
234+
235+
# If Sphinx is installed on the box running setup.py,
236+
# enable setup.py to build the documentation, otherwise,
237+
# just ignore it
238+
try:
239+
from sphinx.setup_command import BuildDoc
240+
241+
class LocalBuildDoc(BuildDoc):
242+
def generate_autoindex(self):
243+
print "**Autodocumenting from %s" % os.path.abspath(os.curdir)
244+
modules = {}
245+
option_dict = self.distribution.get_option_dict('build_sphinx')
246+
source_dir = os.path.join(option_dict['source_dir'][1], 'api')
247+
if not os.path.exists(source_dir):
248+
os.makedirs(source_dir)
249+
for pkg in self.distribution.packages:
250+
if '.' not in pkg:
251+
os.path.walk(pkg, _find_modules, modules)
252+
module_list = modules.keys()
253+
module_list.sort()
254+
autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
255+
with open(autoindex_filename, 'w') as autoindex:
256+
autoindex.write(""".. toctree::
257+
:maxdepth: 1
258+
259+
""")
260+
for module in module_list:
261+
output_filename = os.path.join(source_dir,
262+
"%s.rst" % module)
263+
heading = "The :mod:`%s` Module" % module
264+
underline = "=" * len(heading)
265+
values = dict(module=module, heading=heading,
266+
underline=underline)
267+
268+
print "Generating %s" % output_filename
269+
with open(output_filename, 'w') as output_file:
270+
output_file.write(_rst_template % values)
271+
autoindex.write(" %s.rst\n" % module)
272+
273+
def run(self):
274+
if not os.getenv('SPHINX_DEBUG'):
275+
self.generate_autoindex()
276+
277+
for builder in ['html', 'man']:
278+
self.builder = builder
279+
self.finalize_options()
280+
self.project = self.distribution.get_name()
281+
self.version = self.distribution.get_version()
282+
self.release = self.distribution.get_version()
283+
BuildDoc.run(self)
284+
cmdclass['build_sphinx'] = LocalBuildDoc
285+
except ImportError:
286+
pass
287+
288+
return cmdclass
289+
290+
291+
def get_git_branchname():
292+
for branch in _run_shell_command("git branch --color=never").split("\n"):
293+
if branch.startswith('*'):
294+
_branch_name = branch.split()[1].strip()
295+
if _branch_name == "(no":
296+
_branch_name = "no-branch"
297+
return _branch_name
298+
299+
300+
def get_pre_version(projectname, base_version):
301+
"""Return a version which is based"""
302+
if os.path.isdir('.git'):
303+
current_tag = _get_git_current_tag()
304+
if current_tag is not None:
305+
version = current_tag
306+
else:
307+
branch_name = os.getenv('BRANCHNAME',
308+
os.getenv('GERRIT_REFNAME',
309+
get_git_branchname()))
310+
version_suffix = _get_git_next_version_suffix(branch_name)
311+
version = "%s~%s" % (base_version, version_suffix)
312+
write_versioninfo(projectname, version)
313+
return version.split('~')[0]
314+
else:
315+
with open(os.path.join(projectname, 'versioninfo'), 'r') as vinfo:
316+
full_version = vinfo.read().strip()
317+
return full_version.split('~')[0]
318+
319+
320+
def get_post_version(projectname):
321+
"""Return a version which is equal to the tag that's on the current
322+
revision if there is one, or tag plus number of additional revisions
323+
if the current revision has no tag."""
324+
325+
if os.path.isdir('.git'):
326+
version = _get_git_post_version()
327+
write_versioninfo(projectname, version)
328+
return version
329+
return open(os.path.join(projectname, 'versioninfo'), 'r').read().strip()

setup.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,19 @@
1919

2020
import setuptools
2121

22-
from openstackclient.openstack.common.setup import generate_authors
23-
from openstackclient.openstack.common.setup import parse_requirements
24-
from openstackclient.openstack.common.setup import parse_dependency_links
25-
from openstackclient.openstack.common.setup import write_git_changelog
22+
from openstackclient.openstack.common import setup
2623

2724

28-
requires = parse_requirements()
29-
dependency_links = parse_dependency_links()
30-
write_git_changelog()
31-
generate_authors()
25+
requires = setup.parse_requirements()
26+
dependency_links = setup.parse_dependency_links()
3227

3328

3429
def read(fname):
3530
return open(os.path.join(os.path.dirname(__file__), fname)).read()
3631

3732
setuptools.setup(
3833
name="python-openstackclient",
39-
version="0.1",
34+
version=setup.get_post_version('openstackclient'),
4035
description="OpenStack command-line client",
4136
long_description=read('README.rst'),
4237
url='https://github.com/openstack/python-openstackclient',
@@ -55,6 +50,7 @@ def read(fname):
5550
],
5651
install_requires=requires,
5752
dependency_links=dependency_links,
53+
cmdclass=setup.get_cmdclass(),
5854
test_suite="nose.collector",
5955
entry_points={
6056
'console_scripts': ['openstack=openstackclient.shell:main'],

tools/pip-requires

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ argparse
33
httplib2
44
prettytable
55
simplejson
6-
-e git://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient
7-
-e git+https://github.com/openstack/python-novaclient.git#egg=python_novaclient
8-
6+
https://github.com/openstack/python-keystoneclient/zipball/master#egg=python-keystoneclient
7+
https://github.com/openstack/python-novaclient/zipball/master#egg=python-novaclient

0 commit comments

Comments
 (0)