1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2011 OpenStack LLC.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
19
Utilities with minimum-depends for use in setup.py
28
from setuptools.command import sdist
31
def parse_mailmap(mailmap='.mailmap'):
33
if os.path.exists(mailmap):
34
with open(mailmap, 'r') as fp:
37
if not l.startswith('#') and ' ' in l:
38
canonical_email, alias = [x for x in l.split(' ')
40
mapping[alias] = canonical_email
44
def canonicalize_emails(changelog, mapping):
45
"""Takes in a string and an email alias mapping and replaces all
46
instances of the aliases in the string with their real email.
48
for alias, email in mapping.iteritems():
49
changelog = changelog.replace(alias, email)
53
# Get requirements from the first file that exists
54
def get_reqs_from_files(requirements_files):
55
for requirements_file in requirements_files:
56
if os.path.exists(requirements_file):
57
with open(requirements_file, 'r') as fil:
58
return fil.read().split('\n')
62
def parse_requirements(requirements_files=['requirements.txt',
63
'tools/pip-requires']):
65
for line in get_reqs_from_files(requirements_files):
66
# For the requirements list, we need to inject only the portion
67
# after egg= so that distutils knows the package it's looking for
69
# -e git://github.com/openstack/nova/master#egg=nova
70
if re.match(r'\s*-e\s+', line):
71
requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1',
74
# http://github.com/openstack/nova/zipball/master#egg=nova
75
elif re.match(r'\s*https?:', line):
76
requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1',
78
# -f lines are for index locations, and don't get used here
79
elif re.match(r'\s*-f\s+', line):
81
# argparse is part of the standard library starting with 2.7
82
# adding it to the requirements list screws distro installs
83
elif line == 'argparse' and sys.version_info >= (2, 7):
86
requirements.append(line)
91
def parse_dependency_links(requirements_files=['requirements.txt',
92
'tools/pip-requires']):
94
# dependency_links inject alternate locations to find packages listed
96
for line in get_reqs_from_files(requirements_files):
97
# skip comments and blank lines
98
if re.match(r'(\s*#)|(\s*$)', line):
100
# lines with -e or -f need the whole line, minus the flag
101
if re.match(r'\s*-[ef]\s+', line):
102
dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
103
# lines that are only urls can go in unmolested
104
elif re.match(r'\s*https?:', line):
105
dependency_links.append(line)
106
return dependency_links
109
def write_requirements():
110
venv = os.environ.get('VIRTUAL_ENV', None)
112
with open("requirements.txt", "w") as req_file:
113
output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"],
114
stdout=subprocess.PIPE)
115
requirements = output.communicate()[0].strip()
116
req_file.write(requirements)
119
def _run_shell_command(cmd):
121
output = subprocess.Popen(["cmd.exe", "/C", cmd],
122
stdout=subprocess.PIPE)
124
output = subprocess.Popen(["/bin/sh", "-c", cmd],
125
stdout=subprocess.PIPE)
126
out = output.communicate()
129
if len(out[0].strip()) == 0:
131
return out[0].strip()
134
def _get_git_next_version_suffix(branch_name):
135
datestamp = datetime.datetime.now().strftime('%Y%m%d')
136
if branch_name == 'milestone-proposed':
140
_run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*")
141
milestone_cmd = "git show meta/openstack/release:%s" % branch_name
142
milestonever = _run_shell_command(milestone_cmd)
144
first_half = "%s~%s" % (milestonever, datestamp)
146
first_half = datestamp
148
post_version = _get_git_post_version()
149
# post version should look like:
151
# where the bit after the last . is the short sha, and the bit between
152
# the last and second to last is the revno count
153
(revno, sha) = post_version.split(".")[-2:]
154
second_half = "%s%s.%s" % (revno_prefix, revno, sha)
155
return ".".join((first_half, second_half))
158
def _get_git_current_tag():
159
return _run_shell_command("git tag --contains HEAD")
162
def _get_git_tag_info():
163
return _run_shell_command("git describe --tags")
166
def _get_git_post_version():
167
current_tag = _get_git_current_tag()
168
if current_tag is not None:
171
tag_info = _get_git_tag_info()
174
cmd = "git --no-pager log --oneline"
175
out = _run_shell_command(cmd)
176
revno = len(out.split("\n"))
177
sha = _run_shell_command("git describe --always")
179
tag_infos = tag_info.split("-")
180
base_version = "-".join(tag_infos[:-2])
181
(revno, sha) = tag_infos[-2:]
182
return "%s.%s.%s" % (base_version, revno, sha)
185
def write_git_changelog():
186
"""Write a changelog based on the git changelog."""
187
new_changelog = 'ChangeLog'
188
if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'):
189
if os.path.isdir('.git'):
190
git_log_cmd = 'git log --stat'
191
changelog = _run_shell_command(git_log_cmd)
192
mailmap = parse_mailmap()
193
with open(new_changelog, "w") as changelog_file:
194
changelog_file.write(canonicalize_emails(changelog, mailmap))
196
open(new_changelog, 'w').close()
199
def generate_authors():
200
"""Create AUTHORS file using git commits."""
201
jenkins_email = 'jenkins@review.(openstack|stackforge).org'
202
old_authors = 'AUTHORS.in'
203
new_authors = 'AUTHORS'
204
if not os.getenv('SKIP_GENERATE_AUTHORS'):
205
if os.path.isdir('.git'):
206
# don't include jenkins email address in AUTHORS file
207
git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | "
208
"egrep -v '" + jenkins_email + "'")
209
changelog = _run_shell_command(git_log_cmd)
210
mailmap = parse_mailmap()
211
with open(new_authors, 'w') as new_authors_fh:
212
new_authors_fh.write(canonicalize_emails(changelog, mailmap))
213
if os.path.exists(old_authors):
214
with open(old_authors, "r") as old_authors_fh:
215
new_authors_fh.write('\n' + old_authors_fh.read())
217
open(new_authors, 'w').close()
220
_rst_template = """%(heading)s
223
.. automodule:: %(module)s
230
def read_versioninfo(project):
231
"""Read the versioninfo file. If it doesn't exist, we're in a github
232
zipball, and there's really no way to know what version we really
233
are, but that should be ok, because the utility of that should be
234
just about nil if this code path is in use in the first place."""
235
versioninfo_path = os.path.join(project, 'versioninfo')
236
if os.path.exists(versioninfo_path):
237
with open(versioninfo_path, 'r') as vinfo:
238
version = vinfo.read().strip()
244
def write_versioninfo(project, version):
245
"""Write a simple file containing the version of the package."""
246
with open(os.path.join(project, 'versioninfo'), 'w') as fil:
247
fil.write("%s\n" % version)
251
"""Return dict of commands to run from setup.py."""
255
def _find_modules(arg, dirname, files):
256
for filename in files:
257
if filename.endswith('.py') and filename != '__init__.py':
258
arg["%s.%s" % (dirname.replace('/', '.'),
259
filename[:-3])] = True
261
class LocalSDist(sdist.sdist):
262
"""Builds the ChangeLog and Authors files from VC first."""
265
write_git_changelog()
267
# sdist.sdist is an old style class, can't use super()
268
sdist.sdist.run(self)
270
cmdclass['sdist'] = LocalSDist
272
# If Sphinx is installed on the box running setup.py,
273
# enable setup.py to build the documentation, otherwise,
276
from sphinx.setup_command import BuildDoc
278
class LocalBuildDoc(BuildDoc):
280
builders = ['html', 'man']
282
def generate_autoindex(self):
283
print "**Autodocumenting from %s" % os.path.abspath(os.curdir)
285
option_dict = self.distribution.get_option_dict('build_sphinx')
286
source_dir = os.path.join(option_dict['source_dir'][1], 'api')
287
if not os.path.exists(source_dir):
288
os.makedirs(source_dir)
289
for pkg in self.distribution.packages:
291
os.path.walk(pkg, _find_modules, modules)
292
module_list = modules.keys()
294
autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
295
with open(autoindex_filename, 'w') as autoindex:
296
autoindex.write(""".. toctree::
300
for module in module_list:
301
output_filename = os.path.join(source_dir,
303
heading = "The :mod:`%s` Module" % module
304
underline = "=" * len(heading)
305
values = dict(module=module, heading=heading,
308
print "Generating %s" % output_filename
309
with open(output_filename, 'w') as output_file:
310
output_file.write(_rst_template % values)
311
autoindex.write(" %s.rst\n" % module)
314
if not os.getenv('SPHINX_DEBUG'):
315
self.generate_autoindex()
317
for builder in self.builders:
318
self.builder = builder
319
self.finalize_options()
320
self.project = self.distribution.get_name()
321
self.version = self.distribution.get_version()
322
self.release = self.distribution.get_version()
325
class LocalBuildLatex(LocalBuildDoc):
328
cmdclass['build_sphinx'] = LocalBuildDoc
329
cmdclass['build_sphinx_latex'] = LocalBuildLatex
336
def get_git_branchname():
337
for branch in _run_shell_command("git branch --color=never").split("\n"):
338
if branch.startswith('*'):
339
_branch_name = branch.split()[1].strip()
340
if _branch_name == "(no":
341
_branch_name = "no-branch"
345
def get_pre_version(projectname, base_version):
346
"""Return a version which is leading up to a version that will
347
be released in the future."""
348
if os.path.isdir('.git'):
349
current_tag = _get_git_current_tag()
350
if current_tag is not None:
351
version = current_tag
353
branch_name = os.getenv('BRANCHNAME',
354
os.getenv('GERRIT_REFNAME',
355
get_git_branchname()))
356
version_suffix = _get_git_next_version_suffix(branch_name)
357
version = "%s~%s" % (base_version, version_suffix)
358
write_versioninfo(projectname, version)
361
version = read_versioninfo(projectname)
365
def get_post_version(projectname):
366
"""Return a version which is equal to the tag that's on the current
367
revision if there is one, or tag plus number of additional revisions
368
if the current revision has no tag."""
370
if os.path.isdir('.git'):
371
version = _get_git_post_version()
372
write_versioninfo(projectname, version)
374
return read_versioninfo(projectname)