aboutsummaryrefslogtreecommitdiff
path: root/doxybuild.py
blob: 862c1f43ff4b0d634921da4f7903ab3c2202d871 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"""Script to generate doxygen documentation.
"""
from __future__ import print_function
from __future__ import unicode_literals
from devtools import tarball
from contextlib import contextmanager
import subprocess
import traceback
import re
import os
import sys
import shutil

@contextmanager
def cd(newdir):
    """
    http://stackoverflow.com/questions/431684/how-do-i-cd-in-python
    """
    prevdir = os.getcwd()
    os.chdir(newdir)
    try:
        yield
    finally:
        os.chdir(prevdir)

def find_program(*filenames):
    """find a program in folders path_lst, and sets env[var]
    @param filenames: a list of possible names of the program to search for
    @return: the full path of the filename if found, or '' if filename could not be found
"""
    paths = os.environ.get('PATH', '').split(os.pathsep)
    suffixes = ('win32' in sys.platform) and '.exe .com .bat .cmd' or ''
    for filename in filenames:
        for name in [filename+ext for ext in suffixes.split(' ')]:
            for directory in paths:
                full_path = os.path.join(directory, name)
                if os.path.isfile(full_path):
                    return full_path
    return ''

def do_subst_in_file(targetfile, sourcefile, dict):
    """Replace all instances of the keys of dict with their values.
    For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'},
    then all instances of %VERSION% in the file will be replaced with 1.2345 etc.
    """
    with open(sourcefile, 'r') as f:
        contents = f.read()
    for (k,v) in list(dict.items()):
        v = v.replace('\\','\\\\') 
        contents = re.sub(k, v, contents)
    with open(targetfile, 'w') as f:
        f.write(contents)

def getstatusoutput(cmd):
    """cmd is a list.
    """
    try:
        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        output, _ = process.communicate()
        status = process.returncode
    except:
        status = -1
        output = traceback.format_exc()
    return status, output

def run_cmd(cmd, silent=False):
    """Raise exception on failure.
    """
    info = 'Running: %r in %r' %(' '.join(cmd), os.getcwd())
    print(info)
    sys.stdout.flush()
    if silent:
        status, output = getstatusoutput(cmd)
    else:
        status, output = subprocess.call(cmd), ''
    if status:
        msg = 'Error while %s ...\n\terror=%d, output="""%s"""' %(info, status, output)
        raise Exception(msg)

def assert_is_exe(path):
    if not path:
        raise Exception('path is empty.')
    if not os.path.isfile(path):
        raise Exception('%r is not a file.' %path)
    if not os.access(path, os.X_OK):
        raise Exception('%r is not executable by this user.' %path)

def run_doxygen(doxygen_path, config_file, working_dir, is_silent):
    assert_is_exe(doxygen_path)
    config_file = os.path.abspath(config_file)
    with cd(working_dir):
        cmd = [doxygen_path, config_file]
        run_cmd(cmd, is_silent)

def build_doc(options,  make_release=False):
    if make_release:
        options.make_tarball = True
        options.with_dot = True
        options.with_html_help = True
        options.with_uml_look = True
        options.open = False
        options.silent = True

    version = open('version', 'rt').read().strip()
    output_dir = 'dist/doxygen' # relative to doc/doxyfile location.
    if not os.path.isdir(output_dir):
        os.makedirs(output_dir)
    top_dir = os.path.abspath('.')
    html_output_dirname = 'jsoncpp-api-html-' + version
    tarball_path = os.path.join('dist', html_output_dirname + '.tar.gz')
    warning_log_path = os.path.join(output_dir, '../jsoncpp-doxygen-warning.log')
    html_output_path = os.path.join(output_dir, html_output_dirname)
    def yesno(bool):
        return bool and 'YES' or 'NO'
    subst_keys = {
        '%JSONCPP_VERSION%': version,
        '%DOC_TOPDIR%': '',
        '%TOPDIR%': top_dir,
        '%HTML_OUTPUT%': os.path.join('..', output_dir, html_output_dirname),
        '%HAVE_DOT%': yesno(options.with_dot),
        '%DOT_PATH%': os.path.split(options.dot_path)[0],
        '%HTML_HELP%': yesno(options.with_html_help),
        '%UML_LOOK%': yesno(options.with_uml_look),
        '%WARNING_LOG_PATH%': os.path.join('..', warning_log_path)
        }

    if os.path.isdir(output_dir):
        print('Deleting directory:', output_dir)
        shutil.rmtree(output_dir)
    if not os.path.isdir(output_dir):
        os.makedirs(output_dir)

    do_subst_in_file('doc/doxyfile', options.doxyfile_input_path, subst_keys)
    run_doxygen(options.doxygen_path, 'doc/doxyfile', 'doc', is_silent=options.silent)
    if not options.silent:
        print(open(warning_log_path, 'r').read())
    index_path = os.path.abspath(os.path.join('doc', subst_keys['%HTML_OUTPUT%'], 'index.html'))
    print('Generated documentation can be found in:')
    print(index_path)
    if options.open:
        import webbrowser
        webbrowser.open('file://' + index_path)
    if options.make_tarball:
        print('Generating doc tarball to', tarball_path)
        tarball_sources = [
            output_dir,
            'README.md',
            'LICENSE',
            'NEWS.txt',
            'version'
            ]
        tarball_basedir = os.path.join(output_dir, html_output_dirname)
        tarball.make_tarball(tarball_path, tarball_sources, tarball_basedir, html_output_dirname)
    return tarball_path, html_output_dirname

def main():
    usage = """%prog
    Generates doxygen documentation in build/doxygen.
    Optionally makes a tarball of the documentation to dist/.

    Must be started in the project top directory.    
    """
    from optparse import OptionParser
    parser = OptionParser(usage=usage)
    parser.allow_interspersed_args = False
    parser.add_option('--with-dot', dest="with_dot", action='store_true', default=False,
        help="""Enable usage of DOT to generate collaboration diagram""")
    parser.add_option('--dot', dest="dot_path", action='store', default=find_program('dot'),
        help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""")
    parser.add_option('--doxygen', dest="doxygen_path", action='store', default=find_program('doxygen'),
        help="""Path to Doxygen tool. [Default: %default]""")
    parser.add_option('--in', dest="doxyfile_input_path", action='store', default='doc/doxyfile.in',
        help="""Path to doxygen inputs. [Default: %default]""")
    parser.add_option('--with-html-help', dest="with_html_help", action='store_true', default=False,
        help="""Enable generation of Microsoft HTML HELP""")
    parser.add_option('--no-uml-look', dest="with_uml_look", action='store_false', default=True,
        help="""Generates DOT graph without UML look [Default: False]""")
    parser.add_option('--open', dest="open", action='store_true', default=False,
        help="""Open the HTML index in the web browser after generation""")
    parser.add_option('--tarball', dest="make_tarball", action='store_true', default=False,
        help="""Generates a tarball of the documentation in dist/ directory""")
    parser.add_option('-s', '--silent', dest="silent", action='store_true', default=False,
        help="""Hides doxygen output""")
    parser.enable_interspersed_args()
    options, args = parser.parse_args()
    build_doc(options)

if __name__ == '__main__':
    main()