You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
7.2KB

  1. """Script to generate doxygen documentation.
  2. """
  3. from __future__ import print_function
  4. from __future__ import unicode_literals
  5. from devtools import tarball
  6. from contextlib import contextmanager
  7. import subprocess
  8. import traceback
  9. import re
  10. import os
  11. import sys
  12. import shutil
  13. @contextmanager
  14. def cd(newdir):
  15. """
  16. http://stackoverflow.com/questions/431684/how-do-i-cd-in-python
  17. """
  18. prevdir = os.getcwd()
  19. os.chdir(newdir)
  20. try:
  21. yield
  22. finally:
  23. os.chdir(prevdir)
  24. def find_program(*filenames):
  25. """find a program in folders path_lst, and sets env[var]
  26. @param filenames: a list of possible names of the program to search for
  27. @return: the full path of the filename if found, or '' if filename could not be found
  28. """
  29. paths = os.environ.get('PATH', '').split(os.pathsep)
  30. suffixes = ('win32' in sys.platform) and '.exe .com .bat .cmd' or ''
  31. for filename in filenames:
  32. for name in [filename+ext for ext in suffixes.split(' ')]:
  33. for directory in paths:
  34. full_path = os.path.join(directory, name)
  35. if os.path.isfile(full_path):
  36. return full_path
  37. return ''
  38. def do_subst_in_file(targetfile, sourcefile, dict):
  39. """Replace all instances of the keys of dict with their values.
  40. For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'},
  41. then all instances of %VERSION% in the file will be replaced with 1.2345 etc.
  42. """
  43. with open(sourcefile, 'r') as f:
  44. contents = f.read()
  45. for (k,v) in list(dict.items()):
  46. v = v.replace('\\','\\\\')
  47. contents = re.sub(k, v, contents)
  48. with open(targetfile, 'w') as f:
  49. f.write(contents)
  50. def getstatusoutput(cmd):
  51. """cmd is a list.
  52. """
  53. try:
  54. process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  55. output, _ = process.communicate()
  56. status = process.returncode
  57. except:
  58. status = -1
  59. output = traceback.format_exc()
  60. return status, output
  61. def run_cmd(cmd, silent=False):
  62. """Raise exception on failure.
  63. """
  64. info = 'Running: %r in %r' %(' '.join(cmd), os.getcwd())
  65. print(info)
  66. sys.stdout.flush()
  67. if silent:
  68. status, output = getstatusoutput(cmd)
  69. else:
  70. status, output = subprocess.call(cmd), ''
  71. if status:
  72. msg = 'Error while %s ...\n\terror=%d, output="""%s"""' %(info, status, output)
  73. raise Exception(msg)
  74. def assert_is_exe(path):
  75. if not path:
  76. raise Exception('path is empty.')
  77. if not os.path.isfile(path):
  78. raise Exception('%r is not a file.' %path)
  79. if not os.access(path, os.X_OK):
  80. raise Exception('%r is not executable by this user.' %path)
  81. def run_doxygen(doxygen_path, config_file, working_dir, is_silent):
  82. assert_is_exe(doxygen_path)
  83. config_file = os.path.abspath(config_file)
  84. with cd(working_dir):
  85. cmd = [doxygen_path, config_file]
  86. run_cmd(cmd, is_silent)
  87. def build_doc(options, make_release=False):
  88. if make_release:
  89. options.make_tarball = True
  90. options.with_dot = True
  91. options.with_html_help = True
  92. options.with_uml_look = True
  93. options.open = False
  94. options.silent = True
  95. version = open('version', 'rt').read().strip()
  96. output_dir = 'dist/doxygen' # relative to doc/doxyfile location.
  97. if not os.path.isdir(output_dir):
  98. os.makedirs(output_dir)
  99. top_dir = os.path.abspath('.')
  100. html_output_dirname = 'jsoncpp-api-html-' + version
  101. tarball_path = os.path.join('dist', html_output_dirname + '.tar.gz')
  102. warning_log_path = os.path.join(output_dir, '../jsoncpp-doxygen-warning.log')
  103. html_output_path = os.path.join(output_dir, html_output_dirname)
  104. def yesno(bool):
  105. return bool and 'YES' or 'NO'
  106. subst_keys = {
  107. '%JSONCPP_VERSION%': version,
  108. '%DOC_TOPDIR%': '',
  109. '%TOPDIR%': top_dir,
  110. '%HTML_OUTPUT%': os.path.join('..', output_dir, html_output_dirname),
  111. '%HAVE_DOT%': yesno(options.with_dot),
  112. '%DOT_PATH%': os.path.split(options.dot_path)[0],
  113. '%HTML_HELP%': yesno(options.with_html_help),
  114. '%UML_LOOK%': yesno(options.with_uml_look),
  115. '%WARNING_LOG_PATH%': os.path.join('..', warning_log_path)
  116. }
  117. if os.path.isdir(output_dir):
  118. print('Deleting directory:', output_dir)
  119. shutil.rmtree(output_dir)
  120. if not os.path.isdir(output_dir):
  121. os.makedirs(output_dir)
  122. do_subst_in_file('doc/doxyfile', options.doxyfile_input_path, subst_keys)
  123. run_doxygen(options.doxygen_path, 'doc/doxyfile', 'doc', is_silent=options.silent)
  124. if not options.silent:
  125. print(open(warning_log_path, 'r').read())
  126. index_path = os.path.abspath(os.path.join('doc', subst_keys['%HTML_OUTPUT%'], 'index.html'))
  127. print('Generated documentation can be found in:')
  128. print(index_path)
  129. if options.open:
  130. import webbrowser
  131. webbrowser.open('file://' + index_path)
  132. if options.make_tarball:
  133. print('Generating doc tarball to', tarball_path)
  134. tarball_sources = [
  135. output_dir,
  136. 'README.md',
  137. 'LICENSE',
  138. 'NEWS.txt',
  139. 'version'
  140. ]
  141. tarball_basedir = os.path.join(output_dir, html_output_dirname)
  142. tarball.make_tarball(tarball_path, tarball_sources, tarball_basedir, html_output_dirname)
  143. return tarball_path, html_output_dirname
  144. def main():
  145. usage = """%prog
  146. Generates doxygen documentation in build/doxygen.
  147. Optionally makes a tarball of the documentation to dist/.
  148. Must be started in the project top directory.
  149. """
  150. from optparse import OptionParser
  151. parser = OptionParser(usage=usage)
  152. parser.allow_interspersed_args = False
  153. parser.add_option('--with-dot', dest="with_dot", action='store_true', default=False,
  154. help="""Enable usage of DOT to generate collaboration diagram""")
  155. parser.add_option('--dot', dest="dot_path", action='store', default=find_program('dot'),
  156. help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""")
  157. parser.add_option('--doxygen', dest="doxygen_path", action='store', default=find_program('doxygen'),
  158. help="""Path to Doxygen tool. [Default: %default]""")
  159. parser.add_option('--in', dest="doxyfile_input_path", action='store', default='doc/doxyfile.in',
  160. help="""Path to doxygen inputs. [Default: %default]""")
  161. parser.add_option('--with-html-help', dest="with_html_help", action='store_true', default=False,
  162. help="""Enable generation of Microsoft HTML HELP""")
  163. parser.add_option('--no-uml-look', dest="with_uml_look", action='store_false', default=True,
  164. help="""Generates DOT graph without UML look [Default: False]""")
  165. parser.add_option('--open', dest="open", action='store_true', default=False,
  166. help="""Open the HTML index in the web browser after generation""")
  167. parser.add_option('--tarball', dest="make_tarball", action='store_true', default=False,
  168. help="""Generates a tarball of the documentation in dist/ directory""")
  169. parser.add_option('-s', '--silent', dest="silent", action='store_true', default=False,
  170. help="""Hides doxygen output""")
  171. parser.enable_interspersed_args()
  172. options, args = parser.parse_args()
  173. build_doc(options)
  174. if __name__ == '__main__':
  175. main()