1#!/usr/bin/env python 2 3# SPDX-License-Identifier: Unlicense 4# 5# Based on the template file provided by the 'YCM-Generator' project authored by 6# Reuben D'Netto. 7# Jiahui Xie has re-reformatted and expanded the original script in accordance 8# to the requirements of the PEP 8 style guide and 'systemd' project, 9# respectively. 10# 11# The original license is preserved as it is. 12# 13# 14# This is free and unencumbered software released into the public domain. 15# 16# Anyone is free to copy, modify, publish, use, compile, sell, or 17# distribute this software, either in source code form or as a compiled 18# binary, for any purpose, commercial or non-commercial, and by any 19# means. 20# 21# In jurisdictions that recognize copyright laws, the author or authors 22# of this software dedicate any and all copyright interest in the 23# software to the public domain. We make this dedication for the benefit 24# of the public at large and to the detriment of our heirs and 25# successors. We intend this dedication to be an overt act of 26# relinquishment in perpetuity of all present and future rights to this 27# software under copyright law. 28# 29# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 31# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 32# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 33# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 34# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 35# OTHER DEALINGS IN THE SOFTWARE. 36# 37# For more information, please refer to <http://unlicense.org/> 38 39""" 40YouCompleteMe configuration file tailored to support the 'meson' build system 41used by the 'systemd' project. 42""" 43 44import glob 45import os 46import ycm_core 47 48 49SOURCE_EXTENSIONS = (".C", ".cpp", ".cxx", ".cc", ".c", ".m", ".mm") 50HEADER_EXTENSIONS = (".H", ".h", ".hxx", ".hpp", ".hh") 51 52 53def DirectoryOfThisScript(): 54 """ 55 Return the absolute path of the parent directory containing this 56 script. 57 """ 58 return os.path.dirname(os.path.abspath(__file__)) 59 60 61def GuessBuildDirectory(): 62 """ 63 Guess the build directory using the following heuristics: 64 65 1. Returns the current directory of this script plus 'build' 66 subdirectory in absolute path if this subdirectory exists. 67 68 2. Otherwise, probes whether there exists any directory 69 containing '.ninja_log' file two levels above the current directory; 70 returns this single directory only if there is one candidate. 71 """ 72 result = os.path.join(DirectoryOfThisScript(), "build") 73 74 if os.path.exists(result): 75 return result 76 77 result = glob.glob(os.path.join(DirectoryOfThisScript(), 78 "..", "..", "*", ".ninja_log")) 79 80 if not result: 81 return "" 82 83 if 1 != len(result): 84 return "" 85 86 return os.path.split(result[0])[0] 87 88 89def TraverseByDepth(root, include_extensions): 90 """ 91 Return a set of child directories of the 'root' containing file 92 extensions specified in 'include_extensions'. 93 94 NOTE: 95 1. The 'root' directory itself is excluded from the result set. 96 2. No subdirectories would be excluded if 'include_extensions' is left 97 to 'None'. 98 3. Each entry in 'include_extensions' must begin with string '.'. 99 """ 100 is_root = True 101 result = set() 102 # Perform a depth first top down traverse of the given directory tree. 103 for root_dir, subdirs, file_list in os.walk(root): 104 if not is_root: 105 # print("Relative Root: ", root_dir) 106 # print(subdirs) 107 if include_extensions: 108 get_ext = os.path.splitext 109 subdir_extensions = { 110 get_ext(f)[-1] for f in file_list if get_ext(f)[-1] 111 } 112 if subdir_extensions & include_extensions: 113 result.add(root_dir) 114 else: 115 result.add(root_dir) 116 else: 117 is_root = False 118 119 return result 120 121 122_project_src_dir = os.path.join(DirectoryOfThisScript(), "src") 123_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({".h"})) 124flags = [ 125 "-x", 126 "c" 127 # The following flags are partially redundant due to the existence of 128 # 'compile_commands.json'. 129 # '-Wall', 130 # '-Wextra', 131 # '-Wfloat-equal', 132 # '-Wpointer-arith', 133 # '-Wshadow', 134 # '-std=gnu99', 135] 136 137for include_dir in _include_dirs_set: 138 flags.append("-I" + include_dir) 139 140# Set this to the absolute path to the folder (NOT the file!) containing the 141# compile_commands.json file to use that instead of 'flags'. See here for 142# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 143# 144# You can get CMake to generate this file for you by adding: 145# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) 146# to your CMakeLists.txt file. 147# 148# Most projects will NOT need to set this to anything; you can just change the 149# 'flags' list of compilation flags. Notice that YCM itself uses that approach. 150compilation_database_folder = GuessBuildDirectory() 151 152if os.path.exists(compilation_database_folder): 153 database = ycm_core.CompilationDatabase(compilation_database_folder) 154else: 155 database = None 156 157 158def MakeRelativePathsInFlagsAbsolute(flags, working_directory): 159 """ 160 Iterate through 'flags' and replace the relative paths prefixed by 161 '-isystem', '-I', '-iquote', '--sysroot=' with absolute paths 162 start with 'working_directory'. 163 """ 164 if not working_directory: 165 return list(flags) 166 new_flags = [] 167 make_next_absolute = False 168 path_flags = ["-isystem", "-I", "-iquote", "--sysroot="] 169 for flag in flags: 170 new_flag = flag 171 172 if make_next_absolute: 173 make_next_absolute = False 174 if not flag.startswith("/"): 175 new_flag = os.path.join(working_directory, flag) 176 177 for path_flag in path_flags: 178 if flag == path_flag: 179 make_next_absolute = True 180 break 181 182 if flag.startswith(path_flag): 183 path = flag[len(path_flag):] 184 new_flag = path_flag + os.path.join(working_directory, path) 185 break 186 187 if new_flag: 188 new_flags.append(new_flag) 189 return new_flags 190 191 192def IsHeaderFile(filename): 193 """ 194 Check whether 'filename' is considered as a header file. 195 """ 196 extension = os.path.splitext(filename)[1] 197 return extension in HEADER_EXTENSIONS 198 199 200def GetCompilationInfoForFile(filename): 201 """ 202 Helper function to look up compilation info of 'filename' in the 'database'. 203 """ 204 # The compilation_commands.json file generated by CMake does not have 205 # entries for header files. So we do our best by asking the db for flags for 206 # a corresponding source file, if any. If one exists, the flags for that 207 # file should be good enough. 208 if not database: 209 return None 210 211 if IsHeaderFile(filename): 212 basename = os.path.splitext(filename)[0] 213 for extension in SOURCE_EXTENSIONS: 214 replacement_file = basename + extension 215 if os.path.exists(replacement_file): 216 compilation_info = \ 217 database.GetCompilationInfoForFile(replacement_file) 218 if compilation_info.compiler_flags_: 219 return compilation_info 220 return None 221 return database.GetCompilationInfoForFile(filename) 222 223 224def FlagsForFile(filename, **kwargs): 225 """ 226 Callback function to be invoked by YouCompleteMe in order to get the 227 information necessary to compile 'filename'. 228 229 It returns a dictionary with a single element 'flags'. This element is a 230 list of compiler flags to pass to libclang for the file 'filename'. 231 """ 232 if database: 233 # Bear in mind that compilation_info.compiler_flags_ does NOT return a 234 # python list, but a "list-like" StringVec object 235 compilation_info = GetCompilationInfoForFile(filename) 236 if not compilation_info: 237 return None 238 239 final_flags = MakeRelativePathsInFlagsAbsolute( 240 compilation_info.compiler_flags_, 241 compilation_info.compiler_working_dir_) 242 243 else: 244 relative_to = DirectoryOfThisScript() 245 final_flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to) 246 247 return { 248 "flags": final_flags, 249 "do_cache": True 250 } 251