Skip to content

Instantly share code, notes, and snippets.

@Caellian
Last active April 29, 2020 17:03
Show Gist options
  • Select an option

  • Save Caellian/0648469f6aec65f0f21b689a48480f47 to your computer and use it in GitHub Desktop.

Select an option

Save Caellian/0648469f6aec65f0f21b689a48480f47 to your computer and use it in GitHub Desktop.
Include Fixer
#!/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