Created
April 20, 2018 08:47
-
-
Save dominikmuller/7239680d3f51d822ecfee0fd227406cd to your computer and use it in GitHub Desktop.
YCM extra config for LHCb projects. Finds compilation flags for header files.
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
import os | |
import ycm_core | |
from clang_helpers import PrepareClangFlags | |
import fnmatch | |
def DirectoryOfThisScript(): | |
return os.path.dirname(os.path.abspath(__file__)) | |
# This is the single most important line in this script. Everything else is just nice to have but | |
# not strictly necessary. | |
compilation_database_folder = DirectoryOfThisScript() | |
# This provides a safe fall-back if no compilation commands are available. You could also add a | |
# includes relative to your project directory, for example. | |
flags = [ | |
'-Wall', | |
'-std=c++11', | |
'-stdlib=libc++', | |
'-x', | |
'c++', | |
'-I', | |
'.', | |
'-isystem', '/usr/local/include', | |
'-isystem', '/usr/include', | |
'-I.', | |
] | |
if compilation_database_folder: | |
if os.path.exists('compile_commands.json'): | |
database = ycm_core.CompilationDatabase(compilation_database_folder) | |
else: | |
try: | |
cmtconfig = os.environ['CMTCONFIG'] | |
except KeyError: | |
database = None | |
else: | |
buildpath = "build."+cmtconfig | |
relpath = os.path.abspath(DirectoryOfThisScript()) | |
abortcounter = 0 | |
while not os.path.exists(os.path.join(relpath, buildpath, 'compile_commands.json')): | |
if abortcounter > 6 or relpath == '/': | |
break | |
relpath = os.path.dirname(relpath) | |
abortcounter += 1 | |
if os.path.exists(os.path.join(relpath, buildpath, 'compile_commands.json')): | |
database = ycm_core.CompilationDatabase( | |
os.path.join( | |
relpath, | |
buildpath)) | |
else: | |
database = None | |
else: | |
database = None | |
SOURCE_EXTENSIONS = ['.cpp', '.cxx', '.cc', '.c', '.m', '.mm'] | |
def MakeRelativePathsInFlagsAbsolute(flags, working_directory): | |
if not working_directory: | |
return list(flags) | |
new_flags = [] | |
make_next_absolute = False | |
path_flags = ['-isystem', '-I', '-iquote', '--sysroot='] | |
for flag in flags: | |
new_flag = flag | |
if make_next_absolute: | |
make_next_absolute = False | |
if not flag.startswith('/'): | |
new_flag = os.path.join(working_directory, flag) | |
for path_flag in path_flags: | |
if flag == path_flag: | |
make_next_absolute = True | |
break | |
if flag.startswith(path_flag): | |
path = flag[len(path_flag):] | |
new_flag = path_flag + os.path.join(working_directory, path) | |
break | |
if new_flag: | |
new_flags.append(new_flag) | |
return new_flags | |
def IsHeaderFile(filename): | |
extension = os.path.splitext(filename)[1] | |
return extension in ['.h', '.hxx', '.hpp', '.hh'] | |
def check_for_include(filename, headername): | |
with open(filename) as f: | |
lines = list([a for a in f.readlines() if '#include' in a]) | |
for line in lines: | |
if headername in line: | |
pre_char = line.split(headername)[0][-1] | |
if not pre_char.isalpha(): | |
return True | |
return False | |
def FindSourceFile(filename): | |
print('Calling FindSourceFile with {}'.format(filename)) | |
basename = os.path.splitext(filename)[0] | |
for extension in SOURCE_EXTENSIONS: | |
replacement_file = basename + extension | |
if os.path.exists(replacement_file): | |
return replacement_file | |
dirname = os.path.dirname(filename) | |
headername = os.path.split(filename)[-1] | |
matches = [] | |
matches_headers = [] | |
for root, dirnames, filenames in os.walk(os.path.join(dirname, '..')): | |
for suffix in SOURCE_EXTENSIONS: | |
for tmpname in fnmatch.filter(filenames, '*'+suffix): | |
matches.append(os.path.realpath(os.path.join(root, tmpname))) | |
for suffix in ['.h', '.hxx', '.hpp', '.hh']: | |
for tmpname in fnmatch.filter(filenames, '*'+suffix): | |
matches_headers.append( | |
os.path.realpath( | |
os.path.join( | |
root, | |
tmpname))) | |
# Check if a same named one is in there somewhere, just in a different | |
# directory than the header file | |
for match in matches: | |
if '/{}'.format(headername) in match: | |
return match | |
# Check for includes in the cpp files | |
for match in matches: | |
if check_for_include(match, headername): | |
return match | |
# Last chance, only included by other header files. So recursively process | |
# those and hope for the best | |
for match in matches_headers: | |
if check_for_include(match, headername): | |
return FindSourceFile(match) | |
return None | |
def GoodFlagCheck(stuff): | |
if not stuff.compiler_flags_: | |
return False | |
if len([x for x in stuff.compiler_flags_]) == 0: | |
return False | |
return True | |
def FindSomeSourceFile(filename): | |
# Try to find some source file close to the file that is being edited | |
# and hope that its flags will help. | |
dirname = os.path.dirname(filename) | |
matches = [] | |
for root, dirnames, filenames in os.walk(os.path.join(dirname, '.')): | |
for suffix in SOURCE_EXTENSIONS: | |
for tmpname in fnmatch.filter(filenames, '*'+suffix): | |
matches.append(os.path.realpath(os.path.join(root, tmpname))) | |
for ma in matches: | |
compilation_info = database.GetCompilationInfoForFile(ma) | |
if GoodFlagCheck(compilation_info): | |
return ma | |
matches = [] | |
for root, dirnames, filenames in os.walk(os.path.join(dirname, '..')): | |
for suffix in SOURCE_EXTENSIONS: | |
for tmpname in fnmatch.filter(filenames, '*'+suffix): | |
matches.append(os.path.realpath(os.path.join(root, tmpname))) | |
for ma in matches: | |
compilation_info = database.GetCompilationInfoForFile(ma) | |
if GoodFlagCheck(compilation_info): | |
return ma | |
matches = [] | |
for root, dirnames, filenames in os.walk(os.path.join(dirname, '../..')): | |
for suffix in SOURCE_EXTENSIONS: | |
for tmpname in fnmatch.filter(filenames, '*'+suffix): | |
matches.append(os.path.realpath(os.path.join(root, tmpname))) | |
for ma in matches: | |
compilation_info = database.GetCompilationInfoForFile(ma) | |
if GoodFlagCheck(compilation_info): | |
return ma | |
return 'NOPE' | |
def GetCompilationInfoForFile(filename): | |
# The compilation_commands.json file generated by CMake does not have entries | |
# for header files. So we do our best by asking the db for flags for a | |
# corresponding source file, if any. If one exists, the flags for that file | |
# should be good enough. | |
if IsHeaderFile(filename): | |
replacement_file = FindSourceFile(filename) | |
compilation_info = database.GetCompilationInfoForFile( | |
replacement_file) | |
else: | |
compilation_info = database.GetCompilationInfoForFile(filename) | |
if GoodFlagCheck(compilation_info): | |
return compilation_info | |
# If the flags are not good, just get some source file and take its flags | |
alternative_file = FindSomeSourceFile(filename) | |
ret = database.GetCompilationInfoForFile(alternative_file) | |
return ret | |
def FlagsForFile(filename, **kwargs): | |
if database: | |
# Bear in mind that compilation_info.compiler_flags_ does NOT return a | |
# python list, but a "list-like" StringVec object | |
compilation_info = GetCompilationInfoForFile(filename) | |
if not compilation_info: | |
return None | |
final_flags = MakeRelativePathsInFlagsAbsolute( | |
compilation_info.compiler_flags_, | |
compilation_info.compiler_working_dir_) | |
else: | |
relative_to = DirectoryOfThisScript() | |
final_flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to) | |
return { | |
'flags': final_flags, | |
'do_cache': True | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment