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