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 6 This module implementes the YouCompleteMe configuration API documented at:
7 https://github.com/ycm-core/ycmd#ycm_extra_confpy-specification
8 9 The implementation loads and processes a .clang_complete file, documented at:
10 https://github.com/xavierd/clang_complete/blob/master/README.md
11 """
12 13 import os
14 15 # Flags added to the list in .clang_complete.
16 BASE_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
24 PATH_FLAGS = [
25 '-isystem',
26 '-I',
27 '-iquote',
28 '--sysroot='
29 ]
30 31 32 def DirectoryOfThisScript():
33 """Returns the absolute path to the directory containing this script."""
34 return os.path.dirname(os.path.abspath(__file__))
35 36 37 def 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 73 def 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 106 def 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 125 def 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