1#!/usr/bin/python
2
3"""
4Generate the Botan website
5
6(C) 2017 Jack Lloyd
7"""
8
9import optparse # pylint: disable=deprecated-module
10import subprocess
11import sys
12import errno
13import shutil
14import tempfile
15import os
16
17def run_and_check(cmd_line, cwd=None):
18    print("Executing %s ..." % (' '.join(cmd_line)))
19
20    proc = subprocess.Popen(cmd_line,
21                            cwd=cwd,
22                            close_fds=True,
23                            stdin=subprocess.PIPE,
24                            stdout=subprocess.PIPE,
25                            stderr=subprocess.PIPE)
26
27    (stdout, stderr) = proc.communicate()
28
29    if proc.returncode != 0:
30        print("Error running %s" % (' '.join(cmd_line)))
31        print(stdout)
32        print(stderr)
33        sys.exit(1)
34
35def rmtree_ignore_missing(path):
36    try:
37        shutil.rmtree(path)
38    except OSError:
39        # check errno?
40        pass
41
42def configure_build(botan_dir, build_dir):
43
44    run_and_check([os.path.join(botan_dir, 'configure.py'),
45                   '--with-doxygen', '--with-sphinx',
46                   '--with-build-dir=%s' % (build_dir)])
47
48def run_doxygen(tmp_dir, output_dir):
49    run_and_check(['doxygen', os.path.join(tmp_dir, 'build/botan.doxy')])
50    shutil.move(os.path.join(tmp_dir, 'build/docs/doxygen'), output_dir)
51
52def run_sphinx(botan_dir, tmp_dir, output_dir):
53
54    sphinx_config = os.path.join(botan_dir, 'src/configs/sphinx')
55    sphinx_dir = os.path.join(tmp_dir, 'sphinx')
56    os.mkdir(sphinx_dir)
57
58    shutil.copyfile(os.path.join(botan_dir, 'readme.rst'),
59                    os.path.join(sphinx_dir, 'index.rst'))
60
61    for f in ['news.rst', os.path.join('doc', 'security.rst')]:
62        shutil.copy(os.path.join(botan_dir, f), sphinx_dir)
63
64    toc = """.. toctree::
65
66   index
67   news
68   security
69   User Guide <https://botan.randombit.net/handbook>
70   API Reference <https://botan.randombit.net/doxygen>
71"""
72
73    contents_rst = open(os.path.join(sphinx_dir, 'contents.rst'), 'w')
74    contents_rst.write(toc)
75    contents_rst.close()
76
77    sphinx_invoke = ['sphinx-build', '-t', 'website', '-c', sphinx_config, '-b', 'html']
78
79    handbook_dir = os.path.join(botan_dir, 'doc')
80
81    run_and_check(sphinx_invoke + [sphinx_dir, output_dir])
82    run_and_check(sphinx_invoke + [handbook_dir, os.path.join(output_dir, 'handbook')])
83
84    rmtree_ignore_missing(os.path.join(output_dir, '.doctrees'))
85    rmtree_ignore_missing(os.path.join(output_dir, 'handbook', '.doctrees'))
86    os.remove(os.path.join(output_dir, '.buildinfo'))
87    os.remove(os.path.join(output_dir, 'handbook', '.buildinfo'))
88
89    # share _static subdirs
90    shutil.rmtree(os.path.join(output_dir, 'handbook', '_static'))
91    os.symlink('../_static', os.path.join(output_dir, 'handbook', '_static'))
92
93    # Build PDF
94    latex_output = os.path.join(tmp_dir, 'latex')
95    run_and_check(['sphinx-build', '-c', sphinx_config, '-b', 'latex', handbook_dir, latex_output])
96
97    # Have to run twice because TeX
98    run_and_check(['pdflatex', 'botan.tex'], cwd=latex_output)
99    run_and_check(['pdflatex', 'botan.tex'], cwd=latex_output)
100
101    shutil.copy(os.path.join(latex_output, 'botan.pdf'),
102                os.path.join(output_dir, 'handbook'))
103
104
105def main(args):
106    parser = optparse.OptionParser()
107
108    parser.add_option('-o', '--output-dir', default=None,
109                      help="Where to write output")
110
111    (options, args) = parser.parse_args(args)
112
113    output_dir = options.output_dir
114    tmp_dir = tempfile.mkdtemp(prefix='botan_website_')
115
116    # assumes we live in src/scripts
117    botan_dir = os.path.normpath(os.path.join(os.path.dirname(__file__),
118                                              "..", ".."))
119
120    if os.access(os.path.join(botan_dir, 'configure.py'), os.X_OK) is False:
121        print("Can't find configure.py in %s", botan_dir)
122        return 1
123
124    if output_dir is None:
125        cwd = os.getcwd()
126
127        if os.path.basename(cwd) == 'botan-website':
128            output_dir = '.'
129        else:
130            output_dir = os.path.join(cwd, 'botan-website')
131
132            try:
133                os.mkdir(output_dir)
134            except OSError as e:
135                if e.errno == errno.EEXIST:
136                    pass
137                else:
138                    raise e
139
140    for subdir in ['_static', '_sources', 'doxygen', 'handbook']:
141        try:
142            shutil.rmtree(os.path.join(output_dir, subdir))
143        except OSError as e:
144            if e.errno == errno.ENOENT:
145                pass
146            else:
147                print("Error removing dir", e)
148                return 1
149
150    configure_build(botan_dir, tmp_dir)
151    run_doxygen(tmp_dir, output_dir)
152    run_sphinx(botan_dir, tmp_dir, output_dir)
153
154    for f in ['doc/pgpkey.txt', 'license.txt']:
155        shutil.copy(os.path.join(botan_dir, f), output_dir)
156
157    favicon = open(os.path.join(output_dir, 'favicon.ico'), 'w')
158    # Create an empty favicon.ico file so it gets cached by browsers
159    favicon.close()
160
161    shutil.rmtree(tmp_dir)
162
163    return 0
164
165if __name__ == '__main__':
166    sys.exit(main(sys.argv))
167