generateAmalgamatedFiles.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. #!/usr/bin/env python3
  2. import os
  3. import re
  4. import datetime
  5. from scriptCommon import catchPath
  6. from releaseCommon import Version
  7. root_path = os.path.join(catchPath, 'src')
  8. starting_header = os.path.join(root_path, 'catch2', 'catch_all.hpp')
  9. output_header = os.path.join(catchPath, 'extras', 'catch_amalgamated.hpp')
  10. output_cpp = os.path.join(catchPath, 'extras', 'catch_amalgamated.cpp')
  11. # These are the copyright comments in each file, we want to ignore them
  12. copyright_lines = [
  13. '// Copyright Catch2 Authors\n',
  14. '// Distributed under the Boost Software License, Version 1.0.\n',
  15. '// (See accompanying file LICENSE_1_0.txt or copy at\n',
  16. '// https://www.boost.org/LICENSE_1_0.txt)\n',
  17. '// SPDX-License-Identifier: BSL-1.0\n',
  18. ]
  19. # The header of the amalgamated file: copyright information + explanation
  20. # what this file is.
  21. file_header = '''\
  22. // Copyright Catch2 Authors
  23. // Distributed under the Boost Software License, Version 1.0.
  24. // (See accompanying file LICENSE_1_0.txt or copy at
  25. // https://www.boost.org/LICENSE_1_0.txt)
  26. // SPDX-License-Identifier: BSL-1.0
  27. // Catch v{version_string}
  28. // Generated: {generation_time}
  29. // ----------------------------------------------------------
  30. // This file is an amalgamation of multiple different files.
  31. // You probably shouldn't edit it directly.
  32. // ----------------------------------------------------------
  33. '''
  34. # Returns file header with proper version string and generation time
  35. def formatted_file_header(version):
  36. return file_header.format(version_string=version.getVersionString(),
  37. generation_time=datetime.datetime.now())
  38. # Which headers were already concatenated (and thus should not be
  39. # processed again)
  40. concatenated_headers = set()
  41. internal_include_parser = re.compile(r'\s*#include <(catch2/.*)>.*')
  42. def concatenate_file(out, filename: str, expand_headers: bool) -> int:
  43. # Gathers statistics on how many headers were expanded
  44. concatenated = 1
  45. with open(filename, mode='r', encoding='utf-8') as input:
  46. for line in input:
  47. if line in copyright_lines:
  48. continue
  49. m = internal_include_parser.match(line)
  50. # anything that isn't a Catch2 header can just be copied to
  51. # the resulting file
  52. if not m:
  53. out.write(line)
  54. continue
  55. # TBD: We can also strip out include guards from our own
  56. # headers, but it wasn't worth the time at the time of writing
  57. # this script.
  58. # We do not want to expand headers for the cpp file
  59. # amalgamation but neither do we want to copy them to output
  60. if not expand_headers:
  61. continue
  62. next_header = m.group(1)
  63. # We have to avoid re-expanding the same header over and
  64. # over again, or the header will end up with couple
  65. # hundred thousands lines (~300k as of preview3 :-) )
  66. if next_header in concatenated_headers:
  67. continue
  68. # Skip including the auto-generated user config file,
  69. # because it has not been generated yet at this point.
  70. # The code around it should be written so that just not including
  71. # it is equivalent with all-default user configuration.
  72. if next_header == 'catch2/catch_user_config.hpp':
  73. concatenated_headers.add(next_header)
  74. continue
  75. concatenated_headers.add(next_header)
  76. concatenated += concatenate_file(out, os.path.join(root_path, next_header), expand_headers)
  77. return concatenated
  78. def generate_header():
  79. with open(output_header, mode='w', encoding='utf-8') as header:
  80. header.write(formatted_file_header(Version()))
  81. header.write('#ifndef CATCH_AMALGAMATED_HPP_INCLUDED\n')
  82. header.write('#define CATCH_AMALGAMATED_HPP_INCLUDED\n')
  83. print('Concatenated {} headers'.format(concatenate_file(header, starting_header, True)))
  84. header.write('#endif // CATCH_AMALGAMATED_HPP_INCLUDED\n')
  85. def generate_cpp():
  86. from glob import glob
  87. cpp_files = sorted(glob(os.path.join(root_path, 'catch2', '**/*.cpp'), recursive=True))
  88. with open(output_cpp, mode='w', encoding='utf-8') as cpp:
  89. cpp.write(formatted_file_header(Version()))
  90. cpp.write('\n#include "catch_amalgamated.hpp"\n')
  91. concatenate_file(cpp, os.path.join(root_path, 'catch2/internal/catch_windows_h_proxy.hpp'), False)
  92. for file in cpp_files:
  93. concatenate_file(cpp, file, False)
  94. print('Concatenated {} cpp files'.format(len(cpp_files)))
  95. if __name__ == "__main__":
  96. generate_header()
  97. generate_cpp()
  98. # Notes:
  99. # * For .cpp files, internal includes have to be stripped and rewritten
  100. # * for .hpp files, internal includes have to be resolved and included
  101. # * The .cpp file needs to start with `#include "catch_amalgamated.hpp"
  102. # * include guards can be left/stripped, doesn't matter
  103. # * *.cpp files should be included sorted, to minimize diffs between versions
  104. # * *.hpp files should also be somehow sorted -> use catch_all.hpp as the
  105. # * entrypoint
  106. # * allow disabling main in the .cpp amalgamation