diff --git a/utils/YouCompleteMe.py b/utils/YouCompleteMe.py new file mode 100644 index 00000000000..f51000f34f0 --- /dev/null +++ b/utils/YouCompleteMe.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# +# Copyright (C) 2015 Universite Pierre et marie Curie (UPMC) +# +# You should have received a copy of the GNU General Public License +# along with ns3. If not, see . +# +# Author: Matthieu Coudron +# + +import os +import ycm_core +import logging +import subprocess + +# This script allows to use YouCompleteMe to provide completion in (neo)vim for the ns3 +# source files. +# To enable it, hard symlink (symbolic won't work) or copy this file into the ns3 root folder and rename it to +# .ycm_extra_conf.py +# The script needs ns3 to be compiled with clang, in order to produce +# the "build/compile_commands.json" +# The script should work out of the box, otherwise try changing the +# compilation_database_folder variable + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.abspath( __file__ ) ) + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +compilation_database_folder = os.path.join(DirectoryOfThisScript(),'build/') + +# uncomment the file handler a few lines below to create the logfile +LOG_FILENAME = '/tmp/ns_ycm.log' + + +# Set up a specific logger with our desired output level +log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) + +# Add the log message handler to the logger +handler = logging.FileHandler(LOG_FILENAME) +log.addHandler(handler) + + +def GetClangStandardHeaders(): + """ + Without this, YCM triggers errors on standards files etc... + It parses the output of + """ + system_flags = [] +# https://github.com/Valloric/YouCompleteMe/issues/1478 +# echo | clang -stdlib=libc++ -v -E -x c++ - + cmd = "echo | clang -stdlib=libc++ -v -E -x c++ -- " + cmd += "|sed -n '/#include <...> search starts here/,/End of search list/{//!p}'" + log.info("Running command:\n%s" % cmd) + res = subprocess.check_output(cmd, shell=True) + log.debug("Result:\n%s" % res) + res = res.decode() + system_headers = res.splitlines() + + for include in system_headers: + system_flags.append('-isystem') + log.info("Appends include: %s" % include) + system_flags.append(include.strip()) + + return system_flags + + +if os.path.exists( compilation_database_folder ): + log.info("Loading database") + database = ycm_core.CompilationDatabase( compilation_database_folder ) + log.info("database should be loaded: %r" % database) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + log.info("Make flags absolute with directory [%s]" % 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 MatchHeaderToSource( filename ): + """ + NS3 headers are copied into a specific folder thus stock + YCM functions don't fit the bill + Return the .cc associated with the .h + """ + log.debug("MatchHeaderToSource: %s" % filename) + filename = os.path.basename(filename) + filename = os.path.splitext( filename )[ 0 ] + filename += ".cc" + log.debug("Looking for implementation of %s" % filename) + # be careful it is the python 2.7 version and as such does not return bytes + try: + match = subprocess.check_output("find src -name %s" % (filename), shell=True) + match=match.strip() + except Exception as e: + log.error("Could not map header to implementation: %s:" % e) + + match = os.path.abspath(match) + return match + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by waf 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. + log.debug("GetCompilationInfoForFile called with parameter=%s" % filename) + final_name = filename + if IsHeaderFile( filename ): + final_name = MatchHeaderToSource( filename) + + log.info("compilation_info for =%s" % final_name) + + return database.GetCompilationInfoForFile( final_name ) + + +# This is the entry point; this function is called by ycmd to produce flags for +# a file. +def FlagsForFile( filename, **kwargs ): + log.debug("== Getting flags for file %s" % filename) + 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: + log.warn("Could not find info for this one") + return None + + log.debug(" flags before being made relative %s" % compilation_info.compiler_flags_) + + # this should not be necessary, I leave it just in case + flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + + flags += GetClangStandardHeaders(); + + log.debug("Open filename '%s': found flags=%s\n" % (filename, flags)) + return { + 'flags': flags, + 'do_cache': True + } +