import glob import os import shlex import subprocess import sys # required clang-format version REQUIRED_VERSION = '18.1.3' def check_clang_format_version(): """ Check if the installed clang-format version matches the required version. Raises: SystemExit: If the version does not match the required version or if there is an error checking the version. """ try: result = subprocess.run( ['clang-format', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True ) version_out = result.stdout.strip().split() version_line = '0.0.0' is_next = False for vo in version_out: if vo == 'version': is_next = True continue if is_next: version_line = vo break if version_line != REQUIRED_VERSION: print(f'Error: Required clang-format version is ' f'{REQUIRED_VERSION}, but found {version_line}.') sys.exit(1) else: print('clang-format version equals the required version.') except subprocess.CalledProcessError as e: print(f'Error checking clang-format version: {e.stderr}') sys.exit(1) def format_files(dry_run): """ Format .h and .cpp files in specified directories using clang-format. Args: dry_run (bool): If True, performs a dry run to check for formatting issues without modifying files. If False, formats the files in place. Raises: subprocess.CalledProcessError: If there is an error running clang-format during the actual formatting process. """ patterns = [ 'bench/**/*.h', 'bench/**/*.cpp', 'include/**/*.h', 'tests/**/*.h', 'tests/**/*.cpp', ] files_to_format = [] for pattern in patterns: matched_files = glob.glob(pattern, recursive=True) for file in matched_files: if '\/tmp\/' in file or '\\tmp\\' in file: matched_files.remove(file) files_to_format.extend(matched_files) if not files_to_format: print('No files to format.') return patterns_arg = ' '.join(file.replace('\\', '/') for file in files_to_format) cwd = os.getcwd() if dry_run: # Check for changes without modifying the files command = f'clang-format --style=file --dry-run --Werror {patterns_arg}' result = subprocess.run( shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd ) if result.returncode != 0: print('Files that need formatting:') print(result.stdout) print('Error output:') print(result.stderr) sys.exit(1) else: print('no changes found') else: # Format the files in place command = f'clang-format --style=file -i {patterns_arg}' result = subprocess.run( shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd ) if result.returncode != 0: print('Error formatting files:') print(result.stderr) sys.exit(1) else: print('formatting done') def main(): """ Main function to handle command-line arguments and invoke formatting. Checks for the required command-line argument, verifies the clang-format version, and calls the format_files function to format the code. """ if len(sys.argv) != 2: print( 'Usage: python format.py ' ) sys.exit(1) else: print(f'Running format with {sys.argv[1]}') dry_run = False if sys.argv[1] == 'dry_run': dry_run = True elif sys.argv[1] != 'format': print('Invalid argument. Use "dry_run" or "format".') sys.exit(1) check_clang_format_version() format_files(dry_run) if __name__ == '__main__': main()