blob: 8d19f708fba689ea055dd48f738432fc145ea855 [file] [log] [blame]
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +00001"""Script to generate doxygen documentation.
2"""
Christopher Dunnbd1e8952014-11-19 23:30:47 -06003from __future__ import print_function
4from devtools import tarball
Christopher Dunnff5abe72015-01-24 15:54:08 -06005from contextlib import contextmanager
6import subprocess
Florian Meierbb0c80b2015-01-24 15:48:38 -06007import traceback
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +00008import re
9import os
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000010import sys
11import shutil
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000012
Christopher Dunnff5abe72015-01-24 15:54:08 -060013@contextmanager
14def 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
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +000025def find_program(*filenames):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000026 """find a program in folders path_lst, and sets env[var]
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +000027 @param filenames: a list of possible names of the program to search for
28 @return: the full path of the filename if found, or '' if filename could not be found
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000029"""
30 paths = os.environ.get('PATH', '').split(os.pathsep)
Christopher Dunn494950a2015-01-24 15:29:52 -060031 suffixes = ('win32' in sys.platform) and '.exe .com .bat .cmd' or ''
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +000032 for filename in filenames:
33 for name in [filename+ext for ext in suffixes.split()]:
34 for directory in paths:
35 full_path = os.path.join(directory, name)
36 if os.path.isfile(full_path):
37 return full_path
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000038 return ''
39
40def do_subst_in_file(targetfile, sourcefile, dict):
41 """Replace all instances of the keys of dict with their values.
42 For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'},
43 then all instances of %VERSION% in the file will be replaced with 1.2345 etc.
44 """
Christopher Dunnff5abe72015-01-24 15:54:08 -060045 with open(sourcefile, 'rb') as f:
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000046 contents = f.read()
Christopher Dunnbd1e8952014-11-19 23:30:47 -060047 for (k,v) in list(dict.items()):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000048 v = v.replace('\\','\\\\')
49 contents = re.sub(k, v, contents)
Christopher Dunnff5abe72015-01-24 15:54:08 -060050 with open(targetfile, 'wb') as f:
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000051 f.write(contents)
Christopher Dunnff5abe72015-01-24 15:54:08 -060052
53def getstatusoutput(cmd):
54 """cmd is a list.
55 """
Florian Meierbb0c80b2015-01-24 15:48:38 -060056 try:
57 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
58 output, _ = process.communicate()
59 status = process.returncode
60 except:
61 status = -1
62 output = traceback.format_exc()
Christopher Dunnff5abe72015-01-24 15:54:08 -060063 return status, output
64
65def run_cmd(cmd, silent=False):
Florian Meierbb0c80b2015-01-24 15:48:38 -060066 """Raise exception on failure.
67 """
68 info = 'Running: %r in %r' %(' '.join(cmd), os.getcwd())
69 print(info)
Christopher Dunnff5abe72015-01-24 15:54:08 -060070 sys.stdout.flush()
71 if silent:
72 status, output = getstatusoutput(cmd)
73 else:
74 status, output = os.system(' '.join(cmd)), ''
75 if status:
Florian Meierbb0c80b2015-01-24 15:48:38 -060076 msg = 'Error while %s ...\n\terror=%d, output="""%s"""' %(info, status, output)
77 raise Exception(msg)
78
79def assert_is_exe(path):
80 if not path:
81 raise Exception('path is empty.')
82 if not os.path.isfile(path):
83 raise Exception('%r is not a file.' %path)
84 if not os.access(path, os.X_OK):
85 raise Exception('%r is not executable by this user.' %path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000086
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +000087def run_doxygen(doxygen_path, config_file, working_dir, is_silent):
Florian Meierbb0c80b2015-01-24 15:48:38 -060088 assert_is_exe(doxygen_path)
Christopher Dunn494950a2015-01-24 15:29:52 -060089 config_file = os.path.abspath(config_file)
Christopher Dunnff5abe72015-01-24 15:54:08 -060090 with cd(working_dir):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000091 cmd = [doxygen_path, config_file]
Christopher Dunnff5abe72015-01-24 15:54:08 -060092 run_cmd(cmd, is_silent)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000093
Christopher Dunn494950a2015-01-24 15:29:52 -060094def build_doc(options, make_release=False):
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +000095 if make_release:
96 options.make_tarball = True
97 options.with_dot = True
98 options.with_html_help = True
99 options.with_uml_look = True
100 options.open = False
101 options.silent = True
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000102
103 version = open('version','rt').read().strip()
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +0000104 output_dir = 'dist/doxygen' # relative to doc/doxyfile location.
Christopher Dunn494950a2015-01-24 15:29:52 -0600105 if not os.path.isdir(output_dir):
106 os.makedirs(output_dir)
107 top_dir = os.path.abspath('.')
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000108 html_output_dirname = 'jsoncpp-api-html-' + version
Christopher Dunn494950a2015-01-24 15:29:52 -0600109 tarball_path = os.path.join('dist', html_output_dirname + '.tar.gz')
110 warning_log_path = os.path.join(output_dir, '../jsoncpp-doxygen-warning.log')
111 html_output_path = os.path.join(output_dir, html_output_dirname)
112 def yesno(bool):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000113 return bool and 'YES' or 'NO'
114 subst_keys = {
115 '%JSONCPP_VERSION%': version,
116 '%DOC_TOPDIR%': '',
117 '%TOPDIR%': top_dir,
Christopher Dunn494950a2015-01-24 15:29:52 -0600118 '%HTML_OUTPUT%': os.path.join('..', output_dir, html_output_dirname),
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000119 '%HAVE_DOT%': yesno(options.with_dot),
120 '%DOT_PATH%': os.path.split(options.dot_path)[0],
121 '%HTML_HELP%': yesno(options.with_html_help),
122 '%UML_LOOK%': yesno(options.with_uml_look),
Christopher Dunn494950a2015-01-24 15:29:52 -0600123 '%WARNING_LOG_PATH%': os.path.join('..', warning_log_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000124 }
125
Christopher Dunn494950a2015-01-24 15:29:52 -0600126 if os.path.isdir(output_dir):
Christopher Dunnbd1e8952014-11-19 23:30:47 -0600127 print('Deleting directory:', output_dir)
Christopher Dunn494950a2015-01-24 15:29:52 -0600128 shutil.rmtree(output_dir)
129 if not os.path.isdir(output_dir):
130 os.makedirs(output_dir)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000131
Christopher Dunn494950a2015-01-24 15:29:52 -0600132 do_subst_in_file('doc/doxyfile', 'doc/doxyfile.in', subst_keys)
Christopher Dunnff5abe72015-01-24 15:54:08 -0600133 run_doxygen(options.doxygen_path, 'doc/doxyfile', 'doc', is_silent=options.silent)
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000134 if not options.silent:
Christopher Dunnbd1e8952014-11-19 23:30:47 -0600135 print(open(warning_log_path, 'rb').read())
Christopher Dunn9dd7eea2014-07-05 12:37:27 -0700136 index_path = os.path.abspath(os.path.join('doc', subst_keys['%HTML_OUTPUT%'], 'index.html'))
Christopher Dunnbd1e8952014-11-19 23:30:47 -0600137 print('Generated documentation can be found in:')
138 print(index_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000139 if options.open:
140 import webbrowser
Christopher Dunn494950a2015-01-24 15:29:52 -0600141 webbrowser.open('file://' + index_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000142 if options.make_tarball:
Christopher Dunnbd1e8952014-11-19 23:30:47 -0600143 print('Generating doc tarball to', tarball_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000144 tarball_sources = [
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +0000145 output_dir,
Christopher Dunnf4bc0bf2015-01-24 15:43:23 -0600146 'README.md',
Baptiste Lepilleur7469f1d2010-04-20 21:35:19 +0000147 'LICENSE',
Baptiste Lepilleur130730f2010-03-13 11:14:49 +0000148 'NEWS.txt',
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000149 'version'
150 ]
Christopher Dunn494950a2015-01-24 15:29:52 -0600151 tarball_basedir = os.path.join(output_dir, html_output_dirname)
152 tarball.make_tarball(tarball_path, tarball_sources, tarball_basedir, html_output_dirname)
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +0000153 return tarball_path, html_output_dirname
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000154
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000155def main():
156 usage = """%prog
157 Generates doxygen documentation in build/doxygen.
158 Optionaly makes a tarball of the documentation to dist/.
159
160 Must be started in the project top directory.
161 """
162 from optparse import OptionParser
163 parser = OptionParser(usage=usage)
164 parser.allow_interspersed_args = False
165 parser.add_option('--with-dot', dest="with_dot", action='store_true', default=False,
166 help="""Enable usage of DOT to generate collaboration diagram""")
167 parser.add_option('--dot', dest="dot_path", action='store', default=find_program('dot'),
168 help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""")
169 parser.add_option('--doxygen', dest="doxygen_path", action='store', default=find_program('doxygen'),
170 help="""Path to Doxygen tool. [Default: %default]""")
171 parser.add_option('--with-html-help', dest="with_html_help", action='store_true', default=False,
172 help="""Enable generation of Microsoft HTML HELP""")
173 parser.add_option('--no-uml-look', dest="with_uml_look", action='store_false', default=True,
174 help="""Generates DOT graph without UML look [Default: False]""")
175 parser.add_option('--open', dest="open", action='store_true', default=False,
176 help="""Open the HTML index in the web browser after generation""")
177 parser.add_option('--tarball', dest="make_tarball", action='store_true', default=False,
178 help="""Generates a tarball of the documentation in dist/ directory""")
179 parser.add_option('-s', '--silent', dest="silent", action='store_true', default=False,
180 help="""Hides doxygen output""")
181 parser.enable_interspersed_args()
182 options, args = parser.parse_args()
Christopher Dunn494950a2015-01-24 15:29:52 -0600183 build_doc(options)
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000184
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000185if __name__ == '__main__':
186 main()