From 1c5ea5913e2db15b45d8c2fbe96c6a769850a4e5 Mon Sep 17 00:00:00 2001 From: Jiahui Xie Date: Sun, 26 Nov 2017 10:35:49 -0700 Subject: [PATCH] ycm: add initial support for the meson build system The existing configuration file of 'YouCompleteMe' is outdated as it is still based on 'Makefile' generated by the previous build system (autoconf); the new script should be able to provide proper semantic completion for definitions exposed in the project's header files. --- .ycm_extra_conf.py | 239 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 189 insertions(+), 50 deletions(-) diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 4edd3c8a74..b388b1afa7 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -1,67 +1,206 @@ -import itertools +#!/usr/bin/env python + +# SPDX-License-Identifier: Unlicense +# +# Based on the template file provided by the 'YCM-Generator' project authored by +# Reuben D'Netto. +# Jiahui Xie has re-reformatted and expanded the original script in accordance +# to the requirements of the PEP 8 style guide and 'systemd' project, +# respectively. +# +# The original license is preserved as it is. +# +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +""" +YouCompleteMe configuration file tailored to support the 'meson' build system +used by the 'systemd' project. +""" + +import glob import os -import subprocess - -def GetFlagsFromMakefile(varname): - return subprocess.check_output([ - "make", "-s", "print-%s" % varname]).decode().split() - - -def Flatten(lists): - return list(itertools.chain.from_iterable(lists)) +import ycm_core def DirectoryOfThisScript(): - return os.path.dirname(os.path.abspath(__file__)) + return os.path.dirname(os.path.abspath(__file__)) + + +def GuessBuildDirectory(): + result = os.path.join(DirectoryOfThisScript(), "build") + + if os.path.exists(result): + return result + + result = glob.glob(os.path.join(DirectoryOfThisScript(), + "..", "..", "*", ".ninja_log")) + + if not result: + return "" + + if 1 != len(result): + return "" + + return os.path.split(result[0])[0] + + +def TraverseByDepth(root, include_extensions): + is_root = True + result = set() + # Perform a depth first top down traverse of the given directory tree. + for root_dir, subdirs, file_list in os.walk(root): + if not is_root: + # print("Relative Root: ", root_dir) + # print(subdirs) + if include_extensions: + get_ext = os.path.splitext + subdir_extensions = { + get_ext(f)[-1] for f in file_list if get_ext(f)[-1] + } + if subdir_extensions & include_extensions: + result.add(root_dir) + else: + result.add(root_dir) + else: + is_root = False + + return result + + +_project_src_dir = os.path.join(DirectoryOfThisScript(), "src") +_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({".h"})) +flags = [ + "-x", + "c" + # The following flags are partially redundant due to the existence of + # 'compile_commands.json'. + # '-Wall', + # '-Wextra', + # '-Wfloat-equal', + # '-Wpointer-arith', + # '-Wshadow', + # '-std=gnu99', +] + +for include_dir in _include_dirs_set: + flags.append("-I" + include_dir) + +# 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 +# +# You can get CMake to generate this file for you by adding: +# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) +# to your CMakeLists.txt file. +# +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = GuessBuildDirectory() + +if os.path.exists(compilation_database_folder): + database = ycm_core.CompilationDatabase(compilation_database_folder) +else: + database = None + +SOURCE_EXTENSIONS = [".C", ".cpp", ".cxx", ".cc", ".c", ".m", ".mm"] def MakeRelativePathsInFlagsAbsolute(flags, working_directory): - if not working_directory: - return flags - new_flags = [] - make_next_absolute = False - path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] - for flag in flags: - new_flag = flag + 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) + 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 + 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 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 + if new_flag: + new_flags.append(new_flag) + return new_flags -def FlagsForFile(filename): - relative_to = DirectoryOfThisScript() +def IsHeaderFile(filename): + extension = os.path.splitext(filename)[1] + return extension in [".H", ".h", ".hxx", ".hpp", ".hh"] - return { - 'flags': MakeRelativePathsInFlagsAbsolute(flags, relative_to), - 'do_cache': True - } -flags = Flatten(map(GetFlagsFromMakefile, [ - 'AM_CPPFLAGS', - 'CPPFLAGS', - 'AM_CFLAGS', - 'CFLAGS', -])) +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 not database: + return None -# these flags cause crashes in libclang, so remove them -flags.remove('-Wlogical-op') -flags.remove('-Wsuggest-attribute=noreturn') -flags.remove('-Wdate-time') + if IsHeaderFile(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_: + return compilation_info + return None + return database.GetCompilationInfoForFile(filename) -# vim: set et ts=2 sw=2: + +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 + }