1# Copyright 2017 The CRC32C Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""YouCompleteMe configuration that interprets a .clang_complete file. 5 6This module implementes the YouCompleteMe configuration API documented at: 7https://github.com/ycm-core/ycmd#ycm_extra_confpy-specification 8 9The implementation loads and processes a .clang_complete file, documented at: 10https://github.com/xavierd/clang_complete/blob/master/README.md 11""" 12 13import os 14 15# Flags added to the list in .clang_complete. 16BASE_FLAGS = [ 17 '-Werror', # Unlike clang_complete, YCM can also be used as a linter. 18 '-DUSE_CLANG_COMPLETER', # YCM needs this. 19 '-xc++', # YCM needs this to avoid compiling headers as C code. 20] 21 22# Clang flags that take in paths. 23# See https://clang.llvm.org/docs/ClangCommandLineReference.html 24PATH_FLAGS = [ 25 '-isystem', 26 '-I', 27 '-iquote', 28 '--sysroot=' 29] 30 31 32def DirectoryOfThisScript(): 33 """Returns the absolute path to the directory containing this script.""" 34 return os.path.dirname(os.path.abspath(__file__)) 35 36 37def MakeRelativePathsInFlagsAbsolute(flags, build_root): 38 """Expands relative paths in a list of Clang command-line flags. 39 40 Args: 41 flags: The list of flags passed to Clang. 42 build_root: The current directory when running the Clang compiler. Should be 43 an absolute path. 44 45 Returns: 46 A list of flags with relative paths replaced by absolute paths. 47 """ 48 new_flags = [] 49 make_next_absolute = False 50 for flag in flags: 51 new_flag = flag 52 53 if make_next_absolute: 54 make_next_absolute = False 55 if not flag.startswith('/'): 56 new_flag = os.path.join(build_root, flag) 57 58 for path_flag in PATH_FLAGS: 59 if flag == path_flag: 60 make_next_absolute = True 61 break 62 63 if flag.startswith(path_flag): 64 path = flag[len(path_flag):] 65 new_flag = path_flag + os.path.join(build_root, path) 66 break 67 68 if new_flag: 69 new_flags.append(new_flag) 70 return new_flags 71 72 73def FindNearest(target, path, build_root): 74 """Looks for a file with a specific name closest to a project path. 75 76 This is similar to the logic used by a version-control system (like git) to 77 find its configuration directory (.git) based on the current directory when a 78 command is invoked. 79 80 Args: 81 target: The file name to search for. 82 path: The directory where the search starts. The search will explore the 83 given directory's ascendants using the parent relationship. Should be an 84 absolute path. 85 build_root: A directory that acts as a fence for the search. If the search 86 reaches this directory, it will not advance to its parent. Should be an 87 absolute path. 88 89 Returns: 90 The path to a file with the desired name. None if the search failed. 91 """ 92 candidate = os.path.join(path, target) 93 if os.path.isfile(candidate): 94 return candidate 95 96 if path == build_root: 97 return None 98 99 parent = os.path.dirname(path) 100 if parent == path: 101 return None 102 103 return FindNearest(target, parent, build_root) 104 105 106def FlagsForClangComplete(file_path, build_root): 107 """Reads the .clang_complete flags for a source file. 108 109 Args: 110 file_path: The path to the source file. Should be inside the project. Used 111 to locate the relevant .clang_complete file. 112 build_root: The current directory when running the Clang compiler for this 113 file. Should be an absolute path. 114 115 Returns: 116 A list of strings, where each element is a Clang command-line flag. 117 """ 118 clang_complete_path = FindNearest('.clang_complete', file_path, build_root) 119 if clang_complete_path is None: 120 return None 121 clang_complete_flags = open(clang_complete_path, 'r').read().splitlines() 122 return clang_complete_flags 123 124 125def FlagsForFile(filename, **kwargs): 126 """Implements the YouCompleteMe API.""" 127 128 # kwargs can be used to pass 'client_data' to the YCM configuration. This 129 # configuration script does not need any extra information, so 130 # pylint: disable=unused-argument 131 132 build_root = DirectoryOfThisScript() 133 file_path = os.path.realpath(filename) 134 135 flags = BASE_FLAGS 136 clang_flags = FlagsForClangComplete(file_path, build_root) 137 if clang_flags: 138 flags += clang_flags 139 140 final_flags = MakeRelativePathsInFlagsAbsolute(flags, build_root) 141 142 return {'flags': final_flags} 143