forked from catchorg/Catch2
		
	
		
			
				
	
	
		
			152 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| """
 | |
| Checks that all of the "catch_foo_all.hpp" headers include all subheaders.
 | |
| 
 | |
| The logic is simple: given a folder, e.g. `catch2/matchers`, then the
 | |
| ccorresponding header is called `catch_matchers_all.hpp` and contains
 | |
| * all headers in `catch2/matchers`,
 | |
| * all headers in `catch2/matchers/{internal, detail}`,
 | |
| * all convenience catch_matchers_*_all.hpp headers from any non-internal subfolders
 | |
| 
 | |
| The top level header is called `catch_all.hpp`.
 | |
| """
 | |
| 
 | |
| internal_dirs = ['detail', 'internal']
 | |
| 
 | |
| from scriptCommon import catchPath
 | |
| from glob import glob
 | |
| from pprint import pprint
 | |
| import os
 | |
| import re
 | |
| 
 | |
| def normalized_path(path):
 | |
|     """Replaces \ in paths on Windows with /"""
 | |
|     return path.replace('\\', '/')
 | |
| 
 | |
| def normalized_paths(paths):
 | |
|     """Replaces \ with / in every path"""
 | |
|     return [normalized_path(path) for path in paths]
 | |
| 
 | |
| source_path = catchPath + '/src/catch2'
 | |
| source_path = normalized_path(source_path)
 | |
| include_parser = re.compile(r'#include <(catch2/.+\.hpp)>')
 | |
| 
 | |
| errors_found = False
 | |
| 
 | |
| def headers_in_folder(folder):
 | |
|     return glob(folder + '/*.hpp')
 | |
| 
 | |
| def folders_in_folder(folder):
 | |
|     return [x for x in os.scandir(folder) if x.is_dir()]
 | |
| 
 | |
| def collated_includes(folder):
 | |
|     base = headers_in_folder(folder)
 | |
|     for subfolder in folders_in_folder(folder):
 | |
|         if subfolder.name in internal_dirs:
 | |
|             base.extend(headers_in_folder(subfolder.path))
 | |
|         else:
 | |
|             base.append(subfolder.path + '/catch_{}_all.hpp'.format(subfolder.name))
 | |
|     return normalized_paths(sorted(base))
 | |
| 
 | |
| def includes_from_file(header):
 | |
|     includes = []
 | |
|     with open(header, 'r', encoding = 'utf-8') as file:
 | |
|         for line in file:
 | |
|             if not line.startswith('#include'):
 | |
|                 continue
 | |
|             match = include_parser.match(line)
 | |
|             if match:
 | |
|                 includes.append(match.group(1))
 | |
|     return normalized_paths(includes)
 | |
| 
 | |
| def normalize_includes(includes):
 | |
|     """Returns """
 | |
|     return [include[len(catchPath)+5:] for include in includes]
 | |
| 
 | |
| def get_duplicates(xs):
 | |
|     seen = set()
 | |
|     duplicated = []
 | |
|     for x in xs:
 | |
|         if x in seen:
 | |
|             duplicated.append(x)
 | |
|         seen.add(x)
 | |
|     return duplicated
 | |
| 
 | |
| def verify_convenience_header(folder):
 | |
|     """
 | |
|     Performs the actual checking of convenience header for specific folder.
 | |
|     Checks that
 | |
|     1) The header even exists
 | |
|     2) That all includes in the header are sorted
 | |
|     3) That there are no duplicated includes
 | |
|     4) That all includes that should be in the header are actually present in the header
 | |
|     5) That there are no superfluous includes that should not be in the header
 | |
|     """
 | |
|     global errors_found
 | |
| 
 | |
|     path = normalized_path(folder.path)
 | |
| 
 | |
|     assert path.startswith(source_path), '{} does not start with {}'.format(path, source_path)
 | |
|     stripped_path = path[len(source_path) + 1:]
 | |
|     path_pieces = stripped_path.split('/')
 | |
| 
 | |
|     if path == source_path:
 | |
|         header_name = 'catch_all.hpp'
 | |
|     else:
 | |
|         header_name = 'catch_{}_all.hpp'.format('_'.join(path_pieces))
 | |
| 
 | |
|     # 1) Does it exist?
 | |
|     full_path = path + '/' + header_name
 | |
|     if not os.path.isfile(full_path):
 | |
|         errors_found = True
 | |
|         print('Missing convenience header: {}'.format(full_path))
 | |
|         return
 | |
|     file_incs = includes_from_file(path + '/' + header_name)
 | |
|     # 2) Are the includes are sorted?
 | |
|     if sorted(file_incs) != file_incs:
 | |
|         errors_found = True
 | |
|         print("'{}': Includes are not in sorted order!".format(header_name))
 | |
| 
 | |
|     # 3) Are there no duplicates?
 | |
|     duplicated = get_duplicates(file_incs)
 | |
|     for duplicate in duplicated:
 | |
|         errors_found = True
 | |
|         print("'{}': Duplicated include: '{}'".format(header_name, duplicate))
 | |
| 
 | |
|     target_includes = normalize_includes(collated_includes(path))
 | |
|     # Avoid requiring the convenience header to include itself
 | |
|     target_includes = [x for x in target_includes if header_name not in x]
 | |
|     # 4) Are all required headers present?
 | |
|     file_incs_set = set(file_incs)
 | |
|     for include in target_includes:
 | |
|         if (include not in file_incs_set and
 | |
|             include != 'catch2/internal/catch_windows_h_proxy.hpp'):
 | |
|             errors_found = True
 | |
|             print("'{}': missing include '{}'".format(header_name, include))
 | |
| 
 | |
|     # 5) Are there any superfluous headers?
 | |
|     desired_set = set(target_includes)
 | |
|     for include in file_incs:
 | |
|         if include not in desired_set:
 | |
|             errors_found = True
 | |
|             print("'{}': superfluous include '{}'".format(header_name, include))
 | |
| 
 | |
| 
 | |
| 
 | |
| def walk_source_folders(current):
 | |
|     verify_convenience_header(current)
 | |
|     for folder in folders_in_folder(current.path):
 | |
|         fname = folder.name
 | |
|         if fname not in internal_dirs:
 | |
|             walk_source_folders(folder)
 | |
| 
 | |
| # This is an ugly hack because we cannot instantiate DirEntry manually
 | |
| base_dir = [x for x in os.scandir(catchPath + '/src') if x.name == 'catch2']
 | |
| walk_source_folders(base_dir[0])
 | |
| 
 | |
| # Propagate error "code" upwards
 | |
| if not errors_found:
 | |
|     print('Everything ok')
 | |
| exit(errors_found)
 |