#!/usr/bin/env python3 import os import re import datetime from scriptCommon import catchPath from releaseCommon import Version root_path = os.path.join(catchPath, 'src') starting_header = os.path.join(root_path, 'catch2', 'catch_all.hpp') output_header = os.path.join(catchPath, 'extras', 'catch_amalgamated.hpp') output_cpp = os.path.join(catchPath, 'extras', 'catch_amalgamated.cpp') # These are the copyright comments in each file, we want to ignore them copyright_lines = [ '// Copyright Catch2 Authors\n', '// Distributed under the Boost Software License, Version 1.0.\n', '// (See accompanying file LICENSE_1_0.txt or copy at\n', '// https://www.boost.org/LICENSE_1_0.txt)\n', '// SPDX-License-Identifier: BSL-1.0\n', ] # The header of the amalgamated file: copyright information + explanation # what this file is. file_header = '''\ // Copyright Catch2 Authors // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) // SPDX-License-Identifier: BSL-1.0 // Catch v{version_string} // Generated: {generation_time} // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. // ---------------------------------------------------------- ''' # Returns file header with proper version string and generation time def formatted_file_header(version): return file_header.format(version_string=version.getVersionString(), generation_time=datetime.datetime.now()) # Which headers were already concatenated (and thus should not be # processed again) concatenated_headers = set() internal_include_parser = re.compile(r'\s*#include <(catch2/.*)>.*') def concatenate_file(out, filename: str, expand_headers: bool) -> int: # Gathers statistics on how many headers were expanded concatenated = 1 with open(filename, mode='r', encoding='utf-8') as input: for line in input: if line in copyright_lines: continue m = internal_include_parser.match(line) # anything that isn't a Catch2 header can just be copied to # the resulting file if not m: out.write(line) continue # TBD: We can also strip out include guards from our own # headers, but it wasn't worth the time at the time of writing # this script. # We do not want to expand headers for the cpp file # amalgamation but neither do we want to copy them to output if not expand_headers: continue next_header = m.group(1) # We have to avoid re-expanding the same header over and # over again, or the header will end up with couple # hundred thousands lines (~300k as of preview3 :-) ) if next_header in concatenated_headers: continue # Skip including the auto-generated user config file, # because it has not been generated yet at this point. # The code around it should be written so that just not including # it is equivalent with all-default user configuration. if next_header == 'catch2/catch_user_config.hpp': concatenated_headers.add(next_header) continue concatenated_headers.add(next_header) concatenated += concatenate_file(out, os.path.join(root_path, next_header), expand_headers) return concatenated def generate_header(): with open(output_header, mode='w', encoding='utf-8') as header: header.write(formatted_file_header(Version())) header.write('#ifndef CATCH_AMALGAMATED_HPP_INCLUDED\n') header.write('#define CATCH_AMALGAMATED_HPP_INCLUDED\n') print('Concatenated {} headers'.format(concatenate_file(header, starting_header, True))) header.write('#endif // CATCH_AMALGAMATED_HPP_INCLUDED\n') def generate_cpp(): from glob import glob cpp_files = sorted(glob(os.path.join(root_path, 'catch2', '**/*.cpp'), recursive=True)) with open(output_cpp, mode='w', encoding='utf-8') as cpp: cpp.write(formatted_file_header(Version())) cpp.write('\n#include "catch_amalgamated.hpp"\n') concatenate_file(cpp, os.path.join(root_path, 'catch2/internal/catch_windows_h_proxy.hpp'), False) for file in cpp_files: concatenate_file(cpp, file, False) print('Concatenated {} cpp files'.format(len(cpp_files))) if __name__ == "__main__": generate_header() generate_cpp() # Notes: # * For .cpp files, internal includes have to be stripped and rewritten # * for .hpp files, internal includes have to be resolved and included # * The .cpp file needs to start with `#include "catch_amalgamated.hpp" # * include guards can be left/stripped, doesn't matter # * *.cpp files should be included sorted, to minimize diffs between versions # * *.hpp files should also be somehow sorted -> use catch_all.hpp as the # * entrypoint # * allow disabling main in the .cpp amalgamation