ycmd layer: a better global_conf.py
This commit is contained in:
parent
64a1b85523
commit
88742b564f
|
@ -7,6 +7,8 @@
|
|||
- [[#ycmd][YCMD]]
|
||||
- [[#other-requirements][Other Requirements]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#activating-ycmd-in-a-major-mode][Activating ycmd in a major mode]]
|
||||
- [[#getting-the-compilation-flags][Getting the compilation flags]]
|
||||
- [[#key-bindings][Key Bindings]]
|
||||
|
||||
* Description
|
||||
|
@ -25,7 +27,10 @@ file.
|
|||
#+BEGIN_SRC emacs-lisp
|
||||
(setq ycmd-server-command '("python" "/path/to/YouCompleteMe/third_party/ycmd/ycmd"))
|
||||
#+END_SRC
|
||||
3) Instead of =.clang_complete= ycmd [[https://github.com/Valloric/YouCompleteMe/blob/master/README.md#c-family-semantic-completion][uses a .ycm_extra_conf.py file]].
|
||||
3) By default, spacemacs configures ycmd for getting the compilation flags from either
|
||||
a compile_commands.json or a .clang_complete file and get additionnal flags from a
|
||||
.ycm_extra_flags file. If you do not like this behaviour, you can write your own
|
||||
[[https://github.com/Valloric/YouCompleteMe/blob/master/README.md#c-family-semantic-completion][.ycm_extra_conf.py file]]. See [[#configuration][Configuration]] for more details.
|
||||
4) Whitelist the file by adding the following to =.spacemacs=:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
;; In this example we whitelist everything in the Develop folder
|
||||
|
@ -42,7 +47,9 @@ This package requires the =auto-completion= layer in order to get actual
|
|||
completion. The =syntax-checking= layer is required for flycheck support.
|
||||
|
||||
* Configuration
|
||||
By default this layer only activates ycmd for =c++-mode=.
|
||||
|
||||
** Activating ycmd in a major mode
|
||||
By default this layer only activates ycmd for =c++-mode= and =c-mode=.
|
||||
|
||||
If you want ycmd support in other modes you might just want to add it for
|
||||
specific languages like:
|
||||
|
@ -51,6 +58,31 @@ specific languages like:
|
|||
(add-hook 'c++-mode-hook 'ycmd-mode)
|
||||
#+END_SRC
|
||||
|
||||
** Getting the compilation flags
|
||||
|
||||
Spacemacs uses its own ycmd global configuration file. If you prefer, you can
|
||||
write your own [[https://github.com/Valloric/YouCompleteMe/blob/master/README.md#c-family-semantic-completion][.ycm_extra_conf.py]].
|
||||
|
||||
Spacemacs will search for a compile_command.json or fall back to a
|
||||
.clang_complete file in all parent directories of the current translation unit.
|
||||
Spacemacs will try to make up for missing files in the compile_commands.json
|
||||
using heuristics described in global_conf.py.
|
||||
|
||||
The user can provide additionnal flags by writing a .ycm_extra_flags in any
|
||||
parent directory of the current translation unit. This is particularly useful
|
||||
when cross-compiling.
|
||||
|
||||
Example .ycm_extra_flags:
|
||||
|
||||
#+BEGIN_SRC conf
|
||||
# Additionnal flags for ycmd
|
||||
--sysroot="/path/to/your/toolchain/libc" # if you are cross-compiling
|
||||
#+END_SRC
|
||||
|
||||
If your build system doesn't handle the creation of a compile_commands.json,
|
||||
you can use tools such as [[https://github.com/rizsotto/Bear][Bear]] or [[https://pypi.python.org/pypi/scan-build][scan-build]] to generate it, which both work
|
||||
with almost any build system.
|
||||
|
||||
* Key Bindings
|
||||
|
||||
Adds ~SPC m g g~ go to definition binding to =c++-mode= as well as ~SPC m g G~
|
||||
|
|
|
@ -1,84 +1,274 @@
|
|||
# global_conf.py --- ycmd global configuration file for Spacemacs
|
||||
#
|
||||
# Copyright (c) 2012-2016 Sylvain Benner & Contributors
|
||||
#
|
||||
# Author: Thomas de Beauchene <thomas.de.beauchene@gmail.com>
|
||||
# URL: https://github.com/syl20bnr/spacemacs
|
||||
#
|
||||
# This file is not part of GNU Emacs.
|
||||
#
|
||||
# License: GPLv3
|
||||
#
|
||||
# This script tries to get the compilation flags for a translation unit using
|
||||
# the following logic:
|
||||
#
|
||||
# 1) If there is a compile_commands.json in a parent directory:
|
||||
# a) If the file is a header file:
|
||||
# - search for the header file itself in db
|
||||
# - search for a sibling source file in the same directory (i.e. a source
|
||||
# file with the same name but different extension)
|
||||
# - search for a source file that includes our header's path
|
||||
# - search for the nearest source file in db
|
||||
#
|
||||
# b) If the file is a source file:
|
||||
# - search for the source file itself
|
||||
# - search for the nearest source file in db
|
||||
#
|
||||
# 2) If no compile_commands.json, search for a .clang_complete:
|
||||
# - get flags from .clang_complete
|
||||
#
|
||||
# 3) Always try to add extra flags from a .ycm_extra_flags file in a parent
|
||||
# directory. (like --sysroot="/path/to/your/toolchain/libc" if you are cross-compiling)
|
||||
#
|
||||
# Thanks to Jonas Devlieghere and Gabor Marton for their work on which this code is based.
|
||||
# https://jonasdevlieghere.com/a-better-youcompleteme-config/
|
||||
# https://github.com/martong/ycm_extra_conf.jsondb
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import ycm_core
|
||||
|
||||
default_flags = [
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
'-Werror',
|
||||
'-Wc++98-compat',
|
||||
'-Wno-long-long',
|
||||
'-Wno-variadic-macros',
|
||||
'-fexceptions',
|
||||
'-DNDEBUG',
|
||||
'-std=c++11',
|
||||
'-x',
|
||||
'c++',
|
||||
# This path will only work on OS X, but extra paths that don't exist are not harmful
|
||||
'-isystem'
|
||||
'/System/Library/Frameworks/Python.framework/Headers',
|
||||
'-isystem',
|
||||
'/usr/include',
|
||||
'-isystem',
|
||||
'/usr/local/include',
|
||||
'-isystem',
|
||||
'/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1',
|
||||
'-isystem',
|
||||
'/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include',
|
||||
]
|
||||
SOURCE_EXTENSIONS = ['.cpp', '.cxx', '.cc', '.c', '.m', '.mm']
|
||||
HEADER_EXTENSIONS = ['.h', '.hxx', '.hpp', '.hh']
|
||||
|
||||
def DirectoryOfThisScript():
|
||||
return os.path.dirname( os.path.abspath( __file__ ) )
|
||||
# This function is called by ycmd.
|
||||
def FlagsForFile(filename):
|
||||
logging.info("%s: Getting flags for %s" % (__file__, filename))
|
||||
root = os.path.realpath(filename);
|
||||
compilation_db_flags = FlagsFromCompilationDatabase(root, filename)
|
||||
if compilation_db_flags:
|
||||
flags = compilation_db_flags
|
||||
else:
|
||||
flags = FlagsFromClangComplete(root, filename)
|
||||
extra_flags = GetUserExtraFlags(filename)
|
||||
if extra_flags:
|
||||
if flags:
|
||||
flags += extra_flags
|
||||
else:
|
||||
flags = extra_flags
|
||||
if flags:
|
||||
flags = [ flag for flag in flags if not flag.startswith("-m") ] # strip -m flags
|
||||
logging.info("%s: Flags = [\n\t\t%s\n]"
|
||||
% (os.path.basename(filename), "\n\t\t".join(flags)))
|
||||
else:
|
||||
flags = []
|
||||
logging.error("%s: No flags were found !" % (os.path.basename(filename)))
|
||||
return { 'flags': flags, 'do_cache': True }
|
||||
|
||||
|
||||
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
|
||||
|
||||
# Thanks to https://github.com/decrispell/vim-config for this code
|
||||
def FlagsForFile( filename, **kwargs ):
|
||||
""" given the source filename, return the compiler flags """
|
||||
opt_basename = '.clang_complete'
|
||||
curr_dir = os.path.dirname(filename)
|
||||
opt_fname = os.path.join(curr_dir, opt_basename)
|
||||
# keep traversing up the tree until we find the file, or hit the root
|
||||
while not os.path.exists(opt_fname):
|
||||
new_dir = os.path.dirname(curr_dir)
|
||||
if new_dir == curr_dir:
|
||||
# we've reached the root of the tree
|
||||
break
|
||||
curr_dir = new_dir
|
||||
opt_fname = os.path.join(curr_dir, opt_basename)
|
||||
def FlagsFromClangComplete(root, filename):
|
||||
try:
|
||||
fd = open(opt_fname, 'r')
|
||||
except IOError:
|
||||
return {'flags': default_flags, 'do_cache': True}
|
||||
flags = [line.strip() for line in fd.readlines()]
|
||||
relative_to = os.path.dirname(opt_fname)
|
||||
flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to)
|
||||
return {
|
||||
'flags': flags, 'do_cache': True
|
||||
}
|
||||
clang_complete_path = FindNearest(root, '.clang_complete', filename)
|
||||
clang_complete_flags = open(clang_complete_path, 'r').read().splitlines()
|
||||
logging.info("%s: Using %s" % (os.path.basename(filename), clang_complete_path))
|
||||
return MakeRelativePathsInFlagsAbsolute(clang_complete_flags,
|
||||
os.path.dirname(clang_complete_path))
|
||||
except:
|
||||
return None
|
||||
|
||||
def FlagsFromCompilationDatabase(root, filename):
|
||||
try:
|
||||
compilation_db_path = FindNearest(root, 'compile_commands.json', filename)
|
||||
database = ycm_core.CompilationDatabase(os.path.dirname(compilation_db_path))
|
||||
if not database:
|
||||
logging.info("%s: Compilation database file found but unable to load"
|
||||
% os.path.basename(filename))
|
||||
return None
|
||||
extension = os.path.splitext(filename)[1]
|
||||
if extension in HEADER_EXTENSIONS:
|
||||
compilation_info = GetFlagsForHeader(compilation_db_path, database, filename)
|
||||
else:
|
||||
compilation_info = GetFlagsForSourceFile(database, filename)
|
||||
if not compilation_info:
|
||||
logging.info("%s: No compilation info for %s in compilation database"
|
||||
% (os.path.basename(filename), filename))
|
||||
return None
|
||||
return MakeRelativePathsInFlagsAbsolute(compilation_info.compiler_flags_,
|
||||
compilation_info.compiler_working_dir_)
|
||||
except Exception, e:
|
||||
logging.info("%s: Could not get compilation flags from db: %s"
|
||||
% (os.path.basename(filename), e))
|
||||
return None
|
||||
|
||||
def GetFlagsForHeader(database_path, database, filename):
|
||||
flags = FindFileInDb(database, filename)
|
||||
if flags:
|
||||
return flags
|
||||
flags = FindSiblingFileForHeader(database, filename)
|
||||
if flags:
|
||||
return flags
|
||||
flags = SearchForTranslationUnitWhichIncludesPath(database_path,
|
||||
database,
|
||||
os.path.dirname(filename),
|
||||
filename)
|
||||
if flags:
|
||||
return flags
|
||||
return FindNearestSourceFileInDb(database, os.path.dirname(filename), filename)
|
||||
|
||||
def GetFlagsForSourceFile (database, filename):
|
||||
flags = FindFileInDb(database, filename)
|
||||
if flags:
|
||||
return flags
|
||||
return FindNearestSourceFileInDb(database, os.path.dirname(filename), filename)
|
||||
|
||||
def FindNearest(path, target, filename):
|
||||
candidate = os.path.join(path, target)
|
||||
if(os.path.isfile(candidate) or os.path.isdir(candidate)):
|
||||
logging.info("%s: Found nearest %s at %s"
|
||||
% (os.path.basename(filename), target, candidate))
|
||||
return candidate;
|
||||
else:
|
||||
parent = os.path.dirname(os.path.abspath(path));
|
||||
if(parent == path):
|
||||
raise RuntimeError("could not find %s" % target);
|
||||
return FindNearest(parent, target, filename)
|
||||
|
||||
def FindFileInDb(database, filename):
|
||||
logging.info("%s: Trying to find file in database..."
|
||||
% (os.path.basename(filename)))
|
||||
flags = database.GetCompilationInfoForFile(filename)
|
||||
if flags.compiler_flags_:
|
||||
logging.info("%s: Found file in database."
|
||||
% (os.path.basename(filename)))
|
||||
return flags
|
||||
logging.info("%s: File not found in compilation db."
|
||||
% (os.path.basename(filename)))
|
||||
return None
|
||||
|
||||
def FindSiblingFileForHeader(database, filename):
|
||||
logging.info("%s: Trying to find a sibling source file for that header in database..."
|
||||
% (os.path.basename(filename)))
|
||||
basename = os.path.splitext(filename)[0]
|
||||
for extension in SOURCE_EXTENSIONS:
|
||||
replacement_file = basename + extension
|
||||
if os.path.exists(replacement_file):
|
||||
compilation_info = database.GetCompilationInfoForFile(replacement_file)
|
||||
if compilation_info.compiler_flags_:
|
||||
logging.info("%s: Found sibling source file: %s"
|
||||
% (os.path.basename(filename), replacement_file))
|
||||
return compilation_info
|
||||
logging.info("%s: Did not find sibling source file."
|
||||
% (os.path.basename(filename)))
|
||||
return None
|
||||
|
||||
# Todo: search children directories AND parent directories
|
||||
# Todo: we don't need dirname
|
||||
def FindNearestSourceFileInDb(database, dirname, refFile):
|
||||
logging.info("%s: Trying to find nearest source file in database..."
|
||||
% (os.path.basename(refFile)))
|
||||
refFile = os.path.split(refFile)[1]
|
||||
for root, dirnames, filenames in os.walk(dirname):
|
||||
for filename in filenames:
|
||||
if filename.endswith(tuple(SOURCE_EXTENSIONS)):
|
||||
if str(filename) != str(refFile):
|
||||
compilation_info = database.GetCompilationInfoForFile(str(os.path.join(root, filename)))
|
||||
if compilation_info.compiler_flags_:
|
||||
logging.info("%s: Found nearest source file from %s: %s"
|
||||
% (refFile, refFile, str(os.path.join(root, filename))))
|
||||
return compilation_info
|
||||
logging.info("%s: Could not find nearest source file from %s in compilation db." % (refFile, refFile))
|
||||
return None
|
||||
|
||||
def Pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = itertools.tee(iterable)
|
||||
next(b, None)
|
||||
return itertools.izip(a, b)
|
||||
|
||||
def RemoveClosingSlash(path):
|
||||
if path.endswith('/'):
|
||||
path = path[:-1]
|
||||
return path
|
||||
|
||||
def SearchForTranslationUnitWhichIncludesPath(database_path, database, path, filename):
|
||||
logging.info("%s: Trying to find a translation unit that includes our header's path..."
|
||||
% (os.path.basename(filename)))
|
||||
with open(database_path, 'r') as f:
|
||||
jsonDb = json.load(f)
|
||||
path = RemoveClosingSlash(os.path.abspath(path))
|
||||
found = []
|
||||
for translationUnit in jsonDb:
|
||||
buildDir = translationUnit["directory"]
|
||||
switches = translationUnit["command"].split()
|
||||
for currentSwitch, nextSwitch in Pairwise(switches):
|
||||
matchObj = re.match(r'(-I|-isystem)(.*)', currentSwitch)
|
||||
includeDir = ""
|
||||
isIncFlag = False
|
||||
if currentSwitch == "-I" or currentSwitch == "-isystem":
|
||||
includeDir = nextSwitch
|
||||
isIncFlag = True
|
||||
elif matchObj:
|
||||
includeDir = matchObj.group(2)
|
||||
isIncFlag = True
|
||||
if not isIncFlag:
|
||||
continue
|
||||
includeDir = RemoveClosingSlash(os.path.abspath(os.path.join(buildDir, includeDir)))
|
||||
# Check all the parent dirs in path
|
||||
pathCopy = path
|
||||
distance = 0
|
||||
while pathCopy != os.path.abspath(os.sep):
|
||||
if includeDir == pathCopy:
|
||||
found.append((distance, str(translationUnit["file"])))
|
||||
distance += 1
|
||||
pathCopy, tail = os.path.split(pathCopy)
|
||||
found.sort()
|
||||
if len(found) == 0:
|
||||
logging.info("%s: Did not find translation unit which includes path %s"
|
||||
% (os.path.basename(filename), path))
|
||||
return None
|
||||
else:
|
||||
result = found[0][1]
|
||||
logging.info("%s: Found best source file which includes path: %s"
|
||||
% (os.path.basename(filename), result))
|
||||
return database.GetCompilationInfoForFile(result)
|
||||
|
||||
def GetUserExtraFlags(filename):
|
||||
try:
|
||||
extra_flags_file = FindNearest(os.path.dirname(filename), ".ycm_extra_flags", filename)
|
||||
except:
|
||||
logging.info("%s: No extra flags."
|
||||
% (os.path.basename(filename)))
|
||||
return None
|
||||
with open(extra_flags_file, 'r') as f:
|
||||
lines = f.readlines()
|
||||
lines = [ line[0:line.find("#")].split() for line in lines ]
|
||||
lines = list(itertools.chain.from_iterable(lines))
|
||||
logging.info("%s: Extra flags = [\n\t\t%s\n]"
|
||||
% (os.path.basename(filename), "\n\t\t".join(lines)))
|
||||
return lines
|
||||
|
||||
def MakeRelativePathsInFlagsAbsolute(flags, working_directory):
|
||||
if not working_directory:
|
||||
return list(flags)
|
||||
new_flags = []
|
||||
make_next_absolute = False
|
||||
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 [ '-isystem', '-I', '-iquote', '--sysroot=' ]:
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue