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