1919Utilities with minimum-depends for use in setup.py
2020"""
2121
22+ import datetime
2223import os
2324import re
2425import subprocess
26+ import sys
27+
28+ from setuptools .command import sdist
2529
2630
2731def 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',
7290def 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():
93118def _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
120171def 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 ()
0 commit comments