""" Copyright 2018-2024 Emil Dotchevski and Reverge Studios, Inc. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) This program generates a single header file from a file including multiple C/C++ headers. Usage: python3 generate_single_header.py --help e.g. python3 generate_single_header.py -i include/boost/qvm/all.hpp -p include -o test/qvm.hpp boost/qvm """ import argparse import os import filecmp import re from datetime import date import subprocess def compare_and_update(old_file, new_file): if not os.path.exists(old_file): os.rename(new_file, old_file) elif filecmp.cmp(old_file, new_file, shallow=False): os.remove(new_file) else: os.remove(old_file) os.rename(new_file, old_file) included = {} total_line_count = 12 def append(input_file_name, input_file, output_file, regex_includes, include_folder, line_directive_prefix, depth): global total_line_count inside_copyright = False line_count = 1 for line in input_file: line_count += 1 if 'Emil Dotchevski' in line: inside_copyright = True if inside_copyright: if line.startswith('//') : continue if not line.strip(): inside_copyright = False if depth > 0: output_file.write('%s#line %d "%s"\n' % (line_directive_prefix, line_count, input_file_name)) continue result = regex_includes.search(line) if result: next_input_file_name = result.group('include') if next_input_file_name in included: output_file.write('// %s // Expanded at line %d\n' % (line.strip(), included[next_input_file_name])) total_line_count += 1 else: included[next_input_file_name] = total_line_count print('%s (%d)' % (next_input_file_name, total_line_count)) with open(os.path.join(include_folder, next_input_file_name), 'r') as next_input_file: output_file.write('// >>> %s' % (line)) total_line_count += 2 append(next_input_file_name, next_input_file, output_file, regex_includes, include_folder, line_directive_prefix, depth + 1) if depth > 0: output_file.write('// <<< %s%s#line %d "%s"\n' % (line, line_directive_prefix, line_count, input_file_name)) total_line_count += 2 else: output_file.write(line) total_line_count += 1 def _main(): parser = argparse.ArgumentParser( description='Generates a single include file from a file including multiple C/C++ headers') parser.add_argument('-i', '--input', action='store', type=str, default='in.cpp', help='Input file including the headers to be merged') parser.add_argument('-o', '--output', action='store', type=str, default='out.cpp', help='Output file. NOTE: It will be overwritten!') parser.add_argument('-p', '--path', action='store', type=str, default='.', help='Include path') parser.add_argument('--hash', action='store', type=str, help='The git hash to print in the output file, e.g. the output of "git rev-parse HEAD"') parser.add_argument('prefix', action='store', type=str, help='Non-empty include file prefix (e.g. a/b)') parser.add_argument('--linerefs', action='store_true', help='Output #line references to the original files. By default the line references are written commented out') args = parser.parse_args() regex_includes = re.compile(r"""^\s*#[\t\s]*include[\t\s]*("|\<)(?P%s.*)("|\>)""" % args.prefix) print('Rebuilding %s:' % args.input) tmp_file_name = args.output + '.tmp' with open(tmp_file_name, 'w') as tmp_file, open(args.input, 'r') as input_file: tmp_file.write( '// QVM single header distribution. Do not edit.\n' '// Generated on ' + date.today().strftime('%b %d, %Y')) if args.hash: tmp_file.write( ' from https://github.com/boostorg/qvm/tree/' + args.hash[0:7]) tmp_file.write( '.\n' '\n' '// Latest published versions:\n' '// https://raw.githubusercontent.com/boostorg/qvm/gh-pages/qvm.hpp\n' '// https://raw.githubusercontent.com/boostorg/qvm/gh-pages/qvm_lite.hpp\n' '\n' '// Copyright 2008-2024 Emil Dotchevski and Reverge Studios, Inc.\n' '// Distributed under the Boost Software License, Version 1.0. (See accompanying\n' '// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n' '\n') append(args.input, input_file, tmp_file, regex_includes, args.path, '' if args.linerefs else '// ', 0) compare_and_update(args.output, tmp_file_name) if __name__ == "__main__": _main()