Last active
April 29, 2020 17:03
-
-
Save Caellian/0648469f6aec65f0f21b689a48480f47 to your computer and use it in GitHub Desktop.
Include Fixer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/env python3 | |
| import os | |
| import re | |
| import tempfile | |
| import shutil | |
| RootIncludeDir = "/home/asger/Software/Ogre/ogre-next/OgreMain/include" # Root of include directory | |
| TryRelative = ['.', 'Deprecated'] # Alternative lookup paths for includes. Relative to RootIncludeDir | |
| # Allows ignoring errors on certain missing files | |
| FakeFiles = list(map(lambda x: os.path.join(RootIncludeDir, x), [ | |
| "OgreCompositionPass.h", | |
| "OgreBuildSettings.h", | |
| "Remotery.h", | |
| "Threading/OgreDefaultWorkQueueStandard.h", | |
| "Threading/OgreDefaultWorkQueueTBB.h", | |
| "SSE2/Double/OgreArrayVector3.h", | |
| "SSE2/Double/OgreArrayMatrixAf4x3.h", | |
| "SSE2/Double/OgreArrayMatrix4.h", | |
| "SSE2/Double/OgreArrayAabb.h", | |
| "SSE2/Double/OgreArrayQuaternion.h", | |
| "SSE2/Double/OgreMathlibSSE2.h", | |
| "SSE2/Double/OgreArraySphere.h", | |
| "SSE2/Double/OgreBooleanMask.h", | |
| ])) | |
| ReplaceAbsolutePaths = True # Turn #include <path.h> into #include "rel-path.h" if possible | |
| HeaderFileExts = ('.h', '.hpp', '.hxx') | |
| IncludePattern = re.compile(r"^\s*#include\s+(<(.*\.h)>|\"(.*\.h)\")") | |
| BlockCommentPattern = re.compile(r"/\*.*?\*/", re.DOTALL | re.MULTILINE) | |
| def is_include_relative(requested, from_where): | |
| unmangled = os.path.abspath(os.path.join(from_where, requested)) | |
| if os.path.isfile(unmangled) or unmangled in FakeFiles: | |
| return True | |
| return False | |
| # Returns pairs of X and Y where X should be replaced with Y | |
| def find_include_fixes(f): | |
| print("Processing {}".format(f)) | |
| results = [] | |
| try: | |
| with open(f, 'r') as fp: | |
| file_text = fp.read() | |
| curr_dir_name = os.path.dirname(f) | |
| # Gather comment blocks first to ignore commented out includes | |
| # Include regex ignores line comments so no need to check those | |
| comment_blocks = [] | |
| for block in BlockCommentPattern.finditer(file_text): | |
| start = block.start() | |
| end = block.end() | |
| ln_start = file_text[:start].count('\n') | |
| ln_end = ln_start + file_text[start:end].count('\n') | |
| comment_blocks.append((ln_start, ln_end)) | |
| # Start processing | |
| for i, line in enumerate(file_text.split('\n')): | |
| # Skip lines inside block comments | |
| skip = False | |
| for block in comment_blocks: | |
| if i > block[0] and i <= block[1]: | |
| skip = True | |
| break | |
| if skip: | |
| continue | |
| # Iterate over includes in line (iteration is redundant) | |
| for match in IncludePattern.finditer(line): | |
| if match.group(1).startswith('"'): # #include "something.h" | |
| # These are either relative or absolute. | |
| include_path = match.group(3) | |
| if (is_include_relative(include_path, curr_dir_name)): | |
| # Include must be relative | |
| # Nothing has to be fixed | |
| pass | |
| else: | |
| # Include is either absolute or unconventionally accessed external dependency. | |
| found_any = False | |
| for alt in TryRelative: | |
| full_path = os.path.join(RootIncludeDir, alt, include_path) | |
| if os.path.isfile(full_path) or os.path.abspath(full_path) in FakeFiles: | |
| # Include is absolute inside "" which is bad | |
| if os.path.abspath(full_path) in FakeFiles: | |
| print("Using fake file for '{}'".format(include_path)) | |
| if alt != '.': | |
| print("Found '{}' include in alternative path '{}'.".format(include_path, alt)) | |
| relative_path = os.path.relpath(full_path, curr_dir_name) | |
| relative_path = '"{}"'.format(relative_path) | |
| results.append((i, '"{}"'.format(include_path), relative_path)) | |
| found_any = True | |
| break | |
| if not found_any: | |
| # Nothing else can be done | |
| print("ERROR: Bad include path or unconventionally accessed external dependency!") | |
| print("NOTE: In file {}:{}".format(os.path.relpath(f, RootIncludeDir), i + 1)) | |
| print("NOTE: Include is: {}".format(include_path)) | |
| print("NOTE: Match is: '{}'".format(match.group())) | |
| exit(1) | |
| elif ReplaceAbsolutePaths and match.group(1).startswith('<'): # #include <something.h> | |
| # These must be absolute or external includes. | |
| include_path = match.group(2) | |
| for alt in TryRelative: | |
| full_path = os.path.join(RootIncludeDir, alt, include_path) | |
| if os.path.isfile(full_path) or os.path.abspath(full_path) in FakeFiles: | |
| # Using absolute access path where relative can be used | |
| full_path = os.path.join(RootIncludeDir, include_path) | |
| relative_path = os.path.relpath(full_path, curr_dir_name) | |
| relative_path = '"{}"'.format(relative_path) | |
| results.append((i, '<{}>'.format(include_path), relative_path)) | |
| break | |
| elif not ReplaceAbsolutePaths: # Sanity check, either match.group(1) or match.group(2) must not be None. | |
| print("ERROR: Something, somewhere, went terribly wrong.") | |
| exit(1) | |
| except UnicodeDecodeError: | |
| print("ERROR: File {} is not using UTF-8 encoding!".format(f)) | |
| exit(1) | |
| return results | |
| include_files = [] | |
| for root, dirs, files in os.walk(RootIncludeDir): | |
| for name in files: | |
| if name.lower().endswith(HeaderFileExts): | |
| file_rel_path = os.path.relpath(os.path.join(root, name), RootIncludeDir) | |
| include_files.append(file_rel_path) | |
| include_fixes = [] | |
| # Collect includes first so that any incorrectly encoded files or include errors can be | |
| # fixed before we start doing stuff. | |
| for f in include_files: | |
| fixes = find_include_fixes(os.path.join(RootIncludeDir, f)) | |
| if len(fixes) > 0: | |
| include_fixes.append((f, fixes)) | |
| def get_ln_fix(fix_list, line): | |
| for fix in fix_list: | |
| if fix[0] == line: | |
| return fix | |
| return None | |
| for fix in include_fixes: | |
| tf, fix_path = tempfile.mkstemp() | |
| with os.fdopen(tf, 'w') as new_file: | |
| with open(fix[0]) as old_file: | |
| for i, line in enumerate(old_file): | |
| line_fix = get_ln_fix(fix[1], i) | |
| if line_fix != None: | |
| new_file.write(line.replace(line_fix[1], line_fix[2])) | |
| else: | |
| new_file.write(line) | |
| #Copy the file permissions from the old file to the new file | |
| shutil.copymode(fix[0], fix_path) | |
| os.remove(fix[0]) | |
| shutil.move(fix_path, fix[0]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment