generateSingleHeader.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #!/usr/bin/env python3
  2. from __future__ import print_function
  3. import os
  4. import io
  5. import sys
  6. import re
  7. import datetime
  8. from glob import glob
  9. from scriptCommon import catchPath
  10. def generate(v):
  11. includesParser = re.compile( r'\s*#\s*include\s*"(.*)"' )
  12. guardParser = re.compile( r'\s*#.*(TWOBLUECUBES_)?CATCH_.*_INCLUDED')
  13. defineParser = re.compile( r'\s*#define\s+(TWOBLUECUBES_)?CATCH_.*_INCLUDED')
  14. ifParser = re.compile( r'\s*#ifndef (TWOBLUECUBES_)?CATCH_.*_INCLUDED')
  15. endIfParser = re.compile( r'\s*#endif // (TWOBLUECUBES_)?CATCH_.*_INCLUDED')
  16. ifImplParser = re.compile( r'\s*#ifdef CATCH_CONFIG_RUNNER' )
  17. commentParser1 = re.compile( r'^\s*/\*')
  18. commentParser2 = re.compile( r'^ \*')
  19. blankParser = re.compile( r'^\s*$')
  20. seenHeaders = set([])
  21. possibleHeaders = set([])
  22. rootPath = os.path.join( catchPath, 'include/' )
  23. outputPath = os.path.join( catchPath, 'single_include/catch2/catch.hpp' )
  24. globals = {
  25. 'includeImpl' : True,
  26. 'ifdefs' : 0,
  27. 'implIfDefs' : -1
  28. }
  29. for arg in sys.argv[1:]:
  30. arg = arg.lower()
  31. if arg == "noimpl":
  32. globals['includeImpl'] = False
  33. print( "Not including impl code" )
  34. else:
  35. print( "\n** Unrecognised argument: " + arg + " **\n" )
  36. exit(1)
  37. # ensure that the output directory exists (hopefully no races)
  38. outDir = os.path.dirname(outputPath)
  39. if not os.path.exists(outDir):
  40. os.makedirs(outDir)
  41. out = io.open( outputPath, 'w', newline='\n', encoding='utf-8')
  42. def write( line ):
  43. if globals['includeImpl'] or globals['implIfDefs'] == -1:
  44. out.write( line )
  45. def getDirsToSearch( ):
  46. return [os.path.join( rootPath, s) for s in ['', 'internal', 'reporters', 'internal/benchmark', 'internal/benchmark/detail']]
  47. def collectPossibleHeaders():
  48. dirs = getDirsToSearch()
  49. for dir in dirs:
  50. hpps = glob(os.path.join(dir, '*.hpp'))
  51. hs = glob(os.path.join(dir, '*.h'))
  52. possibleHeaders.update( hpp.rpartition( os.sep )[2] for hpp in hpps )
  53. possibleHeaders.update( h.rpartition( os.sep )[2] for h in hs )
  54. def insertCpps():
  55. dirs = getDirsToSearch()
  56. cppFiles = []
  57. for dir in dirs:
  58. cppFiles += glob(os.path.join(dir, '*.cpp'))
  59. # To minimize random diffs, sort the files before processing them
  60. for fname in sorted(cppFiles):
  61. dir, name = fname.rsplit(os.path.sep, 1)
  62. dir += os.path.sep
  63. parseFile(dir, name)
  64. def parseFile( path, filename ):
  65. f = io.open( os.path.join(path, filename), 'r', encoding='utf-8' )
  66. blanks = 0
  67. write( u"// start {0}\n".format( filename ) )
  68. for line in f:
  69. if '// ~*~* CATCH_CPP_STITCH_PLACE *~*~' in line:
  70. insertCpps()
  71. continue
  72. elif ifParser.match( line ):
  73. globals['ifdefs'] += 1
  74. elif endIfParser.match( line ):
  75. globals['ifdefs'] -= 1
  76. if globals['ifdefs'] == globals['implIfDefs']:
  77. globals['implIfDefs'] = -1
  78. m = includesParser.match( line )
  79. if m:
  80. header = m.group(1)
  81. headerPath, sep, headerFile = header.rpartition( "/" )
  82. if headerFile not in seenHeaders:
  83. if headerFile != "tbc_text_format.h" and headerFile != "clara.h":
  84. seenHeaders.add( headerFile )
  85. if headerPath == "internal" and path.endswith("internal/"):
  86. headerPath = ""
  87. sep = ""
  88. if os.path.exists( path + headerPath + sep + headerFile ):
  89. parseFile( path + headerPath + sep, headerFile )
  90. else:
  91. parseFile( rootPath + headerPath + sep, headerFile )
  92. else:
  93. if ifImplParser.match(line):
  94. globals['implIfDefs'] = globals['ifdefs']
  95. if (not guardParser.match( line ) or defineParser.match( line ) ) and not commentParser1.match( line )and not commentParser2.match( line ):
  96. if blankParser.match( line ):
  97. blanks = blanks + 1
  98. else:
  99. blanks = 0
  100. if blanks < 2 and not defineParser.match(line):
  101. write( line.rstrip() + "\n" )
  102. write( u'// end {}\n'.format(filename) )
  103. def warnUnparsedHeaders():
  104. unparsedHeaders = possibleHeaders.difference( seenHeaders )
  105. # These headers aren't packaged into the unified header, exclude them from any warning
  106. whitelist = ['catch.hpp', 'catch_reporter_teamcity.hpp', 'catch_with_main.hpp', 'catch_reporter_automake.hpp', 'catch_reporter_tap.hpp', 'catch_reporter_sonarqube.hpp']
  107. unparsedHeaders = unparsedHeaders.difference( whitelist )
  108. if unparsedHeaders:
  109. print( "WARNING: unparsed headers detected\n{0}\n".format( unparsedHeaders ) )
  110. write( u"/*\n" )
  111. write( u" * Catch v{0}\n".format( v.getVersionString() ) )
  112. write( u" * Generated: {0}\n".format( datetime.datetime.now() ) )
  113. write( u" * ----------------------------------------------------------\n" )
  114. write( u" * This file has been merged from multiple headers. Please don't edit it directly\n" )
  115. write( u" * Copyright (c) {} Two Blue Cubes Ltd. All rights reserved.\n".format( datetime.date.today().year ) )
  116. write( u" *\n" )
  117. write( u" * Distributed under the Boost Software License, Version 1.0. (See accompanying\n" )
  118. write( u" * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n" )
  119. write( u" */\n" )
  120. write( u"#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n" )
  121. write( u"#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n" )
  122. collectPossibleHeaders()
  123. parseFile( rootPath, 'catch.hpp' )
  124. warnUnparsedHeaders()
  125. write( u"#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n\n" )
  126. out.close()
  127. print( "Generated single include for Catch v{0}\n".format( v.getVersionString() ) )
  128. if __name__ == '__main__':
  129. from releaseCommon import Version
  130. generate(Version())