#!/usr/bin/env python3 """Applies fixes, generated by buildscripts/clang-tidy.py, across the codebase.""" import argparse import hashlib import json import os import sys from collections import defaultdict def is_writeable(file) -> bool: try: with open(file, "a") as f: # noqa: F841 pass return True except OSError: return False def can_replacements_be_applied(replacements) -> bool: """Checks if the files containing replacements are unchanged since clang-tidy was run and are writeable. For any replacement to be valid, all impacted files must be unchanged.""" for replacement in replacements: if ( os.path.exists(replacement["FilePath"]) and is_writeable(replacement["FilePath"]) and replacement["FileContentsMD5"] ): with open(replacement["FilePath"], "rb") as fin: file_bytes = fin.read() current_md5 = hashlib.md5(file_bytes).hexdigest() if current_md5 != replacement["FileContentsMD5"]: return False else: return False return True def get_replacements_to_apply(fixes_file) -> dict: """Gets a per file listing of the valid replacements to apply.""" replacements_to_apply = defaultdict(list) with open(fixes_file) as fin: fixes_data = json.load(fin) for clang_tidy_check in fixes_data: for main_source_file in fixes_data[clang_tidy_check]: for violation_instance in fixes_data[clang_tidy_check][main_source_file]: replacements = fixes_data[clang_tidy_check][main_source_file][ violation_instance ]["replacements"] if can_replacements_be_applied(replacements): for replacement in replacements: replacements_to_apply[replacement["FilePath"]].append(replacement) else: print( f"""WARNING: not applying replacements for {clang_tidy_check} in {main_source_file} at offset {violation_instance}, at least one file that is part of the automatic replacement has changed since clang-tidy was run, or is not writeable.""" ) return replacements_to_apply def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser() parser.add_argument(dest="fixes_file", help="Path to fixes file.") args = parser.parse_args(argv) replacements_to_apply = get_replacements_to_apply(args.fixes_file) for file in replacements_to_apply: with open(file, "rb") as fin: file_bytes = fin.read() # perform the swap replacement of the binary data file_bytes = bytearray(file_bytes) replacements_to_apply[file].sort(key=lambda r: r["Offset"]) adjustments = 0 for replacement in replacements_to_apply[file]: file_bytes[ replacement["Offset"] + adjustments : replacement["Offset"] + adjustments + replacement["Length"] ] = replacement["ReplacementText"].encode() if replacement["Length"] != len(replacement["ReplacementText"]): adjustments += len(replacement["ReplacementText"]) - replacement["Length"] with open(file, "wb") as fout: fout.write(bytes(file_bytes)) if __name__ == "__main__": main()