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