blob: 862c1f43ff4b0d634921da4f7903ab3c2202d871 [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
Christopher Dunnf3576882015-01-24 16:20:25 -06004from __future__ import unicode_literals
Christopher Dunnbd1e8952014-11-19 23:30:47 -06005from devtools import tarball
Christopher Dunnff5abe72015-01-24 15:54:08 -06006from contextlib import contextmanager
7import subprocess
Florian Meierbb0c80b2015-01-24 15:48:38 -06008import traceback
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +00009import re
10import os
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000011import sys
12import shutil
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000013
Christopher Dunnff5abe72015-01-24 15:54:08 -060014@contextmanager
15def cd(newdir):
16 """
17 http://stackoverflow.com/questions/431684/how-do-i-cd-in-python
18 """
19 prevdir = os.getcwd()
20 os.chdir(newdir)
21 try:
22 yield
23 finally:
24 os.chdir(prevdir)
25
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +000026def find_program(*filenames):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000027 """find a program in folders path_lst, and sets env[var]
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +000028 @param filenames: a list of possible names of the program to search for
29 @return: the full path of the filename if found, or '' if filename could not be found
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000030"""
31 paths = os.environ.get('PATH', '').split(os.pathsep)
Christopher Dunn494950a2015-01-24 15:29:52 -060032 suffixes = ('win32' in sys.platform) and '.exe .com .bat .cmd' or ''
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +000033 for filename in filenames:
selaselahc0838352015-03-19 19:18:58 +080034 for name in [filename+ext for ext in suffixes.split(' ')]:
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +000035 for directory in paths:
36 full_path = os.path.join(directory, name)
37 if os.path.isfile(full_path):
38 return full_path
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000039 return ''
40
41def do_subst_in_file(targetfile, sourcefile, dict):
42 """Replace all instances of the keys of dict with their values.
43 For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'},
44 then all instances of %VERSION% in the file will be replaced with 1.2345 etc.
45 """
Christopher Dunnf3576882015-01-24 16:20:25 -060046 with open(sourcefile, 'r') as f:
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000047 contents = f.read()
Christopher Dunnbd1e8952014-11-19 23:30:47 -060048 for (k,v) in list(dict.items()):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000049 v = v.replace('\\','\\\\')
50 contents = re.sub(k, v, contents)
Christopher Dunnf3576882015-01-24 16:20:25 -060051 with open(targetfile, 'w') as f:
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000052 f.write(contents)
Christopher Dunnff5abe72015-01-24 15:54:08 -060053
54def getstatusoutput(cmd):
55 """cmd is a list.
56 """
Florian Meierbb0c80b2015-01-24 15:48:38 -060057 try:
58 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
59 output, _ = process.communicate()
60 status = process.returncode
61 except:
62 status = -1
63 output = traceback.format_exc()
Christopher Dunnff5abe72015-01-24 15:54:08 -060064 return status, output
65
66def run_cmd(cmd, silent=False):
Florian Meierbb0c80b2015-01-24 15:48:38 -060067 """Raise exception on failure.
68 """
69 info = 'Running: %r in %r' %(' '.join(cmd), os.getcwd())
70 print(info)
Christopher Dunnff5abe72015-01-24 15:54:08 -060071 sys.stdout.flush()
72 if silent:
73 status, output = getstatusoutput(cmd)
74 else:
Christopher Dunnbcb83b92015-06-04 21:57:29 -070075 status, output = subprocess.call(cmd), ''
Christopher Dunnff5abe72015-01-24 15:54:08 -060076 if status:
Florian Meierbb0c80b2015-01-24 15:48:38 -060077 msg = 'Error while %s ...\n\terror=%d, output="""%s"""' %(info, status, output)
78 raise Exception(msg)
79
80def assert_is_exe(path):
81 if not path:
82 raise Exception('path is empty.')
83 if not os.path.isfile(path):
84 raise Exception('%r is not a file.' %path)
85 if not os.access(path, os.X_OK):
86 raise Exception('%r is not executable by this user.' %path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000087
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +000088def run_doxygen(doxygen_path, config_file, working_dir, is_silent):
Florian Meierbb0c80b2015-01-24 15:48:38 -060089 assert_is_exe(doxygen_path)
Christopher Dunn494950a2015-01-24 15:29:52 -060090 config_file = os.path.abspath(config_file)
Christopher Dunnff5abe72015-01-24 15:54:08 -060091 with cd(working_dir):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000092 cmd = [doxygen_path, config_file]
Christopher Dunnff5abe72015-01-24 15:54:08 -060093 run_cmd(cmd, is_silent)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +000094
Christopher Dunn494950a2015-01-24 15:29:52 -060095def build_doc(options, make_release=False):
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +000096 if make_release:
97 options.make_tarball = True
98 options.with_dot = True
99 options.with_html_help = True
100 options.with_uml_look = True
101 options.open = False
102 options.silent = True
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000103
Christopher Dunnf3576882015-01-24 16:20:25 -0600104 version = open('version', 'rt').read().strip()
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +0000105 output_dir = 'dist/doxygen' # relative to doc/doxyfile location.
Christopher Dunn494950a2015-01-24 15:29:52 -0600106 if not os.path.isdir(output_dir):
107 os.makedirs(output_dir)
108 top_dir = os.path.abspath('.')
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000109 html_output_dirname = 'jsoncpp-api-html-' + version
Christopher Dunn494950a2015-01-24 15:29:52 -0600110 tarball_path = os.path.join('dist', html_output_dirname + '.tar.gz')
111 warning_log_path = os.path.join(output_dir, '../jsoncpp-doxygen-warning.log')
112 html_output_path = os.path.join(output_dir, html_output_dirname)
113 def yesno(bool):
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000114 return bool and 'YES' or 'NO'
115 subst_keys = {
116 '%JSONCPP_VERSION%': version,
117 '%DOC_TOPDIR%': '',
118 '%TOPDIR%': top_dir,
Christopher Dunn494950a2015-01-24 15:29:52 -0600119 '%HTML_OUTPUT%': os.path.join('..', output_dir, html_output_dirname),
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000120 '%HAVE_DOT%': yesno(options.with_dot),
121 '%DOT_PATH%': os.path.split(options.dot_path)[0],
122 '%HTML_HELP%': yesno(options.with_html_help),
123 '%UML_LOOK%': yesno(options.with_uml_look),
Christopher Dunn494950a2015-01-24 15:29:52 -0600124 '%WARNING_LOG_PATH%': os.path.join('..', warning_log_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000125 }
126
Christopher Dunn494950a2015-01-24 15:29:52 -0600127 if os.path.isdir(output_dir):
Christopher Dunnbd1e8952014-11-19 23:30:47 -0600128 print('Deleting directory:', output_dir)
Christopher Dunn494950a2015-01-24 15:29:52 -0600129 shutil.rmtree(output_dir)
130 if not os.path.isdir(output_dir):
131 os.makedirs(output_dir)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000132
Christopher Dunn16bdfd82015-02-09 11:15:11 -0600133 do_subst_in_file('doc/doxyfile', options.doxyfile_input_path, subst_keys)
Christopher Dunnff5abe72015-01-24 15:54:08 -0600134 run_doxygen(options.doxygen_path, 'doc/doxyfile', 'doc', is_silent=options.silent)
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000135 if not options.silent:
Christopher Dunnf3576882015-01-24 16:20:25 -0600136 print(open(warning_log_path, 'r').read())
Christopher Dunn9dd7eea2014-07-05 12:37:27 -0700137 index_path = os.path.abspath(os.path.join('doc', subst_keys['%HTML_OUTPUT%'], 'index.html'))
Christopher Dunnbd1e8952014-11-19 23:30:47 -0600138 print('Generated documentation can be found in:')
139 print(index_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000140 if options.open:
141 import webbrowser
Christopher Dunn494950a2015-01-24 15:29:52 -0600142 webbrowser.open('file://' + index_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000143 if options.make_tarball:
Christopher Dunnbd1e8952014-11-19 23:30:47 -0600144 print('Generating doc tarball to', tarball_path)
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000145 tarball_sources = [
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +0000146 output_dir,
Christopher Dunnf4bc0bf2015-01-24 15:43:23 -0600147 'README.md',
Baptiste Lepilleur7469f1d2010-04-20 21:35:19 +0000148 'LICENSE',
Baptiste Lepilleur130730f2010-03-13 11:14:49 +0000149 'NEWS.txt',
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000150 'version'
151 ]
Christopher Dunn494950a2015-01-24 15:29:52 -0600152 tarball_basedir = os.path.join(output_dir, html_output_dirname)
153 tarball.make_tarball(tarball_path, tarball_sources, tarball_basedir, html_output_dirname)
Baptiste Lepilleur64ba0622010-02-24 23:08:47 +0000154 return tarball_path, html_output_dirname
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000155
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000156def main():
157 usage = """%prog
158 Generates doxygen documentation in build/doxygen.
Josh Sorefe6a588a2017-12-03 11:54:29 -0500159 Optionally makes a tarball of the documentation to dist/.
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000160
161 Must be started in the project top directory.
162 """
163 from optparse import OptionParser
164 parser = OptionParser(usage=usage)
165 parser.allow_interspersed_args = False
166 parser.add_option('--with-dot', dest="with_dot", action='store_true', default=False,
167 help="""Enable usage of DOT to generate collaboration diagram""")
168 parser.add_option('--dot', dest="dot_path", action='store', default=find_program('dot'),
169 help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""")
170 parser.add_option('--doxygen', dest="doxygen_path", action='store', default=find_program('doxygen'),
171 help="""Path to Doxygen tool. [Default: %default]""")
Christopher Dunn16bdfd82015-02-09 11:15:11 -0600172 parser.add_option('--in', dest="doxyfile_input_path", action='store', default='doc/doxyfile.in',
173 help="""Path to doxygen inputs. [Default: %default]""")
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000174 parser.add_option('--with-html-help', dest="with_html_help", action='store_true', default=False,
175 help="""Enable generation of Microsoft HTML HELP""")
176 parser.add_option('--no-uml-look', dest="with_uml_look", action='store_false', default=True,
177 help="""Generates DOT graph without UML look [Default: False]""")
178 parser.add_option('--open', dest="open", action='store_true', default=False,
179 help="""Open the HTML index in the web browser after generation""")
180 parser.add_option('--tarball', dest="make_tarball", action='store_true', default=False,
181 help="""Generates a tarball of the documentation in dist/ directory""")
182 parser.add_option('-s', '--silent', dest="silent", action='store_true', default=False,
183 help="""Hides doxygen output""")
184 parser.enable_interspersed_args()
185 options, args = parser.parse_args()
Christopher Dunn494950a2015-01-24 15:29:52 -0600186 build_doc(options)
Baptiste Lepilleur1f4847c2010-02-23 07:57:38 +0000187
Baptiste Lepilleur57ee0e32010-02-22 04:16:10 +0000188if __name__ == '__main__':
189 main()