Вы находитесь на странице: 1из 8

Jonas Devlieghere HOME ABOUT TALKS GITHUB C++ LLVM

26 JULY 2015 / C++

A better YouCompleteMe
Config
If you're like me and have (1) been using Vim for a
while and (2) have been programming in C++, you've
likely heard about YouCompleteMe.

YCM is an awesome auto-completion engine for Vim. For C++ and


other C-based languages it uses the libclang under the hood, but it
integrates with other engines as well to support C#, Python and Go
to name a few. If you're not yet convinced, check out the author's
website and this blog post.

To have semantic completion for C-family languages, you need tell


the compiler how to process your files. Out of the box, YCM requires
a per-project configuration file .ycm_extra_conf.py in the root of
our project. Personally, I don't like to have yet another configuration
file clutter my project and I'm pretty sure my colleagues would
agree. Fortunately however, YCM supports you to define a global
configuration file.

let g:ycm_global_ycm_extra_conf = '~/.vim/.ycm_extra_conf.py'


Global YCM Config
I started from the default configuration file which sets a few
compiler flags and uses the file's location to make include paths
absolute. This was the first significant change: looking at the path of
the file currently open in the editor rather than the one from the
configuration file.

The second change involves support for clang_complete files ( .cl


ang_complete ) which are automatically generates by our custom
build system at Amadeus. They contain the include paths to internal
and external dependencies, omitting the current component's
include paths. Our components are structured with header files in
the include directory and source files in the src directory. The
configuration file will recursively search for include folder and add
them to the include path.

How it works
Either obtain compilation flags from the nearest JSON
compilation database ( compile_commands.json ), which is
generated by CMake by adding
set(CMAKE_EXPORT_COMPILE_COMMANDS 1) in the
CMakeLists.txt file.

If no compilation database can be found, fall back to a set of


default compilation flags.

Add the include paths from the nearest


.clang_complete file if present.

Find the nearest include directory and add include


flags for that folder and all of its subfolders. This is
useful when your source files are in the src directory
and the header files in the include directory.
The nearest here means either in the current working directory (the
directory in which the file you're editing is located) or in one of its
enclosing directories.

.ycm_extra_conf.py
The original file can be found in my dotfiles repository.

import os
import os.path
import fnmatch
import logging
import ycm_core
import re

BASE_FLAGS = [
'-Wall',
'-Wextra',
'-Werror',
'-Wno-long-long',
'-Wno-variadic-macros',
'-fexceptions',
'-ferror-limit=10000',
'-DNDEBUG',
'-std=c++11',
'-xc++',
'-I/usr/lib/',
'-I/usr/include/'
]

SOURCE_EXTENSIONS = [
'.cpp',
'.cxx',
'.cc',
'.c',
'.m',
'.mm'
]
SOURCE_DIRECTORIES = [
'src',
'lib'
]

HEADER_EXTENSIONS = [
'.h',
'.hxx',
'.hpp',
'.hh'
]

HEADER_DIRECTORIES = [
'include'
]

def IsHeaderFile(filename):
extension = os.path.splitext(filename)[1]
return extension in HEADER_EXTENSIONS

def GetCompilationInfoForFile(database, filename):


if IsHeaderFile(filename):
basename = os.path.splitext(filename)[0]
for extension in SOURCE_EXTENSIONS:
# Get info from the source files by replacing the ex
replacement_file = basename + extension
if os.path.exists(replacement_file):
compilation_info = database.GetCompilationInfoFo
if compilation_info.compiler_flags_:
return compilation_info
# If that wasn't successful, try replacing possible
for header_dir in HEADER_DIRECTORIES:
for source_dir in SOURCE_DIRECTORIES:
src_file = replacement_file.replace(header_d
if os.path.exists(src_file):
compilation_info = database.GetCompilati
if compilation_info.compiler_flags_:
return compilation_info
return None
return database.GetCompilationInfoForFile(filename)

def FindNearest(path, target, build_folder):


candidate = os.path.join(path, target)
if(os.path.isfile(candidate) or os.path.isdir(candidate)):
logging.info("Found nearest " + target + " at " + candid
return candidate;
parent = os.path.dirname(os.path.abspath(path));
if(parent == path):
raise RuntimeError("Could not find " + target);

if(build_folder):
candidate = os.path.join(parent, build_folder, target)
if(os.path.isfile(candidate) or os.path.isdir(candidate)
logging.info("Found nearest " + target + " in build
return candidate;

return FindNearest(parent, target, build_folder)

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_dire
break

if new_flag:
new_flags.append(new_flag)
return new_flags

def FlagsForClangComplete(root):
try:
clang_complete_path = FindNearest(root, '.clang_complete
clang_complete_flags = open(clang_complete_path, 'r').re
return clang_complete_flags
except:
return None

def FlagsForInclude(root):
try:
include_path = FindNearest(root, 'include')
flags = []
for dirroot, dirnames, filenames in os.walk(include_path
for dir_path in dirnames:
real_path = os.path.join(dirroot, dir_path)
flags = flags + ["-I" + real_path]
return flags
except:
return None

def FlagsForCompilationDatabase(root, filename):


try:
# Last argument of next function is the name of the buil
# out of source projects
compilation_db_path = FindNearest(root, 'compile_command
compilation_db_dir = os.path.dirname(compilation_db_path
logging.info("Set compilation database directory to " +
compilation_db = ycm_core.CompilationDatabase(compilati
if not compilation_db:
logging.info("Compilation database file found but un
return None
compilation_info = GetCompilationInfoForFile(compilation
if not compilation_info:
logging.info("No compilation info for " + filename +
return None
return MakeRelativePathsInFlagsAbsolute(
compilation_info.compiler_flags_,
compilation_info.compiler_working_dir_)
except:
return None

def FlagsForFile(filename):
root = os.path.realpath(filename);
compilation_db_flags = FlagsForCompilationDatabase(root, fil
if compilation_db_flags:
final_flags = compilation_db_flags
else:
final_flags = BASE_FLAGS
clang_flags = FlagsForClangComplete(root)
if clang_flags:
final_flags = final_flags + clang_flags
include_flags = FlagsForInclude(root)
if include_flags:
final_flags = final_flags + include_flags
return {
'flags': final_flags,
'do_cache': True
}

Subscribe to Jonas Devlieghere


Get the latest posts delivered right to your inbox

Jonas Devlieghere Read More


Read more posts by this author.

— Jonas Devlieghere —

C++

Using LSP & clangd in Vim

CLANG

Reference Counting PIMPL Idiom Understanding the Clang


AST
Exposing Containers of Unique Clang is everywhere; It is the core of my
Pointers favorite Vim plugin YouCompleteMe, it
recently got supported by Microsoft
Visual Studio, it is mentioned in
numerous episodes of CppCast and it
powers the
See all 7 posts → 10 MIN READ

LATEX

Packages I Wish I Knew When Starting LaTeX


I've been using LaTeX for almost a decade now. Even after that much time, I keep running into
new problems that require a solution I wasn't aware of. LaTeX has a great community

6 MIN READ

Jonas Devlieghere © 2019


Latest Posts Twitter Ghost

Вам также может понравиться